Вход Регистрация
Файл: vendor/phpunit/phpunit/src/Runner/TestSuiteSorter.php
Строк: 371
<?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 function 
array_diff;
use function 
array_merge;
use function 
array_reverse;
use function 
array_splice;
use function 
count;
use function 
in_array;
use function 
max;
use function 
shuffle;
use function 
usort;
use 
PHPUnitFrameworkDataProviderTestSuite;
use 
PHPUnitFrameworkReorderable;
use 
PHPUnitFrameworkTest;
use 
PHPUnitFrameworkTestCase;
use 
PHPUnitFrameworkTestSuite;
use 
PHPUnitUtilTest as TestUtil;

/**
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
 */
final class TestSuiteSorter
{
    
/**
     * @var int
     */
    
public const ORDER_DEFAULT 0;

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

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

    
/**
     * @var int
     */
    
public const ORDER_DEFECTS_FIRST 3;

    
/**
     * @var int
     */
    
public const ORDER_DURATION 4;

    
/**
     * Order tests by @size annotation 'small', 'medium', 'large'.
     *
     * @var int
     */
    
public const ORDER_SIZE 5;

    
/**
     * List of sorting weights for all test result codes. A higher number gives higher priority.
     */
    
private const DEFECT_SORT_WEIGHT = [
        
BaseTestRunner::STATUS_ERROR      => 6,
        
BaseTestRunner::STATUS_FAILURE    => 5,
        
BaseTestRunner::STATUS_WARNING    => 4,
        
BaseTestRunner::STATUS_INCOMPLETE => 3,
        
BaseTestRunner::STATUS_RISKY      => 2,
        
BaseTestRunner::STATUS_SKIPPED    => 1,
        
BaseTestRunner::STATUS_UNKNOWN    => 0,
    ];

    private const 
SIZE_SORT_WEIGHT = [
        
TestUtil::SMALL   => 1,
        
TestUtil::MEDIUM  => 2,
        
TestUtil::LARGE   => 3,
        
TestUtil::UNKNOWN => 4,
    ];

    
/**
     * @var array<string, int> Associative array of (string => DEFECT_SORT_WEIGHT) elements
     */
    
private $defectSortOrder = [];

    
/**
     * @var TestResultCache
     */
    
private $cache;

    
/**
     * @var array<string> A list of normalized names of tests before reordering
     */
    
private $originalExecutionOrder = [];

    
/**
     * @var array<string> A list of normalized names of tests affected by reordering
     */
    
private $executionOrder = [];

    public function 
__construct(?TestResultCache $cache null)
    {
        
$this->cache $cache ?? new NullTestResultCache;
    }

    
/**
     * @throws SebastianBergmannRecursionContextInvalidArgumentException
     * @throws Exception
     */
    
public function reorderTestsInSuite(Test $suiteint $orderbool $resolveDependenciesint $orderDefectsbool $isRootTestSuite true): void
    
{
        
$allowedOrders = [
            
self::ORDER_DEFAULT,
            
self::ORDER_REVERSED,
            
self::ORDER_RANDOMIZED,
            
self::ORDER_DURATION,
            
self::ORDER_SIZE,
        ];

        if (!
in_array($order$allowedOrderstrue)) {
            throw new 
Exception(
                
'$order must be one of TestSuiteSorter::ORDER_[DEFAULT|REVERSED|RANDOMIZED|DURATION|SIZE]'
            
);
        }

        
$allowedOrderDefects = [
            
self::ORDER_DEFAULT,
            
self::ORDER_DEFECTS_FIRST,
        ];

        if (!
in_array($orderDefects$allowedOrderDefectstrue)) {
            throw new 
Exception(
                
'$orderDefects must be one of TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_DEFECTS_FIRST'
            
);
        }

        if (
$isRootTestSuite) {
            
$this->originalExecutionOrder $this->calculateTestExecutionOrder($suite);
        }

        if (
$suite instanceof TestSuite) {
            foreach (
$suite as $_suite) {
                
$this->reorderTestsInSuite($_suite$order$resolveDependencies$orderDefectsfalse);
            }

            if (
$orderDefects === self::ORDER_DEFECTS_FIRST) {
                
$this->addSuiteToDefectSortOrder($suite);
            }

            
$this->sort($suite$order$resolveDependencies$orderDefects);
        }

        if (
$isRootTestSuite) {
            
$this->executionOrder $this->calculateTestExecutionOrder($suite);
        }
    }

    public function 
getOriginalExecutionOrder(): array
    {
        return 
$this->originalExecutionOrder;
    }

    public function 
getExecutionOrder(): array
    {
        return 
$this->executionOrder;
    }

    private function 
sort(TestSuite $suiteint $orderbool $resolveDependenciesint $orderDefects): void
    
{
        if (empty(
$suite->tests())) {
            return;
        }

        if (
$order === self::ORDER_REVERSED) {
            
$suite->setTests($this->reverse($suite->tests()));
        } elseif (
$order === self::ORDER_RANDOMIZED) {
            
$suite->setTests($this->randomize($suite->tests()));
        } elseif (
$order === self::ORDER_DURATION && $this->cache !== null) {
            
$suite->setTests($this->sortByDuration($suite->tests()));
        } elseif (
$order === self::ORDER_SIZE) {
            
$suite->setTests($this->sortBySize($suite->tests()));
        }

        if (
$orderDefects === self::ORDER_DEFECTS_FIRST && $this->cache !== null) {
            
$suite->setTests($this->sortDefectsFirst($suite->tests()));
        }

        if (
$resolveDependencies && !($suite instanceof DataProviderTestSuite)) {
            
/** @var TestCase[] $tests */
            
$tests $suite->tests();

            
$suite->setTests($this->resolveDependencies($tests));
        }
    }

    
/**
     * @throws SebastianBergmannRecursionContextInvalidArgumentException
     */
    
private function addSuiteToDefectSortOrder(TestSuite $suite): void
    
{
        
$max 0;

        foreach (
$suite->tests() as $test) {
            if (!
$test instanceof Reorderable) {
                continue;
            }

            if (!isset(
$this->defectSortOrder[$test->sortId()])) {
                
$this->defectSortOrder[$test->sortId()] = self::DEFECT_SORT_WEIGHT[$this->cache->getState($test->sortId())];
                
$max                                    max($max$this->defectSortOrder[$test->sortId()]);
            }
        }

        
$this->defectSortOrder[$suite->sortId()] = $max;
    }

    private function 
reverse(array $tests): array
    {
        return 
array_reverse($tests);
    }

    private function 
randomize(array $tests): array
    {
        
shuffle($tests);

        return 
$tests;
    }

    private function 
sortDefectsFirst(array $tests): array
    {
        
usort(
            
$tests,
            
/**
             * @throws SebastianBergmannRecursionContextInvalidArgumentException
             */
            
function ($left$right)
            {
                return 
$this->cmpDefectPriorityAndTime($left$right);
            }
        );

        return 
$tests;
    }

    private function 
sortByDuration(array $tests): array
    {
        
usort(
            
$tests,
            
/**
             * @throws SebastianBergmannRecursionContextInvalidArgumentException
             */
            
function ($left$right)
            {
                return 
$this->cmpDuration($left$right);
            }
        );

        return 
$tests;
    }

    private function 
sortBySize(array $tests): array
    {
        
usort(
            
$tests,
            
/**
             * @throws SebastianBergmannRecursionContextInvalidArgumentException
             */
            
function ($left$right)
            {
                return 
$this->cmpSize($left$right);
            }
        );

        return 
$tests;
    }

    
/**
     * Comparator callback function to sort tests for "reach failure as fast as possible".
     *
     * 1. sort tests by defect weight defined in self::DEFECT_SORT_WEIGHT
     * 2. when tests are equally defective, sort the fastest to the front
     * 3. do not reorder successful tests
     *
     * @throws SebastianBergmannRecursionContextInvalidArgumentException
     */
    
private function cmpDefectPriorityAndTime(Test $aTest $b): int
    
{
        if (!(
$a instanceof Reorderable && $b instanceof Reorderable)) {
            return 
0;
        }

        
$priorityA $this->defectSortOrder[$a->sortId()] ?? 0;
        
$priorityB $this->defectSortOrder[$b->sortId()] ?? 0;

        if (
$priorityB <=> $priorityA) {
            
// Sort defect weight descending
            
return $priorityB <=> $priorityA;
        }

        if (
$priorityA || $priorityB) {
            return 
$this->cmpDuration($a$b);
        }

        
// do not change execution order
        
return 0;
    }

    
/**
     * Compares test duration for sorting tests by duration ascending.
     *
     * @throws SebastianBergmannRecursionContextInvalidArgumentException
     */
    
private function cmpDuration(Test $aTest $b): int
    
{
        if (!(
$a instanceof Reorderable && $b instanceof Reorderable)) {
            return 
0;
        }

        return 
$this->cache->getTime($a->sortId()) <=> $this->cache->getTime($b->sortId());
    }

    
/**
     * Compares test size for sorting tests small->medium->large->unknown.
     */
    
private function cmpSize(Test $aTest $b): int
    
{
        
$sizeA = ($a instanceof TestCase || $a instanceof DataProviderTestSuite)
            ? 
$a->getSize()
            : 
TestUtil::UNKNOWN;
        
$sizeB = ($b instanceof TestCase || $b instanceof DataProviderTestSuite)
            ? 
$b->getSize()
            : 
TestUtil::UNKNOWN;

        return 
self::SIZE_SORT_WEIGHT[$sizeA] <=> self::SIZE_SORT_WEIGHT[$sizeB];
    }

    
/**
     * Reorder Tests within a TestCase in such a way as to resolve as many dependencies as possible.
     * The algorithm will leave the tests in original running order when it can.
     * For more details see the documentation for test dependencies.
     *
     * Short description of algorithm:
     * 1. Pick the next Test from remaining tests to be checked for dependencies.
     * 2. If the test has no dependencies: mark done, start again from the top
     * 3. If the test has dependencies but none left to do: mark done, start again from the top
     * 4. When we reach the end add any leftover tests to the end. These will be marked 'skipped' during execution.
     *
     * @param array<DataProviderTestSuite|TestCase> $tests
     *
     * @return array<DataProviderTestSuite|TestCase>
     */
    
private function resolveDependencies(array $tests): array
    {
        
$newTestOrder = [];
        
$i            0;
        
$provided     = [];

        do {
            if ([] === 
array_diff($tests[$i]->requires(), $provided)) {
                
$provided     array_merge($provided$tests[$i]->provides());
                
$newTestOrder array_merge($newTestOrderarray_splice($tests$i1));
                
$i            0;
            } else {
                
$i++;
            }
        } while (!empty(
$tests) && ($i count($tests)));

        return 
array_merge($newTestOrder$tests);
    }

    
/**
     * @throws SebastianBergmannRecursionContextInvalidArgumentException
     */
    
private function calculateTestExecutionOrder(Test $suite): array
    {
        
$tests = [];

        if (
$suite instanceof TestSuite) {
            foreach (
$suite->tests() as $test) {
                if (!
$test instanceof TestSuite && $test instanceof Reorderable) {
                    
$tests[] = $test->sortId();
                } else {
                    
$tests array_merge($tests$this->calculateTestExecutionOrder($test));
                }
            }
        }

        return 
$tests;
    }
}
Онлайн: 1
Реклама