File Coverage

blib/lib/Test2/Plugin/TodoFailOnSuccess.pm
Criterion Covered Total %
statement 31 31 100.0
branch 8 12 66.6
condition 4 6 66.6
subroutine 7 7 100.0
pod 0 2 0.0
total 50 58 86.2


line stmt bran cond sub pod time code
1             package Test2::Plugin::TodoFailOnSuccess;
2              
3 2     2   5188332 use strict;
  2         9  
  2         65  
4 2     2   12 use warnings;
  2         6  
  2         162  
5              
6             # ABSTRACT: Report failure if a TODO test unexpectedly passes
7             our $VERSION = '0.0.1'; # VERSION
8              
9             our $AUTHORITY = 'cpan:GSG';
10              
11             #pod =encoding utf8
12             #pod
13             #pod =head1 SYNOPSIS
14             #pod
15             #pod package My::Tests;
16             #pod
17             #pod use Test2::V0;
18             #pod
19             #pod use Test2::Plugin::TodoFailOnSuccess; # report unexpected TODO success
20             #pod
21             #pod use Test2::Tools::Basic; # for "todo" sub
22             #pod use Test2::Todo; # for "todo" object
23             #pod
24             #pod sub test_something
25             #pod {
26             #pod # Lexical scope TODO:
27             #pod #
28             #pod {
29             #pod my $todo = todo 'Not expected to pass';
30             #pod is $value, $expected_value, "Got expected value";
31             #pod }
32             #pod
33             #pod # Coderef TODO:
34             #pod #
35             #pod todo 'Not expected to pass either' => sub {
36             #pod is $value, $expected_value, "Got expected value";
37             #pod };
38             #pod
39             #pod # Object-oriented TODO:
40             #pod #
41             #pod my $todo = Test2::Todo->new( reason => 'Still not expected to pass' );
42             #pod is $value, $expected_value, "Got expected value";
43             #pod $todo->end;
44             #pod }
45             #pod
46             #pod =head1 DESCRIPTION
47             #pod
48             #pod Wrapping a test with TODO is a conventient way to avoid being tripped
49             #pod up by test failures until you have a chance to get the code working.
50             #pod It normally won't hurt to leave the TODO in place after the tests
51             #pod start passing, but if you forget to remove the TODO at that point,
52             #pod a subsequent code change could start causing new test failures which
53             #pod would then go unreported and possibly unnoticed.
54             #pod
55             #pod This module provides a mechanism to trigger explicit test failures
56             #pod when TODO tests unexpectedly pass, so that you have an opportunity
57             #pod to remove the TODO.
58             #pod
59             #pod If a TODO test passes, a failure will be reported with a message
60             #pod containing the test description, equivalent to doing:
61             #pod
62             #pod fail "TODO passed unexpectedly: $test_description";
63             #pod
64             #pod which might appear in your TAP output along with the TODO reason as
65             #pod something like:
66             #pod
67             #pod not ok 3 - TODO passed unexpectedly: Got expected value # TODO Not expected to pass
68             #pod
69             #pod Note that due to the additional C<fail> being reported, you may
70             #pod see messages about your planned number of tests being exceeded,
71             #pod for example:
72             #pod
73             #pod # Did not follow plan: expected 5, ran 6.
74             #pod
75             #pod There are no options or arguments, just C<use Test2::Plugin::TodoFailOnSuccess>
76             #pod in your test file.
77             #pod
78             #pod =cut
79              
80 2         836 use Test2::API qw(
81             test2_add_callback_context_init
82             test2_add_callback_context_release
83 2     2   12 );
  2         5  
84              
85             my $PLUGIN_LOADED = 0;
86              
87             sub import
88             {
89 2 50   2   28     return if $PLUGIN_LOADED++;
90              
91 2         15     test2_add_callback_context_init ( \&on_context_init );
92 2         60     test2_add_callback_context_release( \&on_context_release );
93             }
94              
95             sub on_context_init
96             {
97 11     11 0 5007428     my ($ctx) = @_;
98              
99             # Set up a listener on the hub to watch events going by,
100             # looking for the ones that indicate a TODO test which passed:
101             #
102                 $ctx->{_TodoFailOnSuccess_hub_listener} = $ctx->hub->listen(
103                     sub {
104 12     12   10802             my ($hub, $event, $number) = @_;
105              
106 12         62             my $facet_data = $event->facet_data;
107              
108             # Events inside a TODO will have amnesty (although will
109             # need to verify the type of amnesty later):
110             #
111 12         932             my $amnesty_list = $facet_data->{amnesty};
112 12 100 66     112             return unless $amnesty_list && @$amnesty_list;
113              
114             # Only interested if the event made an assertion which passed:
115             #
116 3         14             my $assert = $facet_data->{assert};
117 3 100 66     43             return unless $assert && $assert->{pass};
118              
119             # Make sure at least one of the amnesty reasons
120             # is because of TODO:
121             #
122 1         6             my %todo_reasons;
123 1         6             foreach my $amnesty (@$amnesty_list) {
124 2 50       20                 next unless $amnesty->{tag} eq 'TODO';
125 2         82                 $todo_reasons{ $amnesty->{details} } = 1;
126                         }
127 1 50       11             return unless keys %todo_reasons;
128              
129 1         5             my $details = $assert->{details};
130              
131 1         10             foreach my $todo_reason (sort keys %todo_reasons) {
132 1         15                 $ctx->fail(
133                                 qq{TODO passed unexpectedly: $details}
134                             );
135                         }
136                     },
137 11         75         inherit => 1,
138                 );
139             }
140              
141             sub on_context_release
142             {
143 11     11 0 835     my ($ctx) = @_;
144              
145 11         39     my $hub_listener = delete $ctx->{_TodoFailOnSuccess_hub_listener};
146 11 50       56     $ctx->hub->unlisten($hub_listener) if $hub_listener;
147             }
148              
149             1;
150              
151             __END__
152            
153             =pod
154            
155             =encoding UTF-8
156            
157             =head1 NAME
158            
159             Test2::Plugin::TodoFailOnSuccess - Report failure if a TODO test unexpectedly passes
160            
161             =head1 VERSION
162            
163             version 0.0.1
164            
165             =head1 SYNOPSIS
166            
167             package My::Tests;
168            
169             use Test2::V0;
170            
171             use Test2::Plugin::TodoFailOnSuccess; # report unexpected TODO success
172            
173             use Test2::Tools::Basic; # for "todo" sub
174             use Test2::Todo; # for "todo" object
175            
176             sub test_something
177             {
178             # Lexical scope TODO:
179             #
180             {
181             my $todo = todo 'Not expected to pass';
182             is $value, $expected_value, "Got expected value";
183             }
184            
185             # Coderef TODO:
186             #
187             todo 'Not expected to pass either' => sub {
188             is $value, $expected_value, "Got expected value";
189             };
190            
191             # Object-oriented TODO:
192             #
193             my $todo = Test2::Todo->new( reason => 'Still not expected to pass' );
194             is $value, $expected_value, "Got expected value";
195             $todo->end;
196             }
197            
198             =head1 DESCRIPTION
199            
200             Wrapping a test with TODO is a conventient way to avoid being tripped
201             up by test failures until you have a chance to get the code working.
202             It normally won't hurt to leave the TODO in place after the tests
203             start passing, but if you forget to remove the TODO at that point,
204             a subsequent code change could start causing new test failures which
205             would then go unreported and possibly unnoticed.
206            
207             This module provides a mechanism to trigger explicit test failures
208             when TODO tests unexpectedly pass, so that you have an opportunity
209             to remove the TODO.
210            
211             If a TODO test passes, a failure will be reported with a message
212             containing the test description, equivalent to doing:
213            
214             fail "TODO passed unexpectedly: $test_description";
215            
216             which might appear in your TAP output along with the TODO reason as
217             something like:
218            
219             not ok 3 - TODO passed unexpectedly: Got expected value # TODO Not expected to pass
220            
221             Note that due to the additional C<fail> being reported, you may
222             see messages about your planned number of tests being exceeded,
223             for example:
224            
225             # Did not follow plan: expected 5, ran 6.
226            
227             There are no options or arguments, just C<use Test2::Plugin::TodoFailOnSuccess>
228             in your test file.
229            
230             =head1 AUTHOR
231            
232             Grant Street Group <developers@grantstreet.com>
233            
234             =head1 COPYRIGHT AND LICENSE
235            
236             This software is Copyright (c) 2019 by Grant Street Group.
237            
238             This is free software, licensed under:
239            
240             The Artistic License 2.0 (GPL Compatible)
241            
242             =head1 CONTRIBUTOR
243            
244             =for stopwords Larry Leszczynski
245            
246             Larry Leszczynski <Larry.Leszczynski@GrantStreet.com>
247            
248             =cut
249