File Coverage

blib/lib/Test/Perl/Critic/Progressive.pm
Criterion Covered Total %
statement 60 123 48.7
branch 7 38 18.4
condition 0 6 0.0
subroutine 22 27 81.4
pod 9 9 100.0
total 98 203 48.2


line stmt bran cond sub pod time code
1             ##############################################################################
2             # $URL: http://perlcritic.tigris.org/svn/perlcritic/tags/Test-Perl-Critic-Progressive-0.03/lib/Test/Perl/Critic/Progressive.pm $
3             # $Date: 2008-07-27 16:01:56 -0700 (Sun, 27 Jul 2008) $
4             # $Author: thaljef $
5             # $Revision: 2620 $
6             ##############################################################################
7              
8             package Test::Perl::Critic::Progressive;
9              
10 2     2   27546 use 5.006001;
  2         9  
  2         93  
11              
12 2     2   13 use strict;
  2         4  
  2         73  
13 2     2   30 use warnings;
  2         5  
  2         85  
14              
15 2     2   11 use Carp qw(croak confess);
  2         4  
  2         192  
16 2     2   2531 use Data::Dumper qw(Dumper);
  2         19569  
  2         201  
17 2     2   2000 use English qw(-no_match_vars);
  2         7284  
  2         15  
18 2     2   872 use File::Spec qw();
  2         4  
  2         42  
19 2     2   2161 use FindBin qw($Bin);
  2         11421  
  2         290  
20              
21 2     2   2270 use Perl::Critic qw();
  2         4064604  
  2         60  
22 2     2   37 use Perl::Critic::Utils qw(policy_short_name policy_long_name);
  2         4  
  2         120  
23              
24 2     2   12 use Test::Builder qw();
  2         4  
  2         36  
25              
26 2     2   12 use base 'Exporter';
  2         5  
  2         3008  
27              
28             #---------------------------------------------------------------------------
29              
30             our $VERSION = '0.03';
31              
32             #---------------------------------------------------------------------------
33              
34             our @EXPORT_OK = qw(
35             get_critic_args
36             get_history_file
37             get_total_step_size
38             get_step_size_per_policy
39             progressive_critic_ok
40             set_critic_args
41             set_history_file
42             set_total_step_size
43             set_step_size_per_policy
44             );
45              
46             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
47              
48             #---------------------------------------------------------------------------
49              
50             my $TOTAL_STEP_SIZE = undef;
51             my $DEFAULT_STEP_SIZE = 0;
52             my %STEP_SIZE_PER_POLICY = ();
53              
54             my $HISTORY_FILE = undef;
55             my $DEFAULT_HISTORY_FILE = File::Spec->catfile($Bin, '.perlcritic-history');
56              
57             my $CRITIC = undef;
58             my %CRITIC_ARGS = ();
59              
60             my $TEST = Test::Builder->new();
61              
62             #---------------------------------------------------------------------------
63             # Public functions
64              
65             sub progressive_critic_ok {
66              
67 1     1 1 685 my @dirs = @_;
68 1 50       6 if (not @dirs) {
69 0         0 @dirs = _starting_points();
70             }
71              
72 1         5 my @files = _all_code_files( @dirs );
73 1 50       251 croak qq{No perl files found\n} if not @files;
74              
75 0         0 my $caller = caller;
76 0         0 $TEST->exported_to($caller);
77 0         0 $TEST->plan( tests => 1 );
78              
79 0         0 $CRITIC = Perl::Critic->new( get_critic_args() );
80 0         0 my @violations = map { $CRITIC->critique($_) } @files;
  0         0  
81              
82 0         0 my $ok = _evaluate_test( @violations );
83 0         0 $TEST->ok($ok, __PACKAGE__);
84 0         0 return $ok;
85             }
86              
87             #---------------------------------------------------------------------------
88             # Pulbic accessor functions
89              
90             sub get_history_file {
91 1 50   1 1 8 return defined $HISTORY_FILE ?
92             $HISTORY_FILE : $DEFAULT_HISTORY_FILE;
93             }
94              
95             #---------------------------------------------------------------------------
96              
97             sub set_history_file {
98 1     1 1 1000 $HISTORY_FILE = shift;
99 1         3 return 1;
100             }
101              
102             #---------------------------------------------------------------------------
103              
104             sub get_critic_args {
105 0     0 1 0 return %CRITIC_ARGS;
106             }
107              
108             #---------------------------------------------------------------------------
109              
110             sub set_critic_args {
111 0     0 1 0 %CRITIC_ARGS = @_;
112 0         0 return 1;
113             }
114              
115             #---------------------------------------------------------------------------
116              
117             sub get_total_step_size {
118 1 50   1 1 10 return defined $TOTAL_STEP_SIZE ?
119             $TOTAL_STEP_SIZE : $DEFAULT_STEP_SIZE;
120             }
121              
122              
123             #---------------------------------------------------------------------------
124              
125             sub set_total_step_size {
126 1     1 1 10 $TOTAL_STEP_SIZE = shift;
127 1         3 return 1;
128             }
129              
130             #---------------------------------------------------------------------------
131              
132             sub get_step_size_per_policy {
133 1     1 1 7 return %STEP_SIZE_PER_POLICY;
134             }
135              
136             #---------------------------------------------------------------------------
137              
138             sub set_step_size_per_policy {
139              
140 1     1 1 515 my %args = @_;
141 1         3 my %step_sizes = ();
142 1         4 for my $policy_name ( keys %args ) {
143 1         9 $step_sizes{policy_long_name($policy_name)} = $args{$policy_name};
144             }
145              
146 1         56 %STEP_SIZE_PER_POLICY = %step_sizes;
147 1         4 return 1;
148             }
149              
150             #---------------------------------------------------------------------------
151             # Private functions
152              
153             sub _evaluate_test {
154              
155 0     0   0 my (@viols) = @_;
156              
157 0         0 my $ok = 1;
158 0         0 my $results = {};
159              
160 0         0 my $history_data = _read_history( get_history_file() );
161 0         0 my $last_critique = $history_data->[-1];
162 0         0 my $has_run_before = defined $last_critique;
163 0         0 my $last_total_violations = 0;
164 0         0 my $current_total_violations = 0;
165              
166              
167 0         0 for my $policy ( $CRITIC->policies() ) {
168              
169 0         0 my $policy_name = ref $policy;
170 0         0 my $policy_violations = grep {$_->policy() eq $policy_name} @viols;
  0         0  
171 0         0 $results->{$policy_name} = $policy_violations;
172              
173 0         0 my $last_policy_violations = $last_critique->{$policy_name};
174 0 0       0 next if not defined $last_policy_violations;
175              
176 0         0 $last_total_violations += $last_policy_violations;
177 0         0 $current_total_violations += $policy_violations;
178              
179 0 0       0 my $policy_step_size = defined $STEP_SIZE_PER_POLICY{$policy_name} ?
180             $STEP_SIZE_PER_POLICY{$policy_name} : $DEFAULT_STEP_SIZE;
181              
182 0 0       0 my $target = $policy_step_size > $last_policy_violations ?
183             0 : $last_policy_violations - $policy_step_size;
184              
185 0 0       0 if ( $policy_violations > $target ) {
186 0         0 my $short_name = policy_short_name($policy_name);
187 0         0 my $diagf = '%s: Got %i violation(s). Expected no more than %i.';
188 0         0 $TEST->diag( sprintf $diagf, $short_name, $policy_violations, $target );
189 0         0 $ok = 0; # Failed the test!
190             }
191             }
192              
193              
194              
195 0 0       0 if ( $has_run_before ) {
196              
197 0 0       0 my $target = get_total_step_size() > $last_total_violations ?
198             0 : $last_total_violations - get_total_step_size();
199              
200              
201 0 0       0 if ( $current_total_violations > $target ) {
202 0         0 my $got = $current_total_violations;
203 0         0 $TEST->diag('Too many Perl::Critic violations...');
204 0         0 $TEST->diag("Got a total of $got. Expected no more than $target.");
205 0         0 $ok = 0;
206             }
207             }
208              
209              
210              
211              
212 0 0 0     0 if ( !$has_run_before || ($ok && $last_total_violations > 0) ) {
      0        
213 0         0 push @{$history_data}, $results;
  0         0  
214 0         0 _write_history_file( get_history_file(), $history_data);
215             }
216              
217              
218 0         0 return $ok;
219             }
220              
221             #---------------------------------------------------------------------------
222              
223             sub _all_code_files {
224 1     1   3 my @dirs = @_;
225 1 50       3 if (not @dirs) {
226 0         0 @dirs = _starting_points();
227             }
228 1         6 return Perl::Critic::Utils::all_perl_files(@dirs);
229             }
230              
231             #---------------------------------------------------------------------------
232              
233             sub _starting_points {
234 0 0   0   0 return -e 'blib' ? 'blib' : grep { -e $_ } qw(lib bin script scripts);
  0         0  
235             }
236              
237             #---------------------------------------------------------------------------
238              
239             sub _read_history {
240              
241 1     1   586 my ($history_file) = @_;
242              
243 1 50       16 return [] if not -e $history_file;
244              
245 0         0 my $history_data = eval { do $history_file };
  0         0  
246 0 0       0 croak qq{Can't read history from "$history_file": $EVAL_ERROR}
247             if $EVAL_ERROR;
248              
249 0         0 return $history_data;
250             }
251              
252             #---------------------------------------------------------------------------
253              
254             sub _open_history_file {
255              
256 1     1   420 my ($history_file) = @_;
257              
258 1 50       268 open my $history_fh, '>', $history_file
259             or confess qq{Can't open "$history_file": $OS_ERROR};
260              
261 0           return $history_fh;
262             }
263              
264             #---------------------------------------------------------------------------
265              
266             sub _write_history_file {
267              
268 0     0     my ($history_file, $history_data) = @_;
269              
270 0           my $history_fh = _open_history_file($history_file);
271              
272 0 0         print {$history_fh} Dumper($history_data)
  0            
273             or confess qq{Can't write to "$history_file": $OS_ERROR};
274              
275 0 0         close $history_fh
276             or confess qq{Can't close "$history_file": $OS_ERROR};
277              
278 0           return 1;
279             }
280              
281             #---------------------------------------------------------------------------
282              
283             1;
284              
285              
286             __END__
287              
288             =pod
289              
290             =for stopwords AntHill CruiseControl
291              
292             =head1 NAME
293              
294             Test::Perl::Critic::Progressive - Gradually enforce coding standards.
295              
296              
297             =head1 SYNOPSIS
298              
299             To test one or more files, and/or all files in one or more directories:
300              
301             use Test::Perl::Critic::Progressive qw( progressive_critic_ok );
302             progressive_critic_ok($file1, $file2, $dir1, $dir2);
303              
304             To test all Perl files in a distribution:
305              
306             use Test::Perl::Critic::Progressive qw( progressive_critic_ok );
307             progressive_critic_ok();
308              
309             Recommended usage for public CPAN distributions:
310              
311             use strict;
312             use warnings;
313             use Test::More;
314              
315             eval { require Test::Perl::Critic::Progressive };
316             plan skip_all => 'T::P::C::Progressive required for this test' if $@;
317              
318             Test::Perl::Critic::Progressive::progressive_critic_ok();
319              
320              
321             =head1 DESCRIPTION
322              
323             Applying coding standards to large amounts of legacy code is a daunting task.
324             Often times, legacy code is so non-compliant that it seems downright
325             impossible. But, if you consistently chip away at the problem, you will
326             eventually succeed! Test::Perl::Critic::Progressive uses the L<Perl::Critic>
327             engine to prevent further deterioration of your code and
328             B<gradually> steer it towards conforming with your chosen coding standards.
329              
330             The most effective way to use Test::Perl::Critic::Progressive is as a unit
331             test that is run under a continuous-integration system like CruiseControl or
332             AntHill. Each time a developer commits changes to the code, this test will
333             fail and the build will break unless it has the same (or fewer) Perl::Critic
334             violations than the last successful test.
335              
336             See the L<"NOTES"> for more details about how this test works.
337              
338             =head1 SUBROUTINES
339              
340             All of the following subroutines can be exported upon request. Or you
341             can export all of them at once using the C<':all'> tag.
342              
343             =over
344              
345             =item C< progressive_critic_ok(@FILES [, @DIRECTORIES ]) >
346              
347             =item C< progressive_critic_ok() >
348              
349             Uses Perl::Critic to analyze each of the given @FILES, and/or all Perl files
350             beneath the given list of C<@DIRECTORIES>. If no arguments are given, it
351             analyzes all the Perl files in the F<blib/> directory. If the F<blib/>
352             directory does not exist, then it tries the F<lib/>, F<bin/>, F<script/>, and
353             F<scripts/> directory. The results of the analysis will be stored as
354             F<.perlcritic-history> in the same directory where your test script is
355             located.
356              
357             The first time you run this test, it will always pass. But on each subsequent
358             run, the test will pass only if the number of violations found B<is less than
359             or equal to> the number of violations found during the last passing test. If
360             it does pass, then the history file will be updated with the new analysis
361             results. Once all the violations are removed from the code, this test will
362             always pass, unless a new violation is introduced.
363              
364             This subroutine emits its own L<Test::More> plan, so you do not need to
365             specify an expected number of tests yourself.
366              
367              
368             =item C< get_history_file() >
369              
370             =item C< set_history_file($FILE) >
371              
372             These functions get or set the full path to the history file. This is
373             where Test::Perl::Critic::Progressive will store the results of each passing
374             analysis. If the C<$FILE> does not exist, it will be created anew. The
375             default is C<$Bin/.perlcritic-history> where C<$Bin> is the directory that
376             the calling test script is located in.
377              
378             =item C< get_total_step_size() >
379              
380             =item C< set_total_step_size($INTEGER) >
381              
382             These functions get or set the minimum acceptable decrease in the B<total>
383             number of violations between each test. The default value is zero, which
384             means that you are not required to remove any violations, but you are also not
385             allowed to add any. If you set the step size to a positive number, the test
386             will require you to remove C<$INTEGER> violations each time the test is run.
387             In this case, the particular type of violation that you eliminate doesn't
388             matter. The larger the step size, the faster you'll have to eliminate
389             violations.
390              
391              
392             =item C< get_step_size_per_policy() >
393              
394             =item C< set_step_size_per_policy(%ARGS) >
395              
396             These functions get or set the minimum acceptable decrease in the number of
397             violations of a B<specific policy> between each test. The C<%ARGS> should be
398             C<< $POLICY_NAME => $INTEGER >> pairs, like this:
399              
400             my %step_sizes = (
401             'ValuesAndExpressions::ProhibitLeadingZeros' => 2,
402             'Variables::ProhibitConditionalDeclarations' => 1,
403             'InputOutput::ProhibitTwoArgOpen' => 3,
404             );
405              
406             set_step_size_per_policy( %step_sizes );
407             progressive_critic_ok();
408              
409             The default step size for any given Policy is zero, which means that you are
410             not required to remove any violations, but you are also not allowed to add
411             any. But if you wish to focus on eliminating certain types of violations,
412             then increasing the per-policy step size will force you to B<decrease> the
413             number of violations of that particular Policy, while ignoring other types of
414             violations. The larger the step size, the faster you'll have to eliminate
415             violations.
416              
417             =item C< get_critic_args() >
418              
419             =item C< set_critic_args(%ARGS) >
420              
421             These functions get or set the arguments given to L<Perl::Critic>. By
422             default, Test::Perl::Critic::Progressive invokes Perl::Critic with its default
423             configuration. But if you have developed your code against a custom
424             Perl::Critic configuration, you will want to configure this test to do the
425             same.
426              
427             Any C<%ARGS> given to C<set_critic_args> will be passed directly into the
428             L<Perl::Critic> constructor. So if you have developed your code using a
429             custom F<.perlcriticrc> file, you can direct Test::Perl::Critic::Progressive
430             to use a custom file too.
431              
432             use Test::Perl::Critic::Progressive ( ':all' );
433              
434             set_critic_args(-profile => 't/perlcriticrc);
435             progressive_critic_ok();
436              
437             Now place a copy of your own F<.perlcriticrc> file in the distribution as
438             F<t/perlcriticrc>. Now, C<progressive_critic_ok> will use this same
439             Perl::Critic configuration. See the L<Perl::Critic> documentation for details
440             on the F<.perlcriticrc> file format.
441              
442             Any argument that is supported by the L<Perl::Critic> constructor can be
443             passed through this interface. For example, you can also set the minimum
444             severity level, or include & exclude specific policies like this:
445              
446             use Test::Perl::Critic::Progressive ( ':all' );
447              
448             set_critic_args( -severity => 2, -exclude => ['MixedCaseVars'] );
449             progressive_critic_ok();
450              
451             See the L<Perl::Critic> documentation for complete details on its options and
452             arguments.
453              
454             =back
455              
456              
457             =head1 NOTES
458              
459             The test is evaluated in two ways. First, the number of violations for each
460             Policy must be B<less than or equal to> the number of the violations found
461             during the last passing test, minus the step size B<for that Policy>. Second,
462             the total number of violations must be B<less than or equal> the total number
463             of violations found during the last passing test, minus the B<total> step
464             size. This prevents you from simply substituting one kind of violation for
465             another.
466              
467             You can use the total step size and the per-policy step size at the same time.
468             For example, you can set the total step size to 5, and set the per-policy step
469             size for the C<TestingAndDebugging::RequireStrictures> Policy to 3. In which
470             case, you'll have to remove 5 violations between each test, but 3 of them must
471             be violations of C<TestingAndDebugging::RequireStrictures>.
472              
473             Over time, you'll probably add new Policies to your L<Perl::Critic> setup.
474             When Test::Perl::Critic::Progressive uses a Policy for the first time, any
475             newly discovered violations of that Policy will not be considered in the test.
476             However, they will be considered in subsequent tests.
477              
478             If you are building a CPAN distribution, you'll want to add
479             F<^t/.perlcritic-history$> to the F<MANIFEST.SKIP> file. And if you are using
480             a revision control system like CVS or Subversion, you'll probably want to
481             configure it to ignore the F<t/.perlcritic-history> file as well.
482              
483              
484             =head1 BUGS
485              
486             If you find any bugs, please submit them to
487             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Test-Perl-Critic-Progressive>.
488             Thanks.
489              
490              
491             =head1 SEE ALSO
492              
493             L<criticism>
494              
495             L<Perl::Critic>
496              
497             L<Test::Perl::Critic>
498              
499             L<http://www.perlcritic.com>
500              
501              
502             =head1 AUTHOR
503              
504             Jeffrey Ryan Thalhammer <thaljef@cpan.org>
505              
506              
507             =head1 COPYRIGHT
508              
509             Copyright (c) 2007-2008 Jeffrey Ryan Thalhammer. All rights reserved.
510              
511             This program is free software; you can redistribute it and/or modify
512             it under the same terms as Perl itself. The full text of this license
513             can be found in the LICENSE file included with this module.
514              
515             =cut
516              
517             ##############################################################################
518             # Local Variables:
519             # mode: cperl
520             # cperl-indent-level: 4
521             # fill-column: 78
522             # indent-tabs-mode: nil
523             # c-indentation-style: bsd
524             # End:
525             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab :