%PDF- %PDF-
Direktori : /home/silvzytp/crm-ind-code/vendor/phpunit/phpunit/src/Runner/ |
Current File : //home/silvzytp/crm-ind-code/vendor/phpunit/phpunit/src/Runner/PhptTestCase.php |
<?php declare(strict_types=1); /* * This file is part of PHPUnit. * * (c) Sebastian Bergmann <sebastian@phpunit.de> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace PHPUnit\Runner; use const DEBUG_BACKTRACE_IGNORE_ARGS; use const DIRECTORY_SEPARATOR; use function array_merge; use function basename; use function debug_backtrace; use function defined; use function dirname; use function explode; use function extension_loaded; use function file; use function file_get_contents; use function file_put_contents; use function is_array; use function is_file; use function is_readable; use function is_string; use function ltrim; use function phpversion; use function preg_match; use function preg_replace; use function preg_split; use function realpath; use function rtrim; use function sprintf; use function str_replace; use function strncasecmp; use function strpos; use function substr; use function trim; use function unlink; use function unserialize; use function var_export; use function version_compare; use PHPUnit\Framework\Assert; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\ExecutionOrderDependency; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\IncompleteTestError; use PHPUnit\Framework\PHPTAssertionFailedError; use PHPUnit\Framework\Reorderable; use PHPUnit\Framework\SelfDescribing; use PHPUnit\Framework\SkippedTestError; use PHPUnit\Framework\SyntheticSkippedError; use PHPUnit\Framework\Test; use PHPUnit\Framework\TestResult; use PHPUnit\Util\PHP\AbstractPhpProcess; use SebastianBergmann\CodeCoverage\RawCodeCoverageData; use SebastianBergmann\Template\Template; use SebastianBergmann\Timer\Timer; use Throwable; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class PhptTestCase implements Reorderable, SelfDescribing, Test { /** * @var string */ private $filename; /** * @var AbstractPhpProcess */ private $phpUtil; /** * @var string */ private $output = ''; /** * Constructs a test case with the given filename. * * @throws Exception */ public function __construct(string $filename, AbstractPhpProcess $phpUtil = null) { if (!is_file($filename)) { throw new Exception( sprintf( 'File "%s" does not exist.', $filename ) ); } $this->filename = $filename; $this->phpUtil = $phpUtil ?: AbstractPhpProcess::factory(); } /** * Counts the number of test cases executed by run(TestResult result). */ public function count(): int { return 1; } /** * Runs a test and collects its result in a TestResult instance. * * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception */ public function run(TestResult $result = null): TestResult { if ($result === null) { $result = new TestResult; } try { $sections = $this->parse(); } catch (Exception $e) { $result->startTest($this); $result->addFailure($this, new SkippedTestError($e->getMessage()), 0); $result->endTest($this, 0); return $result; } $code = $this->render($sections['FILE']); $xfail = false; $settings = $this->parseIniSection($this->settings($result->getCollectCodeCoverageInformation())); $result->startTest($this); if (isset($sections['INI'])) { $settings = $this->parseIniSection($sections['INI'], $settings); } if (isset($sections['ENV'])) { $env = $this->parseEnvSection($sections['ENV']); $this->phpUtil->setEnv($env); } $this->phpUtil->setUseStderrRedirection(true); if ($result->enforcesTimeLimit()) { $this->phpUtil->setTimeout($result->getTimeoutForLargeTests()); } $skip = $this->runSkip($sections, $result, $settings); if ($skip) { return $result; } if (isset($sections['XFAIL'])) { $xfail = trim($sections['XFAIL']); } if (isset($sections['STDIN'])) { $this->phpUtil->setStdin($sections['STDIN']); } if (isset($sections['ARGS'])) { $this->phpUtil->setArgs($sections['ARGS']); } if ($result->getCollectCodeCoverageInformation()) { $codeCoverageCacheDirectory = null; $pathCoverage = false; $codeCoverage = $result->getCodeCoverage(); if ($codeCoverage) { if ($codeCoverage->cachesStaticAnalysis()) { $codeCoverageCacheDirectory = $codeCoverage->cacheDirectory(); } $pathCoverage = $codeCoverage->collectsBranchAndPathCoverage(); } $this->renderForCoverage($code, $pathCoverage, $codeCoverageCacheDirectory); } $timer = new Timer; $timer->start(); $jobResult = $this->phpUtil->runJob($code, $this->stringifyIni($settings)); $time = $timer->stop()->asSeconds(); $this->output = $jobResult['stdout'] ?? ''; if (isset($codeCoverage) && ($coverage = $this->cleanupForCoverage())) { $codeCoverage->append($coverage, $this, true, [], []); } try { $this->assertPhptExpectation($sections, $this->output); } catch (AssertionFailedError $e) { $failure = $e; if ($xfail !== false) { $failure = new IncompleteTestError($xfail, 0, $e); } elseif ($e instanceof ExpectationFailedException) { $comparisonFailure = $e->getComparisonFailure(); if ($comparisonFailure) { $diff = $comparisonFailure->getDiff(); } else { $diff = $e->getMessage(); } $hint = $this->getLocationHintFromDiff($diff, $sections); $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); $failure = new PHPTAssertionFailedError( $e->getMessage(), 0, $trace[0]['file'], $trace[0]['line'], $trace, $comparisonFailure ? $diff : '' ); } $result->addFailure($this, $failure, $time); } catch (Throwable $t) { $result->addError($this, $t, $time); } if ($xfail !== false && $result->allCompletelyImplemented()) { $result->addFailure($this, new IncompleteTestError('XFAIL section but test passes'), $time); } $this->runClean($sections, $result->getCollectCodeCoverageInformation()); $result->endTest($this, $time); return $result; } /** * Returns the name of the test case. */ public function getName(): string { return $this->toString(); } /** * Returns a string representation of the test case. */ public function toString(): string { return $this->filename; } public function usesDataProvider(): bool { return false; } public function getNumAssertions(): int { return 1; } public function getActualOutput(): string { return $this->output; } public function hasOutput(): bool { return !empty($this->output); } public function sortId(): string { return $this->filename; } /** * @return list<ExecutionOrderDependency> */ public function provides(): array { return []; } /** * @return list<ExecutionOrderDependency> */ public function requires(): array { return []; } /** * Parse --INI-- section key value pairs and return as array. * * @param array|string $content */ private function parseIniSection($content, array $ini = []): array { if (is_string($content)) { $content = explode("\n", trim($content)); } foreach ($content as $setting) { if (strpos($setting, '=') === false) { continue; } $setting = explode('=', $setting, 2); $name = trim($setting[0]); $value = trim($setting[1]); if ($name === 'extension' || $name === 'zend_extension') { if (!isset($ini[$name])) { $ini[$name] = []; } $ini[$name][] = $value; continue; } $ini[$name] = $value; } return $ini; } private function parseEnvSection(string $content): array { $env = []; foreach (explode("\n", trim($content)) as $e) { $e = explode('=', trim($e), 2); if (!empty($e[0]) && isset($e[1])) { $env[$e[0]] = $e[1]; } } return $env; } /** * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception * @throws ExpectationFailedException */ private function assertPhptExpectation(array $sections, string $output): void { $assertions = [ 'EXPECT' => 'assertEquals', 'EXPECTF' => 'assertStringMatchesFormat', 'EXPECTREGEX' => 'assertMatchesRegularExpression', ]; $actual = preg_replace('/\r\n/', "\n", trim($output)); foreach ($assertions as $sectionName => $sectionAssertion) { if (isset($sections[$sectionName])) { $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName])); $expected = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; if ($expected === '') { throw new Exception('No PHPT expectation found'); } Assert::$sectionAssertion($expected, $actual); return; } } throw new Exception('No PHPT assertion found'); } /** * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ private function runSkip(array &$sections, TestResult $result, array $settings): bool { if (!isset($sections['SKIPIF'])) { return false; } $skipif = $this->render($sections['SKIPIF']); $jobResult = $this->phpUtil->runJob($skipif, $this->stringifyIni($settings)); if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) { $message = ''; if (preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $skipMatch)) { $message = substr($skipMatch[1], 2); } $hint = $this->getLocationHint($message, $sections, 'SKIPIF'); $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); $result->addFailure( $this, new SyntheticSkippedError($message, 0, $trace[0]['file'], $trace[0]['line'], $trace), 0 ); $result->endTest($this, 0); return true; } return false; } private function runClean(array &$sections, bool $collectCoverage): void { $this->phpUtil->setStdin(''); $this->phpUtil->setArgs(''); if (isset($sections['CLEAN'])) { $cleanCode = $this->render($sections['CLEAN']); $this->phpUtil->runJob($cleanCode, $this->settings($collectCoverage)); } } /** * @throws Exception */ private function parse(): array { $sections = []; $section = ''; $unsupportedSections = [ 'CGI', 'COOKIE', 'DEFLATE_POST', 'EXPECTHEADERS', 'EXTENSIONS', 'GET', 'GZIP_POST', 'HEADERS', 'PHPDBG', 'POST', 'POST_RAW', 'PUT', 'REDIRECTTEST', 'REQUEST', ]; $lineNr = 0; foreach (file($this->filename) as $line) { $lineNr++; if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { $section = $result[1]; $sections[$section] = ''; $sections[$section . '_offset'] = $lineNr; continue; } if (empty($section)) { throw new Exception('Invalid PHPT file: empty section header'); } $sections[$section] .= $line; } if (isset($sections['FILEEOF'])) { $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); unset($sections['FILEEOF']); } $this->parseExternal($sections); if (!$this->validate($sections)) { throw new Exception('Invalid PHPT file'); } foreach ($unsupportedSections as $section) { if (isset($sections[$section])) { throw new Exception( "PHPUnit does not support PHPT {$section} sections" ); } } return $sections; } /** * @throws Exception */ private function parseExternal(array &$sections): void { $allowSections = [ 'FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX', ]; $testDirectory = dirname($this->filename) . DIRECTORY_SEPARATOR; foreach ($allowSections as $section) { if (isset($sections[$section . '_EXTERNAL'])) { $externalFilename = trim($sections[$section . '_EXTERNAL']); if (!is_file($testDirectory . $externalFilename) || !is_readable($testDirectory . $externalFilename)) { throw new Exception( sprintf( 'Could not load --%s-- %s for PHPT file', $section . '_EXTERNAL', $testDirectory . $externalFilename ) ); } $sections[$section] = file_get_contents($testDirectory . $externalFilename); } } } private function validate(array &$sections): bool { $requiredSections = [ 'FILE', [ 'EXPECT', 'EXPECTF', 'EXPECTREGEX', ], ]; foreach ($requiredSections as $section) { if (is_array($section)) { $foundSection = false; foreach ($section as $anySection) { if (isset($sections[$anySection])) { $foundSection = true; break; } } if (!$foundSection) { return false; } continue; } if (!isset($sections[$section])) { return false; } } return true; } private function render(string $code): string { return str_replace( [ '__DIR__', '__FILE__', ], [ "'" . dirname($this->filename) . "'", "'" . $this->filename . "'", ], $code ); } private function getCoverageFiles(): array { $baseDir = dirname(realpath($this->filename)) . DIRECTORY_SEPARATOR; $basename = basename($this->filename, 'phpt'); return [ 'coverage' => $baseDir . $basename . 'coverage', 'job' => $baseDir . $basename . 'php', ]; } private function renderForCoverage(string &$job, bool $pathCoverage, ?string $codeCoverageCacheDirectory): void { $files = $this->getCoverageFiles(); $template = new Template( __DIR__ . '/../Util/PHP/Template/PhptTestCase.tpl' ); $composerAutoload = '\'\''; if (defined('PHPUNIT_COMPOSER_INSTALL')) { $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); } $phar = '\'\''; if (defined('__PHPUNIT_PHAR__')) { $phar = var_export(__PHPUNIT_PHAR__, true); } $globals = ''; if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export( $GLOBALS['__PHPUNIT_BOOTSTRAP'], true ) . ";\n"; } if ($codeCoverageCacheDirectory === null) { $codeCoverageCacheDirectory = 'null'; } else { $codeCoverageCacheDirectory = "'" . $codeCoverageCacheDirectory . "'"; } $template->setVar( [ 'composerAutoload' => $composerAutoload, 'phar' => $phar, 'globals' => $globals, 'job' => $files['job'], 'coverageFile' => $files['coverage'], 'driverMethod' => $pathCoverage ? 'forLineAndPathCoverage' : 'forLineCoverage', 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory, ] ); file_put_contents($files['job'], $job); $job = $template->render(); } private function cleanupForCoverage(): RawCodeCoverageData { $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); $files = $this->getCoverageFiles(); if (is_file($files['coverage'])) { $buffer = @file_get_contents($files['coverage']); if ($buffer !== false) { $coverage = @unserialize($buffer); if ($coverage === false) { $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); } } } foreach ($files as $file) { @unlink($file); } return $coverage; } private function stringifyIni(array $ini): array { $settings = []; foreach ($ini as $key => $value) { if (is_array($value)) { foreach ($value as $val) { $settings[] = $key . '=' . $val; } continue; } $settings[] = $key . '=' . $value; } return $settings; } private function getLocationHintFromDiff(string $message, array $sections): array { $needle = ''; $previousLine = ''; $block = 'message'; foreach (preg_split('/\r\n|\r|\n/', $message) as $line) { $line = trim($line); if ($block === 'message' && $line === '--- Expected') { $block = 'expected'; } if ($block === 'expected' && $line === '@@ @@') { $block = 'diff'; } if ($block === 'diff') { if (strpos($line, '+') === 0) { $needle = $this->getCleanDiffLine($previousLine); break; } if (strpos($line, '-') === 0) { $needle = $this->getCleanDiffLine($line); break; } } if (!empty($line)) { $previousLine = $line; } } return $this->getLocationHint($needle, $sections); } private function getCleanDiffLine(string $line): string { if (preg_match('/^[\-+]([\'\"]?)(.*)\1$/', $line, $matches)) { $line = $matches[2]; } return $line; } private function getLocationHint(string $needle, array $sections, ?string $sectionName = null): array { $needle = trim($needle); if (empty($needle)) { return [[ 'file' => realpath($this->filename), 'line' => 1, ]]; } if ($sectionName) { $search = [$sectionName]; } else { $search = [ // 'FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX', ]; } $sectionOffset = null; foreach ($search as $section) { if (!isset($sections[$section])) { continue; } if (isset($sections[$section . '_EXTERNAL'])) { $externalFile = trim($sections[$section . '_EXTERNAL']); return [ [ 'file' => realpath(dirname($this->filename) . DIRECTORY_SEPARATOR . $externalFile), 'line' => 1, ], [ 'file' => realpath($this->filename), 'line' => ($sections[$section . '_EXTERNAL_offset'] ?? 0) + 1, ], ]; } $sectionOffset = $sections[$section . '_offset'] ?? 0; $offset = $sectionOffset + 1; foreach (preg_split('/\r\n|\r|\n/', $sections[$section]) as $line) { if (strpos($line, $needle) !== false) { return [[ 'file' => realpath($this->filename), 'line' => $offset, ]]; } $offset++; } } if ($sectionName) { // String not found in specified section, show user the start of the named section return [[ 'file' => realpath($this->filename), 'line' => $sectionOffset, ]]; } // No section specified, show user start of code return [[ 'file' => realpath($this->filename), 'line' => 1, ]]; } /** * @psalm-return list<string> */ private function settings(bool $collectCoverage): array { $settings = [ 'allow_url_fopen=1', 'auto_append_file=', 'auto_prepend_file=', 'disable_functions=', 'display_errors=1', 'docref_ext=.html', 'docref_root=', 'error_append_string=', 'error_prepend_string=', 'error_reporting=-1', 'html_errors=0', 'log_errors=0', 'open_basedir=', 'output_buffering=Off', 'output_handler=', 'report_memleaks=0', 'report_zend_debug=0', ]; if (extension_loaded('pcov')) { if ($collectCoverage) { $settings[] = 'pcov.enabled=1'; } else { $settings[] = 'pcov.enabled=0'; } } if (extension_loaded('xdebug')) { if (version_compare(phpversion('xdebug'), '3', '>=')) { if ($collectCoverage) { $settings[] = 'xdebug.mode=coverage'; } else { $settings[] = 'xdebug.mode=off'; } } else { $settings[] = 'xdebug.default_enable=0'; if ($collectCoverage) { $settings[] = 'xdebug.coverage_enable=1'; } } } return $settings; } }