<?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 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 PHPUnit\Framework\DataProviderTestSuite;
use PHPUnit\Framework\Reorderable;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Util\Test as TestUtil;
use SebastianBergmann\RecursionContext\InvalidArgumentException;
/**
* @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 Exception
* @throws InvalidArgumentException
*/
public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDependencies, int $orderDefects, bool $isRootTestSuite = true): void
{
$allowedOrders = [
self::ORDER_DEFAULT,
self::ORDER_REVERSED,
self::ORDER_RANDOMIZED,
self::ORDER_DURATION,
self::ORDER_SIZE,
];
if (!in_array($order, $allowedOrders, true)) {
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, $allowedOrderDefects, true)) {
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, $orderDefects, false);
}
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 $suite, int $order, bool $resolveDependencies, int $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 InvalidArgumentException
*/
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 InvalidArgumentException
*/
function ($left, $right)
{
return $this->cmpDefectPriorityAndTime($left, $right);
},
);
return $tests;
}
private function sortByDuration(array $tests): array
{
usort(
$tests,
/**
* @throws InvalidArgumentException
*/
function ($left, $right)
{
return $this->cmpDuration($left, $right);
},
);
return $tests;
}
private function sortBySize(array $tests): array
{
usort(
$tests,
/**
* @throws InvalidArgumentException
*/
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 InvalidArgumentException
*/
private function cmpDefectPriorityAndTime(Test $a, Test $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 InvalidArgumentException
*/
private function cmpDuration(Test $a, Test $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 $a, Test $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($newTestOrder, array_splice($tests, $i, 1));
$i = 0;
} else {
$i++;
}
} while (!empty($tests) && ($i < count($tests)));
return array_merge($newTestOrder, $tests);
}
/**
* @throws InvalidArgumentException
*/
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;
}
}
About Section
NFC Pay was founded with a vision to transform the way people handle transactions. Our journey is defined by a commitment to innovation, security, and convenience. We strive to deliver seamless, user-friendly payment solutions that make everyday transactions effortless and secure. Our mission is to empower you to pay with ease and confidence, anytime, anywhere.
FAQ Section
Here are answers to some common questions about NFC Pay. We aim to provide clear and concise information to help you understand how our platform works and how it can benefit you. If you have any further inquiries, please don’t hesitate to contact our support team.
Download the app and sign up using your email or phone number, then complete the verification process.
Yes, we use advanced encryption and security protocols to protect your payment details.
Absolutely, you can link multiple debit or credit cards to your wallet.
Go to the transfer section, select the recipient, enter the amount, and authorize the transfer.
Use the “Forgot PIN” feature in the app to reset it following the provided instructions.
Sign up for a merchant account through the app and follow the setup instructions to start accepting payments.
Yes, you can view and track your payment status in the account dashboard