Вход Регистрация
Файл: vendor/phpunit/phpunit/src/Runner/PhptTestCase.php
Строк: 820
<?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 PHPUnitRunner;

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 
PHPUnitFrameworkAssert;
use 
PHPUnitFrameworkAssertionFailedError;
use 
PHPUnitFrameworkExecutionOrderDependency;
use 
PHPUnitFrameworkExpectationFailedException;
use 
PHPUnitFrameworkIncompleteTestError;
use 
PHPUnitFrameworkPHPTAssertionFailedError;
use 
PHPUnitFrameworkReorderable;
use 
PHPUnitFrameworkSelfDescribing;
use 
PHPUnitFrameworkSkippedTestError;
use 
PHPUnitFrameworkSyntheticSkippedError;
use 
PHPUnitFrameworkTest;
use 
PHPUnitFrameworkTestResult;
use 
PHPUnitUtilPHPAbstractPhpProcess;
use 
SebastianBergmannCodeCoverageRawCodeCoverageData;
use 
SebastianBergmannTemplateTemplate;
use 
SebastianBergmannTimerTimer;
use 
Throwable;

/**
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
 */
final class PhptTestCase implements ReorderableSelfDescribingTest
{
    
/**
     * @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 $filenameAbstractPhpProcess $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 SebastianBergmannCodeCoverageInvalidArgumentException
     * @throws SebastianBergmannCodeCoverageUnintentionallyCoveredCodeException
     * @throws SebastianBergmannRecursionContextInvalidArgumentException
     * @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($this0);

            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$thistrue, [], []);
        }

        try {
            
$this->assertPhptExpectation($sections$this->output);
        } catch (
AssertionFailedError $e) {
            
$failure $e;

            if (
$xfail !== false) {
                
$failure = new IncompleteTestError($xfail0$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($hintdebug_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('='$setting2);
            
$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 SebastianBergmannRecursionContextInvalidArgumentException
     * @throws Exception
     * @throws ExpectationFailedException
     */
    
private function assertPhptExpectation(array $sectionsstring $output): void
    
{
        
$assertions = [
            
'EXPECT'      => 'assertEquals',
            
'EXPECTF'     => 'assertStringMatchesFormat',
            
'EXPECTREGEX' => 'assertMatchesRegularExpression',
        ];

        
$actual preg_replace('/rn/'"n"trim($output));

        foreach (
$assertions as $sectionName => $sectionAssertion) {
            if (isset(
$sections[$sectionName])) {
                
$sectionContent preg_replace('/rn/'"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 SebastianBergmannRecursionContextInvalidArgumentException
     */
    
private function runSkip(array &$sectionsTestResult $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*skips*(.+)s*/i'$jobResult['stdout'], $skipMatch)) {
                
$message substr($skipMatch[1], 2);
            }

            
$hint  $this->getLocationHint($message$sections'SKIPIF');
            
$trace array_merge($hintdebug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS));
            
$result->addFailure(
                
$this,
                new 
SyntheticSkippedError($message0$trace[0]['file'], $trace[0]['line'], $trace),
                
0
            
);
            
$result->endTest($this0);

            return 
true;
        }

        return 
false;
    }

    private function 
runClean(array &$sectionsbool $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'], "rn");
            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 &$jobbool $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_INSTALLtrue);
        }

        
$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('/rn|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('/rn|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;
    }
}
Онлайн: 1
Реклама