mirror of
https://github.com/pestphp/pest.git
synced 2026-06-05 10:52:14 +02:00
wip
This commit is contained in:
@ -276,14 +276,18 @@ trait Testable
|
|||||||
|
|
||||||
/** @var Tia $tia */
|
/** @var Tia $tia */
|
||||||
$tia = Container::getInstance()->get(Tia::class);
|
$tia = Container::getInstance()->get(Tia::class);
|
||||||
$cached = $tia->getCachedResult(self::$__filename, $this::class.'::'.$this->name());
|
$status = $tia->getStatus(self::$__filename, $this::class.'::'.$this->name());
|
||||||
|
$replay = Replay::fromStatus($status);
|
||||||
|
|
||||||
if ($cached !== null) {
|
if ($replay !== Replay::No) {
|
||||||
match (Replay::from($cached)) {
|
assert($status !== null);
|
||||||
|
|
||||||
|
match ($replay) {
|
||||||
Replay::Pass => $this->__shortCircuitCachedPass(),
|
Replay::Pass => $this->__shortCircuitCachedPass(),
|
||||||
Replay::Skipped => $this->markTestSkipped($cached->message()),
|
Replay::Skipped => $this->markTestSkipped($status->message()),
|
||||||
Replay::Incomplete => $this->markTestIncomplete($cached->message()),
|
Replay::Incomplete => $this->markTestIncomplete($status->message()),
|
||||||
Replay::Failure => throw new AssertionFailedError($cached->message() ?: 'Cached failure'),
|
Replay::Failure => throw new AssertionFailedError($status->message() ?: 'Cached failure'),
|
||||||
|
Replay::No => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -190,7 +190,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
return ! ($watchPatterns->isLocally() && in_array('--ci', $arguments, true));
|
return ! ($watchPatterns->isLocally() && in_array('--ci', $arguments, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCachedResult(string $filename, string $testId): ?TestStatus
|
public function getStatus(string $filename, string $testId): ?TestStatus
|
||||||
{
|
{
|
||||||
if (! $this->replayGraph instanceof Graph) {
|
if (! $this->replayGraph instanceof Graph) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -75,7 +75,7 @@ final readonly class Fingerprint
|
|||||||
return [
|
return [
|
||||||
'structural' => [
|
'structural' => [
|
||||||
'schema' => self::SCHEMA_VERSION,
|
'schema' => self::SCHEMA_VERSION,
|
||||||
'composer_lock' => self::composerLockHash($projectRoot),
|
// 'composer_lock' => self::composerLockHash($projectRoot),
|
||||||
'phpunit_xml' => self::hashIfExists($projectRoot.'/phpunit.xml'),
|
'phpunit_xml' => self::hashIfExists($projectRoot.'/phpunit.xml'),
|
||||||
'phpunit_xml_dist' => self::hashIfExists($projectRoot.'/phpunit.xml.dist'),
|
'phpunit_xml_dist' => self::hashIfExists($projectRoot.'/phpunit.xml.dist'),
|
||||||
'pest_factory' => self::contentHashOrNull(__DIR__.'/../../Factories/TestCaseFactory.php'),
|
'pest_factory' => self::contentHashOrNull(__DIR__.'/../../Factories/TestCaseFactory.php'),
|
||||||
@ -88,9 +88,9 @@ final readonly class Fingerprint
|
|||||||
],
|
],
|
||||||
'environmental' => [
|
'environmental' => [
|
||||||
// Minor only (8.4, not 8.4.19) — CI's patch rarely matches dev installs.
|
// Minor only (8.4, not 8.4.19) — CI's patch rarely matches dev installs.
|
||||||
'php_minor' => PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION,
|
// 'php_minor' => PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION,
|
||||||
'extensions' => self::extensionsFingerprint($projectRoot),
|
// 'extensions' => self::extensionsFingerprint($projectRoot),
|
||||||
'env_files' => self::envFilesHash($projectRoot),
|
// 'env_files' => self::envFilesHash($projectRoot),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -300,54 +300,6 @@ final readonly class Fingerprint
|
|||||||
return $parts === [] ? null : hash('xxh128', implode("\n", $parts));
|
return $parts === [] ? null : hash('xxh128', implode("\n", $parts));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function envFilesHash(string $projectRoot): ?string
|
|
||||||
{
|
|
||||||
$paths = [
|
|
||||||
$projectRoot.'/.env',
|
|
||||||
$projectRoot.'/.env.testing',
|
|
||||||
$projectRoot.'/.env.local',
|
|
||||||
];
|
|
||||||
|
|
||||||
$localVariants = glob($projectRoot.'/.env.*.local');
|
|
||||||
|
|
||||||
if (is_array($localVariants)) {
|
|
||||||
foreach ($localVariants as $path) {
|
|
||||||
$paths[] = $path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$parts = [];
|
|
||||||
$seen = [];
|
|
||||||
|
|
||||||
foreach ($paths as $path) {
|
|
||||||
if (isset($seen[$path])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$seen[$path] = true;
|
|
||||||
|
|
||||||
if (! is_file($path)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$contents = @file_get_contents($path);
|
|
||||||
|
|
||||||
if ($contents === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$parts[] = basename($path).':'.hash('xxh128', $contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($parts === []) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
sort($parts);
|
|
||||||
|
|
||||||
return hash('xxh128', implode("\n", $parts));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function composerJsonHash(string $projectRoot): ?string
|
private static function composerJsonHash(string $projectRoot): ?string
|
||||||
{
|
{
|
||||||
$path = $projectRoot.'/composer.json';
|
$path = $projectRoot.'/composer.json';
|
||||||
@ -395,86 +347,6 @@ final readonly class Fingerprint
|
|||||||
return $json === false ? null : hash('xxh128', $json);
|
return $json === false ? null : hash('xxh128', $json);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function composerLockHash(string $projectRoot): ?string
|
|
||||||
{
|
|
||||||
$path = $projectRoot.'/composer.lock';
|
|
||||||
|
|
||||||
if (! is_file($path)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$raw = @file_get_contents($path);
|
|
||||||
|
|
||||||
if ($raw === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = json_decode($raw, true);
|
|
||||||
|
|
||||||
if (! is_array($data)) {
|
|
||||||
$hash = @hash_file('xxh128', $path);
|
|
||||||
|
|
||||||
return $hash === false ? null : $hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
$relevant = [
|
|
||||||
'platform' => $data['platform'] ?? null,
|
|
||||||
'platform-dev' => $data['platform-dev'] ?? null,
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach (['packages', 'packages-dev'] as $section) {
|
|
||||||
if (! isset($data[$section])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (! is_array($data[$section])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$packages = [];
|
|
||||||
|
|
||||||
foreach ($data[$section] as $package) {
|
|
||||||
if (! is_array($package)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = $package['name'] ?? null;
|
|
||||||
|
|
||||||
if (! is_string($name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$packages[$name] = [
|
|
||||||
'version' => $package['version'] ?? null,
|
|
||||||
'reference' => self::lockReference($package),
|
|
||||||
'autoload' => $package['autoload'] ?? null,
|
|
||||||
'autoload-dev' => $package['autoload-dev'] ?? null,
|
|
||||||
'extra' => $package['extra'] ?? null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
ksort($packages);
|
|
||||||
$relevant[$section] = $packages;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::sortRecursively($relevant);
|
|
||||||
|
|
||||||
$json = json_encode($relevant);
|
|
||||||
|
|
||||||
return $json === false ? null : hash('xxh128', $json);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $package
|
|
||||||
*/
|
|
||||||
private static function lockReference(array $package): ?string
|
|
||||||
{
|
|
||||||
$dist = is_array($package['dist'] ?? null) ? $package['dist'] : [];
|
|
||||||
$source = is_array($package['source'] ?? null) ? $package['source'] : [];
|
|
||||||
|
|
||||||
$reference = $dist['reference'] ?? $source['reference'] ?? null;
|
|
||||||
|
|
||||||
return is_string($reference) ? $reference : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function sortRecursively(mixed &$value): void
|
private static function sortRecursively(mixed &$value): void
|
||||||
{
|
{
|
||||||
if (! is_array($value)) {
|
if (! is_array($value)) {
|
||||||
@ -513,66 +385,4 @@ final readonly class Fingerprint
|
|||||||
|
|
||||||
return $hash === false ? null : $hash;
|
return $hash === false ? null : $hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only hashes `ext-*` entries declared in composer.json — incidental extensions loaded on the
|
|
||||||
// machine but not declared can't affect suite correctness, so they're excluded to reduce noise.
|
|
||||||
private static function extensionsFingerprint(string $projectRoot): string
|
|
||||||
{
|
|
||||||
$extensions = self::declaredExtensions($projectRoot);
|
|
||||||
|
|
||||||
if ($extensions === []) {
|
|
||||||
return hash('xxh128', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
sort($extensions);
|
|
||||||
|
|
||||||
$parts = [];
|
|
||||||
|
|
||||||
foreach ($extensions as $name) {
|
|
||||||
$version = phpversion($name);
|
|
||||||
$parts[] = $name.'@'.($version === false ? 'missing' : $version);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash('xxh128', implode("\n", $parts));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return list<string> */
|
|
||||||
private static function declaredExtensions(string $projectRoot): array
|
|
||||||
{
|
|
||||||
$path = $projectRoot.'/composer.json';
|
|
||||||
|
|
||||||
if (! is_file($path)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$raw = @file_get_contents($path);
|
|
||||||
|
|
||||||
if ($raw === false) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = json_decode($raw, true);
|
|
||||||
|
|
||||||
if (! is_array($data)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$extensions = [];
|
|
||||||
|
|
||||||
foreach (['require', 'require-dev'] as $section) {
|
|
||||||
$packages = $data[$section] ?? null;
|
|
||||||
|
|
||||||
if (! is_array($packages)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (array_keys($packages) as $package) {
|
|
||||||
if (is_string($package) && str_starts_with($package, 'ext-')) {
|
|
||||||
$extensions[] = substr($package, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_values(array_unique($extensions));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,17 +11,22 @@ use PHPUnit\Framework\TestStatus\TestStatus;
|
|||||||
*/
|
*/
|
||||||
enum Replay
|
enum Replay
|
||||||
{
|
{
|
||||||
|
case No;
|
||||||
case Pass;
|
case Pass;
|
||||||
case Skipped;
|
case Skipped;
|
||||||
case Incomplete;
|
case Incomplete;
|
||||||
case Failure;
|
case Failure;
|
||||||
|
|
||||||
public static function from(TestStatus $cached): self
|
public static function fromStatus(?TestStatus $status): self
|
||||||
{
|
{
|
||||||
|
if (! $status instanceof TestStatus) {
|
||||||
|
return self::No;
|
||||||
|
}
|
||||||
|
|
||||||
return match (true) {
|
return match (true) {
|
||||||
$cached->isSuccess(), $cached->isRisky() => self::Pass,
|
$status->isSuccess(), $status->isRisky() => self::Pass,
|
||||||
$cached->isSkipped() => self::Skipped,
|
$status->isSkipped() => self::Skipped,
|
||||||
$cached->isIncomplete() => self::Incomplete,
|
$status->isIncomplete() => self::Incomplete,
|
||||||
default => self::Failure,
|
default => self::Failure,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user