File Coverage

blib/lib/Test/Fatal.pm
Criterion Covered Total %
statement 53 56 94.6
branch 13 20 65.0
condition 9 15 60.0
subroutine 12 12 100.0
pod 4 4 100.0
total 91 107 85.0


line stmt bran cond sub pod time code
1 3     3   304825 use v5.12.0;
  3         12  
2 3     3   14 use warnings;
  3         16  
  3         249  
3             package Test::Fatal 0.018;
4             # ABSTRACT: incredibly simple helpers for testing code with exceptions
5              
6             #pod =head1 SYNOPSIS
7             #pod
8             #pod use Test::More;
9             #pod use Test::Fatal;
10             #pod
11             #pod use System::Under::Test qw(might_die);
12             #pod
13             #pod is(
14             #pod exception { might_die; },
15             #pod undef,
16             #pod "the code lived",
17             #pod );
18             #pod
19             #pod like(
20             #pod exception { might_die; },
21             #pod qr/turns out it died/,
22             #pod "the code died as expected",
23             #pod );
24             #pod
25             #pod isa_ok(
26             #pod exception { might_die; },
27             #pod 'Exception::Whatever',
28             #pod 'the thrown exception',
29             #pod );
30             #pod
31             #pod =head1 DESCRIPTION
32             #pod
33             #pod Test::Fatal is an alternative to the popular L. It does much
34             #pod less, but should allow greater flexibility in testing exception-throwing code
35             #pod with about the same amount of typing.
36             #pod
37             #pod It exports one routine by default: C.
38             #pod
39             #pod B C intentionally does not manipulate the call stack.
40             #pod User-written test functions that use C must be careful to avoid
41             #pod false positives if exceptions use stack traces that show arguments. For a more
42             #pod magical approach involving globally overriding C, see
43             #pod L.
44             #pod
45             #pod =cut
46              
47 3     3   18 use Carp ();
  3         18  
  3         79  
48 3     3   1400 use Try::Tiny 0.07;
  3         6791  
  3         204  
49              
50 3     3   26 use Exporter 5.57 'import';
  3         80  
  3         2143  
51              
52             our @EXPORT = qw(exception);
53             our @EXPORT_OK = qw(exception success dies_ok lives_ok);
54              
55             #pod =func exception
56             #pod
57             #pod my $exception = exception { ... };
58             #pod
59             #pod C takes a bare block of code and returns the exception thrown by
60             #pod that block. If no exception was thrown, it returns undef.
61             #pod
62             #pod B If the block results in an I exception, such as 0
63             #pod or the empty string, Test::Fatal itself will die. Since either of these cases
64             #pod indicates a serious problem with the system under testing, this behavior is
65             #pod considered a I. Note that this issue is only known to occur on perls
66             #pod before 5.14.
67             #pod
68             #pod Exercise caution if you must test for these conditions: wrapping C
69             #pod ... }> in an C block will not give you the result you need, so make sure
70             #pod you use C instead. You can also directly use L's try/catch
71             #pod mechanism, the underlying exception handling system of Test::Fatal.
72             #pod
73             #pod Note that there is no TAP assert being performed. In other words, no "ok" or
74             #pod "not ok" line is emitted. It's up to you to use the rest of C in an
75             #pod existing test like C, C, C, et cetera. Or you may wish to use
76             #pod the C and C wrappers, which do provide TAP output.
77             #pod
78             #pod C does I alter the stack presented to the called block, meaning
79             #pod that if the exception returned has a stack trace, it will include some frames
80             #pod between the code calling C and the thing throwing the exception.
81             #pod This is considered a I because it avoids the occasionally twitchy
82             #pod C mechanism.
83             #pod
84             #pod B This is not a great idea:
85             #pod
86             #pod sub exception_like(&$;$) {
87             #pod my ($code, $pattern, $name) = @_;
88             #pod like( &exception($code), $pattern, $name );
89             #pod }
90             #pod
91             #pod exception_like(sub { }, qr/foo/, 'foo appears in the exception');
92             #pod
93             #pod If the code in the C<...> is going to throw a stack trace with the arguments to
94             #pod each subroutine in its call stack (for example via C,
95             #pod the test name, "foo appears in the exception" will itself be matched by the
96             #pod regex. Instead, write this:
97             #pod
98             #pod like( exception { ... }, qr/foo/, 'foo appears in the exception' );
99             #pod
100             #pod If you really want a test function that passes the test name, wrap the
101             #pod arguments in an array reference to hide the literal text from a stack trace:
102             #pod
103             #pod sub exception_like(&$) {
104             #pod my ($code, $args) = @_;
105             #pod my ($pattern, $name) = @$args;
106             #pod like( &exception($code), $pattern, $name );
107             #pod }
108             #pod
109             #pod exception_like(sub { }, [ qr/foo/, 'foo appears in the exception' ] );
110             #pod
111             #pod To aid in avoiding the problem where the pattern is seen in the exception
112             #pod because of the call stack, C<$Carp::MaxArgNums> is locally set to -1 when the
113             #pod code block is called. If you really don't want that, set it back to whatever
114             #pod value you like at the beginning of the code block. Obviously, this solution
115             #pod doens't affect all possible ways that args of subroutines in the call stack
116             #pod might taint the test. The intention here is to prevent some false passes from
117             #pod people who didn't read the documentation. Your punishment for reading it is
118             #pod that you must consider whether to do anything about this.
119             #pod
120             #pod B: One final bad idea:
121             #pod
122             #pod isnt( exception { ... }, undef, "my code died!");
123             #pod
124             #pod It's true that this tests that your code died, but you should really test that
125             #pod it died I. For example, if you make an unrelated mistake
126             #pod in the block, like using the wrong dereference, your test will pass even though
127             #pod the code to be tested isn't really run at all. If you're expecting an
128             #pod inspectable exception with an identifier or class, test that. If you're
129             #pod expecting a string exception, consider using C.
130             #pod
131             #pod =cut
132              
133             our ($REAL_TBL, $REAL_CALCULATED_TBL) = (1, 1);
134              
135             sub exception (&) {
136 15     15 1 679733 my $code = shift;
137              
138             return try {
139 15 50   15   709 my $incremented = defined $Test::Builder::Level
140             ? $Test::Builder::Level - $REAL_CALCULATED_TBL
141             : 0;
142 15         28 local $Test::Builder::Level = $REAL_CALCULATED_TBL;
143 15 100       62 if ($incremented) {
144             # each call to exception adds 5 stack frames
145 4         5 $Test::Builder::Level += 5;
146 4         11 for my $i (1..$incremented) {
147             # -2 because we want to see it from the perspective of the call to
148             # is() within the call to $code->()
149 5         7 my $caller = caller($Test::Builder::Level - 2);
150 5 50       12 if ($caller eq __PACKAGE__) {
151             # each call to exception adds 5 stack frames
152 0         0 $Test::Builder::Level = $Test::Builder::Level + 5;
153             }
154             else {
155 5         9 $Test::Builder::Level = $Test::Builder::Level + 1;
156             }
157             }
158             }
159              
160 15         22 local $REAL_CALCULATED_TBL = $Test::Builder::Level;
161 15         24 local $Carp::MaxArgNums = -1;
162 15         38 $code->();
163 10         8227 return undef;
164             } catch {
165 5 50 66 5   179 return $_ if $_ or ref; # allow objects with a false boolean overload
166              
167 0 0       0 my $problem = defined $_ ? 'false' : 'undef';
168 0         0 Carp::confess("$problem exception caught by Test::Fatal::exception");
169 15         125 };
170             }
171              
172             #pod =func success
173             #pod
174             #pod try {
175             #pod should_live;
176             #pod } catch {
177             #pod fail("boo, we died");
178             #pod } success {
179             #pod pass("hooray, we lived");
180             #pod };
181             #pod
182             #pod C, exported only by request, is a L helper with semantics
183             #pod identical to L|Try::Tiny/finally>, but the body of the block will
184             #pod only be run if the C block ran without error.
185             #pod
186             #pod Although almost any needed exception tests can be performed with C,
187             #pod success blocks may sometimes help organize complex testing.
188             #pod
189             #pod =cut
190              
191             sub success (&;@) {
192 3     3 1 884 my $code = shift;
193             return finally( sub {
194 3 100   3   1201 return if @_; # <-- only run on success
195 1         4 $code->();
196 3         15 }, @_ );
197             }
198              
199             #pod =func dies_ok
200             #pod
201             #pod =func lives_ok
202             #pod
203             #pod Exported only by request, these two functions run a given block of code, and
204             #pod provide TAP output indicating if it did, or did not throw an exception.
205             #pod These provide an easy upgrade path for replacing existing unit tests based on
206             #pod C.
207             #pod
208             #pod RJBS does not suggest using this except as a convenience while porting tests to
209             #pod use Test::Fatal's C routine.
210             #pod
211             #pod use Test::More tests => 2;
212             #pod use Test::Fatal qw(dies_ok lives_ok);
213             #pod
214             #pod dies_ok { die "I failed" } 'code that fails';
215             #pod
216             #pod lives_ok { return "I'm still alive" } 'code that does not fail';
217             #pod
218             #pod =cut
219              
220             my $Tester;
221              
222             # Signature should match that of Test::Exception
223             sub dies_ok (&;$) {
224 3     3 1 247322 my $code = shift;
225 3         6 my $name = shift;
226              
227 3         25 require Test::Builder;
228 3   66     17 $Tester ||= Test::Builder->new;
229              
230 3         13 my $tap_pos = $Tester->current_test;
231              
232 3         281 my $exception = exception( \&$code );
233              
234 3 50 66     58 $name ||= $tap_pos != $Tester->current_test
235             ? "...and code should throw an exception"
236             : "code should throw an exception";
237              
238 3         116 my $ok = $Tester->ok( $exception, $name );
239 3 100       1465 $ok or $Tester->diag( "expected an exception but none was raised" );
240 3         308 return $ok;
241             }
242              
243             sub lives_ok (&;$) {
244 3     3 1 6553 my $code = shift;
245 3         7 my $name = shift;
246              
247 3         36 require Test::Builder;
248 3   33     9 $Tester ||= Test::Builder->new;
249              
250 3         9 my $tap_pos = $Tester->current_test;
251              
252 3         293 my $exception = exception( \&$code );
253              
254 3 50 66     52 $name ||= $tap_pos != $Tester->current_test
255             ? "...and code should not throw an exception"
256             : "code should not throw an exception";
257              
258 3         126 my $ok = $Tester->ok( ! $exception, $name );
259 3 100       1301 $ok or $Tester->diag( "expected return but an exception was raised" );
260 3         313 return $ok;
261             }
262              
263             1;
264              
265             __END__