File Coverage

blib/lib/Test/File/Cleaner.pm
Criterion Covered Total %
statement 59 59 100.0
branch 11 16 68.7
condition 5 8 62.5
subroutine 14 14 100.0
pod 4 4 100.0
total 93 101 92.0


line stmt bran cond sub pod time code
1             package Test::File::Cleaner;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Test::File::Cleaner - Automatically clean up your filesystem after tests
8              
9             =head1 SYNOPSIS
10              
11             # Create the cleaner
12             my $Cleaner = Test::File::Cleaner->new( 'file_dmz' );
13            
14             # Do some tests that create files
15             touch 'file_dmz/foo';
16            
17             # Cleaner cleans when it is DESTROYed
18             exit();
19            
20             # Alternatively, force an immediate clean up
21             $Cleaner->clean;
22              
23             =head1 DESCRIPTION
24              
25             When writing file-related testing code, it is common to end up with a number
26             of files scattered all over the testing directories. If you are running the
27             test scripts over and over these leftover files can interfere with subsequent
28             test runs, and so they need to be cleaned up.
29              
30             This clean up code typically needs to be done at END-time, so that the files
31             are cleaned up even if you break out of the test script while it is running.
32             The code to do this can get long and is labourious to maintain.
33              
34             Test::File::Cleaner attempts to solve this problem. When you create a
35             Cleaner object for a particular directory, the object scans and saves the
36             contents of the directory.
37              
38             When the object is DESTROYed, it compares the current state to the original,
39             and removes any new files and directories created during the testing process.
40              
41             =head1 METHODS
42              
43             =cut
44              
45 2     2   33684 use 5.005;
  2         7  
  2         63  
46 2     2   10 use strict;
  2         4  
  2         53  
47 2     2   18 use Carp ();
  2         3  
  2         28  
48 2     2   7 use File::Spec ();
  2         3  
  2         29  
49 2     2   8 use File::Basename ();
  2         4  
  2         32  
50 2     2   1675 use File::Find::Rule ();
  2         15881  
  2         58  
51              
52 2     2   16 use vars qw{$VERSION $DEBUG};
  2         4  
  2         129  
53             BEGIN {
54 2     2   6 $VERSION = '0.03';
55 2   50     43 $DEBUG ||= 0;
56             }
57              
58 2     2   1759 use Test::File::Cleaner::State ();
  2         4  
  2         894  
59              
60              
61              
62              
63              
64             #####################################################################
65             # Constructor
66              
67             =pod
68              
69             =head2 new $dir
70              
71             Creates a new Test::File::Cleaner object, which will automatically clean
72             when it is destroyed. The cleaner is passed a directory within which it
73             will operate, which must exist.
74              
75             Since this is intended to be used in test scripts, it will die on error.
76             You will not need to test the return value.
77              
78             =cut
79              
80             sub new {
81 2   33 2 1 11505 my $class = ref $_[0] || $_[0];
82 2 50       67 my $path = -d $_[1] ? $_[1]
83             : Carp::croak("Test::File::Cleaner->new was not passed a directory");
84              
85             # Create the basic object
86 2         22 my $self = bless {
87             alive => 1,
88             path => $path,
89             state => {},
90             }, $class;
91              
92             # Populate the state
93 2         9 $self->reset;
94              
95 2         10 $self;
96             }
97              
98             sub DESTROY {
99 3     3   325627 my $self = shift;
100 3 100       150 return 1 unless $self->{alive};
101 2         10 $self->clean;
102 2         24 return delete $self->{alive};
103             }
104              
105              
106              
107              
108              
109             #####################################################################
110             # Main Methods
111              
112             =pod
113              
114             =head2 path
115              
116             The C accessor returns the current root path for the object.
117             The root path cannot be changed once the Test::File::Cleaner object has
118             been created.
119              
120             =cut
121              
122             sub path {
123 8     8 1 1020 $_[0]->{path};
124             }
125              
126             =pod
127              
128             =head2 clean
129              
130             Calling the C method forces a clean of the directory. The Cleaner
131             will scan it's directory, compare what it finds with it's original scan,
132             and then do whatever is needed to restore the directory to its original
133             state.
134              
135             Returns true if the Cleaner fully restores the directory, or false
136             otherwise.
137              
138             =cut
139              
140             sub clean {
141 5     5 1 9983 my $self = shift;
142              
143             # Fetch the new file list
144 5         30 my @files = File::Find::Rule->in( $self->path );
145              
146             # Sort appropriately.
147             # In this case, we MUST do files first because we arn't going to
148             # be doing recursive delete of directories, and they must be clear
149             # of files first.
150             # We also want to be working bottom up, to help reduce the logic
151             # complexity of the tests below.
152 5         605028 foreach ( @files ) {
153 25 100       2551 my $dir = -d $_ ? $_ : File::Basename::dirname($_);
154 25         419 $_ = [ $_, -d $_, scalar File::Spec->splitdir($dir) ];
155             }
156 25 50 100     64 @files = map { $_->[0] }
  36         278  
157             sort {
158 5         40 $a->[1] <=> $b->[1] # Files first
159             or
160             $b->[2] <=> $a->[2] # Depth first
161             or
162             $a->[0] cmp $b->[0] # Alphabetical otherwise
163             }
164             @files;
165              
166             # Iterate over the files
167 5         15 foreach my $file ( @files ) {
168             # If it existed before, restore it's state
169 25         71 my $State = $self->{state}->{$file};
170 25 100       87 if ( $State ) {
171 10         53 $State->clean;
172 10         31 next;
173             }
174              
175             # Was this already deleted some other way within this loop?
176 15 50       217 next unless -e $file;
177              
178             # This file didn't exist before, delete it.
179 15 50       77 $State = Test::File::Cleaner::State->new( $file )
180             or die "Failed to get a state handle for '$file'";
181 15         53 $State->remove;
182             }
183              
184 5         28 1;
185             }
186              
187             =pod
188              
189             =head2 reset
190              
191             The C method assumes you want to keep any changes that have been
192             made, and will rescan the directory and store the new state instead.
193              
194             Returns true of die on error
195              
196             =cut
197              
198             sub reset {
199 2     2 1 3 my $self = shift;
200              
201             # Catalogue the existing files
202 2         9 my %state = ();
203 2         9 foreach my $file ( File::Find::Rule->in($self->path) ) {
204 4 50       3132458 $state{$file} = Test::File::Cleaner::State->new($file)
205             or die "Failed to create state object for '$file'";
206             }
207 2         9 $self->{state} = \%state;
208              
209 2         6 1;
210             }
211              
212             1;
213              
214             =pod
215              
216             =head1 SUPPORT
217              
218             Bugs should be submitted via the CPAN bug tracker, located at
219              
220             L
221              
222             For other issues, or commercial enhancement or support, contact the author..
223              
224             =head1 AUTHOR
225              
226             Adam Kennedy Eadamk@cpan.orgE
227              
228             =head1 ACKNOWLEDGEMENTS
229              
230             Thank you to Phase N Australia ( L ) for permitting
231             the open sourcing and release of this distribution as a spin-off from a
232             commercial project.
233              
234             =head1 COPYRIGHT
235              
236             Copyright 2004 - 2007 Adam Kennedy.
237              
238             This program is free software; you can redistribute
239             it and/or modify it under the same terms as Perl itself.
240              
241             The full text of the license can be found in the
242             LICENSE file included with this module.
243              
244             =cut