File Coverage

blib/lib/Test/MockFile.pm
Criterion Covered Total %
statement 710 830 85.5
branch 358 516 69.3
condition 144 244 59.0
subroutine 102 106 96.2
pod 34 34 100.0
total 1348 1730 77.9


line stmt bran cond sub pod time code
1             # Copyright (c) 2018, cPanel, LLC.
2             # All rights reserved.
3             # http://cpanel.net
4             #
5             # This is free software; you can redistribute it and/or modify it under the
6             # same terms as Perl itself. See L.
7              
8             package Test::MockFile;
9              
10 50     50   9263168 use strict;
  50         113  
  50         1971  
11 50     50   290 use warnings;
  50         144  
  50         2704  
12              
13             # perl -MFcntl -E'eval "say q{$_: } . $_" foreach sort {eval "$a" <=> eval "$b"} qw/O_RDONLY O_WRONLY O_RDWR O_CREAT O_EXCL O_NOCTTY O_TRUNC O_APPEND O_NONBLOCK O_NDELAY O_EXLOCK O_SHLOCK O_DIRECTORY O_NOFOLLOW O_SYNC O_BINARY O_LARGEFILE/'
14 50     50   327 use Fcntl; # O_RDONLY, etc.
  50         148  
  50         16431  
15              
16 50     50   380 use constant SUPPORTED_SYSOPEN_MODES => O_RDONLY | O_WRONLY | O_RDWR | O_APPEND | O_TRUNC | O_EXCL | O_CREAT | O_NOFOLLOW;
  50         194  
  50         5040  
17              
18 50     50   322 use constant BROKEN_SYMLINK => bless {}, "A::BROKEN::SYMLINK";
  50         99  
  50         3824  
19 50     50   279 use constant CIRCULAR_SYMLINK => bless {}, "A::CIRCULAR::SYMLINK";
  50         130  
  50         3340  
20              
21             # we're going to use carp but the errors should come from outside of our package.
22 50     50   365 use Carp qw(carp confess croak);
  50         164  
  50         4630  
23              
24             BEGIN {
25 50     50   308 $Carp::Internal{ (__PACKAGE__) }++;
26 50         1388 $Carp::Internal{'Overload::FileCheck'}++;
27             }
28 50     50   356 use Cwd ();
  50         122  
  50         1072  
29 50     50   34081 use IO::File ();
  50         391824  
  50         4819  
30 50     50   29123 use Test::MockFile::FileHandle ();
  50         197  
  50         1596  
31 50     50   26859 use Test::MockFile::DirHandle ();
  50         177  
  50         1359  
32 50     50   25849 use Text::Glob ();
  50         51663  
  50         2145  
33 50     50   345 use Scalar::Util ();
  50         161  
  50         1019  
34              
35 50     50   250 use Symbol;
  50         100  
  50         4263  
36              
37 50     50   33236 use Overload::FileCheck '-from-stat' => \&_mock_stat, q{:check};
  50         275756  
  50         437  
38              
39 50     50   49822 use Errno qw/EPERM ENOENT ELOOP EEXIST EISDIR ENOTDIR EINVAL/;
  50         117  
  50         4632  
40              
41 50     50   366 use constant FOLLOW_LINK_MAX_DEPTH => 10;
  50         104  
  50         5650  
42              
43             =head1 NAME
44              
45             Test::MockFile - Allows tests to validate code that can interact with
46             files without touching the file system.
47              
48             =head1 VERSION
49              
50             Version 0.037
51              
52             =cut
53              
54             our $VERSION = '0.037';
55              
56             our %files_being_mocked;
57              
58             # From http://man7.org/linux/man-pages/man7/inode.7.html
59 50     50   336 use constant S_IFMT => 0170000; # bit mask for the file type bit field
  50         115  
  50         4941  
60 50     50   391 use constant S_IFPERMS => 07777; # bit mask for file perms.
  50         318  
  50         2946  
61              
62 50     50   373 use constant S_IFSOCK => 0140000; # socket
  50         114  
  50         3294  
63 50     50   349 use constant S_IFLNK => 0120000; # symbolic link
  50         154  
  50         2747  
64 50     50   288 use constant S_IFREG => 0100000; # regular file
  50         121  
  50         2752  
65 50     50   263 use constant S_IFBLK => 0060000; # block device
  50         140  
  50         2483  
66 50     50   316 use constant S_IFDIR => 0040000; # directory
  50         83  
  50         2482  
67 50     50   247 use constant S_IFCHR => 0020000; # character device
  50         106  
  50         2303  
68 50     50   242 use constant S_IFIFO => 0010000; # FIFO
  50         132  
  50         3691  
69              
70             =head1 SYNOPSIS
71              
72             Intercepts file system calls for specific files so unit testing can
73             take place without any files being altered on disk.
74              
75             This is useful for L
76             tests|https://testing.googleblog.com/2010/12/test-sizes.html> where
77             file interaction is discouraged.
78              
79             A strict mode is even provided (and turned on by default) which can
80             throw a die when files are accessed during your tests!
81              
82             # Loaded before Test::MockFile so uses the core perl functions without any hooks.
83             use Module::I::Dont::Want::To::Alter;
84              
85             # strict mode by default
86             use Test::MockFile ();
87              
88             # non-strict mode
89             use Test::MockFile qw< nostrict >;
90              
91             # Load with one or more plugins
92              
93             use Test::MockFile plugin => 'FileTemp';
94             use Test::MockFile plugin => [ 'FileTemp', ... ];
95              
96             # Be sure to assign the output of mocks, they disappear when they go out of scope
97             my $foobar = Test::MockFile->file( "/foo/bar", "contents\ngo\nhere" );
98             open my $fh, '<', '/foo/bar' or die; # Does not actually open the file on disk
99             say '/foo/bar exists' if -e $fh;
100             close $fh;
101              
102             say '/foo/bar is a file' if -f '/foo/bar';
103             say '/foo/bar is THIS BIG: ' . -s '/foo/bar';
104              
105             my $foobaz = Test::MockFile->file('/foo/baz'); # File starts out missing
106             my $opened = open my $baz_fh, '<', '/foo/baz'; # File reports as missing so fails
107             say '/foo/baz does not exist yet' if !-e '/foo/baz';
108              
109             open $baz_fh, '>', '/foo/baz' or die; # open for writing
110             print {$baz_fh} "first line\n";
111              
112             open $baz_fh, '>>', '/foo/baz' or die; # open for append.
113             print {$baz_fh} "second line";
114             close $baz_fh;
115              
116             say "Contents of /foo/baz:\n>>" . $foobaz->contents() . '<<';
117              
118             # Unmock your file.
119             # (same as the variable going out of scope
120             undef $foobaz;
121              
122             # The file check will now happen on file system now the file is no longer mocked.
123             say '/foo/baz is missing again (no longer mocked)' if !-e '/foo/baz';
124              
125             my $quux = Test::MockFile->file( '/foo/bar/quux.txt', '' );
126             my @matches = ;
127              
128             # ( '/foo/bar/quux.txt' )
129             say "Contents of /foo/bar directory: " . join "\n", @matches;
130              
131             @matches = glob('/foo/bar/*.txt');
132              
133             # same as above
134             say "Contents of /foo/bar directory (using glob()): " . join "\n", @matches;
135              
136             =head1 IMPORT
137              
138             When the module is loaded with no parameters, strict mode is turned on.
139             Any file checks, C, C, C, C, or C
140             will throw a die.
141              
142             For example:
143              
144             use Test::MockFile;
145              
146             # This will not die.
147             my $file = Test::MockFile->file("/bar", "...");
148             my $symlink = Test::MockFile->symlink("/foo", "/bar");
149             -l '/foo' or print "ok\n";
150             open my $fh, '>', '/foo';
151              
152             # All of these will die
153             open my $fh, '>', '/unmocked/file'; # Dies
154             sysopen my $fh, '/other/file', O_RDONLY;
155             opendir my $fh, '/dir';
156             -e '/file';
157             -l '/file';
158              
159             If we want to load the module without strict mode:
160              
161             use Test::MockFile qw< nostrict >;
162              
163             Relative paths are not supported:
164              
165             use Test::MockFile;
166              
167             # Checking relative vs absolute paths
168             $file = Test::MockFile->file( '/foo/../bar', '...' ); # not ok - relative path
169             $file = Test::MockFile->file( '/bar', '...' ); # ok - absolute path
170             $file = Test::MockFile->file( 'bar', '...' ); # ok - current dir
171              
172             =cut
173              
174 50     50   289 use constant STRICT_MODE_DISABLED => 1;
  50         128  
  50         2524  
175 50     50   307 use constant STRICT_MODE_ENABLED => 2;
  50         126  
  50         2672  
176 50     50   302 use constant STRICT_MODE_UNSET => 4;
  50         167  
  50         3059  
177 50     50   327 use constant STRICT_MODE_DEFAULT => STRICT_MODE_ENABLED | STRICT_MODE_UNSET; # default state when unset by user
  50         173  
  50         4485  
178              
179             our $STRICT_MODE_STATUS;
180              
181             BEGIN {
182 50     50   5381 $STRICT_MODE_STATUS = STRICT_MODE_DEFAULT;
183             }
184              
185             # Perl understands barewords are filehandles during compilation and
186             # parsing. If we override the functions, Perl will not show these as
187             # filehandles, but as strings
188             # We can try to convert it to the typeglob in the right namespace
189             sub _upgrade_barewords {
190 49     49   123 my @args = @_;
191 49         113 my $caller = caller(1);
192              
193             # Add bareword information to the args
194             # Default: no
195 49         126 unshift @args, 0;
196              
197             # Ignore variables
198             # Barewords are provided as strings, which means they're read-only
199             # (Of course, readonly scalars here will fool us...)
200 49 50       261 Internals::SvREADONLY( $_[0] )
201             or return @args;
202              
203             # Upgrade the handle
204 0         0 my $handle;
205             {
206 50     50   344 no strict 'refs';
  50         122  
  50         11984  
  0         0  
207 0         0 my $caller_pkg = caller(1);
208 0         0 $handle = *{"$caller_pkg\::$args[1]"};
  0         0  
209             }
210              
211             # Check that the upgrading worked
212 0 0       0 ref \$handle eq 'GLOB'
213             or return @args;
214              
215             # Set to bareword
216 0         0 $args[0] = 1;
217              
218             # Override original handle variable/string
219 0         0 $args[1] = $handle;
220              
221 0         0 return @args;
222             }
223              
224             =head2 authorized_strict_mode_for_package( $pkg )
225              
226             Add a package namespace to the list of authorize namespaces.
227              
228             authorized_strict_mode_for_package( 'Your::Package' );
229              
230             =cut
231              
232             our %authorized_strict_mode_packages;
233              
234             sub authorized_strict_mode_for_package {
235 112     112 1 289 my ($pkg) = @_;
236              
237 112         331 $authorized_strict_mode_packages{$pkg} = 1;
238              
239 112         11868 return;
240             }
241              
242             BEGIN {
243 50     50   315 authorized_strict_mode_for_package($_) for qw{ DynaLoader lib };
244             }
245              
246             =head2 file_arg_position_for_command
247              
248             Args: ($command)
249              
250             Provides a hint with the position of the argument most likely holding
251             the file name for the current C<$command> call.
252              
253             This is used internaly to provide better error messages. This can be
254             used when plugging hooks to know what's the filename we currently try
255             to access.
256              
257             =cut
258              
259             my $_file_arg_post;
260              
261             sub file_arg_position_for_command { # can also be used by user hooks
262 83     83 1 259 my ( $command, $at_under_ref ) = @_;
263              
264 83   100     2158 $_file_arg_post //= {
265             'chmod' => 1,
266             'chown' => 2,
267             'lstat' => 0,
268             'mkdir' => 0,
269             'open' => 2,
270             'opendir' => 1,
271             'readlink' => 0,
272             'rmdir' => 0,
273             'stat' => 0,
274             'sysopen' => 1,
275             'unlink' => 0,
276             'readdir' => 0,
277             };
278              
279 83 50 33     501 return -1 unless defined $command && defined $_file_arg_post->{$command};
280              
281             # exception for open
282 83 100 66     485 return 1 if $command eq 'open' && ref $at_under_ref && scalar @$at_under_ref == 2;
      100        
283              
284 81         265 return $_file_arg_post->{$command};
285             }
286              
287 50     50   386 use constant _STACK_ITERATION_MAX => 100;
  50         137  
  50         447241  
288              
289             sub _get_stack {
290 149     149   262 my @stack;
291              
292 149         427 foreach my $stack_level ( 1 .. _STACK_ITERATION_MAX ) {
293 727         3741 @stack = caller($stack_level);
294 727 50       1581 last if !scalar @stack;
295 727 50       1546 last if !defined $stack[0]; # We don't know when this would ever happen.
296              
297 727 100       1587 next if $stack[0] eq __PACKAGE__;
298 429 100       1001 next if $stack[0] eq 'Overload::FileCheck'; # companion package
299              
300 149 100       622 return if $authorized_strict_mode_packages{ $stack[0] };
301              
302 83         166 last;
303             }
304              
305 83         436 return @stack;
306             }
307              
308             =head2 add_strict_rule( $command_rule, $file_rule, $action )
309              
310             Args: ($command_rule, $file_rule, $action)
311              
312             Add a custom rule to validate strictness mode. This is the fundation to
313             add strict rules. You should use it, when none of the other helper to
314             add rules work for you.
315              
316             =over
317              
318             =item C<$command_rule> a string or regexp or list of any to indicate
319             which command to match
320              
321             =itemC<$file_rule> a string or regexp or undef or list of any to indicate
322             which files your rules apply to.
323              
324             =item C<$action> a CODE ref or scalar to handle the exception.
325             Returning '1' skip all other rules and indicate an exception.
326              
327             =back
328              
329             # Check open() on /this/file
330             add_strict_rule( 'open', '/this/file', sub { ... } );
331              
332             # always bypass the strict rule
333             add_strict_rule( 'open', '/this/file', 1 );
334              
335             # all available options
336             add_strict_rule( 'open', '/this/file', sub {
337             my ($context) = @_;
338              
339             return; # Skip this rule and continue from the next one
340             return 0; # Strict violation, stop testing rules and die
341             return 1; # Strict passing, stop testing rules
342             } );
343              
344             # Disallow open(), close() on everything in /tmp/
345             add_strict_rule(
346             [ qw< open close > ],
347             qr{^/tmp}xms,
348             0,
349             );
350              
351             # Disallow open(), close() on everything (ignore filenames)
352             # Use add_strict_rule_for_command() instead!
353             add_strict_rule(
354             [ qw< open close > ],
355             undef,
356             0,
357             );
358              
359             =cut
360              
361             my @STRICT_RULES;
362              
363             sub add_strict_rule {
364 25     25 1 440 my ( $command_rule, $file_rule, $action ) = @_;
365              
366 25 50       83 defined $command_rule
367             or croak("add_strict_rule( COMMAND, PATH, ACTION )");
368              
369 25 50       72 croak("Invalid rule: missing action code") unless defined $action;
370              
371 25 100       104 my @commands = ref $command_rule eq 'ARRAY' ? @{$command_rule} : ($command_rule);
  3         10  
372 25 100       534 my @files = ref $file_rule eq 'ARRAY' ? @{$file_rule} : ($file_rule);
  7         24  
373              
374 25         65 foreach my $c_rule (@commands) {
375 28         59 foreach my $f_rule (@files) {
376 36 100 100     739 push @STRICT_RULES, {
    100          
377             'command_rule' => ref $c_rule eq 'Regexp' ? $c_rule : qr/^\Q$c_rule\E$/,
378             'file_rule' => ( ref $f_rule eq 'Regexp' || !defined $f_rule ) ? $f_rule : qr/^\Q$f_rule\E$/,
379             'action' => $action,
380             };
381             }
382             }
383              
384 25         97 return;
385             }
386              
387             =head2 clear_strict_rules()
388              
389             Args: none
390              
391             Clear all previously defined rules. (Mainly used for testing purpose)
392              
393             =cut
394              
395             sub clear_strict_rules {
396 7     7 1 3163 @STRICT_RULES = ();
397              
398 7         17 return;
399             }
400              
401             =head2 add_strict_rule_for_filename( $file_rule, $action )
402              
403             Args: ($file_rule, $action)
404              
405             Prefer using that helper when trying to add strict rules targeting
406             files.
407              
408             Apply a rule to one or more files.
409              
410             add_strict_rule_for_filename( '/that/file' => sub { ... } );
411              
412             add_strict_rule_for_filename( [ qw{list of files} ] => sub { ... } );
413              
414             add_strict_rule_for_filename( qr{*\.t$} => sub { ... } );
415              
416             add_strict_rule_for_filename( [ $dir, qr{^${dir}/} ] => 1 );
417              
418             =cut
419              
420             sub add_strict_rule_for_filename {
421 14     14 1 11083 my ( $file_rule, $action ) = @_;
422              
423 14         83 return add_strict_rule( qr/.*/, $file_rule, $action );
424             }
425              
426             =head2 add_strict_rule_for_command( $command_rule, $action )
427              
428             Args: ($command_rule, $action)
429              
430             Prefer using that helper when trying to add strict rules targeting
431             specici commands.
432              
433             Apply a rule to one or more files.
434              
435             add_strict_rule_for_command( 'open' => sub { ... } );
436              
437             add_strict_rule_for_command( [ qw{open readdir} ] => sub { ... } );
438              
439             add_strict_rule_for_command( qr{open.*} => sub { ... } );
440              
441             Test::MockFile::add_strict_rule_for_command(
442             [qw{ readdir closedir readlink }],
443             sub {
444             my ($ctx) = @_;
445             my $command = $ctx->{command} // 'unknown';
446              
447             warn( "Ignoring strict mode violation for $command" );
448             return 1;
449             }
450             );
451              
452             =cut
453              
454             sub add_strict_rule_for_command {
455 5     5 1 1672 my ( $command_rule, $action, $extra ) = @_;
456              
457 5 100       18 if ($extra) {
458 1         9 die q[Syntax not supported (extra arg) for 'add_strict_rule_for_command', please consider using 'add_strict_rule' instead.];
459             }
460              
461 4         13 return add_strict_rule( $command_rule, undef, $action );
462             }
463              
464             =head2 add_strict_rule_generic( $action )
465              
466             Args: ($action)
467              
468             Prefer using that helper when adding a rule which is global and does
469             not apply to a specific command or file.
470              
471             Apply a rule to one or more files.
472              
473             add_strict_rule_generic( sub { ... } );
474              
475             add_strict_rule_generic( sub {
476             my ($ctx) = @_;
477              
478             my $filename = $ctx->{filename};
479              
480             return unless defined $filename;
481              
482             return 1 if UNIVERSAL::isa( $filename, 'GLOB' );
483              
484             return;
485             } );
486              
487             =cut
488              
489             sub add_strict_rule_generic {
490 5     5 1 343 my ($action) = @_;
491              
492 5         35 return add_strict_rule( qr/.*/, undef, $action );
493             }
494              
495             =head2 is_strict_mode
496              
497             Boolean helper to determine if strict mode is currently enabled.
498              
499             =cut
500              
501             sub is_strict_mode {
502 254 100   254 1 1324 return $STRICT_MODE_STATUS & STRICT_MODE_ENABLED ? 1 : 0;
503             }
504              
505             sub _strict_mode_violation {
506 249     249   558 my ( $command, $at_under_ref ) = @_;
507              
508 249 100       660 return unless is_strict_mode();
509              
510             # These commands deal with dir handles we should have already been in violation when we opened the thing originally.
511 149 50       370 return if grep { $command eq $_ } qw/readdir telldir rewinddir seekdir closedir/;
  745         1487  
512              
513 149         395 my @stack = _get_stack();
514 149 100       432 return unless scalar @stack; # skip the package
515              
516 83         200 my $filename;
517              
518             # check it later so we give priority to authorized_strict_mode_packages
519 83         289 my $file_arg = file_arg_position_for_command( $command, $at_under_ref );
520              
521 83 50       310 if ( $file_arg >= 0 ) {
522 83 50       331 $filename = scalar @$at_under_ref <= $file_arg ? '' : $at_under_ref->[$file_arg];
523             }
524              
525             # Ignore stats on STDIN, STDOUT, STDERR
526 83 100 66     503 return if defined $filename && $filename =~ m/^\*?(?:main::)?[<*&+>]*STD(?:OUT|IN|ERR)$/;
527              
528             # The filename passed is actually a handle. This means that, usually,
529             # we don't need to check if it's a violation since something else should
530             # have opened it first. open and sysopen, though, require special care.
531             #
532 82 100       624 if ( UNIVERSAL::isa( $filename, 'GLOB' ) ) {
533 3 100 100     18 return if $command ne 'open' && $command ne 'sysopen';
534             }
535              
536             # open >& is for file dups. this isn't a real file access.
537 81 100 66     441 return if $command eq 'open' && $at_under_ref->[1] && $at_under_ref->[1] =~ m/&/;
      66        
538              
539 79         253 my $path = _abs_path_to_file($filename);
540              
541 79         456 my $context = {
542             command => $command,
543             filename => $path,
544             at_under_ref => $at_under_ref
545             }; # object
546              
547 79         288 my $pass = _validate_strict_rules($context);
548 79 100       374 return if $pass;
549              
550 54 50       1023 croak("Unknown strict mode violation for $command") if $file_arg == -1;
551              
552 54         18995 confess("Use of $command to access unmocked file or directory '$filename' in strict mode at $stack[1] line $stack[2]");
553             }
554              
555             sub _validate_strict_rules {
556 79     79   236 my ($context) = @_;
557              
558             # rules dispatch
559 79         220 foreach my $rule (@STRICT_RULES) {
560              
561             # This is when a rule was added without a filename at all
562             # intending to match whether there's a filename available or not
563             # (open() can be used on a scalar, for example)
564 76 100       239 if ( defined $rule->{'file_rule'} ) {
565 43 100 66     330 defined $context->{'filename'} && $context->{'filename'} =~ $rule->{'file_rule'}
566             or next;
567             }
568              
569 50 100       427 $context->{'command'} =~ $rule->{'command_rule'}
570             or next;
571              
572 43 100       192 my $answer = ref $rule->{'action'} ? $rule->{'action'}->($context) : $rule->{'action'};
573              
574 43 100       219 defined $answer
575             and return $answer;
576             }
577              
578             # We say it failed even though it didn't
579             # It's because we want to test the internal violation rule check
580 54         284 return;
581             }
582              
583             my @plugins;
584              
585             sub import {
586 49     49   514 my ( $class, @args ) = @_;
587              
588 49 100       159 my $strict_mode = ( grep { $_ eq 'nostrict' } @args ) ? STRICT_MODE_DISABLED : STRICT_MODE_ENABLED;
  44         221  
589              
590 49 100 66     440 if (
      100        
591             defined $STRICT_MODE_STATUS
592             && !( $STRICT_MODE_STATUS & STRICT_MODE_UNSET ) # mode is set by user
593             && $STRICT_MODE_STATUS != $strict_mode
594             ) {
595              
596             # could consider using authorized_strict_mode_packages for all packages
597 2         1794 die q[Test::MockFile is imported multiple times with different strict modes (not currently supported) ] . $class;
598             }
599 47         278 $STRICT_MODE_STATUS = $strict_mode;
600              
601 47         461 while ( my $opt = shift @args ) {
602 35 100 66     407 next unless defined $opt && $opt eq 'plugin';
603 7         34 my $what = shift @args;
604 7         3708 require Test::MockFile::Plugins;
605              
606 7         52 push @plugins, Test::MockFile::Plugins::load_plugin($what);
607             }
608              
609 46         1745649 return;
610             }
611              
612             =head1 SUBROUTINES/METHODS
613              
614             =head2 file
615              
616             Args: ($file, $contents, $stats)
617              
618             This will make cause $file to be mocked in all file checks, opens, etc.
619              
620             C contents means that the file should act like it's not there.
621             You can only set the stats if you provide content.
622              
623             If you give file content, the directory inside it will be mocked as
624             well.
625              
626             my $f = Test::MockFile->file( '/foo/bar' );
627             -d '/foo' # not ok
628              
629             my $f = Test::MockFile->file( '/foo/bar', 'some content' );
630             -d '/foo' # ok
631              
632             See L for what goes into the stats hashref.
633              
634             =cut
635              
636             sub file {
637 71     71 1 433066 my ( $class, $file, $contents, @stats ) = @_;
638              
639 71 50 33     473 ( defined $file && length $file ) or confess("No file provided to instantiate $class");
640 71 50       285 _is_path_mocked($file) and confess("It looks like $file is already being mocked. We don't support double mocking yet.");
641              
642 71         206 my $path = _abs_path_to_file($file);
643 71         328 _validate_path($_) for $file, $path;
644              
645 68 50       197 if ( @stats > 1 ) {
646 0         0 confess(
647             sprintf 'Unkownn arguments (%s) passed to file() as stats',
648             join ', ', @stats
649             );
650             }
651              
652 68 50 66     366 !defined $contents && @stats
653             and confess("You cannot set stats for non-existent file '$path'");
654              
655 68         143 my %stats;
656 68 100       172 if (@stats) {
657 1 50       7 ref $stats[0] eq 'HASH'
658             or confess('->file( FILE_NAME, FILE_CONTENT, { STAT_INFORMATION } )');
659              
660 1         3 %stats = %{ $stats[0] };
  1         6  
661             }
662              
663 68 100       270 my $perms = S_IFPERMS & ( defined $stats{'mode'} ? int( $stats{'mode'} ) : 0666 );
664 68         437 $stats{'mode'} = ( $perms ^ umask ) | S_IFREG;
665              
666             # Check if directory for this file is an object we're mocking
667             # If so, mark it now as having content
668             # which is this file or - if this file is undef, . and ..
669 68         2092 ( my $dirname = $path ) =~ s{ / [^/]+ $ }{}xms;
670 68 100 100     380 if ( defined $contents && $files_being_mocked{$dirname} ) {
671 7         41 $files_being_mocked{$dirname}{'has_content'} = 1;
672             }
673              
674 68         493 return $class->new(
675             {
676             'path' => $path,
677             'contents' => $contents,
678             %stats
679             }
680             );
681             }
682              
683             =head2 file_from_disk
684              
685             Args: C<($file_to_mock, $file_on_disk, $stats)>
686              
687             This will make cause C<$file> to be mocked in all file checks, opens,
688             etc.
689              
690             If C isn't present, then this will die.
691              
692             See L for what goes into the stats hashref.
693              
694             =cut
695              
696             sub file_from_disk {
697 1     1 1 15 my ( $class, $file, $file_on_disk, @stats ) = @_;
698              
699 1         1 my $fh;
700 1         13 local $!;
701 1 50       31 if ( !CORE::open( $fh, '<', $file_on_disk ) ) {
702 0   0     0 $file_on_disk //= '';
703 0         0 confess("Sorry, I cannot read from $file_on_disk to mock $file. It doesn't appear to be present ($!)");
704             }
705              
706 1         17 local $/;
707 1         23 my $contents = <$fh>; # Slurp!
708 1         8 close $fh;
709              
710 1         7 return __PACKAGE__->file( $file, $contents, @stats );
711             }
712              
713             =head2 symlink
714              
715             Args: ($readlink, $file )
716              
717             This will cause $file to be mocked in all file checks, opens, etc.
718              
719             C<$readlink> indicates what "fake" file it points to. If the file
720             C<$readlink> points to is not mocked, it will act like a broken link,
721             regardless of what's on disk.
722              
723             If C<$readlink> is undef, then the symlink is mocked but not
724             present.(lstat $file is empty.)
725              
726             Stats are not able to be specified on instantiation but can in theory
727             be altered after the object is created. People don't normally mess with
728             the permissions on a symlink.
729              
730             =cut
731              
732             sub symlink {
733 12     12 1 13811 my ( $class, $readlink, $file ) = @_;
734              
735 12 50 33     80 ( defined $file && length $file ) or confess("No file provided to instantiate $class");
736 12 50 33     126 ( !defined $readlink || length $readlink ) or confess("No file provided for $file to point to in $class");
737              
738 12 50       42 _is_path_mocked($file) and confess("It looks like $file is already being mocked. We don't support double mocking yet.");
739              
740             # Check if directory for this file is an object we're mocking
741             # If so, mark it now as having content
742             # which is this file or - if this file is undef, . and ..
743 12         89 ( my $dirname = $file ) =~ s{ / [^/]+ $ }{}xms;
744 12 100       67 if ( $files_being_mocked{$dirname} ) {
745 4         11 $files_being_mocked{$dirname}{'has_content'} = 1;
746             }
747              
748 12         96 return $class->new(
749             {
750             'path' => $file,
751             'contents' => undef,
752             'readlink' => $readlink,
753             'mode' => 07777 | S_IFLNK,
754             }
755             );
756             }
757              
758             sub _validate_path {
759 229     229   380 my $path = shift;
760              
761             # Reject the following:
762             # ./ ../ /. /.. /./ /../
763 229 100       610 if ( $path =~ m{ ( ^ | / ) \.{2} ( / | $ ) }xms ) {
764 5         1964 confess('Relative paths are not supported');
765             }
766              
767 224         1644 return;
768             }
769              
770             =head2 dir
771              
772             Args: ($dir)
773              
774             This will cause $dir to be mocked in all file checks, and C
775             interactions.
776              
777             The directory name is normalized so any trailing slash is removed.
778              
779             $dir = Test::MockFile->dir( 'mydir/', ... ); # ok
780             $dir->path(); # mydir
781              
782             If there were previously mocked files (within the same scope), the
783             directory will exist. Otherwise, the directory will be nonexistent.
784              
785             my $dir = Test::MockFile->dir('/etc');
786             -d $dir; # not ok since directory wasn't created yet
787             $dir->contents(); # undef
788              
789             # Now we can create an empty directory
790             mkdir '/etc';
791             $dir_etc->contents(); # . ..
792              
793             # Alternatively, we can already create files with ->file()
794             $dir_log = Test::MockFile->dir('/var');
795             $file_log = Test::MockFile->file( '/var/log/access_log', $some_content );
796             $dir_log->contents(); # . .. access_log
797              
798             # If you create a nonexistent file but then give it content, it will create
799             # the directory for you
800             my $file = Test::MockFile->file('/foo/bar');
801             my $dir = Test::MockFile->dir('/foo');
802             -d '/foo' # false
803             -e '/foo/bar'; # false
804             $dir->contents(); # undef
805              
806             $file->contents('hello');
807             -e '/foo/bar'; # true
808             -d '/foo'; # true
809             $dir->contents(); # . .. bar
810              
811             NOTE: Because C<.> and C<..> will always be the first things C
812             returns, These files are automatically inserted at the front of the
813             array. The order of files is sorted.
814              
815             If you want to affect the stat information of a directory, you need to
816             use the available core Perl keywords. (We might introduce a special
817             helper method for it in the future.)
818              
819             $d = Test::MockFile->dir( '/foo', [], { 'mode' => 0755 } ); # dies
820             $d = Test::MockFile->dir( '/foo', undef, { 'mode' => 0755 } ); # dies
821              
822             $d = Test::MockFile->dir('/foo');
823             mkdir $d, 0755; # ok
824              
825             =cut
826              
827             sub dir {
828 46     46 1 131741 my ( $class, $dirname ) = @_;
829              
830 46 50 33     249 ( defined $dirname && length $dirname ) or confess("No directory name provided to instantiate $class");
831 46 50       146 _is_path_mocked($dirname) and confess("It looks like $dirname is already being mocked. We don't support double mocking yet.");
832              
833 46         120 my $path = _abs_path_to_file($dirname);
834 46         227 _validate_path($_) for $dirname, $path;
835              
836             # Cleanup trailing forward slashes
837 44 50       174 $path ne '/'
838             and $path =~ s{[/\\]$}{}xmsg;
839              
840 44 100       1338 @_ > 2
841             and confess("You cannot set stats for nonexistent dir '$path'");
842              
843 39         90 my $perms = S_IFPERMS & 0777;
844 39         352 my %stats = ( 'mode' => ( $perms ^ umask ) | S_IFDIR );
845              
846             # TODO: Add stat information
847              
848             # FIXME: Quick and dirty: provide a helper method?
849 39         688 my $has_content = grep m{^\Q$path/\E}xms, %files_being_mocked;
850 39         327 return $class->new(
851             {
852             'path' => $path,
853             'has_content' => $has_content,
854             %stats
855             }
856             );
857             }
858              
859             =head2 new_dir
860              
861             # short form
862             $new_dir = Test::MockFile->new_dir( '/path' );
863             $new_dir = Test::MockFile->new_dir( '/path', { 'mode' => 0755 } );
864              
865             # longer form 1
866             $dir = Test::MockFile->dir('/path');
867             mkdir $dir->path(), 0755;
868              
869             # longer form 2
870             $dir = Test::MockFile->dir('/path');
871             mkdir $dir->path();
872             chmod $dir->path();
873              
874             This creates a new directory with an optional mode. This is a
875             short-hand that might be removed in the future when a stable, new
876             interface is introduced.
877              
878             =cut
879              
880             sub new_dir {
881 7     7 1 14462 my ( $class, $dirname, $opts ) = @_;
882              
883 7         13 my $mode;
884 7 100       19 my @args = $opts ? $opts : ();
885 7 100 100     31 if ( ref $opts eq 'HASH' && $opts->{'mode'} ) {
886 1         4 $mode = delete $opts->{'mode'};
887              
888             # This is to make sure the error checking still happens as expected
889 1 50       1 if ( keys %{$opts} == 0 ) {
  1         6  
890 1         1 @args = ();
891             }
892             }
893              
894 7         23 my $dir = $class->dir( $dirname, @args );
895 4 100       13 if ($mode) {
896 1         3 __mkdir( $dirname, $mode );
897             }
898             else {
899 3         14 __mkdir($dirname);
900             }
901              
902 4         13 return $dir;
903             }
904              
905             =head2 Mock Stats
906              
907             When creating mocked files or directories, we default their stats to:
908              
909             my $attrs = Test::MockFile->file( $file, $contents, {
910             'dev' => 0, # stat[0]
911             'inode' => 0, # stat[1]
912             'mode' => $mode, # stat[2]
913             'nlink' => 0, # stat[3]
914             'uid' => int $>, # stat[4]
915             'gid' => int $), # stat[5]
916             'rdev' => 0, # stat[6]
917             'atime' => $now, # stat[8]
918             'mtime' => $now, # stat[9]
919             'ctime' => $now, # stat[10]
920             'blksize' => 4096, # stat[11]
921             'fileno' => undef, # fileno()
922             } );
923              
924             You'll notice that mode, size, and blocks have been left out of this.
925             Mode is set to 666 (for files) or 777 (for directories), xored against
926             the current umask. Size and blocks are calculated based on the size of
927             'contents' a.k.a. the fake file.
928              
929             When you want to override one of the defaults, all you need to do is
930             specify that when you declare the file or directory. The rest will
931             continue to default.
932              
933             my $mfile = Test::MockFile->file("/root/abc", "...", {inode => 65, uid => 123, mtime => int((2000-1970) * 365.25 * 24 * 60 * 60 }));
934              
935             my $mdir = Test::MockFile->dir("/sbin", "...", { mode => 0700 }));
936              
937             =head2 new
938              
939             This class method is called by file/symlink/dir. There is no good
940             reason to call this directly.
941              
942             =cut
943              
944             sub new {
945 119     119 1 358 my $class = shift @_;
946              
947 119         200 my %opts;
948 119 50 33     639 if ( scalar @_ == 1 && ref $_[0] ) {
    0          
949 119         194 %opts = %{ $_[0] };
  119         528  
950             }
951             elsif ( scalar @_ % 2 ) {
952 0         0 confess( sprintf( "Unknown args (%d) passed to new", scalar @_ ) );
953             }
954             else {
955 0         0 %opts = @_;
956             }
957              
958 119 50       445 my $path = $opts{'path'} or confess("Mock file created without a path (filename or dirname)!");
959              
960 119 50       489 if ( $path !~ m{^/} ) {
961 0         0 $path = $opts{'path'} = _abs_path_to_file($path);
962             }
963              
964 119         222 my $now = time;
965              
966 119         2716 my $self = bless {
967             'dev' => 0, # stat[0]
968             'inode' => 0, # stat[1]
969             'mode' => 0, # stat[2]
970             'nlink' => 0, # stat[3]
971             'uid' => int $>, # stat[4]
972             'gid' => int $), # stat[5]
973             'rdev' => 0, # stat[6]
974             # 'size' => undef, # stat[7] -- Method call
975             'atime' => $now, # stat[8]
976             'mtime' => $now, # stat[9]
977             'ctime' => $now, # stat[10]
978             'blksize' => 4096, # stat[11]
979             # 'blocks' => 0, # stat[12] -- Method call
980             'fileno' => undef, # fileno()
981             'tty' => 0, # possibly this is already provided in mode?
982             'readlink' => '', # what the symlink points to.
983             'path' => undef,
984             'contents' => undef,
985             'has_content' => undef,
986             }, $class;
987              
988 119         426 foreach my $key ( keys %opts ) {
989              
990             # Ignore Stuff that's not a valid key for this class.
991 369 50       953 next unless exists $self->{$key};
992              
993             # If it's passed in, we override them.
994 369         780 $self->{$key} = $opts{$key};
995             }
996              
997 119   33     680 $self->{'fileno'} //= _unused_fileno();
998              
999 119         350 $files_being_mocked{$path} = $self;
1000 119         274 Scalar::Util::weaken( $files_being_mocked{$path} );
1001              
1002 119         657 return $self;
1003             }
1004              
1005             #Overload::FileCheck::mock_stat(\&mock_stat);
1006             sub _mock_stat {
1007 193     193   7280217 my ( $type, $file_or_fh ) = @_;
1008              
1009 193 100       1234 $type or confess("_mock_stat called without a stat type");
1010              
1011 192 100       1079 my $follow_link =
    100          
1012             $type eq 'stat' ? 1
1013             : $type eq 'lstat' ? 0
1014             : confess("Unexpected stat type '$type'");
1015              
1016             # Overload::FileCheck should always send 2 args.
1017 191 50       829 if ( scalar @_ != 2 ) {
1018 0         0 _real_file_access_hook( $type, [$file_or_fh] );
1019 0         0 return FALLBACK_TO_REAL_OP();
1020             }
1021              
1022             # Overload::FileCheck should always send something and be handling undef on its own??
1023 191 100 66     1772 if ( !defined $file_or_fh || !length $file_or_fh ) {
1024 2         15 _real_file_access_hook( $type, [$file_or_fh] );
1025 1         8 return FALLBACK_TO_REAL_OP();
1026             }
1027              
1028             # Find the path, following the symlink if required.
1029 189         645 my $file = _find_file_or_fh( $file_or_fh, $follow_link );
1030              
1031 189 100 33     2065 return [] if defined $file && defined BROKEN_SYMLINK && $file eq BROKEN_SYMLINK; # Allow an ELOOP to fall through here.
      66        
1032 188 100 33     1238 return [] if defined $file && defined CIRCULAR_SYMLINK && $file eq CIRCULAR_SYMLINK; # Allow an ELOOP to fall through here.
      66        
1033              
1034 187 50 33     844 if ( !defined $file or !length $file ) {
1035 0         0 _real_file_access_hook( $type, [$file_or_fh] );
1036 0         0 return FALLBACK_TO_REAL_OP();
1037             }
1038              
1039 187         466 my $file_data = _get_file_object($file);
1040 187 100       490 if ( !$file_data ) {
1041 109 100       7067 _real_file_access_hook( $type, [$file_or_fh] ) unless ref $file_or_fh;
1042 70         366 return FALLBACK_TO_REAL_OP();
1043             }
1044              
1045             # File is not present so no stats for you!
1046 78 100 100     205 return [] if !$file_data->is_link && !defined $file_data->contents();
1047              
1048             # Make sure the file size is correct in the stats before returning its contents.
1049 54         190 return [ $file_data->stat ];
1050             }
1051              
1052             sub _is_path_mocked {
1053 129     129   290 my ($file_path) = @_;
1054 129 50       319 my $absolute_path_to_file = _find_file_or_fh($file_path) or return;
1055              
1056 129 50       534 return $files_being_mocked{$absolute_path_to_file} ? 1 : 0;
1057             }
1058              
1059             sub _get_file_object {
1060 485     485   1075 my ($file_path) = @_;
1061              
1062 485 50       935 my $file = _find_file_or_fh($file_path) or return;
1063              
1064 485         1232 return $files_being_mocked{$file};
1065             }
1066              
1067             # This subroutine finds the absolute path to a file, returning the absolute path of what it ultimately points to.
1068             # If it is a broken link or what was passed in is undef or '', then we return undef.
1069              
1070             sub _find_file_or_fh {
1071 919     919   2135 my ( $file_or_fh, $follow_link, $depth ) = @_;
1072              
1073             # Find the file handle or fall back to just using the abs path of $file_or_fh
1074 919   66     2007 my $absolute_path_to_file = _fh_to_file($file_or_fh) // _abs_path_to_file($file_or_fh) // '';
      50        
1075 919 50       3542 $absolute_path_to_file ne '/'
1076             and $absolute_path_to_file =~ s{[/\\]$}{}xmsg;
1077              
1078             # Get the pointer to the object.
1079 919         1889 my $mock_object = $files_being_mocked{$absolute_path_to_file};
1080              
1081             # If we're following a symlink and the path we came to is a dead end (broken symlink), then return BROKEN_SYMLINK up the stack.
1082 919 100 100     2255 return BROKEN_SYMLINK if $depth and !$mock_object;
1083              
1084             # If the link we followed isn't a symlink, then return it.
1085 917 100 100     3522 return $absolute_path_to_file unless $mock_object && $mock_object->is_link;
1086              
1087             # ##############
1088             # From here on down we're only dealing with symlinks.
1089             # ##############
1090              
1091             # If we weren't told to follow the symlink then SUCCESS!
1092 34 100       98 return $absolute_path_to_file unless $follow_link;
1093              
1094             # This is still a symlink keep going. Bump our depth counter.
1095 27         42 $depth++;
1096              
1097             #Protect against circular symlink loops.
1098 27 100       66 if ( $depth > FOLLOW_LINK_MAX_DEPTH ) {
1099 2         9 $! = ELOOP;
1100 2         19 return CIRCULAR_SYMLINK;
1101             }
1102              
1103 25         62 return _find_file_or_fh( $mock_object->readlink, 1, $depth );
1104             }
1105              
1106             # Tries to find $fh as a open file handle in one of the mocked files.
1107              
1108             sub _fh_to_file {
1109 927     927   1786 my ($fh) = @_;
1110              
1111 927 100 100     4055 return unless defined $fh && length $fh;
1112              
1113             # See if $fh is a file handle. It might be a path.
1114 925         3552 foreach my $path ( sort keys %files_being_mocked ) {
1115 1167         2039 my $mock_fh = $files_being_mocked{$path}->{'fh'};
1116              
1117 1167 100       2409 next unless $mock_fh; # File isn't open.
1118 58 100       246 next unless "$mock_fh" eq "$fh"; # This mock doesn't have this file handle open.
1119              
1120 45         232 return $path;
1121             }
1122              
1123 880         3579 return;
1124             }
1125              
1126             sub _files_in_dir {
1127 132     132   211 my $dirname = shift;
1128             my @files_in_dir = @files_being_mocked{
1129 132         1452 grep m{^\Q$dirname/\E},
1130             keys %files_being_mocked
1131             };
1132              
1133 132         343 return @files_in_dir;
1134             }
1135              
1136             sub _abs_path_to_file {
1137 1083     1083   66178 my ($path) = shift;
1138              
1139 1083 100       2248 return unless defined $path;
1140              
1141 1082         1605 my $match = 1;
1142 1082         2327 while ($match) {
1143 1141         1657 $match = 0;
1144 1141 100       3463 $match = 1 if $path =~ s{//+}{/}xmsg; # cleanup multiple slashes
1145 1141 100       2647 $match = 1 if $path =~ s{/\.$}{/};
1146 1141 100       4532 $match = 1 if $path =~ s{(?:[^/]+)/\.\.(/|$)}{$1};
1147 1141 100       3417 $match = 1 if $path =~ s{/$}{};
1148             }
1149              
1150 1082 100       2341 return q[/] if $path eq q[/..];
1151              
1152 1081 100       5256 return $path if $path =~ m{^/}xms;
1153              
1154             # ~
1155             # ~/...
1156             # ~sawyer
1157 72 50       205 if ( $path =~ m{ ^(~ ([^/]+)? ) }xms ) {
1158 0         0 my $req_homedir = $1;
1159 0   0     0 my $username = $2 || getpwuid($<);
1160 0         0 my $pw_homedir;
1161              
1162             # Reset iterator so we *definitely* start from the first one
1163             # Then reset when done looping over pw entries
1164 0         0 endpwent;
1165 0         0 while ( my @pwdata = getpwent ) {
1166 0 0       0 if ( $pwdata[0] eq $username ) {
1167 0         0 $pw_homedir = $pwdata[7];
1168 0         0 endpwent;
1169 0         0 last;
1170             }
1171             }
1172 0         0 endpwent;
1173              
1174 0 0       0 $pw_homedir
1175             or die;
1176              
1177 0         0 $path =~ s{\Q$req_homedir\E}{$pw_homedir};
1178 0         0 return $path;
1179             }
1180              
1181 72         832 my $cwd = Cwd::getcwd();
1182              
1183 72 50       215 return $cwd if $path eq '.';
1184 72         707 return Cwd::getcwd() . "/$path";
1185             }
1186              
1187             sub DESTROY {
1188 119     119   54080 my ($self) = @_;
1189 119 50       429 ref $self or return;
1190              
1191             # This is just a safety. It doesn't make much sense if we get here but
1192             # $self doesn't have a path. Either way we can't delete it.
1193 119         276 my $path = $self->{'path'};
1194 119 50       395 defined $path or return;
1195              
1196             # If the object survives into global destruction, the object which is
1197             # the value of $files_being_mocked{$path} might destroy early.
1198             # As a result, don't worry about the self == check just delete the key.
1199 119 50       410 if ( defined $files_being_mocked{$path} ) {
1200 119 50       404 $self == $files_being_mocked{$path} or confess("Tried to destroy object for $path ($self) but something else is mocking it?");
1201             }
1202              
1203 119         2567 delete $files_being_mocked{$path};
1204             }
1205              
1206             =head2 contents
1207              
1208             Optional Arg: $contents
1209              
1210             Retrieves or updates the current contents of the file.
1211              
1212             Only retrieves the content of the directory (as an arrayref). You can
1213             set directory contents with calling the C method described
1214             above.
1215              
1216             Symlinks have no contents.
1217              
1218             =cut
1219              
1220             sub contents {
1221 271     271 1 4382 my ( $self, $new_contents ) = @_;
1222 271 50       623 $self or confess;
1223              
1224 271 50       475 $self->is_link
1225             and confess("checking or setting contents on a symlink is not supported");
1226              
1227             # handle directories
1228 271 100       620 if ( $self->is_dir() ) {
1229 141 50       1689 $new_contents
1230             and confess('To change the contents of the dir, you must work on its files');
1231              
1232 141 100       432 $self->{'has_content'}
1233             or return;
1234              
1235             # TODO: Quick and dirty, but works (maybe provide a ->basename()?)
1236             # Retrieve the files in this directory and removes prefix
1237 126         268 my $dirname = $self->path();
1238             my @existing_files = sort map {
1239              
1240             # strip directory from the path
1241 126         290 ( my $basename = $_->path() ) =~ s{^\Q$dirname/\E}{}xms;
  72         144  
1242              
1243             # Is this content within another directory? strip that out
1244 72         151 $basename =~ s{^( [^/]+ ) / .*}{$1}xms;
1245              
1246 72 100 100     313 defined $_->{'contents'} || $_->is_link() || $_->is_dir() ? ($basename) : ();
1247             } _files_in_dir($dirname);
1248              
1249 126         173 my %uniq;
1250 126         313 $uniq{$_}++ for @existing_files;
1251 126         837 return [ '.', '..', sort keys %uniq ];
1252             }
1253              
1254             # handle files
1255 130 50       333 if ( $self->is_file() ) {
1256 130 100       362 if ( defined $new_contents ) {
1257 12 50       31 ref $new_contents
1258             and confess('File contents must be a simple string');
1259              
1260             # XXX Why use $_[1] directly?
1261 12         24 $self->{'contents'} = $_[1];
1262             }
1263              
1264 130         672 return $self->{'contents'};
1265             }
1266              
1267 0         0 confess('This seems to be neither a file nor a dir - what is it?');
1268             }
1269              
1270             =head2 filename
1271              
1272             Deprecated. Same as C.
1273              
1274             =cut
1275              
1276             sub filename {
1277 0     0 1 0 carp('filename() is deprecated, use path() instead');
1278 0         0 goto &path;
1279             }
1280              
1281             =head2 path
1282              
1283             The path (filename or dirname) of the file or directory this mock
1284             object is controlling.
1285              
1286             =cut
1287              
1288             sub path {
1289 214     214 1 2917 my ($self) = @_;
1290 214 50       399 $self or confess("path is a method");
1291              
1292 214         734 return $self->{'path'};
1293             }
1294              
1295             =head2 unlink
1296              
1297             Makes the virtual file go away. NOTE: This also works for directories.
1298              
1299             =cut
1300              
1301             sub unlink {
1302 9     9 1 722 my ($self) = @_;
1303 9 50       27 $self or confess("unlink is a method");
1304              
1305 9 100       35 if ( !$self->exists ) {
1306 1         3 $! = ENOENT;
1307 1         4 return 0;
1308             }
1309              
1310 8 100       67 if ( $self->is_dir ) {
1311 2 50 0     10 if ( $] < 5.019 && ( $^O eq 'darwin' or $^O =~ m/bsd/i ) ) {
      33        
1312 0         0 $! = EPERM;
1313             }
1314             else {
1315 2         7 $! = EISDIR;
1316             }
1317 2         22 return 0;
1318             }
1319              
1320 6 100       18 if ( $self->is_link ) {
1321 1         2 $self->{'readlink'} = undef;
1322             }
1323             else {
1324 5         15 $self->{'has_content'} = undef;
1325 5         11 $self->{'contents'} = undef;
1326             }
1327 6         21 return 1;
1328             }
1329              
1330             =head2 touch
1331              
1332             Optional Args: ($epoch_time)
1333              
1334             This function acts like the UNIX utility touch. It sets atime, mtime,
1335             ctime to $epoch_time.
1336              
1337             If no arguments are passed, $epoch_time is set to time(). If the file
1338             does not exist, contents are set to an empty string.
1339              
1340             =cut
1341              
1342             sub touch {
1343 6     6 1 2010 my ( $self, $now ) = @_;
1344 6 50       19 $self or confess("touch is a method");
1345 6   66     37 $now //= time;
1346              
1347 6 100       16 $self->is_file or confess("touch only supports files");
1348              
1349 4         16 my $pre_size = $self->size();
1350              
1351 4 100       15 if ( !defined $pre_size ) {
1352 2         63 $self->contents('');
1353             }
1354              
1355             # TODO: Should this happen any time contents goes from undef to existing? Should we be setting perms?
1356             # Normally I'd say yes but it might not matter much for a .005 second test.
1357 4         17 $self->mtime($now);
1358 4         12 $self->ctime($now);
1359 4         14 $self->atime($now);
1360              
1361 4         16 return 1;
1362             }
1363              
1364             =head2 stat
1365              
1366             Returns the stat of a mocked file (does not follow symlinks.)
1367              
1368             =cut
1369              
1370             sub stat {
1371 54     54 1 115 my $self = shift;
1372              
1373             return (
1374             $self->{'dev'}, # stat[0]
1375             $self->{'inode'}, # stat[1]
1376             $self->{'mode'}, # stat[2]
1377             $self->{'nlink'}, # stat[3]
1378             $self->{'uid'}, # stat[4]
1379             $self->{'gid'}, # stat[5]
1380             $self->{'rdev'}, # stat[6]
1381             $self->size, # stat[7]
1382             $self->{'atime'}, # stat[8]
1383             $self->{'mtime'}, # stat[9]
1384             $self->{'ctime'}, # stat[10]
1385 54         261 $self->{'blksize'}, # stat[11]
1386             $self->blocks, # stat[12]
1387             );
1388             }
1389              
1390             sub _unused_fileno {
1391 119     119   399 return 900; # TODO
1392             }
1393              
1394             =head2 readlink
1395              
1396             Optional Arg: $readlink
1397              
1398             Returns the stat of a mocked file (does not follow symlinks.) You can
1399             also use this to change what your symlink is pointing to.
1400              
1401             =cut
1402              
1403             sub readlink {
1404 27     27 1 122 my ( $self, $readlink ) = @_;
1405              
1406 27 50       81 $self->is_link or confess("readlink is only supported for symlinks");
1407              
1408 27 50       70 if ( scalar @_ == 2 ) {
1409 0 0 0     0 if ( defined $readlink && ref $readlink ) {
1410 0         0 confess("readlink can only be set to simple strings.");
1411             }
1412              
1413 0         0 $self->{'readlink'} = $readlink;
1414             }
1415              
1416 27         104 return $self->{'readlink'};
1417             }
1418              
1419             =head2 is_link
1420              
1421             returns true/false, depending on whether this object is a symlink.
1422              
1423             =cut
1424              
1425             sub is_link {
1426 1037     1037 1 1617 my ($self) = @_;
1427              
1428 1037 100 66     6539 return ( defined $self->{'readlink'} && length $self->{'readlink'} && $self->{'mode'} & S_IFLNK ) ? 1 : 0;
1429             }
1430              
1431             =head2 is_dir
1432              
1433             returns true/false, depending on whether this object is a directory.
1434              
1435             =cut
1436              
1437             sub is_dir {
1438 347     347 1 526 my ($self) = @_;
1439              
1440 347 100       1219 return ( ( $self->{'mode'} & S_IFMT ) == S_IFDIR ) ? 1 : 0;
1441             }
1442              
1443             =head2 is_file
1444              
1445             returns true/false, depending on whether this object is a regular file.
1446              
1447             =cut
1448              
1449             sub is_file {
1450 284     284 1 485 my ($self) = @_;
1451              
1452 284 100       1469 return ( ( $self->{'mode'} & S_IFMT ) == S_IFREG ) ? 1 : 0;
1453             }
1454              
1455             =head2 size
1456              
1457             returns the size of the file based on its contents.
1458              
1459             =cut
1460              
1461             sub size {
1462 113     113 1 281 my ($self) = @_;
1463              
1464             # Lstat for a symlink returns 1 for its size.
1465 113 100       238 return 1 if $self->is_link;
1466              
1467             # length undef is 0 not undef in perl 5.10
1468 111 50       327 if ( $] < 5.012 ) {
1469 0 0       0 return undef unless $self->exists;
1470             }
1471              
1472 111         217 return length $self->contents;
1473             }
1474              
1475             =head2 exists
1476              
1477             returns true or false based on if the file exists right now.
1478              
1479             =cut
1480              
1481             sub exists {
1482 143     143 1 258 my ($self) = @_;
1483              
1484             $self->is_link()
1485 143 50       218 and return defined $self->{'readlink'} ? 1 : 0;
    100          
1486              
1487             $self->is_file()
1488 140 100       250 and return defined $self->{'contents'} ? 1 : 0;
    100          
1489              
1490             $self->is_dir()
1491 53 100       110 and return $self->{'has_content'} ? 1 : 0;
    100          
1492              
1493 1         4 return 0;
1494             }
1495              
1496             =head2 blocks
1497              
1498             Calculates the block count of the file based on its size.
1499              
1500             =cut
1501              
1502             sub blocks {
1503 54     54 1 160 my ($self) = @_;
1504              
1505 54         122 my $blocks = int( $self->size / abs( $self->{'blksize'} ) + 1 );
1506 54 50       163 if ( int($blocks) > $blocks ) {
1507 0         0 $blocks = int($blocks) + 1;
1508             }
1509 54         546 return $blocks;
1510             }
1511              
1512             =head2 chmod
1513              
1514             Optional Arg: $perms
1515              
1516             Allows you to alter the permissions of a file. This only allows you to
1517             change the C<07777> bits of the file permissions. The number passed
1518             should be the octal C<0755> form, not the alphabetic C<"755"> form
1519              
1520             =cut
1521              
1522             sub chmod {
1523 0     0 1 0 my ( $self, $mode ) = @_;
1524              
1525 0         0 $mode = ( int($mode) & S_IFPERMS ) ^ umask;
1526              
1527 0         0 $self->{'mode'} = ( $self->{'mode'} & S_IFMT ) + $mode;
1528              
1529 0         0 return $mode;
1530             }
1531              
1532             =head2 permissions
1533              
1534             Returns the permissions of the file.
1535              
1536             =cut
1537              
1538             sub permissions {
1539 4     4 1 11 my ($self) = @_;
1540              
1541 4         17 return int( $self->{'mode'} ) & S_IFPERMS;
1542             }
1543              
1544             =head2 mtime
1545              
1546             Optional Arg: $new_epoch_time
1547              
1548             Returns and optionally sets the mtime of the file if passed as an
1549             integer.
1550              
1551             =cut
1552              
1553             sub mtime {
1554 7     7 1 374 my ( $self, $time ) = @_;
1555              
1556 7 50 66     63 if ( scalar @_ == 2 && defined $time && $time =~ m/^[0-9]+$/ ) {
      66        
1557 5         14 $self->{'mtime'} = $time;
1558             }
1559              
1560 7         17 return $self->{'mtime'};
1561             }
1562              
1563             =head2 ctime
1564              
1565             Optional Arg: $new_epoch_time
1566              
1567             Returns and optionally sets the ctime of the file if passed as an
1568             integer.
1569              
1570             =cut
1571              
1572             sub ctime {
1573 7     7 1 15 my ( $self, $time ) = @_;
1574              
1575 7 50 66     54 if ( @_ == 2 && defined $time && $time =~ m/^[0-9]+$/ ) {
      66        
1576 5         11 $self->{'ctime'} = $time;
1577             }
1578              
1579 7         16 return $self->{'ctime'};
1580             }
1581              
1582             =head2 atime
1583              
1584             Optional Arg: $new_epoch_time
1585              
1586             Returns and optionally sets the atime of the file if passed as an
1587             integer.
1588              
1589             =cut
1590              
1591             sub atime {
1592 7     7 1 19 my ( $self, $time ) = @_;
1593              
1594 7 50 66     65 if ( @_ == 2 && defined $time && $time =~ m/^[0-9]+$/ ) {
      66        
1595 5         12 $self->{'atime'} = $time;
1596             }
1597              
1598 7         17 return $self->{'atime'};
1599             }
1600              
1601             =head2 add_file_access_hook
1602              
1603             Args: ( $code_ref )
1604              
1605             You can use B to add a code ref that gets called
1606             every time a real file (not mocked) operation happens. We use this for
1607             strict mode to die if we detect your program is unexpectedly accessing
1608             files. You are welcome to use it for whatever you like.
1609              
1610             Whenever the code ref is called, we pass 2 arguments:
1611             C<$code-E($access_type, $at_under_ref)>. Be aware that altering the
1612             variables in C<$at_under_ref> will affect the variables passed to open
1613             / sysopen, etc.
1614              
1615             One use might be:
1616              
1617             Test::MockFile::add_file_access_hook(sub { my $type = shift; print "$type called at: " . Carp::longmess() } );
1618              
1619             =cut
1620              
1621             # always use the _strict_mode_violation
1622             my @_public_access_hooks;
1623             my @_internal_access_hooks = ( \&_strict_mode_violation );
1624              
1625             sub add_file_access_hook {
1626 0     0 1 0 my ($code_ref) = @_;
1627              
1628 0 0 0     0 ( $code_ref && ref $code_ref eq 'CODE' ) or confess("add_file_access_hook needs to be passed a code reference.");
1629 0         0 push @_public_access_hooks, $code_ref;
1630              
1631 0         0 return 1;
1632             }
1633              
1634             =head2 clear_file_access_hooks
1635              
1636             Calling this subroutine will clear everything that was passed to
1637             B
1638              
1639             =cut
1640              
1641             sub clear_file_access_hooks {
1642 0     0 1 0 @_public_access_hooks = ();
1643              
1644 0         0 return 1;
1645             }
1646              
1647             # This code is called whenever an unmocked file is accessed. Any hooks that are setup get called from here.
1648              
1649             sub _real_file_access_hook {
1650 249     249   613 my ( $access_type, $at_under_ref ) = @_;
1651              
1652 249         723 foreach my $code ( @_internal_access_hooks, @_public_access_hooks ) {
1653 249         876 $code->( $access_type, $at_under_ref );
1654             }
1655              
1656 195         372 return 1;
1657             }
1658              
1659             =head2 How this mocking is done:
1660              
1661             Test::MockFile uses 2 methods to mock file access:
1662              
1663             =head3 -X via L
1664              
1665             It is currently not possible in pure perl to override
1666             L,
1667             L and L<-X
1668             operators|http://perldoc.perl.org/functions/-X.html>. In conjunction
1669             with this module, we've developed L.
1670              
1671             This enables us to intercept calls to stat, lstat and -X operators
1672             (like -e, -f, -d, -s, etc.) and pass them to our control. If the file
1673             is currently being mocked, we return the stat (or lstat) information on
1674             the file to be used to determine the answer to whatever check was made.
1675             This even works for things like C<-e _>. If we do not control the file
1676             in question, we return C which then makes a
1677             normal check.
1678              
1679             =head3 CORE::GLOBAL:: overrides
1680              
1681             Since 5.10, it has been possible to override function calls by defining
1682             them. like:
1683              
1684             *CORE::GLOBAL::open = sub(*;$@) {...}
1685              
1686             Any code which is loaded B this happens will use the alternate
1687             open. This means you can place your C statement
1688             after statements you don't want to be mocked and there is no risk that
1689             the code will ever be altered by Test::MockFile.
1690              
1691             We oveload the following statements and then return tied handles to
1692             enable the rest of the IO functions to work properly. Only B /
1693             B are needed to address file operations. However B
1694             file handles were never setup for tie so we have to override all of
1695             B's related functions.
1696              
1697             =over
1698              
1699             =item * open
1700              
1701             =item * sysopen
1702              
1703             =item * opendir
1704              
1705             =item * readdir
1706              
1707             =item * telldir
1708              
1709             =item * seekdir
1710              
1711             =item * rewinddir
1712              
1713             =item * closedir
1714              
1715             =back
1716              
1717             =cut
1718              
1719             # goto doesn't work below 5.16
1720             #
1721             # goto messed up refcount between 5.22 and 5.26.
1722             # Broken in 7bdb4ff0943cf93297712faf504cdd425426e57f
1723             # Fixed in https://rt.perl.org/Public/Bug/Display.html?id=115814
1724             sub _goto_is_available {
1725 131 100   131   30516 return 0 if $] < 5.015;
1726 129 100       330 return 1 if $] < 5.021;
1727 126 100       32859 return 1 if $] > 5.027;
1728 3         13 return 0; # 5.
1729             }
1730              
1731             ############
1732             # KEYWORDS #
1733             ############
1734              
1735             sub __glob {
1736 10     10   18 my $spec = shift;
1737              
1738             # Text::Glob does not understand multiple patterns
1739 10         22 my @patterns = split /\s+/xms, $spec;
1740              
1741             # Text::Glob does not accept directories in globbing
1742             # But csh (and thus, Perl) does, so we need to add them
1743 10         39 my @mocked_files = grep $files_being_mocked{$_}->exists(), keys %files_being_mocked;
1744 10 100       101 @mocked_files = map /^(.+)\/[^\/]+$/xms ? ( $_, $1 ) : ($_), @mocked_files;
1745              
1746             # Might as well be consistent
1747 10         37 @mocked_files = sort @mocked_files;
1748              
1749 10         29 my @results = map Text::Glob::match_glob( $_, @mocked_files ), @patterns;
1750 10         1448 return @results;
1751             }
1752              
1753             sub __open (*;$@) {
1754 87     87   400777 my $likely_bareword;
1755             my $arg0;
1756 87 50 66     501 if ( defined $_[0] && !ref $_[0] ) {
1757              
1758             # We need to remember the first arg to override the typeglob for barewords
1759 0         0 $arg0 = $_[0];
1760 0         0 ( $likely_bareword, @_ ) = _upgrade_barewords(@_);
1761             }
1762              
1763             # We need to take out the mode and file
1764             # but we must keep using $_[0] for the file-handle to update the caller
1765 87         330 my ( undef, $mode, $file ) = @_;
1766 87         307 my $arg_count = @_;
1767              
1768             # Normalize two-arg to three-arg
1769 87 100       323 if ( $arg_count == 2 ) {
1770              
1771             # The order here matters, so '>>' won't turn into '>'
1772 10 100       160 if ( $_[1] =~ /^ ( >> | [+]?> | [+]?< ) (.+) $/xms ) {
    100          
    100          
    50          
1773 7         21 $mode = $1;
1774 7         33 $file = $2;
1775             }
1776             elsif ( $_[1] =~ /^[\.\/\\\w\d\-]+$/xms ) {
1777 1         2 $mode = '<';
1778 1         2 $file = $_[1];
1779             }
1780             elsif ( $_[1] =~ /^\|/xms ) {
1781 1         10 $mode = '|-';
1782 1         7 $file = $_[1];
1783             }
1784             elsif ( $_[1] =~ /\|$/xms ) {
1785 1         7 $mode = '-|';
1786 1         6 $file = $_[1];
1787             }
1788             else {
1789 0         0 die "Unsupported two-way open: $_[1]\n";
1790             }
1791              
1792             # We have all args
1793 10         15 $arg_count++;
1794             }
1795              
1796             # We're not supporting 1 arg opens yet
1797 87 50       394 if ( $arg_count != 3 ) {
1798 0         0 _real_file_access_hook( "open", \@_ );
1799 0 0       0 goto \&CORE::open if _goto_is_available();
1800 0 0       0 if ( @_ == 1 ) {
    0          
    0          
1801 0         0 return CORE::open( $_[0] );
1802             }
1803             elsif ( @_ == 2 ) {
1804 0         0 return CORE::open( $_[0], $_[1] );
1805             }
1806             elsif ( @_ >= 3 ) {
1807 0         0 return CORE::open( $_[0], $_[1], @_[ 2 .. $#_ ] );
1808             }
1809             }
1810              
1811             # Allows for scalar file handles.
1812 87 50 66     434 if ( ref $file && ref $file eq 'SCALAR' ) {
1813 0 0       0 goto \&CORE::open if _goto_is_available();
1814 0         0 return CORE::open( $_[0], $mode, $file );
1815             }
1816              
1817 87         364 my $abs_path = _find_file_or_fh( $file, 1 ); # Follow the link.
1818 87 0 33     349 confess() if !$abs_path && $mode ne '|-' && $mode ne '-|';
      33        
1819 87 50       384 confess() if $abs_path eq BROKEN_SYMLINK;
1820 87         250 my $mock_file = _get_file_object($abs_path);
1821              
1822             # For now we're going to just strip off the binmode and hope for the best.
1823 87         338 $mode =~ s/(:.+$)//;
1824 87         256 my $encoding_mode = $1;
1825              
1826             # TODO: We don't yet support |- or -|
1827             # TODO: We don't yet support modes outside of > < >> +< +> +>>
1828             # We just pass through to open if we're not mocking the file right now.
1829 87 100 100     671 if ( ( $mode eq '|-' || $mode eq '-|' )
      100        
      100        
1830 498         1335 or !grep { $_ eq $mode } qw/> < >> +< +> +>>/
1831             or !defined $mock_file ) {
1832 58         326 _real_file_access_hook( "open", \@_ );
1833 49 50       181 goto \&CORE::open if _goto_is_available();
1834 0 0       0 if ( @_ == 1 ) {
    0          
    0          
1835 0         0 return CORE::open( $_[0] );
1836             }
1837             elsif ( @_ == 2 ) {
1838 0         0 return CORE::open( $_[0], $_[1] );
1839             }
1840             elsif ( @_ >= 3 ) {
1841 0         0 return CORE::open( $_[0], $_[1], @_[ 2 .. $#_ ] );
1842             }
1843             }
1844              
1845             # At this point we're mocking the file. Let's do it!
1846              
1847             # If contents is undef, we act like the file isn't there.
1848 29 100 100     107 if ( !defined $mock_file->contents() && grep { $mode eq $_ } qw/< +
  12         47  
1849 3         10 $! = ENOENT;
1850 3         18 return;
1851             }
1852              
1853 26         73 my $rw = '';
1854 26 100       96 $rw .= 'r' if grep { $_ eq $mode } qw/+< +> +>>
  104         215  
1855 26 100       81 $rw .= 'w' if grep { $_ eq $mode } qw/+< +> +>> > >>/;
  130         354  
1856              
1857 26         282 my $filefh = IO::File->new;
1858 26         1327 tie *{$filefh}, 'Test::MockFile::FileHandle', $abs_path, $rw;
  26         409  
1859              
1860 26 50       75 if ($likely_bareword) {
1861 0         0 my $caller = caller();
1862 50     50   606 no strict;
  50         108  
  50         64816  
1863 0         0 *{"${caller}::$arg0"} = $filefh;
  0         0  
1864 0 0       0 @_ = ( $filefh, $_[1] ? @_[ 1 .. $#_ ] : () );
1865             }
1866             else {
1867 26         64 $_[0] = $filefh;
1868             }
1869              
1870             # This is how we tell if the file is open by something.
1871              
1872 26         70 $mock_file->{'fh'} = $_[0];
1873 26 50       122 Scalar::Util::weaken( $mock_file->{'fh'} ) if ref $_[0]; # Will this make it go out of scope?
1874              
1875             # Fix tell based on open options.
1876 26 100 66     280 if ( $mode eq '>>' or $mode eq '+>>' ) {
    100 100        
1877 2   50     11 $mock_file->{'contents'} //= '';
1878 2         18 seek $_[0], length( $mock_file->{'contents'} ), 0;
1879             }
1880             elsif ( $mode eq '>' or $mode eq '+>' ) {
1881 8         29 $mock_file->{'contents'} = '';
1882             }
1883              
1884 26         138 return 1;
1885             }
1886              
1887             # sysopen FILEHANDLE, FILENAME, MODE, MASK
1888             # sysopen FILEHANDLE, FILENAME, MODE
1889              
1890             # We curently support:
1891             # 1 - O_RDONLY - Read only.
1892             # 2 - O_WRONLY - Write only.
1893             # 3 - O_RDWR - Read and write.
1894             # 6 - O_APPEND - Append to the file.
1895             # 7 - O_TRUNC - Truncate the file.
1896             # 5 - O_EXCL - Fail if the file already exists.
1897             # 4 - O_CREAT - Create the file if it doesn't exist.
1898             # 8 - O_NOFOLLOW - Fail if the last path component is a symbolic link.
1899              
1900             sub __sysopen (*$$;$) {
1901 23     23   15740 my $mock_file = _get_file_object( $_[1] );
1902              
1903 23 100       75 if ( !$mock_file ) {
1904 20         82 _real_file_access_hook( "sysopen", \@_ );
1905 18 50       68 goto \&CORE::sysopen if _goto_is_available();
1906 0         0 return CORE::sysopen( $_[0], $_[1], @_[ 2 .. $#_ ] );
1907             }
1908              
1909 3         7 my $sysopen_mode = $_[2];
1910              
1911             # Not supported by my linux vendor: O_EXLOCK | O_SHLOCK
1912 3 50       28 if ( ( $sysopen_mode & SUPPORTED_SYSOPEN_MODES ) != $sysopen_mode ) {
1913 0         0 confess( sprintf( "Sorry, can't open %s with 0x%x permissions. Some of your permissions are not yet supported by %s", $_[1], $sysopen_mode, __PACKAGE__ ) );
1914             }
1915              
1916             # O_NOFOLLOW
1917 3 50 66     18 if ( ( $sysopen_mode & O_NOFOLLOW ) == O_NOFOLLOW && $mock_file->is_link ) {
1918 0         0 $! = 40;
1919 0         0 return undef;
1920             }
1921              
1922             # O_EXCL
1923 3 50 66     17 if ( $sysopen_mode & O_EXCL && $sysopen_mode & O_CREAT && defined $mock_file->{'contents'} ) {
      66        
1924 0         0 $! = EEXIST;
1925 0         0 return;
1926             }
1927              
1928             # O_CREAT
1929 3 100 66     26 if ( $sysopen_mode & O_CREAT && !defined $mock_file->{'contents'} ) {
1930 1         4 $mock_file->{'contents'} = '';
1931             }
1932              
1933             # O_TRUNC
1934 3 100 66     53 if ( $sysopen_mode & O_TRUNC && defined $mock_file->{'contents'} ) {
1935 1         3 $mock_file->{'contents'} = '';
1936              
1937             }
1938              
1939 3         7 my $rd_wr_mode = $sysopen_mode & 3;
1940 3 0       15 my $rw =
    50          
    100          
1941             $rd_wr_mode == O_RDONLY ? 'r'
1942             : $rd_wr_mode == O_WRONLY ? 'w'
1943             : $rd_wr_mode == O_RDWR ? 'rw'
1944             : confess("Unexpected sysopen read/write mode ($rd_wr_mode)"); # O_WRONLY| O_RDWR mode makes no sense and we should die.
1945              
1946             # If contents is undef, we act like the file isn't there.
1947 3 50 33     62 if ( !defined $mock_file->{'contents'} && $rd_wr_mode == O_RDONLY ) {
1948 0         0 $! = ENOENT;
1949 0         0 return;
1950             }
1951              
1952 3         8 my $abs_path = $mock_file->{'path'};
1953              
1954 3         29 $_[0] = IO::File->new;
1955 3         161 tie *{ $_[0] }, 'Test::MockFile::FileHandle', $abs_path, $rw;
  3         52  
1956              
1957             # This is how we tell if the file is open by something.
1958 3         10 $files_being_mocked{$abs_path}->{'fh'} = $_[0];
1959 3 50       12 Scalar::Util::weaken( $files_being_mocked{$abs_path}->{'fh'} ) if ref $_[0]; # Will this make it go out of scope?
1960              
1961             # O_TRUNC
1962 3 100       13 if ( $sysopen_mode & O_TRUNC ) {
1963 1         3 $mock_file->{'contents'} = '';
1964             }
1965              
1966             # O_APPEND
1967 3 50       10 if ( $sysopen_mode & O_APPEND ) {
1968 0         0 seek $_[0], length $mock_file->{'contents'}, 0;
1969             }
1970              
1971 3         19 return 1;
1972             }
1973              
1974             sub __opendir (*$) {
1975              
1976             # Upgrade but ignore bareword indicator
1977 26 100 66 26   13465 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
1978              
1979 26         82 my $mock_dir = _get_file_object( $_[1] );
1980              
1981             # 1 arg Opendir doesn't work??
1982 26 50 33     188 if ( scalar @_ != 2 or !defined $_[1] ) {
1983 0         0 _real_file_access_hook( "opendir", \@_ );
1984              
1985 0 0       0 goto \&CORE::opendir if _goto_is_available();
1986              
1987 0         0 return CORE::opendir( $_[0], @_[ 1 .. $#_ ] );
1988             }
1989              
1990 26 100       242 if ( !$mock_dir ) {
1991 12         50 _real_file_access_hook( "opendir", \@_ );
1992 9 50       27 goto \&CORE::opendir if _goto_is_available();
1993 0         0 return CORE::opendir( $_[0], $_[1] );
1994             }
1995              
1996 14 50       55 if ( !defined $mock_dir->contents ) {
1997 0         0 $! = ENOENT;
1998 0         0 return undef;
1999             }
2000              
2001 14 100       50 if ( !( $mock_dir->{'mode'} & S_IFDIR ) ) {
2002 1         4 $! = ENOTDIR;
2003 1         8 return undef;
2004             }
2005              
2006 13 100       75 if ( !defined $_[0] ) {
    50          
2007 12         50 $_[0] = Symbol::gensym;
2008             }
2009             elsif ( ref $_[0] ) {
2010 50     50   453 no strict 'refs';
  50         118  
  50         168831  
2011 1         6 *{ $_[0] } = Symbol::geniosym;
  1         38  
2012             }
2013              
2014             # This is how we tell if the file is open by something.
2015 13         225 my $abs_path = $mock_dir->{'path'};
2016 13         37 $mock_dir->{'obj'} = Test::MockFile::DirHandle->new( $abs_path, $mock_dir->contents() );
2017 13         44 $mock_dir->{'fh'} = "$_[0]";
2018              
2019 13         51 return 1;
2020              
2021             }
2022              
2023             sub __readdir (*) {
2024              
2025             # Upgrade but ignore bareword indicator
2026 28 50 33 28   3781 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
2027              
2028 28         74 my $mocked_dir = _get_file_object( $_[0] );
2029              
2030 28 100       93 if ( !$mocked_dir ) {
2031 6         45 _real_file_access_hook( 'readdir', \@_ );
2032 6 50       12 goto \&CORE::readdir if _goto_is_available();
2033 0         0 return CORE::readdir( $_[0] );
2034             }
2035              
2036 22         61 my $obj = $mocked_dir->{'obj'};
2037 22 50       72 if ( !$obj ) {
2038 0         0 confess("Read on a closed handle");
2039             }
2040              
2041 22 50       96 if ( !defined $obj->{'files_in_readdir'} ) {
2042 0         0 confess("Did a readdir on an empty dir. This shouldn't have been able to have been opened!");
2043             }
2044              
2045 22 50       89 if ( !defined $obj->{'tell'} ) {
2046 0         0 confess("readdir called on a closed dirhandle");
2047             }
2048              
2049             # At EOF for the dir handle.
2050 22 100       61 return undef if $obj->{'tell'} > $#{ $obj->{'files_in_readdir'} };
  22         87  
2051              
2052 18 100       50 if (wantarray) {
2053 14         22 my @return;
2054 14         22 foreach my $pos ( $obj->{'tell'} .. $#{ $obj->{'files_in_readdir'} } ) {
  14         51  
2055 39         80 push @return, $obj->{'files_in_readdir'}->[$pos];
2056             }
2057 14         50 $obj->{'tell'} = $#{ $obj->{'files_in_readdir'} } + 1;
  14         34  
2058 14         78 return @return;
2059             }
2060              
2061 4         26 return $obj->{'files_in_readdir'}->[ $obj->{'tell'}++ ];
2062             }
2063              
2064             sub __telldir (*) {
2065              
2066             # Upgrade but ignore bareword indicator
2067 4 50 33 4   30 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
2068              
2069 4         8 my ($fh) = @_;
2070 4         10 my $mocked_dir = _get_file_object($fh);
2071              
2072 4 50 33     19 if ( !$mocked_dir || !$mocked_dir->{'obj'} ) {
2073 0         0 _real_file_access_hook( 'telldir', \@_ );
2074 0 0       0 goto \&CORE::telldir if _goto_is_available();
2075 0         0 return CORE::telldir($fh);
2076             }
2077              
2078 4         6 my $obj = $mocked_dir->{'obj'};
2079              
2080 4 50       21 if ( !defined $obj->{'files_in_readdir'} ) {
2081 0         0 confess("Did a telldir on an empty dir. This shouldn't have been able to have been opened!");
2082             }
2083              
2084 4 50       10 if ( !defined $obj->{'tell'} ) {
2085 0         0 confess("telldir called on a closed dirhandle");
2086             }
2087              
2088 4         25 return $obj->{'tell'};
2089             }
2090              
2091             sub __rewinddir (*) {
2092              
2093             # Upgrade but ignore bareword indicator
2094 1 50 33 1   7 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
2095              
2096 1         2 my ($fh) = @_;
2097 1         3 my $mocked_dir = _get_file_object($fh);
2098              
2099 1 50 33     5 if ( !$mocked_dir || !$mocked_dir->{'obj'} ) {
2100 0         0 _real_file_access_hook( 'rewinddir', \@_ );
2101 0 0       0 goto \&CORE::rewinddir if _goto_is_available();
2102 0         0 return CORE::rewinddir( $_[0] );
2103             }
2104              
2105 1         2 my $obj = $mocked_dir->{'obj'};
2106              
2107 1 50       15 if ( !defined $obj->{'files_in_readdir'} ) {
2108 0         0 confess("Did a rewinddir on an empty dir. This shouldn't have been able to have been opened!");
2109             }
2110              
2111 1 50       3 if ( !defined $obj->{'tell'} ) {
2112 0         0 confess("rewinddir called on a closed dirhandle");
2113             }
2114              
2115 1         2 $obj->{'tell'} = 0;
2116 1         4 return 1;
2117             }
2118              
2119             sub __seekdir (*$) {
2120              
2121             # Upgrade but ignore bareword indicator
2122 1 50 33 1   13 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
2123              
2124 1         3 my ( $fh, $goto ) = @_;
2125 1         4 my $mocked_dir = _get_file_object($fh);
2126              
2127 1 50 33     8 if ( !$mocked_dir || !$mocked_dir->{'obj'} ) {
2128 0         0 _real_file_access_hook( 'seekdir', \@_ );
2129 0 0       0 goto \&CORE::seekdir if _goto_is_available();
2130 0         0 return CORE::seekdir( $fh, $goto );
2131             }
2132              
2133 1         4 my $obj = $mocked_dir->{'obj'};
2134              
2135 1 50       22 if ( !defined $obj->{'files_in_readdir'} ) {
2136 0         0 confess("Did a seekdir on an empty dir. This shouldn't have been able to have been opened!");
2137             }
2138              
2139 1 50       5 if ( !defined $obj->{'tell'} ) {
2140 0         0 confess("seekdir called on a closed dirhandle");
2141             }
2142              
2143 1         8 return $obj->{'tell'} = $goto;
2144             }
2145              
2146             sub __closedir (*) {
2147              
2148             # Upgrade but ignore bareword indicator
2149 14 50 33 14   7736 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
2150              
2151 14         31 my ($fh) = @_;
2152 14         31 my $mocked_dir = _get_file_object($fh);
2153              
2154 14 100 66     72 if ( !$mocked_dir || !$mocked_dir->{'obj'} ) {
2155 1         4 _real_file_access_hook( 'closedir', \@_ );
2156 1 50       1 goto \&CORE::closedir if _goto_is_available();
2157 0         0 return CORE::closedir($fh);
2158             }
2159              
2160 13         71 delete $mocked_dir->{'obj'};
2161 13         25 delete $mocked_dir->{'fh'};
2162              
2163 13         38 return 1;
2164             }
2165              
2166             sub __unlink (@) {
2167 13     13   102222 my @files_to_unlink = @_;
2168 13         32 my $files_deleted = 0;
2169              
2170 13         40 foreach my $file (@files_to_unlink) {
2171 13         49 my $mock = _get_file_object($file);
2172              
2173 13 100       49 if ( !$mock ) {
2174 9         50 _real_file_access_hook( "unlink", [$file] );
2175 9         1107 $files_deleted += CORE::unlink($file);
2176             }
2177             else {
2178 4         14 $files_deleted += $mock->unlink;
2179             }
2180             }
2181              
2182 13         177 return $files_deleted;
2183              
2184             }
2185              
2186             sub __readlink (_) {
2187 7     7   3436 my ($file) = @_;
2188              
2189 7 100       22 if ( !defined $file ) {
2190 2         296 carp('Use of uninitialized value in readlink');
2191 2 50       21 if ( $^O eq 'freebsd' ) {
2192 0         0 $! = EINVAL;
2193             }
2194             else {
2195 2         5 $! = ENOENT;
2196             }
2197 2         7 return;
2198             }
2199              
2200 5         25 my $mock_object = _get_file_object($file);
2201 5 100       20 if ( !$mock_object ) {
2202 1         6 _real_file_access_hook( 'readlink', \@_ );
2203 1 50       4 goto \&CORE::readlink if _goto_is_available();
2204 0         0 return CORE::readlink($file);
2205             }
2206              
2207 4 100       9 if ( !$mock_object->is_link ) {
2208 2         5 $! = EINVAL;
2209 2         12 return;
2210             }
2211 2         7 return $mock_object->readlink;
2212             }
2213              
2214             # $file is always passed because of the prototype.
2215             sub __mkdir (_;$) {
2216 48     48   12469 my ( $file, $perms ) = @_;
2217              
2218 48   100     184 $perms = ( $perms // 0777 ) & S_IFPERMS;
2219              
2220 48 100       119 if ( !defined $file ) {
2221              
2222             # mkdir warns if $file is undef
2223 1         173 carp("Use of uninitialized value in mkdir");
2224 1         9 $! = ENOENT;
2225 1         3 return 0;
2226             }
2227              
2228 47         116 my $mock = _get_file_object($file);
2229              
2230 47 100       149 if ( !$mock ) {
2231 22         68 _real_file_access_hook( 'mkdir', \@_ );
2232 22 50       48 goto \&CORE::mkdir if _goto_is_available();
2233 0         0 return CORE::mkdir(@_);
2234             }
2235              
2236             # File or directory, this exists and should fail
2237 25 100       88 if ( $mock->exists ) {
2238 6         18 $! = EEXIST;
2239 6         32 return 0;
2240             }
2241              
2242             # If the mock was a symlink or a file, we've just made it a dir.
2243 19         116 $mock->{'mode'} = ( $perms ^ umask ) | S_IFDIR;
2244 19         49 delete $mock->{'readlink'};
2245              
2246             # This should now start returning content
2247 19         45 $mock->{'has_content'} = 1;
2248              
2249 19         113 return 1;
2250             }
2251              
2252             # $file is always passed because of the prototype.
2253             sub __rmdir (_) {
2254 14     14   12343 my ($file) = @_;
2255              
2256             # technically this is a minor variation from core. We don't seem to be able to
2257             # detect when they didn't pass an arg like core can.
2258             # Core sometimes warns: 'Use of uninitialized value $_ in rmdir'
2259 14 100       56 if ( !defined $file ) {
2260 1         135 carp('Use of uninitialized value in rmdir');
2261 1         6 return 0;
2262             }
2263              
2264 13         32 my $mock = _get_file_object($file);
2265              
2266 13 100       34 if ( !$mock ) {
2267 4         16 _real_file_access_hook( 'rmdir', \@_ );
2268 4 50       10 goto \&CORE::rmdir if _goto_is_available();
2269 0         0 return CORE::rmdir($file);
2270             }
2271              
2272             # Because we've mocked this to be a file and it doesn't exist we are going to die here.
2273             # The tester needs to fix this presumably.
2274 9 100       27 if ( $mock->exists ) {
2275 8 100       17 if ( $mock->is_file ) {
2276 1         2 $! = ENOTDIR;
2277 1         4 return 0;
2278             }
2279              
2280 7 100       24 if ( $mock->is_link ) {
2281 1         2 $! = ENOTDIR;
2282 1         4 return 0;
2283             }
2284             }
2285              
2286 7 100       16 if ( !$mock->exists ) {
2287 1         3 $! = ENOENT;
2288 1         4 return 0;
2289             }
2290              
2291 6 100       15 if ( _files_in_dir($file) ) {
2292 1         2 $! = 39;
2293 1         4 return 0;
2294             }
2295              
2296 5         15 $mock->{'has_content'} = undef;
2297 5         24 return 1;
2298             }
2299              
2300             sub __chown (@) {
2301 13     13   14058 my ( $uid, $gid, @files ) = @_;
2302              
2303 13 50       57 $^O eq 'MSWin32'
2304             and return 0; # does nothing on Windows
2305              
2306             # Not an error, report we changed zero files
2307             @files
2308 13 50       27 or return 0;
2309              
2310 13         105 my %mocked_files = map +( $_ => _get_file_object($_) ), @files;
2311 13         43 my @unmocked_files = grep !$mocked_files{$_}, @files;
2312 13 100       75 my @mocked_files = map ref $_ ? $_->{'path'} : (), values %mocked_files;
2313              
2314             # The idea is that if some are mocked and some are not,
2315             # it's probably a mistake
2316 13 100 66     52 if ( @mocked_files && @mocked_files != @files ) {
2317 1         302 confess(
2318             sprintf 'You called chown() on a mix of mocked (%s) and unmocked files (%s) ' . ' - this is very likely a bug on your side',
2319             ( join ', ', @mocked_files ),
2320             ( join ', ', @unmocked_files ),
2321             );
2322             }
2323              
2324             # -1 means "keep as is"
2325 12 100       36 $uid == -1 and $uid = $>;
2326 12 100       39 $gid == -1 and $gid = $);
2327              
2328 12   33     66 my $is_root = $> == 0 || $) =~ /( ^ | \s ) 0 ( \s | $)/xms;
2329 12         573 my $is_in_group = grep /(^ | \s ) \Q$gid\E ( \s | $ )/xms, $);
2330              
2331             # TODO: Perl has an odd behavior that -1, -1 on a file that isn't owned by you still works
2332             # Not sure how to write a test for it though...
2333              
2334 12         31 my $set_error;
2335 12         18 my $num_changed = 0;
2336 12         28 foreach my $file (@files) {
2337 12         21 my $mock = $mocked_files{$file};
2338              
2339             # If this file is not mocked, none of the files are
2340             # which means we can send them all and let the CORE function handle it
2341 12 50       47 if ( !$mock ) {
2342 0         0 _real_file_access_hook( 'chown', \@_ );
2343 0 0       0 goto \&CORE::chown if _goto_is_available();
2344 0         0 return CORE::chown(@files);
2345             }
2346              
2347             # Even if you're root, nonexistent file is nonexistent
2348 12 100       35 if ( !$mock->exists() ) {
2349              
2350             # Only set the error once
2351 1 50       7 $set_error
2352             or $! = ENOENT;
2353              
2354 1         4 next;
2355             }
2356              
2357             # root can do anything, but you can't
2358             # and if we are here, no point in keep trying
2359 11 50       23 if ( !$is_root ) {
2360 0 0 0     0 if ( $> != $uid || !$is_in_group ) {
2361 0 0       0 $set_error
2362             or $! = EPERM;
2363              
2364 0         0 last;
2365             }
2366             }
2367              
2368 11         28 $mock->{'uid'} = $uid;
2369 11         21 $mock->{'gid'} = $gid;
2370              
2371 11         23 $num_changed++;
2372             }
2373              
2374 12         111 return $num_changed;
2375             }
2376              
2377             sub __chmod (@) {
2378 19     19   2910 my ( $mode, @files ) = @_;
2379              
2380             # Not an error, report we changed zero files
2381             @files
2382 19 50       68 or return 0;
2383              
2384             # Grab numbers - nothing means "0" (which is the behavior of CORE::chmod)
2385             # (This will issue a warning, that's also the expected behavior)
2386             {
2387 50     50   566 no warnings;
  50         107  
  50         39080  
  19         32  
2388 19 100       151 $mode =~ /^[0-9]+/xms
2389             or warn "Argument \"$mode\" isn't numeric in chmod";
2390 19         97 $mode = int $mode;
2391             }
2392              
2393 19         104 my %mocked_files = map +( $_ => _get_file_object($_) ), @files;
2394 19         85 my @unmocked_files = grep !$mocked_files{$_}, @files;
2395 19 100       93 my @mocked_files = map ref $_ ? $_->{'path'} : (), values %mocked_files;
2396              
2397             # The idea is that if some are mocked and some are not,
2398             # it's probably a mistake
2399 19 100 100     97 if ( @mocked_files && @mocked_files != @files ) {
2400 1         225 confess(
2401             sprintf 'You called chmod() on a mix of mocked (%s) and unmocked files (%s) ' . ' - this is very likely a bug on your side',
2402             ( join ', ', @mocked_files ),
2403             ( join ', ', @unmocked_files ),
2404             );
2405             }
2406              
2407 18         35 my $num_changed = 0;
2408 18         43 foreach my $file (@files) {
2409 20         60 my $mock = $mocked_files{$file};
2410              
2411 20 100       56 if ( !$mock ) {
2412 11         61 _real_file_access_hook( 'chmod', \@_ );
2413 11 50       170 goto \&CORE::chmod if _goto_is_available();
2414 0         0 return CORE::chmod(@files);
2415             }
2416              
2417             # chmod is less specific in such errors
2418             # chmod $mode, '/foo/' still yields ENOENT
2419 9 50       26 if ( !$mock->exists() ) {
2420 0         0 $! = ENOENT;
2421 0         0 next;
2422             }
2423              
2424 9         24 $mock->{'mode'} = ( $mock->{'mode'} & S_IFMT ) + $mode;
2425              
2426 9         22 $num_changed++;
2427             }
2428              
2429 7         35 return $num_changed;
2430             }
2431              
2432             BEGIN {
2433             *CORE::GLOBAL::glob = !$^V || $^V lt 5.18.0
2434             ? sub {
2435 0         0 pop;
2436 0         0 goto &__glob;
2437             }
2438 50 50 33 50   2047 : sub (_;) { goto &__glob; };
  10     10   321  
2439              
2440 50         318 *CORE::GLOBAL::open = \&__open;
2441 50         130 *CORE::GLOBAL::sysopen = \&__sysopen;
2442 50         195 *CORE::GLOBAL::opendir = \&__opendir;
2443 50         151 *CORE::GLOBAL::readdir = \&__readdir;
2444 50         197 *CORE::GLOBAL::telldir = \&__telldir;
2445 50         123 *CORE::GLOBAL::rewinddir = \&__rewinddir;
2446 50         161 *CORE::GLOBAL::seekdir = \&__seekdir;
2447 50         107 *CORE::GLOBAL::closedir = \&__closedir;
2448 50         139 *CORE::GLOBAL::unlink = \&__unlink;
2449 50         133 *CORE::GLOBAL::readlink = \&__readlink;
2450 50         174 *CORE::GLOBAL::mkdir = \&__mkdir;
2451              
2452 50         269 *CORE::GLOBAL::rmdir = \&__rmdir;
2453 50         275 *CORE::GLOBAL::chown = \&__chown;
2454 50         4370 *CORE::GLOBAL::chmod = \&__chmod;
2455             }
2456              
2457             =head1 CAEATS AND LIMITATIONS
2458              
2459             =head2 DEBUGGER UNDER STRICT MODE
2460              
2461             If you want to use the Perl debugger (L) on any code that
2462             uses L in strict mode, you will need to load
2463             L beforehand, because it loads a file. Under the
2464             debugger, the debugger will load the module after L and
2465             get mad.
2466              
2467             # Load it from the command line
2468             perl -MTerm::ReadLine -d code.pl
2469              
2470             # Or alternatively, add this to the top of your code:
2471             use Term::ReadLine
2472              
2473             =head2 FILENO IS UNSUPPORTED
2474              
2475             Filehandles can provide the file descriptor (in number) using the
2476             C keyword but this is purposefully unsupported in
2477             L.
2478              
2479             The reaosn is that by mocking a file, we're creating an alternative
2480             file system. Returning a C (file descriptor number) would
2481             require creating file descriptor numbers that would possibly conflict
2482             with the file desciptors you receive from the real filesystem.
2483              
2484             In short, this is a recipe for buggy tests or worse - truly destructive
2485             behavior. If you have a need for a real file, we suggest L.
2486              
2487             =head2 BAREWORD FILEHANDLE FAILURES
2488              
2489             There is a particular type of bareword filehandle failures that cannot
2490             be fixed.
2491              
2492             These errors occur because there's compile-time code that uses bareword
2493             filehandles in a function call that cannot be expressed by this
2494             module's prototypes for core functions.
2495              
2496             The only solution to these is loading `Test::MockFile` after the other
2497             code:
2498              
2499             This will fail:
2500              
2501             # This will fail because Test2::V0 will eventually load Term::Table::Util
2502             # which calls open() with a bareword filehandle that is misparsed by this module's
2503             # opendir prototypes
2504             use Test::MockFile ();
2505             use Test2::V0;
2506              
2507             This will succeed:
2508              
2509             # This will succeed because open() will be parsed by perl
2510             # and only then we override those functions
2511             use Test2::V0;
2512             use Test::MockFile ();
2513              
2514             (Using strict-mode will not fix it, even though you should use it.)
2515              
2516             =head1 AUTHOR
2517              
2518             Todd Rinaldo, C<< >>
2519              
2520             =head1 BUGS
2521              
2522             Please report any bugs or feature requests to
2523             L.
2524              
2525             =head1 SUPPORT
2526              
2527             You can find documentation for this module with the perldoc command.
2528              
2529             perldoc Test::MockFile
2530              
2531              
2532             You can also look for information at:
2533              
2534             =over 4
2535              
2536             =item * CPAN Ratings
2537              
2538             L
2539              
2540             =item * Search CPAN
2541              
2542             L
2543              
2544             =back
2545              
2546             =head1 ACKNOWLEDGEMENTS
2547              
2548             Thanks to Nicolas R., C<< >> for help with
2549             L. This module could not have been completed
2550             without it.
2551              
2552             =head1 LICENSE AND COPYRIGHT
2553              
2554             Copyright 2018 cPanel L.L.C.
2555              
2556             All rights reserved.
2557              
2558             L
2559              
2560             This is free software; you can redistribute it and/or modify it under
2561             the same terms as Perl itself. See L.
2562              
2563             =cut
2564              
2565             1; # End of Test::MockFile