File Coverage

blib/lib/Test/MockFile.pm
Criterion Covered Total %
statement 711 827 85.9
branch 352 512 68.7
condition 133 241 55.1
subroutine 102 106 96.2
pod 34 34 100.0
total 1332 1720 77.4


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