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