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