File Coverage

blib/lib/Test/Fixme.pm
Criterion Covered Total %
statement 97 112 86.6
branch 31 44 70.4
condition 10 21 47.6
subroutine 16 17 94.1
pod 1 6 16.6
total 155 200 77.5


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