This commit is contained in:
nuno maduro
2026-08-07 01:11:53 +01:00
parent 7bfb2a6185
commit cc7acfe485
16 changed files with 900 additions and 8 deletions
+30 -1
View File
@@ -298,6 +298,31 @@ final readonly class ChangedFiles
return $this->gitOutput(['git', 'remote']) !== null;
}
public function isRepository(): bool
{
$process = new Process(['git', 'rev-parse', '--git-dir'], $this->projectRoot);
$process->setTimeout(5.0);
$process->run();
return $process->getExitCode() === 0;
}
/**
* Whether this repository has a revision to anchor a baseline to.
*
* A freshly initialised repository has none, and every other git call TIA
* makes — {@see self::currentBranch()}, {@see self::currentSha()} — fails on
* `HEAD` there and reports git as missing, which it is not.
*/
public function hasCommits(): bool
{
$process = new Process(['git', 'rev-parse', '--verify', '--quiet', 'HEAD'], $this->projectRoot);
$process->setTimeout(5.0);
$process->run();
return $process->getExitCode() === 0;
}
/**
* @param array<int, string> $command
*/
@@ -332,8 +357,12 @@ final readonly class ChangedFiles
*/
private function diffSinceSha(string $sha): array
{
// `--no-renames` matters: with rename detection on, git reports only the
// destination of a moved file, so the path the graph has edges for — the
// one that is gone — never reaches selection, and every test that
// depended on it replays its recorded pass.
$process = new Process(
['git', 'diff', '--name-only', $sha.'..HEAD'],
['git', 'diff', '--name-only', '--no-renames', $sha.'..HEAD'],
$this->projectRoot,
);
$process->run();
+26
View File
@@ -119,6 +119,32 @@ final class Graph
return array_keys($affectedSet);
}
/**
* Keep only the test files this checkout actually has.
*
* A stale edge key — a test file a fetched baseline knew, or one another
* branch deleted — cannot be run by anyone, and selecting it strands
* `--filtered` on a run that matches nothing and reports success on a
* change no test looked at. {@see self::testFilesToRerun()} has always
* dropped these; the change-driven half of selection must agree.
*
* @param array<int, string> $testFiles Project-relative paths.
* @return list<string>
*/
public function testFilesOnDisk(array $testFiles): array
{
$root = rtrim($this->projectRoot, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
$onDisk = [];
foreach ($testFiles as $testFile) {
if (is_file($root.$testFile)) {
$onDisk[] = $testFile;
}
}
return $onDisk;
}
/**
* @param array<int, string> $changedFiles
* @return array{0: list<string>, 1: list<string>}
+12 -1
View File
@@ -359,8 +359,19 @@ final class Recorder
continue;
}
// A file whose *only* executed line is its last one was included,
// not used — the trailing line of an include is all that ran.
//
// That reading only holds for a driver that reports unexecuted
// lines too: pcov returns every executable line (`-1` for the ones
// that did not run), so "the single covered line is the highest
// line reported" means something. Xdebug reports executed lines
// only, where it is true of *any* file that ran a single line —
// which is most of them, and dropping those loses the edge.
$lineKeys = array_keys($lines);
if ($lineKeys !== [] && count($covered) === 1 && $covered[0] === max($lineKeys)) {
$reportsUnexecutedLines = count($covered) < count($lines);
if ($reportsUnexecutedLines && $lineKeys !== [] && count($covered) === 1 && $covered[0] === max($lineKeys)) {
continue;
}