File Coverage

blib/lib/Test2/Tools/PerlCritic.pm
Criterion Covered Total %
statement 78 78 100.0
branch 20 20 100.0
condition 7 9 77.7
subroutine 13 13 100.0
pod 1 1 100.0
total 119 121 98.3


line stmt bran cond sub pod time code
1             package Test2::Tools::PerlCritic;
2              
3 1     1   198611 use strict;
  1         7  
  1         24  
4 1     1   4 use warnings;
  1         2  
  1         22  
5 1     1   3 use base qw( Exporter );
  1         2  
  1         68  
6 1     1   24 use 5.020;
  1         3  
7 1     1   432 use experimental qw( postderef );
  1         2982  
  1         4  
8 1     1   192 use Carp qw( croak );
  1         2  
  1         54  
9 1     1   445 use Ref::Util qw( is_ref is_plain_arrayref is_plain_hashref );
  1         1405  
  1         60  
10 1     1   6 use Test2::API qw( context );
  1         1  
  1         57  
11 1     1   518 use Perl::Critic ();
  1         863217  
  1         25  
12 1     1   8 use Perl::Critic::Utils ();
  1         3  
  1         778  
13              
14             our @EXPORT = qw( perl_critic_ok );
15              
16             # ABSTRACT: Testing tools to enforce Perl::Critic policies
17             our $VERSION = '0.02'; # VERSION
18              
19              
20             sub _args
21             {
22 20     20   1116146 my $files = shift;
23              
24 20 100       63 if(defined $files)
25             {
26 19 100       55 if(is_ref $files)
27             {
28 3 100       15 unless(is_plain_arrayref $files)
29             {
30 1         112 croak "file argument muse be a file/directory name or and array of reference of file/directory names";
31             }
32             }
33             else
34             {
35 16         64 $files = [$files];
36             }
37              
38 18         65 @$files = map { "$_" } @$files;
  19         81  
39              
40             }
41             else
42             {
43 1         189 croak "no files provided";
44             }
45              
46 18         47 my @opts;
47             my $critic;
48              
49 18 100 100     111 if(defined $_[0] && is_ref $_[0]) {
50 7 100       47 if(is_plain_arrayref $_[0])
    100          
    100          
51             {
52 1         3 @opts = @{ shift() };
  1         3  
53             }
54             elsif(is_plain_hashref $_[0])
55             {
56 1         3 @opts = %{ shift() };
  1         4  
57             }
58 5         39 elsif(eval { $_[0]->isa('Perl::Critic') })
59             {
60 4         10 $critic = shift;
61             }
62             else
63             {
64 1         198 croak "options must be either an array or hash reference";
65             }
66             }
67              
68 17   66     121 $critic ||= Perl::Critic->new(@opts);
69              
70 17         7571991 my $test_name = shift;
71              
72 17   66     153 $test_name //= "no Perl::Critic policy violations for @$files";
73              
74             @$files = sort map {
75 17 100       62 -f $_
  18 100       1218  
76             ? $_
77             : -d $_
78             ? Perl::Critic::Utils::all_perl_files("$_")
79             : croak "not a file or directory: $_";
80             } @$files;
81              
82 16         7164 ($files, $critic, $test_name);
83             }
84              
85             sub _chomp
86             {
87 1     1   3090 my $str = shift;
88 1         5 chomp $str;
89 1         3 $str;
90             }
91              
92              
93             sub perl_critic_ok
94             {
95 3     3 1 580181 my($files, $critic, $test_name) = _args(@_);
96              
97 3         10 my %violations;
98              
99 3         18 foreach my $file (@$files)
100             {
101 5         34 foreach my $violation ($critic->critique($file))
102             {
103 3         15859 push $violations{$violation->policy}->@*, $violation;
104             }
105             }
106              
107 3         31 my $ctx = context();
108              
109 3 100       449 if(%violations)
110             {
111 1         2 my @diag;
112              
113 1         6 foreach my $policy (sort keys %violations)
114             {
115 1         4 my($first) = $violations{$policy}->@*;
116 1         3 push @diag, '';
117 1         5 push @diag, sprintf("%s [sev %s]", $policy, $first->severity);
118 1         16 push @diag, $first->description;
119 1         9 push @diag, _chomp($first->diagnostics);
120 1         4 push @diag, '';
121 1         3 foreach my $violation ($violations{$policy}->@*)
122             {
123 3         50 push @diag, sprintf("found at %s line %s column %s",
124             $violation->logical_filename,
125             $violation->logical_line_number,
126             $violation->visual_column_number,
127             );
128             }
129             }
130              
131 1         22 $ctx->fail_and_release($test_name, @diag);
132 1         548 return 0;
133             }
134             else
135             {
136 2         14 $ctx->pass_and_release($test_name);
137 2         315 return 1;
138             }
139             }
140              
141             1;
142              
143             __END__
144              
145             =pod
146              
147             =encoding UTF-8
148              
149             =head1 NAME
150              
151             Test2::Tools::PerlCritic - Testing tools to enforce Perl::Critic policies
152              
153             =head1 VERSION
154              
155             version 0.02
156              
157             =head1 SYNOPSIS
158              
159             use Test2::V0;
160             use Test2::Tools::PerlCritic;
161            
162             perl_critic_ok 'lib', 'test library files';
163             perl_critic_ok 't', 'test test files';
164            
165             done_testing;
166              
167             =head1 DESCRIPTION
168              
169             Test for L<Perl::Critic> violations using L<Test2>. Although this testing tool
170             uses the L<Test2> API instead of the older L<Test::Builder> API, the primary
171             motivation is to provide output in a more useful form. That is policy violations
172             are grouped by policy class, and the policy class name is clearly displayed as
173             a diagnostic. The author finds the former more useful because he tends to address
174             one type of violation at a time. The author finds the latter more useful because
175             he tends to want to lookup or adjust the configuration of the policy as he is
176             addressing violations.
177              
178             =head1 FUNCTIONS
179              
180             =head2 perl_critic_ok
181              
182             perl_critic_ok $file_or_directory, \@options, $test_name;
183             perl_critic_ok $file_or_directory, \%options, $test_name;
184             perl_critic_ok $file_or_directory, $critic, $test_name;
185             perl_critic_ok $file_or_directory, $test_name;
186             perl_critic_ok $file_or_directory;
187              
188             Run L<Perl::Critic> on the given file or directory. If C<\@options> or
189             C<\%options> are provided, then they will be passed into the
190             L<Perl::Critic> constructor. If C<$critic> (an instance of L<Perl::Critic>)
191             is provided, then that L<Perl::Critic> instance will be used instead
192             of creating one internally. Finally the C<$test_name> may be provided
193             if you do not like the default test name.
194              
195             Only a single test is run regardless of how many files are processed.
196             this is so that the policy violations can be grouped by policy class
197             across multiple files.
198              
199             As a convenience, if the test passes then a true value is returned.
200             Otherwise a false will be returned.
201              
202             C<done_testing> or the equivalent is NOT called by this function.
203             You are responsible for calling that yourself.
204              
205             =head1 CAVEATS
206              
207             L<Test::Perl::Critic> has been around longer, and probably does at least some things smarter.
208             The fact that this module groups policy violations for all files by class means that it has
209             to store more diagnostics in memory before sending them out I<en masse>, where as
210             L<Test::Perl::Critic> sends violations for each file as it processes them. L<Test::Perl::Critic>
211             also comes with some code to optionally do processing in parallel. Some of these issues may
212             or may not be addressed in future versions of this module.
213              
214             Since this module formats it's output the C<-verbose> option is ignored at the C<set_format>
215             value is ignored.
216              
217             =head1 SEE ALSO
218              
219             =over 4
220              
221             =item L<Test::Perl::Critic>
222              
223             =item L<Perl::Critic>
224              
225             =back
226              
227             =head1 AUTHOR
228              
229             Graham Ollis <plicease@cpan.org>
230              
231             =head1 COPYRIGHT AND LICENSE
232              
233             This software is copyright (c) 2019 by Graham Ollis.
234              
235             This is free software; you can redistribute it and/or modify it under
236             the same terms as the Perl 5 programming language system itself.
237              
238             =cut