Skip to content

Commit 33b45d7

Browse files
committed
fix: recognise Windows drive-letter absolute paths in FileFinder
A Windows absolute path starts with a drive letter (C:\...), not DIRECTORY_SEPARATOR, so FileFinder treated PHPUnit's absolute <source> paths as relative, prepended getcwd(), and dropped them — leaving `pest --mutate` with nothing to mutate on Windows. Adds an isAbsolutePath() helper (drive-letter + UNC aware, mirroring PHPUnit) and a FileFinder test.
1 parent 1cefd75 commit 33b45d7

2 files changed

Lines changed: 89 additions & 1 deletion

File tree

src/Support/FileFinder.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ private static function separateDirectoriesAndFiles(array $paths): array
3636
$dirs = [];
3737
$files = [];
3838
foreach ($paths as $path) {
39-
if (! str_starts_with($path, DIRECTORY_SEPARATOR)) {
39+
if (! self::isAbsolutePath($path)) {
4040
$path = getcwd().DIRECTORY_SEPARATOR.$path;
4141
}
4242
if (is_dir($path)) {
@@ -50,6 +50,31 @@ private static function separateDirectoriesAndFiles(array $paths): array
5050
return ['directories' => $dirs, 'files' => $files];
5151
}
5252

53+
/**
54+
* Determine whether a path is absolute, in a cross-platform way.
55+
*
56+
* The previous check — str_starts_with($path, DIRECTORY_SEPARATOR) — only
57+
* recognised a leading separator. On Windows that misread a drive-letter
58+
* absolute path (e.g. "C:\project\app", which PHPUnit hands us for every
59+
* <source> include directory) as *relative*: getcwd() was then prepended,
60+
* producing a non-existent doubled path that is_dir() rejected, so the
61+
* directory was silently dropped and no mutations were generated. This
62+
* mirrors PHPUnit's own drive-letter-aware absoluteness check.
63+
*/
64+
private static function isAbsolutePath(string $path): bool
65+
{
66+
// A leading separator: POSIX "/…", or "\…" / UNC "\\server\share" on Windows.
67+
if (str_starts_with($path, '/') || str_starts_with($path, '\\')) {
68+
return true;
69+
}
70+
71+
// A Windows drive-letter prefix: "C:\…" or "C:/…".
72+
return strlen($path) >= 3
73+
&& ctype_alpha($path[0])
74+
&& $path[1] === ':'
75+
&& ($path[2] === '/' || $path[2] === '\\');
76+
}
77+
5378
/**
5479
* @param array<int, string> $pathsToIgnore
5580
* @param array<int, string> $dirs
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Pest\Mutate\Support\FileFinder;
6+
use Symfony\Component\Finder\SplFileInfo;
7+
8+
/**
9+
* Invoke the private cross-platform absolute-path classifier.
10+
*
11+
* Reflection keeps the helper private (no public API change) while letting the
12+
* Linux CI runner verify the Windows drive-letter cases it cannot exercise
13+
* through real directories. Matches the reflection usage already in tests/Arch.php.
14+
*/
15+
function isAbsolutePath(string $path): bool
16+
{
17+
return (bool) (new ReflectionMethod(FileFinder::class, 'isAbsolutePath'))->invoke(null, $path);
18+
}
19+
20+
describe('isAbsolutePath()', function (): void {
21+
it('treats leading-separator paths as absolute', function (string $path): void {
22+
expect(isAbsolutePath($path))->toBeTrue();
23+
})->with([
24+
'POSIX root' => '/var/www/app',
25+
'POSIX short' => '/app',
26+
'UNC share' => '\\\\server\\share',
27+
'leading backslash' => '\\app',
28+
]);
29+
30+
it('treats Windows drive-letter paths as absolute', function (string $path): void {
31+
expect(isAbsolutePath($path))->toBeTrue();
32+
})->with([
33+
'drive + backslash' => 'C:\\project\\app', // the path PHPUnit hands the plugin on Windows
34+
'drive + forward slash' => 'C:/project/app',
35+
'lower-case drive' => 'd:\\app',
36+
]);
37+
38+
it('treats relative paths as not absolute', function (string $path): void {
39+
expect(isAbsolutePath($path))->toBeFalse();
40+
})->with([
41+
'bare directory' => 'app',
42+
'nested' => 'tests/Unit',
43+
'src' => 'src',
44+
'drive-relative (no separator)' => 'C:app',
45+
'empty' => '',
46+
]);
47+
});
48+
49+
it('finds php files when given an absolute source directory', function (): void {
50+
// Regression for the Windows bug: PHPUnit supplies an ABSOLUTE <source>
51+
// directory; FileFinder must not prepend getcwd() to it (which doubled the
52+
// path and dropped the directory, yielding "0 Mutations for 0 Files created").
53+
$absolute = dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'Classes';
54+
55+
expect($absolute)->toBeDirectory();
56+
57+
$names = array_map(
58+
fn (SplFileInfo $file): string => $file->getFilename(),
59+
iterator_to_array(FileFinder::files([$absolute], [])),
60+
);
61+
62+
expect($names)->toContain('AgeHelper.php', 'SizeHelper.php');
63+
});

0 commit comments

Comments
 (0)