File Coverage

blib/lib/Test/MockFile.pm
Criterion Covered Total %
statement 714 830 86.0
branch 357 516 69.1
condition 137 244 56.1
subroutine 102 106 96.2
pod 34 34 100.0
total 1344 1730 77.6


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 37     37   8375818 use strict;
  37         401  
  37         1084  
11 37     37   228 use warnings;
  37         92  
  37         1089  
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 37     37   201 use Fcntl; # O_RDONLY, etc.
  37         89  
  37         10347  
15              
16 37     37   299 use constant SUPPORTED_SYSOPEN_MODES => O_RDONLY | O_WRONLY | O_RDWR | O_APPEND | O_TRUNC | O_EXCL | O_CREAT | O_NOFOLLOW;
  37         102  
  37         2815  
17              
18 37     37   263 use constant BROKEN_SYMLINK => bless {}, "A::BROKEN::SYMLINK";
  37         125  
  37         2752  
19 37     37   272 use constant CIRCULAR_SYMLINK => bless {}, "A::CIRCULAR::SYMLINK";
  37         78  
  37         2850  
20              
21             # we're going to use carp but the errors should come from outside of our package.
22 37     37   256 use Carp qw(carp confess croak);
  37         83  
  37         3080  
23              
24             BEGIN {
25 37     37   241 $Carp::Internal{ (__PACKAGE__) }++;
26 37         964 $Carp::Internal{'Overload::FileCheck'}++;
27             }
28 37     37   251 use Cwd ();
  37         94  
  37         743  
29 37     37   18837 use IO::File ();
  37         215661  
  37         971  
30 37     37   17711 use Test::MockFile::FileHandle ();
  37         102  
  37         852  
31 37     37   15361 use Test::MockFile::DirHandle ();
  37         97  
  37         1020  
32 37     37   16821 use Text::Glob ();
  37         30169  
  37         1041  
33 37     37   259 use Scalar::Util ();
  37         77  
  37         577  
34              
35 37     37   180 use Symbol;
  37         69  
  37         2280  
36              
37 37     37   22329 use Overload::FileCheck '-from-stat' => \&_mock_stat, q{:check};
  37         154307  
  37         378  
38              
39 37     37   31765 use Errno qw/EPERM ENOENT ELOOP EEXIST EISDIR ENOTDIR EINVAL/;
  37         97  
  37         2393  
40              
41 37     37   266 use constant FOLLOW_LINK_MAX_DEPTH => 10;
  37         87  
  37         3557  
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.036
51              
52             =cut
53              
54             our $VERSION = '0.036';
55              
56             our %files_being_mocked;
57              
58             # From http://man7.org/linux/man-pages/man7/inode.7.html
59 37     37   290 use constant S_IFMT => 0170000; # bit mask for the file type bit field
  37         89  
  37         2527  
60 37     37   295 use constant S_IFPERMS => 07777; # bit mask for file perms.
  37         102  
  37         2027  
61              
62 37     37   266 use constant S_IFSOCK => 0140000; # socket
  37         91  
  37         2033  
63 37     37   259 use constant S_IFLNK => 0120000; # symbolic link
  37         114  
  37         2104  
64 37     37   244 use constant S_IFREG => 0100000; # regular file
  37         125  
  37         1867  
65 37     37   231 use constant S_IFBLK => 0060000; # block device
  37         105  
  37         1933  
66 37     37   228 use constant S_IFDIR => 0040000; # directory
  37         98  
  37         2199  
67 37     37   228 use constant S_IFCHR => 0020000; # character device
  37         91  
  37         1927  
68 37     37   247 use constant S_IFIFO => 0010000; # FIFO
  37         82  
  37         2411  
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 37     37   285 use constant STRICT_MODE_DISABLED => 1;
  37         92  
  37         2017  
175 37     37   260 use constant STRICT_MODE_ENABLED => 2;
  37         93  
  37         1963  
176 37     37   242 use constant STRICT_MODE_UNSET => 4;
  37         79  
  37         2207  
177 37     37   243 use constant STRICT_MODE_DEFAULT => STRICT_MODE_ENABLED | STRICT_MODE_UNSET; # default state when unset by user
  37         73  
  37         2514  
178              
179             our $STRICT_MODE_STATUS;
180              
181             BEGIN {
182 37     37   3326 $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   133 my @args = @_;
191 49         102 my $caller = caller(1);
192              
193             # Add bareword information to the args
194             # Default: no
195 49         97 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       218 Internals::SvREADONLY( $_[0] )
201             or return @args;
202              
203             # Upgrade the handle
204 0         0 my $handle;
205             {
206 37     37   284 no strict 'refs';
  37         81  
  37         7304  
  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 74     74 1 223 my ($pkg) = @_;
236              
237 74         220 $authorized_strict_mode_packages{$pkg} = 1;
238              
239 74         6514 return;
240             }
241              
242             BEGIN {
243 37     37   233 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 45     45 1 87 my ( $command, $at_under_ref ) = @_;
263              
264 45   100     159 $_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 45 50 33     180 return -1 unless defined $command && defined $_file_arg_post->{$command};
280              
281             # exception for open
282 45 100 66     191 return 1 if $command eq 'open' && ref $at_under_ref && scalar @$at_under_ref == 2;
      100        
283              
284 43         96 return $_file_arg_post->{$command};
285             }
286              
287 37     37   312 use constant _STACK_ITERATION_MAX => 100;
  37         119  
  37         246902  
288              
289             sub _get_stack {
290 46     46   60 my @stack;
291              
292 46         107 foreach my $stack_level ( 1 .. _STACK_ITERATION_MAX ) {
293 198         1004 @stack = caller($stack_level);
294 198 50       416 last if !scalar @stack;
295 198 50       371 last if !defined $stack[0]; # We don't know when this would ever happen.
296              
297 198 100       381 next if $stack[0] eq __PACKAGE__;
298 106 100       192 next if $stack[0] eq 'Overload::FileCheck'; # companion package
299              
300 46 100       136 return if $authorized_strict_mode_packages{ $stack[0] };
301              
302 45         81 last;
303             }
304              
305 45         157 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 13     13 1 443 my ( $command_rule, $file_rule, $action ) = @_;
365              
366 13 50       37 defined $command_rule
367             or croak("add_strict_rule( COMMAND, PATH, ACTION )");
368              
369 13 50       28 croak("Invalid rule: missing action code") unless defined $action;
370              
371 13 100       44 my @commands = ref $command_rule eq 'ARRAY' ? @{$command_rule} : ($command_rule);
  3         10  
372 13 100       42 my @files = ref $file_rule eq 'ARRAY' ? @{$file_rule} : ($file_rule);
  2         5  
373              
374 13         24 foreach my $c_rule (@commands) {
375 16         32 foreach my $f_rule (@files) {
376 19 100 100     297 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 13         44 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 2198 @STRICT_RULES = ();
397              
398 7         14 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 6     6 1 9570 my ( $file_rule, $action ) = @_;
422              
423 6         33 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 1803 my ( $command_rule, $action, $extra ) = @_;
456              
457 5 100       16 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 2     2 1 300 my ($action) = @_;
491              
492 2         12 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 97 100   97 1 462 return $STRICT_MODE_STATUS & STRICT_MODE_ENABLED ? 1 : 0;
503             }
504              
505             sub _strict_mode_violation {
506 97     97   213 my ( $command, $at_under_ref ) = @_;
507              
508 97 100       199 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 46 50       95 return if grep { $command eq $_ } qw/readdir telldir rewinddir seekdir closedir/;
  230         430  
512              
513 46         97 my @stack = _get_stack();
514 46 100       106 return unless scalar @stack; # skip the package
515              
516 45         64 my $filename;
517              
518             # check it later so we give priority to authorized_strict_mode_packages
519 45         94 my $file_arg = file_arg_position_for_command( $command, $at_under_ref );
520              
521 45 50       108 if ( $file_arg >= 0 ) {
522 45 50       121 $filename = scalar @$at_under_ref <= $file_arg ? '' : $at_under_ref->[$file_arg];
523             }
524              
525             # Ignore stats on STDIN, STDOUT, STDERR
526 45 100 66     211 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 44 100       205 if ( UNIVERSAL::isa( $filename, 'GLOB' ) ) {
533 1 50 33     4 return if $command ne 'open' && $command ne 'sysopen';
534             }
535              
536             # open >& is for file dups. this isn't a real file access.
537 44 100 66     197 return if $command eq 'open' && $at_under_ref->[1] && $at_under_ref->[1] =~ m/&/;
      66        
538              
539 42         166 my $path = _abs_path_to_file($filename);
540              
541 42         166 my $context = {
542             command => $command,
543             filename => $path,
544             at_under_ref => $at_under_ref
545             }; # object
546              
547 42         88 my $pass = _validate_strict_rules($context);
548 42 100       136 return if $pass;
549              
550 24 50       76 croak("Unknown strict mode violation for $command") if $file_arg == -1;
551              
552 24         4670 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 42     42   82 my ($context) = @_;
557              
558             # rules dispatch
559 42         90 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 41 100       98 if ( defined $rule->{'file_rule'} ) {
565 19 100 66     136 defined $context->{'filename'} && $context->{'filename'} =~ $rule->{'file_rule'}
566             or next;
567             }
568              
569 32 100       153 $context->{'command'} =~ $rule->{'command_rule'}
570             or next;
571              
572 25 100       86 my $answer = ref $rule->{'action'} ? $rule->{'action'}->($context) : $rule->{'action'};
573              
574 25 100       136 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 24         41 return;
581             }
582              
583             my @plugins;
584              
585             sub import {
586 34     34   319 my ( $class, @args ) = @_;
587              
588 34 100       107 my $strict_mode = ( grep { $_ eq 'nostrict' } @args ) ? STRICT_MODE_DISABLED : STRICT_MODE_ENABLED;
  22         133  
589              
590 34 50 33     365 if (
      33        
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 0         0 die q[Test::MockFile is imported multiple times with different strict modes (not currently supported) ] . $class;
598             }
599 34         73 $STRICT_MODE_STATUS = $strict_mode;
600              
601 34         163 while ( my $opt = shift @args ) {
602 22 50 33     275 next unless defined $opt && $opt eq 'plugin';
603 0         0 my $what = shift @args;
604 0         0 require Test::MockFile::Plugins;
605              
606 0         0 push @plugins, Test::MockFile::Plugins::load_plugin($what);
607             }
608              
609 34         66720 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 70     70 1 118772 my ( $class, $file, $contents, @stats ) = @_;
638              
639 70 50 33     440 ( defined $file && length $file ) or confess("No file provided to instantiate $class");
640 70 50       257 _is_path_mocked($file) and confess("It looks like $file is already being mocked. We don't support double mocking yet.");
641              
642 70         220 my $path = _abs_path_to_file($file);
643 70         303 _validate_path($_) for $file, $path;
644              
645 67 50       228 if ( @stats > 1 ) {
646 0         0 confess(
647             sprintf 'Unkownn arguments (%s) passed to file() as stats',
648             join ', ', @stats
649             );
650             }
651              
652 67 50 66     277 !defined $contents && @stats
653             and confess("You cannot set stats for non-existent file '$path'");
654              
655 67         115 my %stats;
656 67 100       170 if (@stats) {
657 1 50       47 ref $stats[0] eq 'HASH'
658             or confess('->file( FILE_NAME, FILE_CONTENT, { STAT_INFORMATION } )');
659              
660 1         4 %stats = %{ $stats[0] };
  1         9  
661             }
662              
663 67 100       278 my $perms = S_IFPERMS & ( defined $stats{'mode'} ? int( $stats{'mode'} ) : 0666 );
664 67         656 $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 67         578 ( my $dirname = $path ) =~ s{ / [^/]+ $ }{}xms;
670 67 100 100     409 if ( defined $contents && $files_being_mocked{$dirname} ) {
671 7         22 $files_being_mocked{$dirname}{'has_content'} = 1;
672             }
673              
674 67         487 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 16 my ( $class, $file, $file_on_disk, @stats ) = @_;
698              
699 1         2 my $fh;
700 1         16 local $!;
701 1 50       38 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         5 local $/;
707 1         38 my $contents = <$fh>; # Slurp!
708 1         13 close $fh;
709              
710 1         10 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 14446 my ( $class, $readlink, $file ) = @_;
734              
735 12 50 33     98 ( defined $file && length $file ) or confess("No file provided to instantiate $class");
736 12 50 33     75 ( !defined $readlink || length $readlink ) or confess("No file provided for $file to point to in $class");
737              
738 12 50       85 _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         86 ( my $dirname = $file ) =~ s{ / [^/]+ $ }{}xms;
744 12 100       54 if ( $files_being_mocked{$dirname} ) {
745 4         10 $files_being_mocked{$dirname}{'has_content'} = 1;
746             }
747              
748 12         73 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 227     227   360 my $path = shift;
760              
761             # Reject the following:
762             # ./ ../ /. /.. /./ /../
763 227 100       555 if ( $path =~ m{ ( ^ | / ) \.{2} ( / | $ ) }xms ) {
764 5         676 confess('Relative paths are not supported');
765             }
766              
767 222         441 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 70752 my ( $class, $dirname ) = @_;
829              
830 46 50 33     347 ( defined $dirname && length $dirname ) or confess("No directory name provided to instantiate $class");
831 46 50       152 _is_path_mocked($dirname) and confess("It looks like $dirname is already being mocked. We don't support double mocking yet.");
832              
833 46         128 my $path = _abs_path_to_file($dirname);
834 46         161 _validate_path($_) for $dirname, $path;
835              
836             # Cleanup trailing forward slashes
837 44 50       165 $path ne '/'
838             and $path =~ s{[/\\]$}{}xmsg;
839              
840 44 100       787 @_ > 2
841             and confess("You cannot set stats for nonexistent dir '$path'");
842              
843 39         77 my $perms = S_IFPERMS & 0777;
844 39         389 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         552 my $has_content = grep m{^\Q$path/\E}xms, %files_being_mocked;
850 39         263 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 18354 my ( $class, $dirname, $opts ) = @_;
882              
883 7         13 my $mode;
884 7 100       18 my @args = $opts ? $opts : ();
885 7 100 100     30 if ( ref $opts eq 'HASH' && $opts->{'mode'} ) {
886 1         3 $mode = delete $opts->{'mode'};
887              
888             # This is to make sure the error checking still happens as expected
889 1 50       4 if ( keys %{$opts} == 0 ) {
  1         7  
890 1         2 @args = ();
891             }
892             }
893              
894 7         18 my $dir = $class->dir( $dirname, @args );
895 4 100       14 if ($mode) {
896 1         12 __mkdir( $dirname, $mode );
897             }
898             else {
899 3         10 __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 118     118 1 268 my $class = shift @_;
946              
947 118         186 my %opts;
948 118 50 33     650 if ( scalar @_ == 1 && ref $_[0] ) {
    0          
949 118         191 %opts = %{ $_[0] };
  118         549  
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 118 50       437 my $path = $opts{'path'} or confess("Mock file created without a path (filename or dirname)!");
959              
960 118 50       481 if ( $path !~ m{^/} ) {
961 0         0 $path = $opts{'path'} = _abs_path_to_file($path);
962             }
963              
964 118         237 my $now = time;
965              
966 118         2659 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 118         492 foreach my $key ( keys %opts ) {
989              
990             # Ignore Stuff that's not a valid key for this class.
991 366 50       869 next unless exists $self->{$key};
992              
993             # If it's passed in, we override them.
994 366         657 $self->{$key} = $opts{$key};
995             }
996              
997 118   33     593 $self->{'fileno'} //= _unused_fileno();
998              
999 118         298 $files_being_mocked{$path} = $self;
1000 118         571 Scalar::Util::weaken( $files_being_mocked{$path} );
1001              
1002 118         616 return $self;
1003             }
1004              
1005             #Overload::FileCheck::mock_stat(\&mock_stat);
1006             sub _mock_stat {
1007 111     111   66964 my ( $type, $file_or_fh ) = @_;
1008              
1009 111 100       535 $type or confess("_mock_stat called without a stat type");
1010              
1011 110 100       526 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 109 50       297 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 109 100 66     554 if ( !defined $file_or_fh || !length $file_or_fh ) {
1024 2         16 _real_file_access_hook( $type, [$file_or_fh] );
1025 1         6 return FALLBACK_TO_REAL_OP();
1026             }
1027              
1028             # Find the path, following the symlink if required.
1029 107         269 my $file = _find_file_or_fh( $file_or_fh, $follow_link );
1030              
1031 107 100 33     812 return [] if defined $file && defined BROKEN_SYMLINK && $file eq BROKEN_SYMLINK; # Allow an ELOOP to fall through here.
      66        
1032 106 100 33     674 return [] if defined $file && defined CIRCULAR_SYMLINK && $file eq CIRCULAR_SYMLINK; # Allow an ELOOP to fall through here.
      66        
1033              
1034 105 50 33     425 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 105         254 my $file_data = _get_file_object($file);
1040 105 100       292 if ( !$file_data ) {
1041 27 100       155 _real_file_access_hook( $type, [$file_or_fh] ) unless ref $file_or_fh;
1042 17         81 return FALLBACK_TO_REAL_OP();
1043             }
1044              
1045             # File is not present so no stats for you!
1046 78 100 100     169 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         186 return [ $file_data->stat ];
1050             }
1051              
1052             sub _is_path_mocked {
1053 128     128   300 my ($file_path) = @_;
1054 128 50       279 my $absolute_path_to_file = _find_file_or_fh($file_path) or return;
1055              
1056 128 50       510 return $files_being_mocked{$absolute_path_to_file} ? 1 : 0;
1057             }
1058              
1059             sub _get_file_object {
1060 325     325   667 my ($file_path) = @_;
1061              
1062 325 50       581 my $file = _find_file_or_fh($file_path) or return;
1063              
1064 325         861 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 651     651   1298 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 651   66     1176 my $absolute_path_to_file = _fh_to_file($file_or_fh) // _abs_path_to_file($file_or_fh) // '';
      50        
1075 651 50       2288 $absolute_path_to_file ne '/'
1076             and $absolute_path_to_file =~ s{[/\\]$}{}xmsg;
1077              
1078             # Get the pointer to the object.
1079 651         1204 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 651 100 100     1523 return BROKEN_SYMLINK if $depth and !$mock_object;
1083              
1084             # If the link we followed isn't a symlink, then return it.
1085 649 100 100     2136 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       90 return $absolute_path_to_file unless $follow_link;
1093              
1094             # This is still a symlink keep going. Bump our depth counter.
1095 27         40 $depth++;
1096              
1097             #Protect against circular symlink loops.
1098 27 100       48 if ( $depth > FOLLOW_LINK_MAX_DEPTH ) {
1099 2         6 $! = ELOOP;
1100 2         14 return CIRCULAR_SYMLINK;
1101             }
1102              
1103 25         46 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 659     659   1117 my ($fh) = @_;
1110              
1111 659 100 100     2340 return unless defined $fh && length $fh;
1112              
1113             # See if $fh is a file handle. It might be a path.
1114 657         2459 foreach my $path ( sort keys %files_being_mocked ) {
1115 1144         1786 my $mock_fh = $files_being_mocked{$path}->{'fh'};
1116              
1117 1144 100       2227 next unless $mock_fh; # File isn't open.
1118 57 100       226 next unless "$mock_fh" eq "$fh"; # This mock doesn't have this file handle open.
1119              
1120 44         168 return $path;
1121             }
1122              
1123 613         2049 return;
1124             }
1125              
1126             sub _files_in_dir {
1127 132     132   345 my $dirname = shift;
1128             my @files_in_dir = @files_being_mocked{
1129 132         1361 grep m{^\Q$dirname/\E},
1130             keys %files_being_mocked
1131             };
1132              
1133 132         393 return @files_in_dir;
1134             }
1135              
1136             sub _abs_path_to_file {
1137 778     778   11370 my ($path) = shift;
1138              
1139 778 100       1532 return unless defined $path;
1140              
1141 777         1094 my $match = 1;
1142 777         1556 while ($match) {
1143 828         1103 $match = 0;
1144 828 100       2234 $match = 1 if $path =~ s{//+}{/}xmsg; # cleanup multiple slashes
1145 828 100       1619 $match = 1 if $path =~ s{/\.$}{/};
1146 828 100       1786 $match = 1 if $path =~ s{(?:[^/]+)/\.\.(/|$)}{$1};
1147 828 100       2089 $match = 1 if $path =~ s{/$}{};
1148             }
1149              
1150 777 100       1519 return q[/] if $path eq q[/..];
1151              
1152 776 100       4010 return $path if $path =~ m{^/}xms;
1153              
1154             # ~
1155             # ~/...
1156             # ~sawyer
1157 63 50       162 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 63         661 my $cwd = Cwd::getcwd();
1182              
1183 63 50       240 return $cwd if $path eq '.';
1184 63         681 return Cwd::getcwd() . "/$path";
1185             }
1186              
1187             sub DESTROY {
1188 118     118   54066 my ($self) = @_;
1189 118 50       396 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 118         244 my $path = $self->{'path'};
1194 118 50       290 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 118 50       313 if ( defined $files_being_mocked{$path} ) {
1200 118 50       330 $self == $files_being_mocked{$path} or confess("Tried to destroy object for $path ($self) but something else is mocking it?");
1201             }
1202              
1203 118         2494 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 270     270 1 4718 my ( $self, $new_contents ) = @_;
1222 270 50       526 $self or confess;
1223              
1224 270 50       550 $self->is_link
1225             and confess("checking or setting contents on a symlink is not supported");
1226              
1227             # handle directories
1228 270 100       584 if ( $self->is_dir() ) {
1229 141 50       289 $new_contents
1230             and confess('To change the contents of the dir, you must work on its files');
1231              
1232 141 100       381 $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         237 my $dirname = $self->path();
1238             my @existing_files = sort map {
1239              
1240             # strip directory from the path
1241 126         257 ( my $basename = $_->path() ) =~ s{^\Q$dirname/\E}{}xms;
  72         148  
1242              
1243             # Is this content within another directory? strip that out
1244 72         222 $basename =~ s{^( [^/]+ ) / .*}{$1}xms;
1245              
1246 72 100 100     350 defined $_->{'contents'} || $_->is_link() || $_->is_dir() ? ($basename) : ();
1247             } _files_in_dir($dirname);
1248              
1249 126         215 my %uniq;
1250 126         355 $uniq{$_}++ for @existing_files;
1251 126         962 return [ '.', '..', sort keys %uniq ];
1252             }
1253              
1254             # handle files
1255 129 50       295 if ( $self->is_file() ) {
1256 129 100       295 if ( defined $new_contents ) {
1257 12 50       52 ref $new_contents
1258             and confess('File contents must be a simple string');
1259              
1260             # XXX Why use $_[1] directly?
1261 12         29 $self->{'contents'} = $_[1];
1262             }
1263              
1264 129         657 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 2026 my ($self) = @_;
1290 214 50       411 $self or confess("path is a method");
1291              
1292 214         737 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 971 my ($self) = @_;
1303 9 50       38 $self or confess("unlink is a method");
1304              
1305 9 100       29 if ( !$self->exists ) {
1306 1         3 $! = ENOENT;
1307 1         4 return 0;
1308             }
1309              
1310 8 100       33 if ( $self->is_dir ) {
1311 2 50 0     14 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         9 return 0;
1318             }
1319              
1320 6 100       19 if ( $self->is_link ) {
1321 1         3 $self->{'readlink'} = undef;
1322             }
1323             else {
1324 5         15 $self->{'has_content'} = undef;
1325 5         15 $self->{'contents'} = undef;
1326             }
1327 6         52 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 2378 my ( $self, $now ) = @_;
1344 6 50       20 $self or confess("touch is a method");
1345 6   66     32 $now //= time;
1346              
1347 6 100       12 $self->is_file or confess("touch only supports files");
1348              
1349 4         11 my $pre_size = $self->size();
1350              
1351 4 100       13 if ( !defined $pre_size ) {
1352 2         8 $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         35 $self->mtime($now);
1358 4         15 $self->ctime($now);
1359 4         16 $self->atime($now);
1360              
1361 4         26 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 123 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         195 $self->{'blksize'}, # stat[11]
1386             $self->blocks, # stat[12]
1387             );
1388             }
1389              
1390             sub _unused_fileno {
1391 118     118   341 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 48 my ( $self, $readlink ) = @_;
1405              
1406 27 50       49 $self->is_link or confess("readlink is only supported for symlinks");
1407              
1408 27 50       66 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         87 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 1030     1030 1 1677 my ($self) = @_;
1427              
1428 1030 100 66     5290 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 346     346 1 543 my ($self) = @_;
1439              
1440 346 100       1153 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 281     281 1 432 my ($self) = @_;
1451              
1452 281 100       1186 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 194 my ($self) = @_;
1463              
1464             # Lstat for a symlink returns 1 for its size.
1465 113 100       206 return 1 if $self->is_link;
1466              
1467             # length undef is 0 not undef in perl 5.10
1468 111 50       328 if ( $] < 5.012 ) {
1469 0 0       0 return undef unless $self->exists;
1470             }
1471              
1472 111         239 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 141     141 1 236 my ($self) = @_;
1483              
1484             $self->is_link()
1485 141 50       237 and return defined $self->{'readlink'} ? 1 : 0;
    100          
1486              
1487             $self->is_file()
1488 138 100       299 and return defined $self->{'contents'} ? 1 : 0;
    100          
1489              
1490             $self->is_dir()
1491 53 100       122 and return $self->{'has_content'} ? 1 : 0;
    100          
1492              
1493 1         5 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 139 my ($self) = @_;
1504              
1505 54         113 my $blocks = int( $self->size / abs( $self->{'blksize'} ) + 1 );
1506 54 50       179 if ( int($blocks) > $blocks ) {
1507 0         0 $blocks = int($blocks) + 1;
1508             }
1509 54         364 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         19 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 684 my ( $self, $time ) = @_;
1555              
1556 7 50 66     68 if ( scalar @_ == 2 && defined $time && $time =~ m/^[0-9]+$/ ) {
      66        
1557 5         16 $self->{'mtime'} = $time;
1558             }
1559              
1560 7         24 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 17 my ( $self, $time ) = @_;
1574              
1575 7 50 66     62 if ( @_ == 2 && defined $time && $time =~ m/^[0-9]+$/ ) {
      66        
1576 5         12 $self->{'ctime'} = $time;
1577             }
1578              
1579 7         21 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 16 my ( $self, $time ) = @_;
1593              
1594 7 50 66     50 if ( @_ == 2 && defined $time && $time =~ m/^[0-9]+$/ ) {
      66        
1595 5         17 $self->{'atime'} = $time;
1596             }
1597              
1598 7         23 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 97     97   217 my ( $access_type, $at_under_ref ) = @_;
1651              
1652 97         243 foreach my $code ( @_internal_access_hooks, @_public_access_hooks ) {
1653 97         252 $code->( $access_type, $at_under_ref );
1654             }
1655              
1656 73         140 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 59 100   59   293 return 0 if $] < 5.015;
1726 57 100       158 return 1 if $] < 5.021;
1727 54 100       156 return 1 if $] > 5.027;
1728 52         140 return 0; # 5.
1729             }
1730              
1731             ############
1732             # KEYWORDS #
1733             ############
1734              
1735             sub __glob {
1736 10     10   24 my $spec = shift;
1737              
1738             # Text::Glob does not understand multiple patterns
1739 10         41 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         51 my @mocked_files = grep $files_being_mocked{$_}->exists(), keys %files_being_mocked;
1744 10 100       132 @mocked_files = map /^(.+)\/[^\/]+$/xms ? ( $_, $1 ) : ($_), @mocked_files;
1745              
1746             # Might as well be consistent
1747 10         47 @mocked_files = sort @mocked_files;
1748              
1749 10         46 my @results = map Text::Glob::match_glob( $_, @mocked_files ), @patterns;
1750 10         1963 return @results;
1751             }
1752              
1753             sub __open (*;$@) {
1754 62     62   29212 my $likely_bareword;
1755             my $arg0;
1756 62 50 66     295 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 62         197 my ( undef, $mode, $file ) = @_;
1766 62         146 my $arg_count = @_;
1767              
1768             # Normalize two-arg to three-arg
1769 62 100       165 if ( $arg_count == 2 ) {
1770              
1771             # The order here matters, so '>>' won't turn into '>'
1772 10 100       161 if ( $_[1] =~ /^ ( >> | [+]?> | [+]?< ) (.+) $/xms ) {
    100          
    100          
    50          
1773 7         32 $mode = $1;
1774 7         15 $file = $2;
1775             }
1776             elsif ( $_[1] =~ /^[\.\/\\\w\d\-]+$/xms ) {
1777 1         58 $mode = '<';
1778 1         5 $file = $_[1];
1779             }
1780             elsif ( $_[1] =~ /^\|/xms ) {
1781 1         9 $mode = '|-';
1782 1         8 $file = $_[1];
1783             }
1784             elsif ( $_[1] =~ /\|$/xms ) {
1785 1         6 $mode = '-|';
1786 1         11 $file = $_[1];
1787             }
1788             else {
1789 0         0 die "Unsupported two-way open: $_[1]\n";
1790             }
1791              
1792             # We have all args
1793 10         24 $arg_count++;
1794             }
1795              
1796             # We're not supporting 1 arg opens yet
1797 62 50       193 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 62 50 66     201 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 62         171 my $abs_path = _find_file_or_fh( $file, 1 ); # Follow the link.
1818 62 0 33     218 confess() if !$abs_path && $mode ne '|-' && $mode ne '-|';
      33        
1819 62 50       231 confess() if $abs_path eq BROKEN_SYMLINK;
1820 62         177 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 62         167 $mode =~ s/(:.+$)//;
1824 62         149 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 62 100 100     418 if ( ( $mode eq '|-' || $mode eq '-|' )
      100        
      100        
1830 348         1007 or !grep { $_ eq $mode } qw/> < >> +< +> +>>/
1831             or !defined $mock_file ) {
1832 34         134 _real_file_access_hook( "open", \@_ );
1833 25 50       54 goto \&CORE::open if _goto_is_available();
1834 25 50       127 if ( @_ == 1 ) {
    100          
    50          
1835 0         0 return CORE::open( $_[0] );
1836             }
1837             elsif ( @_ == 2 ) {
1838 3         7710 return CORE::open( $_[0], $_[1] );
1839             }
1840             elsif ( @_ >= 3 ) {
1841 22         10798 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 28 100 100     98 if ( !defined $mock_file->contents() && grep { $mode eq $_ } qw/< +
  12         66  
1849 3         13 $! = ENOENT;
1850 3         31 return;
1851             }
1852              
1853 25         64 my $rw = '';
1854 25 100       56 $rw .= 'r' if grep { $_ eq $mode } qw/+< +> +>>
  100         227  
1855 25 100       64 $rw .= 'w' if grep { $_ eq $mode } qw/+< +> +>> > >>/;
  125         245  
1856              
1857 25         187 my $filefh = IO::File->new;
1858 25         1607 tie *{$filefh}, 'Test::MockFile::FileHandle', $abs_path, $rw;
  25         359  
1859              
1860 25 50       85 if ($likely_bareword) {
1861 0         0 my $caller = caller();
1862 37     37   418 no strict;
  37         91  
  37         36414  
1863 0         0 *{"${caller}::$arg0"} = $filefh;
  0         0  
1864 0 0       0 @_ = ( $filefh, $_[1] ? @_[ 1 .. $#_ ] : () );
1865             }
1866             else {
1867 25         85 $_[0] = $filefh;
1868             }
1869              
1870             # This is how we tell if the file is open by something.
1871              
1872 25         72 $mock_file->{'fh'} = $_[0];
1873 25 50       143 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 25 100 66     212 if ( $mode eq '>>' or $mode eq '+>>' ) {
    100 100        
1877 2   50     9 $mock_file->{'contents'} //= '';
1878 2         15 seek $_[0], length( $mock_file->{'contents'} ), 0;
1879             }
1880             elsif ( $mode eq '>' or $mode eq '+>' ) {
1881 7         32 $mock_file->{'contents'} = '';
1882             }
1883              
1884 25         125 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 7     7   8508 my $mock_file = _get_file_object( $_[1] );
1902              
1903 7 100       20 if ( !$mock_file ) {
1904 4         17 _real_file_access_hook( "sysopen", \@_ );
1905 3 50       6 goto \&CORE::sysopen if _goto_is_available();
1906 3         184 return CORE::sysopen( $_[0], $_[1], @_[ 2 .. $#_ ] );
1907             }
1908              
1909 3         15 my $sysopen_mode = $_[2];
1910              
1911             # Not supported by my linux vendor: O_EXLOCK | O_SHLOCK
1912 3 50       11 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     13 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     33 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     12 if ( $sysopen_mode & O_CREAT && !defined $mock_file->{'contents'} ) {
1930 1         3 $mock_file->{'contents'} = '';
1931             }
1932              
1933             # O_TRUNC
1934 3 100 66     12 if ( $sysopen_mode & O_TRUNC && defined $mock_file->{'contents'} ) {
1935 1         5 $mock_file->{'contents'} = '';
1936              
1937             }
1938              
1939 3         6 my $rd_wr_mode = $sysopen_mode & 3;
1940 3 0       11 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     16 if ( !defined $mock_file->{'contents'} && $rd_wr_mode == O_RDONLY ) {
1948 0         0 $! = ENOENT;
1949 0         0 return;
1950             }
1951              
1952 3         9 my $abs_path = $mock_file->{'path'};
1953              
1954 3         19 $_[0] = IO::File->new;
1955 3         124 tie *{ $_[0] }, 'Test::MockFile::FileHandle', $abs_path, $rw;
  3         34  
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       14 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       8 if ( $sysopen_mode & O_TRUNC ) {
1963 1         3 $mock_file->{'contents'} = '';
1964             }
1965              
1966             # O_APPEND
1967 3 50       7 if ( $sysopen_mode & O_APPEND ) {
1968 0         0 seek $_[0], length $mock_file->{'contents'}, 0;
1969             }
1970              
1971 3         15 return 1;
1972             }
1973              
1974             sub __opendir (*$) {
1975              
1976             # Upgrade but ignore bareword indicator
1977 24 100 66 24   10351 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
1978              
1979 24         65 my $mock_dir = _get_file_object( $_[1] );
1980              
1981             # 1 arg Opendir doesn't work??
1982 24 50 33     142 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 24 100       105 if ( !$mock_dir ) {
1991 10         43 _real_file_access_hook( "opendir", \@_ );
1992 7 50       17 goto \&CORE::opendir if _goto_is_available();
1993 7         361 return CORE::opendir( $_[0], $_[1] );
1994             }
1995              
1996 14 50       38 if ( !defined $mock_dir->contents ) {
1997 0         0 $! = ENOENT;
1998 0         0 return undef;
1999             }
2000              
2001 14 100       46 if ( !( $mock_dir->{'mode'} & S_IFDIR ) ) {
2002 1         2 $! = ENOTDIR;
2003 1         5 return undef;
2004             }
2005              
2006 13 100       35 if ( !defined $_[0] ) {
    50          
2007 12         43 $_[0] = Symbol::gensym;
2008             }
2009             elsif ( ref $_[0] ) {
2010 37     37   342 no strict 'refs';
  37         97  
  37         93043  
2011 1         6 *{ $_[0] } = Symbol::geniosym;
  1         33  
2012             }
2013              
2014             # This is how we tell if the file is open by something.
2015 13         180 my $abs_path = $mock_dir->{'path'};
2016 13         30 $mock_dir->{'obj'} = Test::MockFile::DirHandle->new( $abs_path, $mock_dir->contents() );
2017 13         43 $mock_dir->{'fh'} = "$_[0]";
2018              
2019 13         44 return 1;
2020              
2021             }
2022              
2023             sub __readdir (*) {
2024              
2025             # Upgrade but ignore bareword indicator
2026 28 50 33 28   4138 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
2027              
2028 28         72 my $mocked_dir = _get_file_object( $_[0] );
2029              
2030 28 100       77 if ( !$mocked_dir ) {
2031 6         20 _real_file_access_hook( 'readdir', \@_ );
2032 6 50       10 goto \&CORE::readdir if _goto_is_available();
2033 6         102 return CORE::readdir( $_[0] );
2034             }
2035              
2036 22         42 my $obj = $mocked_dir->{'obj'};
2037 22 50       123 if ( !$obj ) {
2038 0         0 confess("Read on a closed handle");
2039             }
2040              
2041 22 50       67 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       49 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       35 return undef if $obj->{'tell'} > $#{ $obj->{'files_in_readdir'} };
  22         75  
2051              
2052 18 100       43 if (wantarray) {
2053 14         26 my @return;
2054 14         26 foreach my $pos ( $obj->{'tell'} .. $#{ $obj->{'files_in_readdir'} } ) {
  14         43  
2055 39         81 push @return, $obj->{'files_in_readdir'}->[$pos];
2056             }
2057 14         24 $obj->{'tell'} = $#{ $obj->{'files_in_readdir'} } + 1;
  14         40  
2058 14         79 return @return;
2059             }
2060              
2061 4         22 return $obj->{'files_in_readdir'}->[ $obj->{'tell'}++ ];
2062             }
2063              
2064             sub __telldir (*) {
2065              
2066             # Upgrade but ignore bareword indicator
2067 4 50 33 4   24 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
2068              
2069 4         8 my ($fh) = @_;
2070 4         9 my $mocked_dir = _get_file_object($fh);
2071              
2072 4 50 33     20 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         8 my $obj = $mocked_dir->{'obj'};
2079              
2080 4 50       8 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         18 return $obj->{'tell'};
2089             }
2090              
2091             sub __rewinddir (*) {
2092              
2093             # Upgrade but ignore bareword indicator
2094 1 50 33 1   11 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
2095              
2096 1         4 my ($fh) = @_;
2097 1         3 my $mocked_dir = _get_file_object($fh);
2098              
2099 1 50 33     8 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         4 my $obj = $mocked_dir->{'obj'};
2106              
2107 1 50       4 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       5 if ( !defined $obj->{'tell'} ) {
2112 0         0 confess("rewinddir called on a closed dirhandle");
2113             }
2114              
2115 1         3 $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   10 ( 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     9 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         3 my $obj = $mocked_dir->{'obj'};
2134              
2135 1 50       27 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         7 return $obj->{'tell'} = $goto;
2144             }
2145              
2146             sub __closedir (*) {
2147              
2148             # Upgrade but ignore bareword indicator
2149 14 50 33 14   8763 ( undef, @_ ) = _upgrade_barewords(@_) if defined $_[0] && !ref $_[9];
2150              
2151 14         33 my ($fh) = @_;
2152 14         42 my $mocked_dir = _get_file_object($fh);
2153              
2154 14 100 66     74 if ( !$mocked_dir || !$mocked_dir->{'obj'} ) {
2155 1         6 _real_file_access_hook( 'closedir', \@_ );
2156 1 50       2 goto \&CORE::closedir if _goto_is_available();
2157 1         24 return CORE::closedir($fh);
2158             }
2159              
2160 13         72 delete $mocked_dir->{'obj'};
2161 13         28 delete $mocked_dir->{'fh'};
2162              
2163 13         35 return 1;
2164             }
2165              
2166             sub __unlink (@) {
2167 11     11   8686 my @files_to_unlink = @_;
2168 11         28 my $files_deleted = 0;
2169              
2170 11         32 foreach my $file (@files_to_unlink) {
2171 11         31 my $mock = _get_file_object($file);
2172              
2173 11 100       38 if ( !$mock ) {
2174 7         35 _real_file_access_hook( "unlink", [$file] );
2175 7         461 $files_deleted += CORE::unlink($file);
2176             }
2177             else {
2178 4         10 $files_deleted += $mock->unlink;
2179             }
2180             }
2181              
2182 11         13008 return $files_deleted;
2183              
2184             }
2185              
2186             sub __readlink (_) {
2187 7     7   3336 my ($file) = @_;
2188              
2189 7 100       19 if ( !defined $file ) {
2190 2         295 carp('Use of uninitialized value in readlink');
2191 2 50       22 if ( $^O eq 'freebsd' ) {
2192 0         0 $! = EINVAL;
2193             }
2194             else {
2195 2         6 $! = ENOENT;
2196             }
2197 2         6 return;
2198             }
2199              
2200 5         12 my $mock_object = _get_file_object($file);
2201 5 100       20 if ( !$mock_object ) {
2202 1         7 _real_file_access_hook( 'readlink', \@_ );
2203 1 50       3 goto \&CORE::readlink if _goto_is_available();
2204 1         111 return CORE::readlink($file);
2205             }
2206              
2207 4 100       9 if ( !$mock_object->is_link ) {
2208 2         6 $! = EINVAL;
2209 2         10 return;
2210             }
2211 2         7 return $mock_object->readlink;
2212             }
2213              
2214             # $file is always passed because of the prototype.
2215             sub __mkdir (_;$) {
2216 28     28   11674 my ( $file, $perms ) = @_;
2217              
2218 28   100     137 $perms = ( $perms // 0777 ) & S_IFPERMS;
2219              
2220 28 100       70 if ( !defined $file ) {
2221              
2222             # mkdir warns if $file is undef
2223 1         174 carp("Use of uninitialized value in mkdir");
2224 1         8 $! = ENOENT;
2225 1         4 return 0;
2226             }
2227              
2228 27         74 my $mock = _get_file_object($file);
2229              
2230 27 100       111 if ( !$mock ) {
2231 2         10 _real_file_access_hook( 'mkdir', \@_ );
2232 2 50       5 goto \&CORE::mkdir if _goto_is_available();
2233 2         109 return CORE::mkdir(@_);
2234             }
2235              
2236             # File or directory, this exists and should fail
2237 25 100       97 if ( $mock->exists ) {
2238 6         23 $! = EEXIST;
2239 6         52 return 0;
2240             }
2241              
2242             # If the mock was a symlink or a file, we've just made it a dir.
2243 19         165 $mock->{'mode'} = ( $perms ^ umask ) | S_IFDIR;
2244 19         66 delete $mock->{'readlink'};
2245              
2246             # This should now start returning content
2247 19         51 $mock->{'has_content'} = 1;
2248              
2249 19         115 return 1;
2250             }
2251              
2252             # $file is always passed because of the prototype.
2253             sub __rmdir (_) {
2254 14     14   19184 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       45 if ( !defined $file ) {
2260 1         182 carp('Use of uninitialized value in rmdir');
2261 1         8 return 0;
2262             }
2263              
2264 13         30 my $mock = _get_file_object($file);
2265              
2266 13 100       37 if ( !$mock ) {
2267 4         20 _real_file_access_hook( 'rmdir', \@_ );
2268 4 50       12 goto \&CORE::rmdir if _goto_is_available();
2269 4         97 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       28 if ( $mock->exists ) {
2275 8 100       23 if ( $mock->is_file ) {
2276 1         2 $! = ENOTDIR;
2277 1         7 return 0;
2278             }
2279              
2280 7 100       45 if ( $mock->is_link ) {
2281 1         4 $! = ENOTDIR;
2282 1         5 return 0;
2283             }
2284             }
2285              
2286 7 100       25 if ( !$mock->exists ) {
2287 1         5 $! = ENOENT;
2288 1         6 return 0;
2289             }
2290              
2291 6 100       38 if ( _files_in_dir($file) ) {
2292 1         3 $! = 39;
2293 1         5 return 0;
2294             }
2295              
2296 5         18 $mock->{'has_content'} = undef;
2297 5         31 return 1;
2298             }
2299              
2300             sub __chown (@) {
2301 13     13   14852 my ( $uid, $gid, @files ) = @_;
2302              
2303 13 50       48 $^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       35 or return 0;
2309              
2310 13         36 my %mocked_files = map +( $_ => _get_file_object($_) ), @files;
2311 13         56 my @unmocked_files = grep !$mocked_files{$_}, @files;
2312 13 100       58 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     55 if ( @mocked_files && @mocked_files != @files ) {
2317 1         343 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       40 $uid == -1 and $uid = $>;
2326 12 100       38 $gid == -1 and $gid = $);
2327              
2328 12   33     88 my $is_root = $> == 0 || $) =~ /( ^ | \s ) 0 ( \s | $)/xms;
2329 12         289 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         37 my $set_error;
2335 12         18 my $num_changed = 0;
2336 12         29 foreach my $file (@files) {
2337 12         25 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       32 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       36 if ( !$mock->exists() ) {
2349              
2350             # Only set the error once
2351 1 50       5 $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         23 $mock->{'uid'} = $uid;
2369 11         16 $mock->{'gid'} = $gid;
2370              
2371 11         19 $num_changed++;
2372             }
2373              
2374 12         79 return $num_changed;
2375             }
2376              
2377             sub __chmod (@) {
2378 6     6   2239 my ( $mode, @files ) = @_;
2379              
2380             # Not an error, report we changed zero files
2381             @files
2382 6 50       20 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 37     37   413 no warnings;
  37         119  
  37         22433  
  6         15  
2388 6 100       47 $mode =~ /^[0-9]+/xms
2389             or warn "Argument \"$mode\" isn't numeric in chmod";
2390 6         20 $mode = int $mode;
2391             }
2392              
2393 6         22 my %mocked_files = map +( $_ => _get_file_object($_) ), @files;
2394 6         28 my @unmocked_files = grep !$mocked_files{$_}, @files;
2395 6 100       37 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 6 100 66     40 if ( @mocked_files && @mocked_files != @files ) {
2400 1         171 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 5         22 my $num_changed = 0;
2408 5         14 foreach my $file (@files) {
2409 7         13 my $mock = $mocked_files{$file};
2410              
2411 7 50       16 if ( !$mock ) {
2412 0         0 _real_file_access_hook( 'chmod', \@_ );
2413 0 0       0 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 7 50       17 if ( !$mock->exists() ) {
2420 0         0 $! = ENOENT;
2421 0         0 next;
2422             }
2423              
2424 7         19 $mock->{'mode'} = ( $mock->{'mode'} & S_IFMT ) + $mode;
2425              
2426 7         12 $num_changed++;
2427             }
2428              
2429 5         18 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 37 50 33 37   1769 : sub (_;) { goto &__glob; };
  10     10   534  
2439              
2440 37         285 *CORE::GLOBAL::open = \&__open;
2441 37         100 *CORE::GLOBAL::sysopen = \&__sysopen;
2442 37         83 *CORE::GLOBAL::opendir = \&__opendir;
2443 37         87 *CORE::GLOBAL::readdir = \&__readdir;
2444 37         89 *CORE::GLOBAL::telldir = \&__telldir;
2445 37         80 *CORE::GLOBAL::rewinddir = \&__rewinddir;
2446 37         83 *CORE::GLOBAL::seekdir = \&__seekdir;
2447 37         82 *CORE::GLOBAL::closedir = \&__closedir;
2448 37         79 *CORE::GLOBAL::unlink = \&__unlink;
2449 37         89 *CORE::GLOBAL::readlink = \&__readlink;
2450 37         115 *CORE::GLOBAL::mkdir = \&__mkdir;
2451              
2452 37         81 *CORE::GLOBAL::rmdir = \&__rmdir;
2453 37         72 *CORE::GLOBAL::chown = \&__chown;
2454 37         2004 *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