File Coverage

blib/lib/Test/MockFile.pm
Criterion Covered Total %
statement 711 828 85.8
branch 352 514 68.4
condition 133 244 54.5
subroutine 102 106 96.2
pod 34 34 100.0
total 1332 1726 77.1


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