diff --git a/plugin/codecov/src/Internal/Driver/PseudoFile.php b/plugin/codecov/src/Internal/Driver/PseudoFile.php index 426d3e69..3cddbe98 100644 --- a/plugin/codecov/src/Internal/Driver/PseudoFile.php +++ b/plugin/codecov/src/Internal/Driver/PseudoFile.php @@ -7,8 +7,8 @@ use Testo\Inline\TestInline; /** - * Recognizes the names coverage engines give to code without a file of its own: `eval()`'d code, - * runtime-created functions, assertion strings, standard input. + * Recognizes coverage entries without a file on disk: `eval()`'d code, runtime-created functions, + * assertion strings, standard input, and any other path that does not exist. * * Such a name starts with the path of the file that produced the code, so a path-prefix filter alone lets it through. * @@ -26,6 +26,13 @@ final class PseudoFile 'Standard input code', ]; + /** + * Holds `false` for missing paths too: PHP's stat cache does not remember a failed lookup. + * + * @var array + */ + private static array $isFile = []; + #[TestInline(["/app/src/Foo.php(64) : eval()'d code"], result: true)] #[TestInline(["C:\\app\\src\\Foo.php(3) : eval()'d code"], result: true)] #[TestInline(['xdebug://debug-eval'], result: true)] @@ -33,8 +40,9 @@ final class PseudoFile #[TestInline(['Standard input code'], result: true)] #[TestInline(['-'], result: true)] #[TestInline(['vfs://root/Foo.php'], result: true)] - #[TestInline(['/app/src/Foo.php'], result: false)] - #[TestInline(['C:\\app\\src\\Foo.php'], result: false)] + #[TestInline(['/missing/src/Foo.php'], result: true)] + #[TestInline([__DIR__], result: true)] + #[TestInline([__FILE__], result: false)] public static function is(string $path): bool { if ($path === '-' || \str_starts_with($path, 'vfs://')) { @@ -47,7 +55,7 @@ public static function is(string $path): bool } } - return false; + return !(self::$isFile[$path] ??= \is_file($path)); } /** diff --git a/plugin/codecov/tests/Unit/Driver/PseudoFileTest.php b/plugin/codecov/tests/Unit/Driver/PseudoFileTest.php new file mode 100644 index 00000000..1c9a3986 --- /dev/null +++ b/plugin/codecov/tests/Unit/Driver/PseudoFileTest.php @@ -0,0 +1,87 @@ +root = \dirname(__DIR__, 2) . '/runtime/pseudo_file_' . \uniqid(); + \mkdir($this->root, recursive: true); + } + + #[AfterTest] + public function removeRoot(): void + { + foreach (\glob($this->root . '/*') as $file) { + \unlink($file); + } + \rmdir($this->root); + } + + public function removeKeepsRealFilesWithTheirData(): void + { + $data = [ + __FILE__ => [10 => 1, 11 => -1], + __FILE__ . "(64) : eval()'d code" => [1 => 1], + $this->root . '/missing.php' => [1 => 1], + __DIR__ . '/NormalizePathTest.php' => [20 => -2], + ]; + + $result = PseudoFile::remove($data); + + Assert::same($result, [ + __FILE__ => [10 => 1, 11 => -1], + __DIR__ . '/NormalizePathTest.php' => [20 => -2], + ]); + } + + public function removeKeepsBranchCoverageEntries(): void + { + $entry = ['lines' => [10 => 1], 'functions' => []]; + + $result = PseudoFile::remove([__FILE__ => $entry, 'xdebug://debug-eval' => $entry]); + + Assert::same($result, [__FILE__ => $entry]); + } + + public function removeOfEmptyDataIsEmpty(): void + { + Assert::same(PseudoFile::remove([]), []); + } + + public function missingPathIsCheckedOncePerRun(): void + { + $path = $this->root . '/appears-later.php'; + Assert::true(PseudoFile::is($path)); + + \file_put_contents($path, 'root . '/removed-later.php'; + \file_put_contents($path, '