File Coverage

blib/lib/Test/Perl/Critic/Policy.pm
Criterion Covered Total %
statement 35 142 24.6
branch 0 42 0.0
condition 0 6 0.0
subroutine 12 26 46.1
pod 1 1 100.0
total 48 217 22.1


line stmt bran cond sub pod time code
1             package Test::Perl::Critic::Policy;
2              
3 1     1   80439 use 5.010001;
  1         13  
4              
5 1     1   6 use strict;
  1         2  
  1         21  
6 1     1   5 use warnings;
  1         2  
  1         41  
7              
8 1     1   6 use Carp qw< croak confess >;
  1         2  
  1         70  
9 1     1   503 use English qw< -no_match_vars >;
  1         4549  
  1         15  
10 1     1   1232 use List::SomeUtils qw( all none );
  1         14272  
  1         71  
11 1     1   535 use Readonly;
  1         4073  
  1         66  
12              
13 1     1   7 use Test::Builder qw<>;
  1         2  
  1         16  
14 1     1   7 use Test::More;
  1         3  
  1         6  
15              
16 1     1   749 use Perl::Critic::Violation;
  1         3  
  1         56  
17 1         20 use Perl::Critic::TestUtils qw<
18             pcritique_with_violations fcritique_with_violations subtests_in_tree
19 1     1   529 >;
  1         6  
20              
21             #-----------------------------------------------------------------------------
22              
23             our $VERSION = '1.150';
24              
25             #-----------------------------------------------------------------------------
26              
27 1     1   155 use Exporter 'import';
  1         2  
  1         1687  
28              
29             Readonly::Array our @EXPORT_OK => qw< all_policies_ok >;
30             Readonly::Hash our %EXPORT_TAGS => (all => \@EXPORT_OK);
31              
32             #-----------------------------------------------------------------------------
33              
34             Perl::Critic::Violation::set_format( "%m at line %l, column %c. (%r)\n" );
35             Perl::Critic::TestUtils::block_perlcriticrc();
36              
37             #-----------------------------------------------------------------------------
38              
39             my $TEST = Test::Builder->new();
40              
41             #-----------------------------------------------------------------------------
42              
43             sub all_policies_ok {
44 0     0 1   my (%args) = @_;
45 0           my $wanted_policies = $args{-policies};
46 0   0       my $test_dir = $args{'-test-directory'} || 't';
47              
48 0           my $subtests_with_extras = subtests_in_tree( $test_dir, 'include extras' );
49              
50 0 0         if ($wanted_policies) {
51 0           _validate_wanted_policy_names($wanted_policies, $subtests_with_extras);
52 0           _filter_unwanted_subtests($wanted_policies, $subtests_with_extras);
53             }
54              
55 0           $TEST->plan( tests => _compute_test_count($subtests_with_extras) );
56 0           my $policies_to_test = join q{, }, keys %{$subtests_with_extras};
  0            
57 0           $TEST->note("Running tests for policies: $policies_to_test");
58              
59 0           for my $policy ( sort keys %{$subtests_with_extras} ) {
  0            
60              
61 0           my ($full_policy_name, $method) = ("Perl::Critic::Policy::$policy", 'violates');
62 0           my $can_ok_label = qq{Class '$full_policy_name' has method '$method'};
63 0           $TEST->ok( $full_policy_name->can($method), $can_ok_label );
64              
65 0           for my $subtest ( @{ $subtests_with_extras->{$policy}{subtests} } ) {
  0            
66 0           my $todo = $subtest->{TODO};
67 0 0         if ($todo) { $TEST->todo_start( $todo ); }
  0            
68              
69 0           my ($error, @violations) = _run_subtest($policy, $subtest);
70 0           my ($ok, @diag)= _evaluate_test_results($subtest, $error, \@violations);
71 0           $TEST->ok( $ok, _create_test_name($policy, $subtest) );
72              
73 0 0         if (@diag) { $TEST->diag(@diag); }
  0            
74 0 0         if ($todo) { $TEST->todo_end(); }
  0            
75             }
76             }
77              
78 0           return;
79             }
80              
81             #-----------------------------------------------------------------------------
82              
83             sub _validate_wanted_policy_names {
84 0     0     my ($wanted_policies, $subtests_with_extras) = @_;
85 0 0         return 1 if not $wanted_policies;
86 0           my @all_testable_policies = keys %{ $subtests_with_extras };
  0            
87 0           my @wanted_policies = @{ $wanted_policies };
  0            
88              
89              
90 0     0     my @invalid = grep {my $p = $_; none { $_ =~ $p } @all_testable_policies} @wanted_policies;
  0            
  0            
  0            
91 0 0         croak( q{No tests found for policies matching: } . join q{, }, @invalid ) if @invalid;
92 0           return 1;
93             }
94              
95             #-----------------------------------------------------------------------------
96              
97             sub _filter_unwanted_subtests {
98 0     0     my ($wanted_policies, $subtests_with_extras) = @_;
99 0 0         return 1 if not $wanted_policies;
100 0           my @all_testable_policies = keys %{ $subtests_with_extras };
  0            
101 0           my @wanted_policies = @{ $wanted_policies };
  0            
102              
103 0           for my $p (@all_testable_policies) {
104 0 0   0     if (none {$p =~ m/$_/xism} @wanted_policies) {
  0            
105 0           delete $subtests_with_extras->{$p}; # side-effects!
106             }
107             }
108 0           return 1;
109             }
110              
111             #-----------------------------------------------------------------------------
112              
113             sub _run_subtest {
114 0     0     my ($policy, $subtest) = @_;
115              
116 0           my @violations;
117             my $error;
118 0 0         if ( $subtest->{filename} ) {
119             eval {
120             @violations =
121             fcritique_with_violations(
122             $policy,
123             \$subtest->{code},
124             $subtest->{filename},
125             $subtest->{parms},
126 0           );
127 0           1;
128 0 0         } or do {
129 0   0       $error = $EVAL_ERROR || 'An unknown problem occurred.';
130             };
131             }
132             else {
133             eval {
134             @violations =
135             pcritique_with_violations(
136             $policy,
137             \$subtest->{code},
138             $subtest->{parms},
139 0           );
140 0           1;
141 0 0         } or do {
142 0   0       $error = $EVAL_ERROR || 'An unknown problem occurred.';
143             };
144             }
145              
146 0           return ($error, @violations);
147             }
148              
149             #-----------------------------------------------------------------------------
150              
151             sub _evaluate_test_results {
152 0     0     my ($subtest, $error, $violations) = @_;
153              
154 0 0         if ($subtest->{error}) {
    0          
155 0           return _evaluate_error_case($subtest, $error);
156             }
157             elsif ($error) {
158 0           confess $error;
159             }
160             else {
161 0           return _evaluate_violation_case($subtest, $violations);
162             }
163             }
164              
165             #-----------------------------------------------------------------------------
166              
167             sub _evaluate_violation_case {
168 0     0     my ($subtest, $violations) = @_;
169 0           my ($ok, @diagnostics);
170              
171 0           my @violations = @{$violations};
  0            
172 0           my $have = @violations;
173 0           my $want = _compute_wanted_violation_count($subtest);
174 0 0         if ( not $ok = $have == $want ) {
175 0           my $msg = qq(Expected $want violations, got $have. );
176 0 0         if (@violations) { $msg .= q(Found violations follow...); }
  0            
177 0           push @diagnostics, $msg . "\n";
178 0           push @diagnostics, map { qq(Found violation: $_) } @violations;
  0            
179             }
180              
181 0           return ($ok, @diagnostics);
182             }
183              
184             #-----------------------------------------------------------------------------
185              
186             sub _evaluate_error_case {
187 0     0     my ($subtest, $error) = @_;
188 0           my ($ok, @diagnostics);
189              
190 0 0         if ( 'Regexp' eq ref $subtest->{error} ) {
191             $ok = $error =~ $subtest->{error}
192 0 0         or push @diagnostics, qq(Error message '$error' doesn't match $subtest->{error}.);
193             }
194             else {
195             $ok = $subtest->{error}
196 0 0         or push @diagnostics, q(Didn't get an error message when we expected one.);
197             }
198              
199 0           return ($ok, @diagnostics);
200             }
201              
202             #-----------------------------------------------------------------------------
203              
204             sub _compute_test_count {
205 0     0     my ($subtests_with_extras) = @_;
206              
207             # one can_ok() for each policy
208 0           my $npolicies = scalar keys %{ $subtests_with_extras };
  0            
209              
210 0           my $nsubtests = 0;
211 0           for my $subtest_with_extras ( values %{$subtests_with_extras} ) {
  0            
212             # one [pf]critique() test per subtest
213 0           $nsubtests += @{ $subtest_with_extras->{subtests} };
  0            
214             }
215              
216 0           return $nsubtests + $npolicies;
217             }
218              
219             #-----------------------------------------------------------------------------
220              
221             sub _compute_wanted_violation_count {
222 0     0     my ($subtest) = @_;
223              
224             # If any optional modules are NOT available, then there should be no violations.
225 0 0         return 0 if not _all_optional_modules_are_available($subtest);
226 0           return $subtest->{failures};
227             }
228              
229             #-----------------------------------------------------------------------------
230              
231             sub _all_optional_modules_are_available {
232 0     0     my ($subtest) = @_;
233 0 0         my $optional_modules = $subtest->{optional_modules} or return 1;
234 0 0   0     return all {eval "require $_;" or 0;} split m/,\s*/xms, $optional_modules;
  0            
235             }
236              
237             #-----------------------------------------------------------------------------
238              
239             sub _create_test_name {
240 0     0     my ($policy, $subtest) = @_;
241 0           return join ' - ', $policy, "line $subtest->{lineno}", $subtest->{name};
242             }
243              
244             #-----------------------------------------------------------------------------
245             1;
246              
247             __END__
248              
249             #-----------------------------------------------------------------------------
250              
251             =pod
252              
253             =for stopwords subtest subtests RCS
254              
255             =head1 NAME
256              
257             Test::Perl::Critic::Policy - A framework for testing your custom Policies
258              
259             =head1 SYNOPSIS
260              
261             use Test::Perl::Critic::Policy qw< all_policies_ok >;
262              
263             # Assuming .run files are inside 't' directory...
264             all_policies_ok()
265              
266             # Or if your .run files are in a different directory...
267             all_policies_ok( '-test-directory' => 'run' );
268              
269             # And if you just want to run tests for some polices...
270             all_policies_ok( -policies => ['Some::Policy', 'Another::Policy'] );
271              
272             # If you want your test program to accept short Policy names as
273             # command-line parameters...
274             #
275             # You can then test a single policy by running
276             # "perl -Ilib t/policy-test.t My::Policy".
277             my %args = @ARGV ? ( -policies => [ @ARGV ] ) : ();
278             all_policies_ok(%args);
279              
280              
281             =head1 DESCRIPTION
282              
283             This module provides a framework for function-testing your custom
284             L<Perl::Critic::Policy|Perl::Critic::Policy> modules. Policy testing usually
285             involves feeding it a string of Perl code and checking its behavior. In the
286             old days, those strings of Perl code were mixed directly in the test script.
287             That sucked.
288              
289             =head1 IMPORTABLE SUBROUTINES
290              
291             =over
292              
293             =item all_policies_ok('-test-directory' => $path, -policies => \@policy_names)
294              
295             Loads all the F<*.run> files beneath the C<-test-directory> and runs the
296             tests. If C<-test-directory> is not specified, it defaults to F<t/>.
297             C<-policies> is an optional reference to an array of shortened Policy names.
298             If C<-policies> specified, only the tests for Policies that match one of the
299             C<m/$POLICY_NAME/imx> will be run.
300              
301              
302             =back
303              
304              
305             =head1 CREATING THE *.run FILES
306              
307             Testing a policy follows a very simple pattern:
308              
309             * Policy name
310             * Subtest name
311             * Optional parameters
312             * Number of failures expected
313             * Optional exception expected
314             * Optional filename for code
315              
316             Each of the subtests for a policy is collected in a single F<.run>
317             file, with test properties as comments in front of each code block
318             that describes how we expect Perl::Critic to react to the code. For
319             example, say you have a policy called Variables::ProhibitVowels:
320              
321             (In file t/Variables/ProhibitVowels.run)
322              
323             ## name Basics
324             ## failures 1
325             ## cut
326              
327             my $vrbl_nm = 'foo'; # Good, vowel-free name
328             my $wango = 12; # Bad, pronouncable name
329              
330              
331             ## name Sometimes Y
332             ## failures 1
333             ## cut
334              
335             my $yllw = 0; # "y" not a vowel here
336             my $rhythm = 12; # But here it is
337              
338             These are called "subtests", and two are shown above. The beauty of
339             incorporating multiple subtests in a file is that the F<.run> is
340             itself a (mostly) valid Perl file, and not hidden in a HEREDOC, so
341             your editor's color-coding still works, and it is much easier to work
342             with the code and the POD.
343              
344             If you need to pass any configuration parameters for your subtest, do
345             so like this:
346              
347             ## parms { allow_y => '0' }
348              
349             Note that all the values in this hash must be strings because that's
350             what Perl::Critic will hand you from a F<.perlcriticrc>.
351              
352             If it's a TODO subtest (probably because of some weird corner of PPI
353             that we exercised that Adam is getting around to fixing, right?), then
354             make a C<##TODO> entry.
355              
356             ## TODO Should pass when PPI 1.xxx comes out
357              
358             If the code is expected to trigger an exception in the policy,
359             indicate that like so:
360              
361             ## error 1
362              
363             If you want to test the error message, mark it with C</.../> to
364             indicate a C<like()> test:
365              
366             ## error /Can't load Foo::Bar/
367              
368             If the policy you are testing cares about the filename of the code,
369             you can indicate that C<fcritique> should be used like so (see
370             C<fcritique> for more details):
371              
372             ## filename lib/Foo/Bar.pm
373              
374             The value of C<parms> will get C<eval>ed and passed to C<pcritique()>,
375             so be careful.
376              
377             In general, a subtest document runs from the C<## cut> that starts it to
378             either the next C<## name> or the end of the file. In very rare circumstances
379             you may need to end the test document earlier. A second C<## cut> will do
380             this. The only known need for this is in
381             F<t/Miscellanea/RequireRcsKeywords.run>, where it is used to prevent the RCS
382             keywords in the file footer from producing false positives or negatives in the
383             last test.
384              
385             Note that nowhere within the F<.run> file itself do you specify the
386             policy that you're testing. That's implicit within the filename.
387              
388              
389             =head1 BUGS AND CAVEATS AND TODO ITEMS
390              
391             Add policy_ok() method for running subtests in just a single TODO file.
392              
393             Can users mark this entire test as TODO or SKIP, using the normal mechanisms?
394              
395             Allow us to specify the nature of the failures, and which one. If there are
396             15 lines of code, and six of them fail, how do we know they're the right six?
397              
398             Consolidate code from L<Perl::Critic::TestUtils|Perl::Critic::TestUtils> and possibly deprecate some
399             functions there.
400              
401             Write unit tests for this module.
402              
403             Test that we have a t/*/*.run for each lib/*/*.pm
404              
405             =head1 AUTHOR
406              
407             Andy Lester, Jeffrey Ryan Thalhammer <thaljef@cpan.org>
408              
409             =head1 COPYRIGHT
410              
411             Copyright (c) 2009-2023 Andy Lester
412              
413             This program is free software; you can redistribute it and/or modify
414             it under the same terms as Perl itself. The full text of this license
415             can be found in the LICENSE file included with this module.
416              
417             =cut
418              
419             ##############################################################################
420             # Local Variables:
421             # mode: cperl
422             # cperl-indent-level: 4
423             # fill-column: 78
424             # indent-tabs-mode: nil
425             # c-indentation-style: bsd
426             # End:
427             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :