File Coverage

blib/lib/Test/Mock/FileSystem.pm
Criterion Covered Total %
statement 132 166 79.5
branch 17 36 47.2
condition 11 37 29.7
subroutine 20 31 64.5
pod 4 4 100.0
total 184 274 67.1


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