*/ private $refl; /** @var ReflectionClass[] */ 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 */ 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; } }