File Coverage

blib/lib/Test/Fixme.pm
Criterion Covered Total %
statement 92 107 85.9
branch 31 44 70.4
condition 9 21 42.8
subroutine 14 15 93.3
pod 1 6 16.6
total 147 193 76.1


line stmt bran cond sub pod time code
1             package Test::Fixme;
2              
3             require 5.006;
4 9     9   35596 use strict;
  9         10  
  9         230  
5 9     9   25 use warnings;
  9         10  
  9         189  
6              
7 9     9   24 use Carp;
  9         8  
  9         488  
8 9     9   52 use File::Find;
  9         10  
  9         396  
9 9     9   4100 use ExtUtils::Manifest qw( maniread );
  9         59937  
  9         487  
10              
11 9     9   1758 use Test::Builder;
  9         21693  
  9         2172  
12             require Exporter;
13             our @ISA = qw( Exporter );
14             our @EXPORT = qw( run_tests );
15              
16             # ABSTRACT: Check code for FIXMEs.
17             our $VERSION = '0.16'; # VERSION
18              
19             my $Test = Test::Builder->new;
20              
21             sub run_tests {
22              
23             # Get the values and setup defaults if needed.
24 3     3 1 38 my %args = @_;
25 3 100 66     17 $args{match} = 'FIXME' unless defined $args{match} && length $args{match};
26 3 50 33     17 $args{where} = '.' unless defined $args{where} && length $args{where};
27 3 50 33     13 $args{warn} = 0 unless defined $args{warn} && length $args{warn};
28 3 50       9 $args{format} = $ENV{TEST_FIXME_FORMAT} if defined $ENV{TEST_FIXME_FORMAT};
29             $args{format} = 'original'
30 3 50 33     12 unless defined $args{format} && $args{format} =~ /^(original|perl)$/;
31             $args{filename_match} = qr/./
32 3 50 33     20 unless defined $args{filename_match} && length $args{filename_match};
33 3         5 my $first = 1;
34              
35             # Skip all tests if instructed to.
36 3 100       13 $Test->skip_all("All tests skipped.") if $args{skip_all};
37              
38             # Get files to work with and set the plan.
39 2         3 my @files;
40 2 100       7 if(defined $args{manifest}) {
41 1         1 @files = keys %{ maniread( $args{manifest} ) };
  1         14  
42             } else {
43 1         3 @files = list_files( $args{where}, $args{filename_match} );
44             }
45 2         98 $Test->plan( tests => scalar @files );
46              
47             # Check ech file in turn.
48 2         242 foreach my $file (@files) {
49 62         105 my $results = scan_file( file => $file, match => $args{match} );
50 62         64 my $ok = scalar @$results == 0;
51 62   33     234 $Test->ok($ok || $args{warn}, "'$file'");
52 62 50       10876 next if $ok;
53 0 0       0 $Test->diag('') if $first++;
54 0         0 $Test->diag(do {
55 9     9   40 no strict 'refs';
  9         9  
  9         5452  
56 0         0 &{"format_file_results_$args{format}"}($results)
  0         0  
57             });
58             }
59             }
60              
61             sub scan_file {
62 69     69 0 1685 my %args = @_;
63 69 100 66     190 return undef unless $args{file} && $args{match};
64              
65             # Get the contents of the files and split content into lines.
66 66         89 my $content = load_file( $args{file} );
67 66         1079 my @lines = split $/, $content;
68 66         61 my $line_number = 0;
69              
70             # Set up return array.
71 66         66 my @results = ();
72              
73 66         93 foreach my $line (@lines) {
74 3404         1800 $line_number++;
75 3404 100       4483 next unless $line =~ m/$args{match}/;
76              
77             # We have a match - add it to array.
78             push @results,
79             {
80             file => $args{file},
81             match => $args{match},
82 5         13 line => $line_number,
83             text => $line,
84             };
85             }
86              
87 66         261 return \@results;
88             }
89              
90             sub format_file_results_original {
91 1     1 0 175 my $results = shift;
92 1 50       3 return undef unless defined $results;
93              
94 1         1 my $out = '';
95              
96             # format the file name.
97 1         1 $out .= "File: '" . ${$results}[0]->{file} . "'\n";
  1         2  
98              
99             # format the results.
100 1         2 foreach my $result (@$results) {
101 2         2 my $line = $$result{line};
102 2         2 my $txt = " $line";
103 2         4 $txt .= ' ' x ( 8 - length $line );
104 2         3 $txt .= $$result{text} . "\n";
105 2         3 $out .= $txt;
106             }
107              
108 1         11 return $out;
109             }
110              
111             sub format_file_results_perl {
112 0     0 0 0 my $results = shift;
113 0 0       0 return undef unless defined $results;
114              
115 0         0 my $out = '';
116              
117             # format the results.
118 0         0 foreach my $result (@$results) {
119 0         0 my $file = ${$results}[0]->{file};
  0         0  
120 0         0 my $line = $$result{line};
121 0         0 my $text = $$result{text};
122            
123 0         0 $out .= "Pattern found at $file line $line:\n $text\n";
124             }
125              
126 0         0 return $out;
127             }
128              
129             sub list_files {
130 10     10 0 6150 my $path_arg = shift;
131 10 100       97 croak
132             'You must specify a single directory, or reference to a list of directories'
133             unless defined $path_arg;
134              
135 9         11 my $filename_match = shift;
136 9 100       13 if ( !defined $filename_match ) {
137              
138             # Filename match defaults to matching any single character, for
139             # backwards compatibility with one-arg list_files() invocation
140 7         21 $filename_match = qr/./;
141             }
142              
143 9         12 my @paths;
144 9 100       25 if ( ref $path_arg eq 'ARRAY' ) {
    50          
145              
146             # Ref to array
147 1         2 @paths = @{$path_arg};
  1         2  
148             }
149             elsif ( ref $path_arg eq '' ) {
150              
151             # one path
152 8         11 @paths = ($path_arg);
153             }
154             else {
155              
156             # something else
157 0         0 croak
158             'Argument to list_files must be a single path, or a reference to an array of paths';
159             }
160              
161 9         12 foreach my $path (@paths) {
162              
163             # Die if we got a bad dir.
164 10 100       325 croak "'$path' does not exist" unless -e $path;
165             }
166              
167 8         10 my @files;
168             find(
169             {
170             preprocess => sub {
171             # no GIT, Subversion or CVS directory contents
172 41     41   773 grep !/^(.git|.svn|CVS)$/, @_,
173             },
174             wanted => sub {
175 125 100   125   2213 push @files, $File::Find::name
176             if -f $File::Find::name;
177             },
178 8         477 no_chdir => 1,
179             },
180             @paths
181             );
182              
183             @files =
184             sort # sort the files
185 83         176 grep { m/$filename_match/ }
186 8         34 grep { !-l $_ } # no symbolic links
  84         421  
187             @files;
188              
189 8         35 return @files;
190             }
191              
192             sub load_file {
193 69     69 0 1253 my $filename = shift;
194              
195             # If the file is not regular then return undef.
196 69 100       802 return undef unless -f $filename;
197              
198             # Slurp the file.
199 68 50       1165 open(my $fh, '<', $filename) || croak "error reading $filename $!";
200 68         60 my $content = do { local $/; <$fh> };
  68         142  
  68         827  
201 68         268 close $fh;
202 68         164 return $content;
203             }
204              
205             1;
206              
207             =pod
208              
209             =encoding UTF-8
210              
211             =head1 NAME
212              
213             Test::Fixme - Check code for FIXMEs.
214              
215             =head1 VERSION
216              
217             version 0.16
218              
219             =head1 SYNOPSIS
220              
221             # In a test script like 't/test-fixme.t'
222             use Test::Fixme;
223             run_tests();
224            
225             # You can also tailor the behaviour.
226             use Test::Fixme;
227             run_tests( where => 'lib', # where to find files to check
228             match => 'TODO', # what to check for
229             skip_all => $ENV{SKIP} # should all tests be skipped
230             );
231              
232             =head1 DESCRIPTION
233              
234             When coding it is common to come up against problems that need to be
235             addressed but that are not a big deal at the moment. What generally
236             happens is that the coder adds comments like:
237              
238             # FIXME - what about windows that are bigger than the screen?
239              
240             # FIXME - add checking of user privileges here.
241              
242             L allows you to add a test file that ensures that none of
243             these get forgotten in the module.
244              
245             =head1 METHODS
246              
247             =head2 run_tests
248              
249             By default run_tests will search for 'FIXME' in all the files it can
250             find in the project. You can change these defaults by using 'where' or
251             'match' as follows:
252              
253             run_tests( where => 'lib', # just check the modules.
254             match => 'TODO' # look for things that are not done yet.
255             );
256              
257             =over 4
258              
259             =item where
260              
261             Specifies where to search for files. This can be a scalar containing a
262             single directory name, or it can be a list reference containing multiple
263             directory names.
264              
265             =item match
266              
267             Expression to search for within the files. This may be a simple
268             string, or a qr//-quoted regular expression. For example:
269              
270             match => qr/[T]ODO|[F]IXME|[B]UG/,
271              
272             =item filename_match
273              
274             Expression to filter file names. This should be a qr//-quoted regular
275             expression. For example:
276              
277             match => qr/\.(:pm|pl)$/,
278              
279             would only match .pm and .pl files under your specified directory.
280              
281             =item manifest
282              
283             Specifies the name of your MANIFEST file which will be used as the list
284             of files to test instead of I or I.
285              
286             manifest => 'MANIFEST',
287              
288             =item warn
289              
290             Do not fail when a FIXME or other pattern is matched. Tests that would
291             have been failures will still issue a diagnostic that will be viewed
292             when you run C without C<-v>, C or C<./Build test>.
293              
294             =item format
295              
296             Specifies format to be used for display of pattern matches.
297              
298             =over 4
299              
300             =item original
301              
302             The original and currently default format looks something like this:
303              
304             # File: './lib/Test/Fixme.pm'
305             # 16 # ABSTRACT: Check code for FIXMEs.
306             # 25 $args{match} = 'FIXME' unless defined $args{match} && length $args{match};
307             # 28 $args{format} ||= $ENV{TEST_FIXME_FORMAT};
308             # 228 # FIXME - what about windows that are bigger than the screen?
309             # 230 # FIXME - add checking of user privileges here.
310             # 239 By default run_tests will search for 'FIXME' in all the files it can
311             # 280 Do not fail when a FIXME or other pattern is matched. Tests that would
312             # 288 If you want to match something other than 'FIXME' then you may find
313             # 296 run_tests( skip_all => $ENV{SKIP_TEST_FIXME} );
314             # 303 L
315              
316             With the line numbers on the left and the offending text on the right.
317              
318             =item perl
319              
320             The "perl" format is that used by Perl itself to report warnings and errors.
321              
322             # Pattern found at ./lib/Test/Fixme.pm line 16:
323             # # ABSTRACT: Check code for FIXMEs.
324             # Pattern found at ./lib/Test/Fixme.pm line 25:
325             # $args{match} = 'FIXME' unless defined $args{match} && length $args{match};
326             # Pattern found at ./lib/Test/Fixme.pm line 28:
327             # $args{format} ||= $ENV{TEST_FIXME_FORMAT};
328             # Pattern found at ./lib/Test/Fixme.pm line 228:
329             # # FIXME - what about windows that are bigger than the screen?
330             # Pattern found at ./lib/Test/Fixme.pm line 230:
331             # # FIXME - add checking of user privileges here.
332             # Pattern found at ./lib/Test/Fixme.pm line 239:
333             # By default run_tests will search for 'FIXME' in all the files it can
334             # Pattern found at ./lib/Test/Fixme.pm line 280:
335             # Do not fail when a FIXME or other pattern is matched. Tests that would
336             # Pattern found at ./lib/Test/Fixme.pm line 288:
337             # If you want to match something other than 'FIXME' then you may find
338             # Pattern found at ./lib/Test/Fixme.pm line 296:
339             # run_tests( skip_all => $ENV{SKIP_TEST_FIXME} );
340             # Pattern found at ./lib/Test/Fixme.pm line 303:
341             # L
342              
343             For files that contain many offending patterns it may be a bit harder to read for
344             humans, but easier to parse for IDEs.
345              
346             =back
347              
348             You may also use the C environment variable to override either
349             the default or the value specified in the test file.
350              
351             =back
352              
353             =head1 HINTS
354              
355             If you want to match something other than 'FIXME' then you may find
356             that the test file itself is being caught. Try doing this:
357              
358             run_tests( match => 'TO'.'DO' );
359              
360             You may also wish to suppress the tests - try this:
361              
362             use Test::Fixme;
363             run_tests( skip_all => $ENV{SKIP_TEST_FIXME} );
364              
365             You can only run run_tests once per file. Please use several test
366             files if you want to run several different tests.
367              
368             =head1 CAVEATS
369              
370             This module is fully supported back to Perl 5.8.1. It may work on 5.8.0.
371             It should work on Perl 5.6.x and I may even test on 5.6.2. I will accept
372             patches to maintain compatibility for such older Perls, but you may
373             need to fix it on 5.6.x / 5.8.0 and send me a patch.
374              
375             =head1 SEE ALSO
376              
377             L
378              
379             =head1 ACKNOWLEDGMENTS
380              
381             Dave O'Neill added support for 'filename_match' and also being able to pass a
382             list of several directories in the 'where' argument. Many thanks.
383              
384             =head1 AUTHOR
385              
386             Original author: Edmund von der Burg
387              
388             Current maintainer: Graham Ollis Eplicease@cpan.orgE
389              
390             Contributors:
391              
392             Dave O'Neill
393              
394             gregor herrmann Egregoa@debian.orgE
395              
396             =head1 COPYRIGHT AND LICENSE
397              
398             This software is copyright (c) 2016 by Edmund von der Burg , Graham Ollis .
399              
400             This is free software; you can redistribute it and/or modify it under
401             the same terms as the Perl 5 programming language system itself.
402              
403             =cut
404              
405             __END__