File Coverage

blib/lib/Test2/Tools/PerlCritic.pm
Criterion Covered Total %
statement 82 82 100.0
branch 20 20 100.0
condition 7 9 77.7
subroutine 14 14 100.0
pod 1 1 100.0
total 124 126 98.4


line stmt bran cond sub pod time code
1             package Test2::Tools::PerlCritic;
2              
3 1     1   221869 use strict;
  1         6  
  1         29  
4 1     1   5 use warnings;
  1         2  
  1         26  
5 1     1   4 use base qw( Exporter );
  1         2  
  1         104  
6 1     1   24 use 5.020;
  1         4  
7 1     1   514 use experimental qw( postderef );
  1         3481  
  1         5  
8 1     1   198 use Carp qw( croak );
  1         2  
  1         47  
9 1     1   617 use Ref::Util qw( is_ref is_plain_arrayref is_plain_hashref );
  1         1766  
  1         106  
10 1     1   8 use Test2::API qw( context );
  1         2  
  1         46  
11 1     1   592 use Perl::Critic ();
  1         1066898  
  1         32  
12 1     1   13 use Perl::Critic::Utils ();
  1         2  
  1         19  
13 1     1   956 use Path::Tiny ();
  1         12486  
  1         1147  
14              
15             our @EXPORT = qw( perl_critic_ok );
16              
17             # ABSTRACT: Testing tools to enforce Perl::Critic policies
18             our $VERSION = '0.04'; # VERSION
19              
20              
21             sub _args
22             {
23 20     20   1338206 my $files = shift;
24              
25 20 100       80 if(defined $files)
26             {
27 19 100       75 if(is_ref $files)
28             {
29 3 100       16 unless(is_plain_arrayref $files)
30             {
31 1         141 croak "file argument muse be a file/directory name or and array of reference of file/directory names";
32             }
33             }
34             else
35             {
36 16         48 $files = [$files];
37             }
38              
39 18         59 @$files = map { "$_" } @$files;
  19         96  
40              
41             }
42             else
43             {
44 1         297 croak "no files provided";
45             }
46              
47 18         58 my @opts;
48             my $critic;
49              
50 18 100 100     122 if(defined $_[0] && is_ref $_[0]) {
51 7 100       41 if(is_plain_arrayref $_[0])
    100          
    100          
52             {
53 1         3 @opts = @{ shift() };
  1         4  
54             }
55             elsif(is_plain_hashref $_[0])
56             {
57 1         3 @opts = %{ shift() };
  1         4  
58             }
59 5         44 elsif(eval { $_[0]->isa('Perl::Critic') })
60             {
61 4         13 $critic = shift;
62             }
63             else
64             {
65 1         208 croak "options must be either an array or hash reference";
66             }
67             }
68              
69 17   66     152 $critic ||= Perl::Critic->new(@opts);
70              
71 17         9377311 my $test_name = shift;
72              
73 17   66     171 $test_name //= "no Perl::Critic policy violations for @$files";
74              
75 33         8030 @$files = sort map { Path::Tiny->new($_)->stringify } map {
76 17 100       77 -f $_
  18 100       1348  
77             ? $_
78             : -d $_
79             ? Perl::Critic::Utils::all_perl_files("$_")
80             : croak "not a file or directory: $_";
81             } @$files;
82              
83 16         1127 ($files, $critic, $test_name);
84             }
85              
86             sub _chomp
87             {
88 1     1   3404 my $str = shift;
89 1         35 chomp $str;
90 1         3 $str;
91             }
92              
93              
94             sub perl_critic_ok
95             {
96 3     3 1 713696 my($files, $critic, $test_name) = _args(@_);
97              
98 3         10 my %violations;
99              
100 3         13 foreach my $file (@$files)
101             {
102 5         35 foreach my $violation ($critic->critique($file))
103             {
104 3         18610 push $violations{$violation->policy}->@*, $violation;
105             }
106             }
107              
108 3         34 my $ctx = context();
109              
110 3 100       579 if(%violations)
111             {
112 1         2 my @diag;
113              
114 1         6 foreach my $policy (sort keys %violations)
115             {
116 1         4 my($first) = $violations{$policy}->@*;
117 1         4 push @diag, '';
118 1         5 push @diag, sprintf("%s [sev %s]", $policy, $first->severity);
119 1         16 push @diag, $first->description;
120 1         10 push @diag, _chomp($first->diagnostics);
121 1         3 push @diag, '';
122 1         4 foreach my $violation ($violations{$policy}->@*)
123             {
124 3         125 push @diag, sprintf("found at %s line %s column %s",
125             Path::Tiny->new($violation->logical_filename)->stringify,
126             $violation->logical_line_number,
127             $violation->visual_column_number,
128             );
129             }
130             }
131              
132 1         51 $ctx->fail_and_release($test_name, @diag);
133 1         584 return 0;
134             }
135             else
136             {
137 2         17 $ctx->pass_and_release($test_name);
138 2         360 return 1;
139             }
140             }
141              
142             1;
143              
144             __END__
145              
146             =pod
147              
148             =encoding UTF-8
149              
150             =head1 NAME
151              
152             Test2::Tools::PerlCritic - Testing tools to enforce Perl::Critic policies
153              
154             =head1 VERSION
155              
156             version 0.04
157              
158             =head1 SYNOPSIS
159              
160             use Test2::V0;
161             use Test2::Tools::PerlCritic;
162            
163             perl_critic_ok ['lib','t'], 'test library 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 $path, \@options, $test_name;
183             perl_critic_ok \@path, \@options, $test_name;
184             perl_critic_ok $path, \%options, $test_name;
185             perl_critic_ok \@path, \%options, $test_name;
186             perl_critic_ok $path, $critic, $test_name;
187             perl_critic_ok \@path, $critic, $test_name;
188             perl_critic_ok $path, $test_name;
189             perl_critic_ok \@path, $test_name;
190             perl_critic_ok $path;
191             perl_critic_ok \@path;
192              
193             Run L<Perl::Critic> on the given files or directories. The first argument
194             (C<$path> or C<\@path>) can be either the path to a file or directory, or
195             a array reference to a list of paths to files and directories. If C<\@options> or
196             C<\%options> are provided, then they will be passed into the
197             L<Perl::Critic> constructor. If C<$critic> (an instance of L<Perl::Critic>)
198             is provided, then that L<Perl::Critic> instance will be used instead
199             of creating one internally. Finally the C<$test_name> may be provided
200             if you do not like the default test name.
201              
202             Only a single test is run regardless of how many files are processed.
203             this is so that the policy violations can be grouped by policy class
204             across multiple files.
205              
206             As a convenience, if the test passes then a true value is returned.
207             Otherwise a false will be returned.
208              
209             C<done_testing> or the equivalent is NOT called by this function.
210             You are responsible for calling that yourself.
211              
212             Since we do not automatically call C<done_testing>, you can call C<perl_critic_ok>
213             multiple times, but keep in mind that the policy violations will only be grouped
214             in each individual call, so it is probably better to provide a list of paths,
215             rather than make multiple calls.
216              
217             =head1 CAVEATS
218              
219             L<Test::Perl::Critic> has been around longer, and probably does at least some things smarter.
220             The fact that this module groups policy violations for all files by class means that it has
221             to store more diagnostics in memory before sending them out I<en masse>, where as
222             L<Test::Perl::Critic> sends violations for each file as it processes them. L<Test::Perl::Critic>
223             also comes with some code to optionally do processing in parallel. Some of these issues may
224             or may not be addressed in future versions of this module.
225              
226             Since this module formats it's output the C<-verbose> option is ignored at the C<set_format>
227             value is ignored.
228              
229             =head1 SEE ALSO
230              
231             =over 4
232              
233             =item L<Test::Perl::Critic>
234              
235             =item L<Perl::Critic>
236              
237             =back
238              
239             =head1 AUTHOR
240              
241             Graham Ollis <plicease@cpan.org>
242              
243             =head1 COPYRIGHT AND LICENSE
244              
245             This software is copyright (c) 2019-2021 by Graham Ollis.
246              
247             This is free software; you can redistribute it and/or modify it under
248             the same terms as the Perl 5 programming language system itself.
249              
250             =cut