line
stmt
bran
cond
sub
pod
time
code
1
package Test::Mock::FileSystem;
2
3
3
3
96842
use strict;
3
10
3
123
4
3
3
17
use warnings;
3
6
3
87
5
3
3
17
use File::Spec;
3
10
3
92
6
3
3
3851
use POSIX qw(ceil getgid getuid);
3
23874
3
22
7
8
=head1 NAME
9
10
Mock::FileSystem - Simulate filesystem resources to help testing modules that depends on filesystem objects
11
12
=head1 VERSION
13
14
Version 0.01_001
15
16
=cut
17
18
our $VERSION = '0.01_001';
19
20
=head1 SYNOPSIS
21
22
Quick summary of what the module does.
23
24
Perhaps a little code snippet.
25
26
use Some::Module;
27
use Test::Mock::FileSystem 'Some::Module';
28
29
mock_file '/tmp/something' => (
30
path => "/tmp/something",
31
content => "Some content",
32
mode => oct("4"), # read-only
33
ctime => time() - 3600, # one hour ago
34
);
35
36
# Then a sub in Some::Module
37
sub open_file {
38
my $self = shift;
39
open my $fh, '<', '/tmp/something';
40
41
# This will print Some content
42
print <$fh>;
43
44
close $fh;
45
}
46
47
...
48
49
=cut
50
51
my $file_system = {};
52
my $block_size = 4096;
53
54
sub import {
55
3
3
35
my ( $class, @modules ) = @_;
56
57
3
10
my $package = caller;
58
59
3
9
_export_functions_to($package);
60
61
3
50
14
unless (@modules) {
62
3
7
push @modules, $package;
63
}
64
65
3
50
15
if (@modules) {
66
3
19
_override_builtins($_) for @modules;
67
}
68
else {
69
0
0
_override_builtins($package);
70
}
71
}
72
73
=head1 EXPORTED FUNCTIONS
74
75
A list of functions that can be exported. You can delete this section
76
if you don't export anything, such as for a purely object-oriented module.
77
78
=head2 mock_dir $file => %options
79
80
=cut
81
82
sub mock_dir {
83
2
2
1
18
my $path = shift;
84
2
100
13
my $args = shift || {};
85
86
2
30
my ( $vol, $dir ) = File::Spec->splitpath( $path, 1 );
87
88
2
32
my @dirs = File::Spec->splitdir($dir);
89
90
2
50
12
unshift @dirs, $vol if $vol;
91
92
2
7
my $entry = $file_system;
93
2
5
foreach (@dirs) {
94
7
100
21
next unless $_;
95
4
50
15
if ( $entry->{$_} ) {
96
0
0
0
unless ( $entry->{type} eq 'd' ) {
97
0
0
die "Not a Directory";
98
}
99
0
0
$entry = $entry->{$_}->{content};
100
}
101
102
# Create it
103
else {
104
4
17
$entry->{$_} = {
105
type => 'd',
106
content => {},
107
};
108
4
13
$entry = $entry->{$_}->{content};
109
}
110
}
111
2
8
return $entry;
112
}
113
114
=head2 C %options>
115
116
This will create a C<$file> in the virtual file system and the parents directories. Additionally you can control the meta information of the file using the C<%options> parameter. Here is a list of the valid options
117
118
=over 4
119
120
=item C $content>
121
122
The fills the virtual file with C<$content>. By default file have no content
123
124
=item C $access>
125
126
Use this option to control the access bits of the file. The available bits are B. So for example if C<$access> is the value C the file will be readable and writable.
127
128
=item C $uid>
129
130
The option C sets the owner of the file with C<$uid>. The default value is whatever C returns.
131
132
=item C $gid>
133
134
The option C sets the owning group of the file with C<$gid>. The default value is whatever C returns
135
136
=item C $time>
137
138
The option C set the access time with C<$time>. The default value is the value returned by C at the moment of file creation
139
140
=item C $time>.
141
142
The option C set the create time with C<$time>. The default value is the value returned by C at the moment of file creation
143
144
=item C $time>
145
146
The option C set the modified time with C<$time>. The default value is the value returned by C at the moment of file creation
147
148
=back
149
150
=cut
151
152
sub mock_file {
153
1
1
1
44
my $path = File::Spec->rel2abs(shift);
154
1
4
my %args = @_;
155
156
1
50
8
my $content = $args{content} || '';
157
1
4
$args{content} = \$content;
158
1
50
10
$args{access} ||= 7;
159
1
33
13
$args{uid} ||= getuid();
160
1
33
241
$args{gid} ||= getgid();
161
1
33
190
$args{ctime} ||= time();
162
1
33
7
$args{mtime} ||= time();
163
1
33
9
$args{atime} ||= time();
164
1
3
$args{type} = 'f';
165
166
1
23
my ( $vol, $dir, $name ) = File::Spec->splitpath($path);
167
168
1
21
my $dir_path = File::Spec->catpath( $vol, $dir );
169
170
# Mock the route to it
171
1
7
my $entry = mock_dir $dir_path => (
172
uid => $args{uid},
173
gid => $args{gid},
174
);
175
176
1
6
$entry->{$name} = \%args;
177
}
178
179
sub _export_functions_to {
180
3
3
4
my $package = shift;
181
182
3
3
5344
no strict 'refs';
3
6
3
238
183
184
3
7
*{"$package\::mock_file"} = \&mock_file;
3
17
185
3
7
*{"$package\::mock_dir"} = \&mock_dir;
3
16
186
187
3
3
16
use strict 'refs';
3
4
3
159
188
189
}
190
191
sub _override_builtins {
192
3
3
5
my $package = shift;
193
194
3
3
17
no strict 'refs';
3
5
3
768
195
196
3
6
*{"$package\::open"} = \&_open;
3
13
197
3
5
*{"$package\::close"} = \&_close;
3
12
198
3
4
*{"$package\::stat"} = \&_stat;
3
12
199
3
6
*{"$package\::unlink"} = \&_unlink;
3
13
200
3
5
*{"$package\::opendir"} = \&_opendir;
3
12
201
3
5
*{"$package\::closedir"} = \&_closedir;
3
12
202
3
5
*{"$package\::readdir"} = \&_readdir;
3
13
203
3
4
*{"$package\::seekdir"} = \&_seekdir;
3
13
204
3
7
*{"$package\::telldir"} = \&_telldir;
3
15
205
3
5
*{"$package\::mkdir"} = \&_mkdir;
3
12
206
3
9
*{"$package\::rmdir"} = \&_rmdirm;
3
4655
207
208
3
3
18
use strict 'refs';
3
3
3
3636
209
}
210
211
sub _close {
212
2
2
5015
CORE::close( $_[0] );
213
}
214
215
sub _closedir {
216
1
1
3
my $dh = \$_[0];
217
1
2
$$dh = undef;
218
1
5
return 1;
219
}
220
221
0
0
0
sub _mkdir { }
222
223
sub _open (\[*$];@$) {
224
2
2
10
my ( $fh, $access, $name ) = @_;
225
226
2
50
8
$name ||= '';
227
2
8
my $compound = "$access $name";
228
229
2
50
15
if ( $compound =~ /\s*(<|>|>>|\+<|\+>|\+>>)?\s*(\S+)\s*/ ) {
230
2
50
10
$access = $1 || '<';
231
2
13
$name = $2;
232
}
233
else {
234
0
0
die 'Unexpected open() parameters for file mocking';
235
}
236
237
2
10
my $entry = _getpath($name);
238
239
2
50
16
if ( not defined $entry ) {
240
0
0
$! = 2;
241
0
0
return 0;
242
}
243
244
2
1
4322
return CORE::open( $$fh, $access, $entry->{content} );
1
14
1
3
1
11
245
}
246
247
sub _opendir (\[*$];$) {
248
1
1
8
my ( $dh, $path ) = @_;
249
250
1
6
my $entry = _getpath($path);
251
252
1
50
6
if ( not defined $entry ) {
253
0
0
$! = 2;
254
0
0
return undef;
255
}
256
257
1
7
my $dir_handle = {
258
index => 0,
259
content => [ '.', '..' ],
260
};
261
262
1
2
foreach ( keys %{ $entry->{content} } ) {
1
6
263
1
3
push @{ $dir_handle->{content} }, $_;
1
3
264
}
265
266
1
9
$$dh = $dir_handle;
267
}
268
269
sub _readdir {
270
3
3
5
my $dh = shift;
271
272
3
7
my $current_index = $dh->{index};
273
3
4
my $last_index = scalar( @{ $dh->{content} } ) - 1;
3
7
274
275
3
50
8
if ( wantarray() ) {
276
277
0
0
$dh->{index} = $last_index;
278
0
0
return @{ $dh->{content} }[ $current_index .. $last_index ];
0
0
279
}
280
else {
281
3
50
25
unless ( $current_index > $last_index ) {
282
3
7
$dh->{index} = $current_index + 1;
283
3
15
return $dh->{content}->[$current_index];
284
}
285
}
286
}
287
288
0
0
0
sub _rmdir { }
289
290
sub _seekdir {
291
0
0
0
my ( $dh, $pos ) = @_;
292
0
0
$dh->{index} = $pos;
293
}
294
295
sub _stat ($) {
296
0
0
0
my $filename = shift;
297
298
0
0
my $entry = _getentry($filename);
299
300
0
0
0
if ($entry) {
301
0
0
my $size = _calculate_size($entry);
302
303
return (
304
0
0
0
1, # dev id,
0
0
305
1, # inode id
306
$entry->{mode}, # mode
307
0, # number of harlinks to file
308
1, # uid
309
1, # gid
310
0, # rdev
311
$size, # size
312
$entry->{atime} || time(), # atime,
313
$entry->{mtime} || time(), # mtime,
314
$entry->{ctime} || time(), # ctime,
315
$block_size, # blksize
316
ceil( $size / $block_size ) * $block_size # number of bloks
317
);
318
}
319
}
320
321
sub _sysopen {
322
0
0
0
die "_sysopen\n";
323
}
324
325
sub _telldir {
326
0
0
0
my $dh = shift;
327
0
0
return $dh->{index};
328
}
329
330
0
0
0
sub _unlink { }
331
332
0
0
0
sub _utime { }
333
334
sub _getpath {
335
3
3
5
my $path = shift;
336
337
3
53
my ( $vol, $dir, $file ) = File::Spec->splitpath($path);
338
339
3
23
my @dirs = File::Spec->splitdir($dir);
340
341
3
50
16
unshift @dirs, $vol if $vol;
342
3
50
12
push @dirs, $file if $file;
343
344
3
6
my $last = pop @dirs;
345
346
3
7
my $entry = $file_system;
347
3
7
foreach (@dirs) {
348
10
100
29
next unless $_;
349
4
50
11
return undef unless $entry->{$_};
350
351
4
50
10
unless ( $entry->{$_}->{type} eq 'd' ) {
352
0
0
die "Not a Directory";
353
}
354
355
4
8
$entry = $entry->{$_}->{content};
356
}
357
358
3
15
return $entry->{$last};
359
}
360
361
sub _calculate_size {
362
0
0
0
my $file = shift;
363
364
0
0
my $size = 0;
365
366
0
0
0
0
if ( $file->{type} eq 'f' && $file->{content} ) {
367
0
0
$size = length( $file->{content} );
368
}
369
370
0
0
return $size;
371
}
372
373
=head1 SUBROUTINES/METHODS
374
375
=head2 function1
376
377
=cut
378
379
0
0
1
0
sub function1 {
380
}
381
382
=head2 function2
383
384
=cut
385
386
0
0
1
0
sub function2 {
387
}
388
389
=head1 AUTHOR
390
391
Mariano Waghlmann, C<< >>
392
393
=head1 BUGS
394
395
Please report any bugs or feature requests to C, or through
396
the web interface at L. I will be notified, and then you'll
397
automatically be notified of progress on your bug as I make changes.
398
399
400
401
402
=head1 SUPPORT
403
404
You can find documentation for this module with the perldoc command.
405
406
perldoc Test::Mock::FileSystem
407
408
409
You can also look for information at:
410
411
=over 4
412
413
=item * RT: CPAN's request tracker (report bugs here)
414
415
L
416
417
=item * AnnoCPAN: Annotated CPAN documentation
418
419
L
420
421
=item * CPAN Ratings
422
423
L
424
425
=item * Search CPAN
426
427
L
428
429
=back
430
431
432
=head1 ACKNOWLEDGEMENTS
433
434
435
=head1 LICENSE AND COPYRIGHT
436
437
Copyright 2011 Mariano Wahlmann.
438
439
This program is free software; you can redistribute it and/or modify it
440
under the terms of either: the GNU General Public License as published
441
by the Free Software Foundation; or the Artistic License.
442
443
See http://dev.perl.org/licenses/ for more information.
444
445
446
=cut
447
448
1; # End of Test::Mock::FileSystem
449