132 lines
3.7 KiB
PHP
132 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace ParaTest\Parser;
|
|
|
|
use InvalidArgumentException;
|
|
use PHPUnit\Framework\Assert;
|
|
use PHPUnit\Framework\TestCase;
|
|
use PHPUnit\Runner\Exception;
|
|
use PHPUnit\Runner\StandardTestSuiteLoader;
|
|
use PHPUnit\Util\Test as TestUtil;
|
|
use ReflectionClass;
|
|
use ReflectionMethod;
|
|
|
|
use function array_diff;
|
|
use function assert;
|
|
use function get_declared_classes;
|
|
use function is_file;
|
|
use function realpath;
|
|
|
|
/** @internal */
|
|
final class Parser
|
|
{
|
|
/** @var ReflectionClass<TestCase> */
|
|
private $refl;
|
|
|
|
/** @var ReflectionClass<TestCase>[] */
|
|
private static $alreadyLoadedSources = [];
|
|
|
|
/** @var class-string[] */
|
|
private static $externalClassesFound = [];
|
|
|
|
public function __construct(string $srcPath)
|
|
{
|
|
if (! is_file($srcPath)) {
|
|
throw new InvalidArgumentException('file not found: ' . $srcPath);
|
|
}
|
|
|
|
$srcPath = realpath($srcPath);
|
|
assert($srcPath !== false);
|
|
|
|
if (! isset(self::$alreadyLoadedSources[$srcPath])) {
|
|
$declaredClasses = get_declared_classes();
|
|
try {
|
|
$refClass = (new StandardTestSuiteLoader())->load($srcPath);
|
|
if (! $refClass->isSubclassOf(TestCase::class)) {
|
|
throw new NoClassInFileException($srcPath);
|
|
}
|
|
|
|
self::$alreadyLoadedSources[$srcPath] = $refClass;
|
|
|
|
self::$externalClassesFound += array_diff(
|
|
get_declared_classes(),
|
|
$declaredClasses,
|
|
[self::$alreadyLoadedSources[$srcPath]->getName()],
|
|
);
|
|
} catch (Exception $exception) {
|
|
self::$externalClassesFound += array_diff(get_declared_classes(), $declaredClasses);
|
|
|
|
$reflFound = null;
|
|
foreach (self::$externalClassesFound as $newClass) {
|
|
$refClass = new ReflectionClass($newClass);
|
|
if ($refClass->getFileName() !== $srcPath) {
|
|
continue;
|
|
}
|
|
|
|
$reflFound = $refClass;
|
|
break;
|
|
}
|
|
|
|
if ($reflFound === null || ! $reflFound->isSubclassOf(TestCase::class) || $reflFound->isAbstract()) {
|
|
throw new NoClassInFileException($srcPath, 0, $exception);
|
|
}
|
|
|
|
self::$alreadyLoadedSources[$srcPath] = $reflFound;
|
|
}
|
|
}
|
|
|
|
$this->refl = self::$alreadyLoadedSources[$srcPath];
|
|
}
|
|
|
|
/**
|
|
* Returns the fully constructed class
|
|
* with methods or null if the class is abstract.
|
|
*/
|
|
public function getClass(): ParsedClass
|
|
{
|
|
$parentsCount = 0;
|
|
$class = $this->refl;
|
|
while (($parent = $class->getParentClass()) !== false) {
|
|
++$parentsCount;
|
|
$class = $parent;
|
|
}
|
|
|
|
return new ParsedClass(
|
|
$this->refl->getName(),
|
|
$this->getMethods(),
|
|
$parentsCount,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Return all test methods present in the file.
|
|
*
|
|
* @return ReflectionMethod[]
|
|
* @psalm-return list<ReflectionMethod>
|
|
*/
|
|
private function getMethods(): array
|
|
{
|
|
$methods = [];
|
|
// @see \PHPUnit\Framework\TestSuite::__construct
|
|
foreach ($this->refl->getMethods() as $method) {
|
|
if ($method->getDeclaringClass()->getName() === Assert::class) {
|
|
continue;
|
|
}
|
|
|
|
if ($method->getDeclaringClass()->getName() === TestCase::class) {
|
|
continue;
|
|
}
|
|
|
|
if (! TestUtil::isTestMethod($method)) {
|
|
continue;
|
|
}
|
|
|
|
$methods[] = $method;
|
|
}
|
|
|
|
return $methods;
|
|
}
|
|
}
|