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

use const 
PHP_OS;
use const 
PHP_VERSION;
use function 
addcslashes;
use function 
array_flip;
use function 
array_key_exists;
use function 
array_merge;
use function 
array_unique;
use function 
array_unshift;
use function 
class_exists;
use function 
count;
use function 
explode;
use function 
extension_loaded;
use function 
function_exists;
use function 
get_class;
use function 
ini_get;
use function 
interface_exists;
use function 
is_array;
use function 
is_int;
use function 
method_exists;
use function 
phpversion;
use function 
preg_match;
use function 
preg_replace;
use function 
sprintf;
use function 
strncmp;
use function 
strpos;
use function 
strtolower;
use function 
trim;
use function 
version_compare;
use 
PHPUnitFrameworkCodeCoverageException;
use 
PHPUnitFrameworkExecutionOrderDependency;
use 
PHPUnitFrameworkInvalidCoversTargetException;
use 
PHPUnitFrameworkSelfDescribing;
use 
PHPUnitFrameworkTestCase;
use 
PHPUnitFrameworkWarning;
use 
PHPUnitRunnerVersion;
use 
PHPUnitUtilAnnotationRegistry;
use 
ReflectionClass;
use 
ReflectionException;
use 
ReflectionMethod;
use 
SebastianBergmannCodeUnitCodeUnitCollection;
use 
SebastianBergmannCodeUnitInvalidCodeUnitException;
use 
SebastianBergmannCodeUnitMapper;
use 
SebastianBergmannEnvironmentOperatingSystem;

/**
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
 */
final class Test
{
    
/**
     * @var int
     */
    
public const UNKNOWN = -1;

    
/**
     * @var int
     */
    
public const SMALL 0;

    
/**
     * @var int
     */
    
public const MEDIUM 1;

    
/**
     * @var int
     */
    
public const LARGE 2;

    
/**
     * @var array
     */
    
private static $hookMethods = [];

    
/**
     * @throws SebastianBergmannRecursionContextInvalidArgumentException
     */
    
public static function describe(PHPUnitFrameworkTest $test): array
    {
        if (
$test instanceof TestCase) {
            return [
get_class($test), $test->getName()];
        }

        if (
$test instanceof SelfDescribing) {
            return [
''$test->toString()];
        }

        return [
''get_class($test)];
    }

    public static function 
describeAsString(PHPUnitFrameworkTest $test): string
    
{
        if (
$test instanceof SelfDescribing) {
            return 
$test->toString();
        }

        return 
get_class($test);
    }

    
/**
     * @throws CodeCoverageException
     *
     * @return array|bool
     *
     * @psalm-param class-string $className
     */
    
public static function getLinesToBeCovered(string $classNamestring $methodName)
    {
        
$annotations self::parseTestMethodAnnotations(
            
$className,
            
$methodName
        
);

        if (!
self::shouldCoversAnnotationBeUsed($annotations)) {
            return 
false;
        }

        return 
self::getLinesToBeCoveredOrUsed($className$methodName'covers');
    }

    
/**
     * Returns lines of code specified with the @uses annotation.
     *
     * @throws CodeCoverageException
     *
     * @psalm-param class-string $className
     */
    
public static function getLinesToBeUsed(string $classNamestring $methodName): array
    {
        return 
self::getLinesToBeCoveredOrUsed($className$methodName'uses');
    }

    public static function 
requiresCodeCoverageDataCollection(TestCase $test): bool
    
{
        
$annotations self::parseTestMethodAnnotations(
            
get_class($test),
            
$test->getName(false)
        );

        
// If there is no @covers annotation but a @coversNothing annotation on
        // the test method then code coverage data does not need to be collected
        
if (isset($annotations['method']['coversNothing'])) {
            
// @see https://github.com/sebastianbergmann/phpunit/issues/4947#issuecomment-1084480950
            // return false;
        
}

        
// If there is at least one @covers annotation then
        // code coverage data needs to be collected
        
if (isset($annotations['method']['covers'])) {
            return 
true;
        }

        
// If there is no @covers annotation but a @coversNothing annotation
        // then code coverage data does not need to be collected
        
if (isset($annotations['class']['coversNothing'])) {
            
// @see https://github.com/sebastianbergmann/phpunit/issues/4947#issuecomment-1084480950
            // return false;
        
}

        
// If there is no @coversNothing annotation then
        // code coverage data may be collected
        
return true;
    }

    
/**
     * @throws Exception
     *
     * @psalm-param class-string $className
     */
    
public static function getRequirements(string $classNamestring $methodName): array
    {
        return 
self::mergeArraysRecursively(
            
Registry::getInstance()->forClassName($className)->requirements(),
            
Registry::getInstance()->forMethod($className$methodName)->requirements()
        );
    }

    
/**
     * Returns the missing requirements for a test.
     *
     * @throws Exception
     * @throws Warning
     *
     * @psalm-param class-string $className
     */
    
public static function getMissingRequirements(string $classNamestring $methodName): array
    {
        
$required self::getRequirements($className$methodName);
        
$missing  = [];
        
$hint     null;

        if (!empty(
$required['PHP'])) {
            
$operator = new VersionComparisonOperator(empty($required['PHP']['operator']) ? '>=' $required['PHP']['operator']);

            if (!
version_compare(PHP_VERSION$required['PHP']['version'], $operator->asString())) {
                
$missing[] = sprintf('PHP %s %s is required.'$operator->asString(), $required['PHP']['version']);
                
$hint      'PHP';
            }
        } elseif (!empty(
$required['PHP_constraint'])) {
            
$version = new PharIoVersionVersion(self::sanitizeVersionNumber(PHP_VERSION));

            if (!
$required['PHP_constraint']['constraint']->complies($version)) {
                
$missing[] = sprintf(
                    
'PHP version does not match the required constraint %s.',
                    
$required['PHP_constraint']['constraint']->asString()
                );

                
$hint 'PHP_constraint';
            }
        }

        if (!empty(
$required['PHPUnit'])) {
            
$phpunitVersion Version::id();

            
$operator = new VersionComparisonOperator(empty($required['PHPUnit']['operator']) ? '>=' $required['PHPUnit']['operator']);

            if (!
version_compare($phpunitVersion$required['PHPUnit']['version'], $operator->asString())) {
                
$missing[] = sprintf('PHPUnit %s %s is required.'$operator->asString(), $required['PHPUnit']['version']);
                
$hint      $hint ?? 'PHPUnit';
            }
        } elseif (!empty(
$required['PHPUnit_constraint'])) {
            
$phpunitVersion = new PharIoVersionVersion(self::sanitizeVersionNumber(Version::id()));

            if (!
$required['PHPUnit_constraint']['constraint']->complies($phpunitVersion)) {
                
$missing[] = sprintf(
                    
'PHPUnit version does not match the required constraint %s.',
                    
$required['PHPUnit_constraint']['constraint']->asString()
                );

                
$hint $hint ?? 'PHPUnit_constraint';
            }
        }

        if (!empty(
$required['OSFAMILY']) && $required['OSFAMILY'] !== (new OperatingSystem)->getFamily()) {
            
$missing[] = sprintf('Operating system %s is required.'$required['OSFAMILY']);
            
$hint      $hint ?? 'OSFAMILY';
        }

        if (!empty(
$required['OS'])) {
            
$requiredOsPattern sprintf('/%s/i'addcslashes($required['OS'], '/'));

            if (!
preg_match($requiredOsPatternPHP_OS)) {
                
$missing[] = sprintf('Operating system matching %s is required.'$requiredOsPattern);
                
$hint      $hint ?? 'OS';
            }
        }

        if (!empty(
$required['functions'])) {
            foreach (
$required['functions'] as $function) {
                
$pieces explode('::'$function);

                if (
count($pieces) === && class_exists($pieces[0]) && method_exists($pieces[0], $pieces[1])) {
                    continue;
                }

                if (
function_exists($function)) {
                    continue;
                }

                
$missing[] = sprintf('Function %s is required.'$function);
                
$hint      $hint ?? 'function_' $function;
            }
        }

        if (!empty(
$required['setting'])) {
            foreach (
$required['setting'] as $setting => $value) {
                if (
ini_get($setting) !== $value) {
                    
$missing[] = sprintf('Setting "%s" must be "%s".'$setting$value);
                    
$hint      $hint ?? '__SETTING_' $setting;
                }
            }
        }

        if (!empty(
$required['extensions'])) {
            foreach (
$required['extensions'] as $extension) {
                if (isset(
$required['extension_versions'][$extension])) {
                    continue;
                }

                if (!
extension_loaded($extension)) {
                    
$missing[] = sprintf('Extension %s is required.'$extension);
                    
$hint      $hint ?? 'extension_' $extension;
                }
            }
        }

        if (!empty(
$required['extension_versions'])) {
            foreach (
$required['extension_versions'] as $extension => $req) {
                
$actualVersion phpversion($extension);

                
$operator = new VersionComparisonOperator(empty($req['operator']) ? '>=' $req['operator']);

                if (
$actualVersion === false || !version_compare($actualVersion$req['version'], $operator->asString())) {
                    
$missing[] = sprintf('Extension %s %s %s is required.'$extension$operator->asString(), $req['version']);
                    
$hint      $hint ?? 'extension_' $extension;
                }
            }
        }

        if (
$hint && isset($required['__OFFSET'])) {
            
array_unshift($missing'__OFFSET_FILE=' $required['__OFFSET']['__FILE']);
            
array_unshift($missing'__OFFSET_LINE=' . ($required['__OFFSET'][$hint] ?? 1));
        }

        return 
$missing;
    }

    
/**
     * Returns the provided data for a method.
     *
     * @throws Exception
     *
     * @psalm-param class-string $className
     */
    
public static function getProvidedData(string $classNamestring $methodName): ?array
    {
        return 
Registry::getInstance()->forMethod($className$methodName)->getProvidedData();
    }

    
/**
     * @psalm-param class-string $className
     */
    
public static function parseTestMethodAnnotations(string $className, ?string $methodName ''): array
    {
        
$registry Registry::getInstance();

        if (
$methodName !== null) {
            try {
                return [
                    
'method' => $registry->forMethod($className$methodName)->symbolAnnotations(),
                    
'class'  => $registry->forClassName($className)->symbolAnnotations(),
                ];
            } catch (
Exception $methodNotFound) {
                
// ignored
            
}
        }

        return [
            
'method' => null,
            
'class'  => $registry->forClassName($className)->symbolAnnotations(),
        ];
    }

    
/**
     * @psalm-param class-string $className
     */
    
public static function getInlineAnnotations(string $classNamestring $methodName): array
    {
        return 
Registry::getInstance()->forMethod($className$methodName)->getInlineAnnotations();
    }

    
/** @psalm-param class-string $className */
    
public static function getBackupSettings(string $classNamestring $methodName): array
    {
        return [
            
'backupGlobals' => self::getBooleanAnnotationSetting(
                
$className,
                
$methodName,
                
'backupGlobals'
            
),
            
'backupStaticAttributes' => self::getBooleanAnnotationSetting(
                
$className,
                
$methodName,
                
'backupStaticAttributes'
            
),
        ];
    }

    
/**
     * @psalm-param class-string $className
     *
     * @return ExecutionOrderDependency[]
     */
    
public static function getDependencies(string $classNamestring $methodName): array
    {
        
$annotations self::parseTestMethodAnnotations(
            
$className,
            
$methodName
        
);

        
$dependsAnnotations $annotations['class']['depends'] ?? [];

        if (isset(
$annotations['method']['depends'])) {
            
$dependsAnnotations array_merge(
                
$dependsAnnotations,
                
$annotations['method']['depends']
            );
        }

        
// Normalize dependency name to className::methodName
        
$dependencies = [];

        foreach (
$dependsAnnotations as $value) {
            
$dependencies[] = ExecutionOrderDependency::createFromDependsAnnotation($className$value);
        }

        return 
array_unique($dependencies);
    }

    
/** @psalm-param class-string $className */
    
public static function getGroups(string $className, ?string $methodName ''): array
    {
        
$annotations self::parseTestMethodAnnotations(
            
$className,
            
$methodName
        
);

        
$groups = [];

        if (isset(
$annotations['method']['author'])) {
            
$groups[] = $annotations['method']['author'];
        } elseif (isset(
$annotations['class']['author'])) {
            
$groups[] = $annotations['class']['author'];
        }

        if (isset(
$annotations['class']['group'])) {
            
$groups[] = $annotations['class']['group'];
        }

        if (isset(
$annotations['method']['group'])) {
            
$groups[] = $annotations['method']['group'];
        }

        if (isset(
$annotations['class']['ticket'])) {
            
$groups[] = $annotations['class']['ticket'];
        }

        if (isset(
$annotations['method']['ticket'])) {
            
$groups[] = $annotations['method']['ticket'];
        }

        foreach ([
'method''class'] as $element) {
            foreach ([
'small''medium''large'] as $size) {
                if (isset(
$annotations[$element][$size])) {
                    
$groups[] = [$size];

                    break 
2;
                }
            }
        }

        foreach ([
'method''class'] as $element) {
            if (isset(
$annotations[$element]['covers'])) {
                foreach (
$annotations[$element]['covers'] as $coversTarget) {
                    
$groups[] = ['__phpunit_covers_' self::canonicalizeName($coversTarget)];
                }
            }

            if (isset(
$annotations[$element]['uses'])) {
                foreach (
$annotations[$element]['uses'] as $usesTarget) {
                    
$groups[] = ['__phpunit_uses_' self::canonicalizeName($usesTarget)];
                }
            }
        }

        return 
array_unique(array_merge([], ...$groups));
    }

    
/** @psalm-param class-string $className */
    
public static function getSize(string $className, ?string $methodName): int
    
{
        
$groups array_flip(self::getGroups($className$methodName));

        if (isset(
$groups['large'])) {
            return 
self::LARGE;
        }

        if (isset(
$groups['medium'])) {
            return 
self::MEDIUM;
        }

        if (isset(
$groups['small'])) {
            return 
self::SMALL;
        }

        return 
self::UNKNOWN;
    }

    
/** @psalm-param class-string $className */
    
public static function getProcessIsolationSettings(string $classNamestring $methodName): bool
    
{
        
$annotations self::parseTestMethodAnnotations(
            
$className,
            
$methodName
        
);

        return isset(
$annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess']);
    }

    
/** @psalm-param class-string $className */
    
public static function getClassProcessIsolationSettings(string $classNamestring $methodName): bool
    
{
        
$annotations self::parseTestMethodAnnotations(
            
$className,
            
$methodName
        
);

        return isset(
$annotations['class']['runClassInSeparateProcess']);
    }

    
/** @psalm-param class-string $className */
    
public static function getPreserveGlobalStateSettings(string $classNamestring $methodName): ?bool
    
{
        return 
self::getBooleanAnnotationSetting(
            
$className,
            
$methodName,
            
'preserveGlobalState'
        
);
    }

    
/** @psalm-param class-string $className */
    
public static function getHookMethods(string $className): array
    {
        if (!
class_exists($classNamefalse)) {
            return 
self::emptyHookMethodsArray();
        }

        if (!isset(
self::$hookMethods[$className])) {
            
self::$hookMethods[$className] = self::emptyHookMethodsArray();

            try {
                foreach ((new 
Reflection)->methodsInTestClass(new ReflectionClass($className)) as $method) {
                    
$docBlock Registry::getInstance()->forMethod($className$method->getName());

                    if (
$method->isStatic()) {
                        if (
$docBlock->isHookToBeExecutedBeforeClass()) {
                            
array_unshift(
                                
self::$hookMethods[$className]['beforeClass'],
                                
$method->getName()
                            );
                        }

                        if (
$docBlock->isHookToBeExecutedAfterClass()) {
                            
self::$hookMethods[$className]['afterClass'][] = $method->getName();
                        }
                    }

                    if (
$docBlock->isToBeExecutedBeforeTest()) {
                        
array_unshift(
                            
self::$hookMethods[$className]['before'],
                            
$method->getName()
                        );
                    }

                    if (
$docBlock->isToBeExecutedAsPreCondition()) {
                        
array_unshift(
                            
self::$hookMethods[$className]['preCondition'],
                            
$method->getName()
                        );
                    }

                    if (
$docBlock->isToBeExecutedAsPostCondition()) {
                        
self::$hookMethods[$className]['postCondition'][] = $method->getName();
                    }

                    if (
$docBlock->isToBeExecutedAfterTest()) {
                        
self::$hookMethods[$className]['after'][] = $method->getName();
                    }
                }
            } catch (
ReflectionException $e) {
            }
        }

        return 
self::$hookMethods[$className];
    }

    public static function 
isTestMethod(ReflectionMethod $method): bool
    
{
        if (!
$method->isPublic()) {
            return 
false;
        }

        if (
strpos($method->getName(), 'test') === 0) {
            return 
true;
        }

        return 
array_key_exists(
            
'test',
            
Registry::getInstance()->forMethod(
                
$method->getDeclaringClass()->getName(),
                
$method->getName()
            )
            ->
symbolAnnotations()
        );
    }

    
/**
     * @throws CodeCoverageException
     *
     * @psalm-param class-string $className
     */
    
private static function getLinesToBeCoveredOrUsed(string $classNamestring $methodNamestring $mode): array
    {
        
$annotations self::parseTestMethodAnnotations(
            
$className,
            
$methodName
        
);

        
$classShortcut null;

        if (!empty(
$annotations['class'][$mode 'DefaultClass'])) {
            if (
count($annotations['class'][$mode 'DefaultClass']) > 1) {
                throw new 
CodeCoverageException(
                    
sprintf(
                        
'More than one @%sClass annotation in class or interface "%s".',
                        
$mode,
                        
$className
                    
)
                );
            }

            
$classShortcut $annotations['class'][$mode 'DefaultClass'][0];
        }

        
$list $annotations['class'][$mode] ?? [];

        if (isset(
$annotations['method'][$mode])) {
            
$list array_merge($list$annotations['method'][$mode]);
        }

        
$codeUnits CodeUnitCollection::fromArray([]);
        
$mapper    = new Mapper;

        foreach (
array_unique($list) as $element) {
            if (
$classShortcut && strncmp($element'::'2) === 0) {
                
$element $classShortcut $element;
            }

            
$element preg_replace('/[s()]+$/'''$element);
            
$element explode(' '$element);
            
$element $element[0];

            if (
$mode === 'covers' && interface_exists($element)) {
                throw new 
InvalidCoversTargetException(
                    
sprintf(
                        
'Trying to @cover interface "%s".',
                        
$element
                    
)
                );
            }

            try {
                
$codeUnits $codeUnits->mergeWith($mapper->stringToCodeUnits($element));
            } catch (
InvalidCodeUnitException $e) {
                throw new 
InvalidCoversTargetException(
                    
sprintf(
                        
'"@%s %s" is invalid',
                        
$mode,
                        
$element
                    
),
                    
$e->getCode(),
                    
$e
                
);
            }
        }

        return 
$mapper->codeUnitsToSourceLines($codeUnits);
    }

    private static function 
emptyHookMethodsArray(): array
    {
        return [
            
'beforeClass'   => ['setUpBeforeClass'],
            
'before'        => ['setUp'],
            
'preCondition'  => ['assertPreConditions'],
            
'postCondition' => ['assertPostConditions'],
            
'after'         => ['tearDown'],
            
'afterClass'    => ['tearDownAfterClass'],
        ];
    }

    
/** @psalm-param class-string $className */
    
private static function getBooleanAnnotationSetting(string $className, ?string $methodNamestring $settingName): ?bool
    
{
        
$annotations self::parseTestMethodAnnotations(
            
$className,
            
$methodName
        
);

        if (isset(
$annotations['method'][$settingName])) {
            if (
$annotations['method'][$settingName][0] === 'enabled') {
                return 
true;
            }

            if (
$annotations['method'][$settingName][0] === 'disabled') {
                return 
false;
            }
        }

        if (isset(
$annotations['class'][$settingName])) {
            if (
$annotations['class'][$settingName][0] === 'enabled') {
                return 
true;
            }

            if (
$annotations['class'][$settingName][0] === 'disabled') {
                return 
false;
            }
        }

        return 
null;
    }

    
/**
     * Trims any extensions from version string that follows after
     * the <major>.<minor>[.<patch>] format.
     */
    
private static function sanitizeVersionNumber(string $version)
    {
        return 
preg_replace(
            
'/^(d+.d+(?:.d+)?).*$/',
            
'$1',
            
$version
        
);
    }

    private static function 
shouldCoversAnnotationBeUsed(array $annotations): bool
    
{
        if (isset(
$annotations['method']['coversNothing'])) {
            return 
false;
        }

        if (isset(
$annotations['method']['covers'])) {
            return 
true;
        }

        if (isset(
$annotations['class']['coversNothing'])) {
            return 
false;
        }

        return 
true;
    }

    
/**
     * Merge two arrays together.
     *
     * If an integer key exists in both arrays and preserveNumericKeys is false, the value
     * from the second array will be appended to the first array. If both values are arrays, they
     * are merged together, else the value of the second array overwrites the one of the first array.
     *
     * This implementation is copied from https://github.com/zendframework/zend-stdlib/blob/76b653c5e99b40eccf5966e3122c90615134ae46/src/ArrayUtils.php
     *
     * Zend Framework (http://framework.zend.com/)
     *
     * @see      http://github.com/zendframework/zf2 for the canonical source repository
     *
     * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
     * @license   http://framework.zend.com/license/new-bsd New BSD License
     */
    
private static function mergeArraysRecursively(array $a, array $b): array
    {
        foreach (
$b as $key => $value) {
            if (
array_key_exists($key$a)) {
                if (
is_int($key)) {
                    
$a[] = $value;
                } elseif (
is_array($value) && is_array($a[$key])) {
                    
$a[$key] = self::mergeArraysRecursively($a[$key], $value);
                } else {
                    
$a[$key] = $value;
                }
            } else {
                
$a[$key] = $value;
            }
        }

        return 
$a;
    }

    private static function 
canonicalizeName(string $name): string
    
{
        return 
strtolower(trim($name'\'));
    }
}
Онлайн: 0
Реклама