You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
922 lines
26 KiB
922 lines
26 KiB
<?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\Framework;
|
|
|
|
use const PHP_EOL;
|
|
use function array_keys;
|
|
use function array_map;
|
|
use function array_merge;
|
|
use function array_slice;
|
|
use function array_unique;
|
|
use function basename;
|
|
use function call_user_func;
|
|
use function class_exists;
|
|
use function count;
|
|
use function dirname;
|
|
use function get_declared_classes;
|
|
use function implode;
|
|
use function is_bool;
|
|
use function is_callable;
|
|
use function is_file;
|
|
use function is_object;
|
|
use function is_string;
|
|
use function method_exists;
|
|
use function preg_match;
|
|
use function preg_quote;
|
|
use function sprintf;
|
|
use function strpos;
|
|
use function substr;
|
|
use Iterator;
|
|
use IteratorAggregate;
|
|
use PHPUnit\Runner\BaseTestRunner;
|
|
use PHPUnit\Runner\Filter\Factory;
|
|
use PHPUnit\Runner\PhptTestCase;
|
|
use PHPUnit\Util\FileLoader;
|
|
use PHPUnit\Util\Reflection;
|
|
use PHPUnit\Util\Test as TestUtil;
|
|
use ReflectionClass;
|
|
use ReflectionException;
|
|
use ReflectionMethod;
|
|
use Throwable;
|
|
|
|
/**
|
|
* @template-implements IteratorAggregate<int, Test>
|
|
*
|
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
|
*/
|
|
class TestSuite implements IteratorAggregate, Reorderable, SelfDescribing, Test
|
|
{
|
|
/**
|
|
* Enable or disable the backup and restoration of the $GLOBALS array.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $backupGlobals;
|
|
|
|
/**
|
|
* Enable or disable the backup and restoration of static attributes.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $backupStaticAttributes;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $runTestInSeparateProcess = false;
|
|
|
|
/**
|
|
* The name of the test suite.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $name = '';
|
|
|
|
/**
|
|
* The test groups of the test suite.
|
|
*
|
|
* @psalm-var array<string,list<Test>>
|
|
*/
|
|
protected $groups = [];
|
|
|
|
/**
|
|
* The tests in the test suite.
|
|
*
|
|
* @var Test[]
|
|
*/
|
|
protected $tests = [];
|
|
|
|
/**
|
|
* The number of tests in the test suite.
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $numTests = -1;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $testCase = false;
|
|
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
protected $foundClasses = [];
|
|
|
|
/**
|
|
* @var null|list<ExecutionOrderDependency>
|
|
*/
|
|
protected $providedTests;
|
|
|
|
/**
|
|
* @var null|list<ExecutionOrderDependency>
|
|
*/
|
|
protected $requiredTests;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
private $beStrictAboutChangesToGlobalState;
|
|
|
|
/**
|
|
* @var Factory
|
|
*/
|
|
private $iteratorFilter;
|
|
|
|
/**
|
|
* @var int
|
|
*/
|
|
private $declaredClassesPointer;
|
|
|
|
/**
|
|
* @psalm-var array<int,string>
|
|
*/
|
|
private $warnings = [];
|
|
|
|
/**
|
|
* Constructs a new TestSuite.
|
|
*
|
|
* - PHPUnit\Framework\TestSuite() constructs an empty TestSuite.
|
|
*
|
|
* - PHPUnit\Framework\TestSuite(ReflectionClass) constructs a
|
|
* TestSuite from the given class.
|
|
*
|
|
* - PHPUnit\Framework\TestSuite(ReflectionClass, String)
|
|
* constructs a TestSuite from the given class with the given
|
|
* name.
|
|
*
|
|
* - PHPUnit\Framework\TestSuite(String) either constructs a
|
|
* TestSuite from the given class (if the passed string is the
|
|
* name of an existing class) or constructs an empty TestSuite
|
|
* with the given name.
|
|
*
|
|
* @param ReflectionClass|string $theClass
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function __construct($theClass = '', string $name = '')
|
|
{
|
|
if (!is_string($theClass) && !$theClass instanceof ReflectionClass) {
|
|
throw InvalidArgumentException::create(
|
|
1,
|
|
'ReflectionClass object or string',
|
|
);
|
|
}
|
|
|
|
$this->declaredClassesPointer = count(get_declared_classes());
|
|
|
|
if (!$theClass instanceof ReflectionClass) {
|
|
if (class_exists($theClass, true)) {
|
|
if ($name === '') {
|
|
$name = $theClass;
|
|
}
|
|
|
|
try {
|
|
$theClass = new ReflectionClass($theClass);
|
|
} catch (ReflectionException $e) {
|
|
throw new Exception(
|
|
$e->getMessage(),
|
|
$e->getCode(),
|
|
$e,
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
} else {
|
|
$this->setName($theClass);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!$theClass->isSubclassOf(TestCase::class)) {
|
|
$this->setName((string) $theClass);
|
|
|
|
return;
|
|
}
|
|
|
|
if ($name !== '') {
|
|
$this->setName($name);
|
|
} else {
|
|
$this->setName($theClass->getName());
|
|
}
|
|
|
|
$constructor = $theClass->getConstructor();
|
|
|
|
if ($constructor !== null &&
|
|
!$constructor->isPublic()) {
|
|
$this->addTest(
|
|
new WarningTestCase(
|
|
sprintf(
|
|
'Class "%s" has no public constructor.',
|
|
$theClass->getName(),
|
|
),
|
|
),
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
foreach ((new Reflection)->publicMethodsInTestClass($theClass) as $method) {
|
|
if (!TestUtil::isTestMethod($method)) {
|
|
continue;
|
|
}
|
|
|
|
$this->addTestMethod($theClass, $method);
|
|
}
|
|
|
|
if (empty($this->tests)) {
|
|
$this->addTest(
|
|
new WarningTestCase(
|
|
sprintf(
|
|
'No tests found in class "%s".',
|
|
$theClass->getName(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
$this->testCase = true;
|
|
}
|
|
|
|
/**
|
|
* Returns a string representation of the test suite.
|
|
*/
|
|
public function toString(): string
|
|
{
|
|
return $this->getName();
|
|
}
|
|
|
|
/**
|
|
* Adds a test to the suite.
|
|
*
|
|
* @param array $groups
|
|
*/
|
|
public function addTest(Test $test, $groups = []): void
|
|
{
|
|
try {
|
|
$class = new ReflectionClass($test);
|
|
// @codeCoverageIgnoreStart
|
|
} catch (ReflectionException $e) {
|
|
throw new Exception(
|
|
$e->getMessage(),
|
|
$e->getCode(),
|
|
$e,
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
if (!$class->isAbstract()) {
|
|
$this->tests[] = $test;
|
|
$this->clearCaches();
|
|
|
|
if ($test instanceof self && empty($groups)) {
|
|
$groups = $test->getGroups();
|
|
}
|
|
|
|
if ($this->containsOnlyVirtualGroups($groups)) {
|
|
$groups[] = 'default';
|
|
}
|
|
|
|
foreach ($groups as $group) {
|
|
if (!isset($this->groups[$group])) {
|
|
$this->groups[$group] = [$test];
|
|
} else {
|
|
$this->groups[$group][] = $test;
|
|
}
|
|
}
|
|
|
|
if ($test instanceof TestCase) {
|
|
$test->setGroups($groups);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the tests from the given class to the suite.
|
|
*
|
|
* @psalm-param object|class-string $testClass
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function addTestSuite($testClass): void
|
|
{
|
|
if (!(is_object($testClass) || (is_string($testClass) && class_exists($testClass)))) {
|
|
throw InvalidArgumentException::create(
|
|
1,
|
|
'class name or object',
|
|
);
|
|
}
|
|
|
|
if (!is_object($testClass)) {
|
|
try {
|
|
$testClass = new ReflectionClass($testClass);
|
|
// @codeCoverageIgnoreStart
|
|
} catch (ReflectionException $e) {
|
|
throw new Exception(
|
|
$e->getMessage(),
|
|
$e->getCode(),
|
|
$e,
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
if ($testClass instanceof self) {
|
|
$this->addTest($testClass);
|
|
} elseif ($testClass instanceof ReflectionClass) {
|
|
$suiteMethod = false;
|
|
|
|
if (!$testClass->isAbstract() && $testClass->hasMethod(BaseTestRunner::SUITE_METHODNAME)) {
|
|
try {
|
|
$method = $testClass->getMethod(
|
|
BaseTestRunner::SUITE_METHODNAME,
|
|
);
|
|
// @codeCoverageIgnoreStart
|
|
} catch (ReflectionException $e) {
|
|
throw new Exception(
|
|
$e->getMessage(),
|
|
$e->getCode(),
|
|
$e,
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
if ($method->isStatic()) {
|
|
$this->addTest(
|
|
$method->invoke(null, $testClass->getName()),
|
|
);
|
|
|
|
$suiteMethod = true;
|
|
}
|
|
}
|
|
|
|
if (!$suiteMethod && !$testClass->isAbstract() && $testClass->isSubclassOf(TestCase::class)) {
|
|
$this->addTest(new self($testClass));
|
|
}
|
|
} else {
|
|
throw new Exception;
|
|
}
|
|
}
|
|
|
|
public function addWarning(string $warning): void
|
|
{
|
|
$this->warnings[] = $warning;
|
|
}
|
|
|
|
/**
|
|
* Wraps both <code>addTest()</code> and <code>addTestSuite</code>
|
|
* as well as the separate import statements for the user's convenience.
|
|
*
|
|
* If the named file cannot be read or there are no new tests that can be
|
|
* added, a <code>PHPUnit\Framework\WarningTestCase</code> will be created instead,
|
|
* leaving the current test run untouched.
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function addTestFile(string $filename): void
|
|
{
|
|
if (is_file($filename) && substr($filename, -5) === '.phpt') {
|
|
$this->addTest(new PhptTestCase($filename));
|
|
|
|
$this->declaredClassesPointer = count(get_declared_classes());
|
|
|
|
return;
|
|
}
|
|
|
|
$numTests = count($this->tests);
|
|
|
|
// The given file may contain further stub classes in addition to the
|
|
// test class itself. Figure out the actual test class.
|
|
$filename = FileLoader::checkAndLoad($filename);
|
|
$newClasses = array_slice(get_declared_classes(), $this->declaredClassesPointer);
|
|
|
|
// The diff is empty in case a parent class (with test methods) is added
|
|
// AFTER a child class that inherited from it. To account for that case,
|
|
// accumulate all discovered classes, so the parent class may be found in
|
|
// a later invocation.
|
|
if (!empty($newClasses)) {
|
|
// On the assumption that test classes are defined first in files,
|
|
// process discovered classes in approximate LIFO order, so as to
|
|
// avoid unnecessary reflection.
|
|
$this->foundClasses = array_merge($newClasses, $this->foundClasses);
|
|
$this->declaredClassesPointer = count(get_declared_classes());
|
|
}
|
|
|
|
// The test class's name must match the filename, either in full, or as
|
|
// a PEAR/PSR-0 prefixed short name ('NameSpace_ShortName'), or as a
|
|
// PSR-1 local short name ('NameSpace\ShortName'). The comparison must be
|
|
// anchored to prevent false-positive matches (e.g., 'OtherShortName').
|
|
$shortName = basename($filename, '.php');
|
|
$shortNameRegEx = '/(?:^|_|\\\\)' . preg_quote($shortName, '/') . '$/';
|
|
|
|
foreach ($this->foundClasses as $i => $className) {
|
|
if (preg_match($shortNameRegEx, $className)) {
|
|
try {
|
|
$class = new ReflectionClass($className);
|
|
// @codeCoverageIgnoreStart
|
|
} catch (ReflectionException $e) {
|
|
throw new Exception(
|
|
$e->getMessage(),
|
|
$e->getCode(),
|
|
$e,
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
if ($class->getFileName() == $filename) {
|
|
$newClasses = [$className];
|
|
unset($this->foundClasses[$i]);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($newClasses as $className) {
|
|
try {
|
|
$class = new ReflectionClass($className);
|
|
// @codeCoverageIgnoreStart
|
|
} catch (ReflectionException $e) {
|
|
throw new Exception(
|
|
$e->getMessage(),
|
|
$e->getCode(),
|
|
$e,
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
if (dirname($class->getFileName()) === __DIR__) {
|
|
continue;
|
|
}
|
|
|
|
if ($class->isAbstract() && $class->isSubclassOf(TestCase::class)) {
|
|
$this->addWarning(
|
|
sprintf(
|
|
'Abstract test case classes with "Test" suffix are deprecated (%s)',
|
|
$class->getName(),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (!$class->isAbstract()) {
|
|
if ($class->hasMethod(BaseTestRunner::SUITE_METHODNAME)) {
|
|
try {
|
|
$method = $class->getMethod(
|
|
BaseTestRunner::SUITE_METHODNAME,
|
|
);
|
|
// @codeCoverageIgnoreStart
|
|
} catch (ReflectionException $e) {
|
|
throw new Exception(
|
|
$e->getMessage(),
|
|
$e->getCode(),
|
|
$e,
|
|
);
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
if ($method->isStatic()) {
|
|
$this->addTest($method->invoke(null, $className));
|
|
}
|
|
} elseif ($class->implementsInterface(Test::class)) {
|
|
// Do we have modern namespacing ('Foo\Bar\WhizBangTest') or old-school namespacing ('Foo_Bar_WhizBangTest')?
|
|
$isPsr0 = (!$class->inNamespace()) && (strpos($class->getName(), '_') !== false);
|
|
$expectedClassName = $isPsr0 ? $className : $shortName;
|
|
|
|
if (($pos = strpos($expectedClassName, '.')) !== false) {
|
|
$expectedClassName = substr(
|
|
$expectedClassName,
|
|
0,
|
|
$pos,
|
|
);
|
|
}
|
|
|
|
if ($class->getShortName() !== $expectedClassName) {
|
|
$this->addWarning(
|
|
sprintf(
|
|
"Test case class not matching filename is deprecated\n in %s\n Class name was '%s', expected '%s'",
|
|
$filename,
|
|
$class->getShortName(),
|
|
$expectedClassName,
|
|
),
|
|
);
|
|
}
|
|
|
|
$this->addTestSuite($class);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count($this->tests) > ++$numTests) {
|
|
$this->addWarning(
|
|
sprintf(
|
|
"Multiple test case classes per file is deprecated\n in %s",
|
|
$filename,
|
|
),
|
|
);
|
|
}
|
|
|
|
$this->numTests = -1;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for addTestFile() that adds multiple test files.
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function addTestFiles(iterable $fileNames): void
|
|
{
|
|
foreach ($fileNames as $filename) {
|
|
$this->addTestFile((string) $filename);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Counts the number of test cases that will be run by this test.
|
|
*
|
|
* @todo refactor usage of numTests in DefaultResultPrinter
|
|
*/
|
|
public function count(): int
|
|
{
|
|
$this->numTests = 0;
|
|
|
|
foreach ($this as $test) {
|
|
$this->numTests += count($test);
|
|
}
|
|
|
|
return $this->numTests;
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the suite.
|
|
*/
|
|
public function getName(): string
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
/**
|
|
* Returns the test groups of the suite.
|
|
*
|
|
* @psalm-return list<string>
|
|
*/
|
|
public function getGroups(): array
|
|
{
|
|
return array_map(
|
|
static function ($key): string
|
|
{
|
|
return (string) $key;
|
|
},
|
|
array_keys($this->groups),
|
|
);
|
|
}
|
|
|
|
public function getGroupDetails(): array
|
|
{
|
|
return $this->groups;
|
|
}
|
|
|
|
/**
|
|
* Set tests groups of the test case.
|
|
*/
|
|
public function setGroupDetails(array $groups): void
|
|
{
|
|
$this->groups = $groups;
|
|
}
|
|
|
|
/**
|
|
* Runs the tests and collects their result in a TestResult.
|
|
*
|
|
* @throws \PHPUnit\Framework\CodeCoverageException
|
|
* @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
|
|
* @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException
|
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
|
* @throws Warning
|
|
*/
|
|
public function run(TestResult $result = null): TestResult
|
|
{
|
|
if ($result === null) {
|
|
$result = $this->createResult();
|
|
}
|
|
|
|
if (count($this) === 0) {
|
|
return $result;
|
|
}
|
|
|
|
/** @psalm-var class-string $className */
|
|
$className = $this->name;
|
|
$hookMethods = TestUtil::getHookMethods($className);
|
|
|
|
$result->startTestSuite($this);
|
|
|
|
$test = null;
|
|
|
|
if ($this->testCase && class_exists($this->name, false)) {
|
|
try {
|
|
foreach ($hookMethods['beforeClass'] as $beforeClassMethod) {
|
|
if (method_exists($this->name, $beforeClassMethod)) {
|
|
if ($missingRequirements = TestUtil::getMissingRequirements($this->name, $beforeClassMethod)) {
|
|
$this->markTestSuiteSkipped(implode(PHP_EOL, $missingRequirements));
|
|
}
|
|
|
|
call_user_func([$this->name, $beforeClassMethod]);
|
|
}
|
|
}
|
|
} catch (SkippedTestError|SkippedTestSuiteError $error) {
|
|
foreach ($this->tests() as $test) {
|
|
$result->startTest($test);
|
|
$result->addFailure($test, $error, 0);
|
|
$result->endTest($test, 0);
|
|
}
|
|
|
|
$result->endTestSuite($this);
|
|
|
|
return $result;
|
|
} catch (Throwable $t) {
|
|
$errorAdded = false;
|
|
|
|
foreach ($this->tests() as $test) {
|
|
if ($result->shouldStop()) {
|
|
break;
|
|
}
|
|
|
|
$result->startTest($test);
|
|
|
|
if (!$errorAdded) {
|
|
$result->addError($test, $t, 0);
|
|
|
|
$errorAdded = true;
|
|
} else {
|
|
$result->addFailure(
|
|
$test,
|
|
new SkippedTestError('Test skipped because of an error in hook method'),
|
|
0,
|
|
);
|
|
}
|
|
|
|
$result->endTest($test, 0);
|
|
}
|
|
|
|
$result->endTestSuite($this);
|
|
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
foreach ($this as $test) {
|
|
if ($result->shouldStop()) {
|
|
break;
|
|
}
|
|
|
|
if ($test instanceof TestCase || $test instanceof self) {
|
|
$test->setBeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState);
|
|
$test->setBackupGlobals($this->backupGlobals);
|
|
$test->setBackupStaticAttributes($this->backupStaticAttributes);
|
|
$test->setRunTestInSeparateProcess($this->runTestInSeparateProcess);
|
|
}
|
|
|
|
$test->run($result);
|
|
}
|
|
|
|
if ($this->testCase && class_exists($this->name, false)) {
|
|
foreach ($hookMethods['afterClass'] as $afterClassMethod) {
|
|
if (method_exists($this->name, $afterClassMethod)) {
|
|
try {
|
|
call_user_func([$this->name, $afterClassMethod]);
|
|
} catch (Throwable $t) {
|
|
$message = "Exception in {$this->name}::{$afterClassMethod}" . PHP_EOL . $t->getMessage();
|
|
$error = new SyntheticError($message, 0, $t->getFile(), $t->getLine(), $t->getTrace());
|
|
|
|
$placeholderTest = clone $test;
|
|
$placeholderTest->setName($afterClassMethod);
|
|
|
|
$result->startTest($placeholderTest);
|
|
$result->addFailure($placeholderTest, $error, 0);
|
|
$result->endTest($placeholderTest, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$result->endTestSuite($this);
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void
|
|
{
|
|
$this->runTestInSeparateProcess = $runTestInSeparateProcess;
|
|
}
|
|
|
|
public function setName(string $name): void
|
|
{
|
|
$this->name = $name;
|
|
}
|
|
|
|
/**
|
|
* Returns the tests as an enumeration.
|
|
*
|
|
* @return Test[]
|
|
*/
|
|
public function tests(): array
|
|
{
|
|
return $this->tests;
|
|
}
|
|
|
|
/**
|
|
* Set tests of the test suite.
|
|
*
|
|
* @param Test[] $tests
|
|
*/
|
|
public function setTests(array $tests): void
|
|
{
|
|
$this->tests = $tests;
|
|
}
|
|
|
|
/**
|
|
* Mark the test suite as skipped.
|
|
*
|
|
* @param string $message
|
|
*
|
|
* @throws SkippedTestSuiteError
|
|
*
|
|
* @psalm-return never-return
|
|
*/
|
|
public function markTestSuiteSkipped($message = ''): void
|
|
{
|
|
throw new SkippedTestSuiteError($message);
|
|
}
|
|
|
|
/**
|
|
* @param bool $beStrictAboutChangesToGlobalState
|
|
*/
|
|
public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState): void
|
|
{
|
|
if (null === $this->beStrictAboutChangesToGlobalState && is_bool($beStrictAboutChangesToGlobalState)) {
|
|
$this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param bool $backupGlobals
|
|
*/
|
|
public function setBackupGlobals($backupGlobals): void
|
|
{
|
|
if (null === $this->backupGlobals && is_bool($backupGlobals)) {
|
|
$this->backupGlobals = $backupGlobals;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param bool $backupStaticAttributes
|
|
*/
|
|
public function setBackupStaticAttributes($backupStaticAttributes): void
|
|
{
|
|
if (null === $this->backupStaticAttributes && is_bool($backupStaticAttributes)) {
|
|
$this->backupStaticAttributes = $backupStaticAttributes;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an iterator for this test suite.
|
|
*/
|
|
public function getIterator(): Iterator
|
|
{
|
|
$iterator = new TestSuiteIterator($this);
|
|
|
|
if ($this->iteratorFilter !== null) {
|
|
$iterator = $this->iteratorFilter->factory($iterator, $this);
|
|
}
|
|
|
|
return $iterator;
|
|
}
|
|
|
|
public function injectFilter(Factory $filter): void
|
|
{
|
|
$this->iteratorFilter = $filter;
|
|
|
|
foreach ($this as $test) {
|
|
if ($test instanceof self) {
|
|
$test->injectFilter($filter);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @psalm-return array<int,string>
|
|
*/
|
|
public function warnings(): array
|
|
{
|
|
return array_unique($this->warnings);
|
|
}
|
|
|
|
/**
|
|
* @return list<ExecutionOrderDependency>
|
|
*/
|
|
public function provides(): array
|
|
{
|
|
if ($this->providedTests === null) {
|
|
$this->providedTests = [];
|
|
|
|
if (is_callable($this->sortId(), true)) {
|
|
$this->providedTests[] = new ExecutionOrderDependency($this->sortId());
|
|
}
|
|
|
|
foreach ($this->tests as $test) {
|
|
if (!($test instanceof Reorderable)) {
|
|
// @codeCoverageIgnoreStart
|
|
continue;
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
$this->providedTests = ExecutionOrderDependency::mergeUnique($this->providedTests, $test->provides());
|
|
}
|
|
}
|
|
|
|
return $this->providedTests;
|
|
}
|
|
|
|
/**
|
|
* @return list<ExecutionOrderDependency>
|
|
*/
|
|
public function requires(): array
|
|
{
|
|
if ($this->requiredTests === null) {
|
|
$this->requiredTests = [];
|
|
|
|
foreach ($this->tests as $test) {
|
|
if (!($test instanceof Reorderable)) {
|
|
// @codeCoverageIgnoreStart
|
|
continue;
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
$this->requiredTests = ExecutionOrderDependency::mergeUnique(
|
|
ExecutionOrderDependency::filterInvalid($this->requiredTests),
|
|
$test->requires(),
|
|
);
|
|
}
|
|
|
|
$this->requiredTests = ExecutionOrderDependency::diff($this->requiredTests, $this->provides());
|
|
}
|
|
|
|
return $this->requiredTests;
|
|
}
|
|
|
|
public function sortId(): string
|
|
{
|
|
return $this->getName() . '::class';
|
|
}
|
|
|
|
/**
|
|
* Creates a default TestResult object.
|
|
*/
|
|
protected function createResult(): TestResult
|
|
{
|
|
return new TestResult;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method): void
|
|
{
|
|
$methodName = $method->getName();
|
|
|
|
$test = (new TestBuilder)->build($class, $methodName);
|
|
|
|
if ($test instanceof TestCase || $test instanceof DataProviderTestSuite) {
|
|
$test->setDependencies(
|
|
TestUtil::getDependencies($class->getName(), $methodName),
|
|
);
|
|
}
|
|
|
|
$this->addTest(
|
|
$test,
|
|
TestUtil::getGroups($class->getName(), $methodName),
|
|
);
|
|
}
|
|
|
|
private function clearCaches(): void
|
|
{
|
|
$this->numTests = -1;
|
|
$this->providedTests = null;
|
|
$this->requiredTests = null;
|
|
}
|
|
|
|
private function containsOnlyVirtualGroups(array $groups): bool
|
|
{
|
|
foreach ($groups as $group) {
|
|
if (strpos($group, '__phpunit_') !== 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|