Skip to content

Commit 1b08cf2

Browse files
Koan-Botclaude
andcommitted
fix: glob now falls back to real filesystem for unmocked paths (#158)
The __glob override only searched %files_being_mocked via Text::Glob, never falling back to CORE::glob for real filesystem results. This caused glob() to return empty results for real files on disk when Test::MockFile was loaded (even in nostrict mode with nothing mocked). Now __glob merges real CORE::glob results with mocked file matches, skipping any paths that are actively mocked (mock takes precedence). Results are sorted to maintain glob's alphabetical order contract. Closes #158 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7d115e8 commit 1b08cf2

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

lib/Test/MockFile.pm

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1747,7 +1747,22 @@ sub __glob {
17471747
@mocked_files = sort @mocked_files;
17481748

17491749
my @results = map Text::Glob::match_glob( $_, @mocked_files ), @patterns;
1750-
return @results;
1750+
1751+
# Fall back to CORE::glob for real filesystem results (issue #158)
1752+
_real_file_access_hook( 'glob', [$spec] );
1753+
my @real_results = CORE::glob($spec);
1754+
1755+
# Merge real results, excluding any paths that are being mocked
1756+
# (mocked paths take precedence whether they exist or not)
1757+
my %seen = map { $_ => 1 } @results;
1758+
foreach my $real_path (@real_results) {
1759+
my $abs = _abs_path_to_file($real_path);
1760+
next if $files_being_mocked{$abs};
1761+
next if $seen{$real_path}++;
1762+
push @results, $real_path;
1763+
}
1764+
1765+
return sort @results;
17511766
}
17521767

17531768
sub __open (*;$@) {

t/glob_real_files.t

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use strict;
2+
use warnings;
3+
4+
use Test2::Bundle::Extended;
5+
use Test2::Tools::Explain;
6+
use Test2::Plugin::NoWarnings;
7+
8+
use File::Temp;
9+
10+
use Test::MockFile qw< nostrict >;
11+
12+
# Issue #158: glob should return real files when nothing is mocked
13+
# for the given pattern.
14+
15+
my $dir = File::Temp->newdir();
16+
17+
# Create real files on disk
18+
my $log_file = "$dir/file.log";
19+
open( my $fh, '>', $log_file ) or die "Cannot create $log_file: $!";
20+
print {$fh} "test";
21+
close $fh;
22+
23+
my $txt_file = "$dir/file.txt";
24+
open( $fh, '>', $txt_file ) or die "Cannot create $txt_file: $!";
25+
print {$fh} "test";
26+
close $fh;
27+
28+
# Test 1: glob should find real files when nothing is mocked
29+
my @logs = glob("$dir/*.log");
30+
is \@logs, [$log_file], 'glob finds real .log file on disk';
31+
32+
# Test 2: glob with multiple results
33+
my @all = sort glob("$dir/*");
34+
is \@all, [ sort( $log_file, $txt_file ) ], 'glob finds all real files on disk';
35+
36+
# Test 3: glob returns empty for non-matching pattern
37+
my @none = glob("$dir/*.xyz");
38+
is \@none, [], 'glob returns empty for non-matching pattern';
39+
40+
# Test 4: diamond operator (angle bracket) glob should also work
41+
my @diamond = <$dir/*.log>;
42+
is \@diamond, [$log_file], 'angle bracket glob finds real .log file on disk';
43+
44+
# Test 5: mocked files should still work alongside real files, results sorted
45+
my $mock = Test::MockFile->file("$dir/mock.log", "mocked");
46+
my @mixed = glob("$dir/*.log");
47+
is \@mixed, [ sort( $log_file, "$dir/mock.log" ) ],
48+
'glob returns both real and mocked files in sorted order';
49+
50+
# Test 6: mocked file that shadows a real file (no duplicates)
51+
my $shadow = Test::MockFile->file($log_file, "shadow");
52+
my @shadowed = glob("$dir/*.log");
53+
is \@shadowed, [ sort( $log_file, "$dir/mock.log" ) ],
54+
'glob returns mocked files that shadow real files without duplicates';
55+
56+
done_testing();

0 commit comments

Comments
 (0)