File Coverage

blib/lib/Devel/EvalError.pm
Criterion Covered Total %
statement 83 105 79.0
branch 25 50 50.0
condition 6 15 40.0
subroutine 16 21 76.1
pod 9 9 100.0
total 139 200 69.5


line stmt bran cond sub pod time code
1             package Devel::EvalError;
2 1     1   808 use strict;
  1         2  
  1         48  
3              
4              
5 1     1   5 use vars qw( $VERSION );
  1         2  
  1         162  
6              
7             BEGIN {
8 1     1   44 $VERSION= 0.001_002;
9 1         3 my $idx= 0;
10 1         3 for my $name (
11             '_prevReason', # Value from $@ when c'ted, restored to $@ in d'tor
12             '_reasons', # List of possible failure reasons from $SIG{__DIE__}
13             '_prevHandler', # Value of $SIG{__DIE__} before c'ted
14             '_newHandler', # Address of $SIG{__DIE__} after c'ted
15             '_succeeded', # Whether the eval() was successful
16             '_knowReason', # True if $@ survived
17             ) {
18 6         15 my $code= "sub $name() { $idx; }";
19 6 50       430 eval "$code; 1"
20             or die "Couldn't compile constant ($code): $@\n";
21 6         3572 $idx++;
22             }
23             }
24              
25              
26             sub _croak { # Die but report the caller's line number
27 0     0   0 require Carp;
28 0         0 Carp::croak( @_ );
29             }
30              
31             sub _confess { # Die with stack trace
32 0     0   0 require Carp;
33 0         0 local $Carp::CarpLevel= $Carp::CarpLevel + 1;
34 0         0 Carp::confess( @_ );
35             }
36              
37             sub _cluck { # Warn with stack trace
38 0     0   0 require Carp;
39 0         0 local $Carp::CarpLevel= $Carp::CarpLevel + 1;
40 0         0 Carp::cluck( @_ );
41             }
42              
43              
44             sub new {
45 2     2 1 1388 my( $we )= @_;
46 2 50       8 _croak( "Devel::EvalError: new() via an object is not allowed" )
47             if ref $we;
48 2         7 my $me= bless [], $we;
49 2         7 $me->_init();
50 2         5 return $me;
51             }
52              
53              
54             sub _init {
55 2     2   4 my( $me )= @_;
56 2 50       11 _confess( "Devel::EvalError: Erase() required before _init()" )
57             if $me->[_reasons];
58 2         6 $me->[_prevReason]= $@;
59 2         4 $me->[_reasons]= my $reasons= [];
60 2         5 $me->[_prevHandler]= my $prevHandler= $SIG{__DIE__};
61 2     1   8 my $newHandler= sub { _handleDie( $reasons, $prevHandler, @_ ); };
  1         440  
62 2         6 $me->[_newHandler]= 0 + $newHandler;
63 2         6 $SIG{__DIE__}= $newHandler;
64 2         4 return;
65             }
66              
67              
68             sub _handleDie {
69 1     1   3 my $list= shift @_;
70 1         2 my $handler= shift @_;
71 1 50       2 push @{ $list },
  1         5  
72             ( 1 == @_ ) ? $_[0] : join '', @_;
73 1 50       5 $handler->( @_ )
74             if $handler;
75 1         9 return;
76             }
77              
78              
79             sub Erase {
80 2     2 1 4 my( $me )= @_;
81 2         7 $me->_revertHandler();
82 2         3 my $prevReason= $me->[_prevReason];
83 2 50       7 $@= $prevReason
84             if $prevReason;
85 2         17 return;
86             }
87              
88              
89             sub Reuse {
90 0     0 1 0 my( $me )= @_;
91 0         0 $me->Erase();
92 0         0 $me->_init();
93 0         0 return $me;
94             }
95              
96              
97             sub ExpectOne {
98 1     1 1 2 my $me= shift @_;
99 1         3 my $okay;
100 1 50 33     8 if( @_ <= 1 && ! defined $_[0] ) {
    0 0        
101 1         2 $okay= 0;
102             } elsif( 1 == @_ && 1 eq $_[0] ) {
103 0         0 $okay= 1;
104             }
105 1         5 $me->_expected( $okay, @_ );
106 1         3 return $me;
107             }
108              
109              
110             sub ExpectNonEmpty {
111 1     1 1 8 my $me= shift @_;
112 1 50       5 my $okay= 0 == @_ ? 0 : 1;
113 1         4 $me->_expected( $okay, @_ );
114 1 50       7 return wantarray ? @_ : $_[0];
115             }
116              
117              
118             sub _expected {
119 2     2   5 my $me= shift @_;
120 2         2 my $okay= shift @_;
121 2         3 my $reason= $@; # Copy early as $@ can easily change
122 2         13 my $caller= (caller 1)[3];
123 2         8 $me->_revertHandler();
124 2 50       5 _croak( 'Devel::EvalError: $caller() called on Erase()d object' )
125             if ! $me->[_reasons];
126 2 50       6 _croak( 'Devel::EvalError: $caller() called on object more than once' )
127             if defined $me->[_succeeded];
128 2         4 $me->[_succeeded]= $okay;
129 2 50       5 if( ! defined $okay ) {
130 0 0       0 _croak(
131             "Devel::EvalError: $caller() misused, passed( ",
132 0         0 join( ", ", map { defined $_ ? $_ : "(undef)" } @_ ),
133             " )",
134             );
135             }
136 2 100 66     9 if( ! $okay && $reason ) {
137 1         2 $me->[_knowReason]= 1;
138 1 50       6 push @{ $me->[_reasons] }, $reason
  0         0  
139             if $reason ne $me->[_reasons][-1];
140             }
141             }
142              
143              
144             sub _revertHandler {
145 4     4   5 my( $me )= @_;
146             return
147 4 50       22 if ! $me->[_reasons];
148 4         7 my $handler= $me->[_prevHandler];
149 4 100       222 if( '0' ne $handler ) {
150 2         8 my $current= $SIG{__DIE__};
151 2         5 $SIG{__DIE__}= $handler;
152 2 50       16 if( $current != $me->[_newHandler] ) {
153 0         0 _cluck(
154             '$SIG{__DIE__} changed out from under Devel::EvalError',
155             );
156             }
157 2         10 $me->[_prevHandler]= 0;
158             }
159 4         10 return;
160             }
161              
162              
163             sub AllReasons {
164 0     0 1 0 my( $me )= @_;
165 0 0       0 return @{ $me->[_reasons] || [] };
  0         0  
166             }
167              
168              
169             sub Reason {
170 2     2 1 4 my( $me )= @_;
171 2 50       4 my @reasons= @{ $me->[_reasons] || [] };
  2         11  
172 2 100 66     18 return ''
173             if ! defined $me->[_succeeded] # if eval() not called (yet?)
174             || $me->[_succeeded]; # or eval() succeeded.
175 1 50       4 if( ! @reasons ) {
176             # We "know" eval() failed but we never intercepted a die() message.
177             # There are several possibilities (likeliest first):
178             # 1) $ee->ExpectNonEmpty( eval { getList(); } );
179             # but getList() returned an empty list.
180             # 2) $ee->ExpectOne( eval { return; } ); # or similar
181             # 3) Some unexpected way for eval() to fail without our
182             # $SIG{__DIE__} handler ever getting called. This might be
183             # a Perl bug or something strange that XS code can do or such.
184             # 4) Somebody meddling with our object's internals. C'est le Vie.
185             # 5) etc.
186 0         0 return 'Unknown failure reason or returned empty list!';
187             }
188 1 50 33     9 return $reasons[-1]
189             if 1 == @reasons || $me->[_knowReason];
190 0         0 return join "\nTHEN ", @reasons;
191             }
192              
193              
194             sub Succeeded {
195 2     2 1 496 my( $me )= @_;
196 2         5 my $okay= $me->[_succeeded];
197 2 50       7 _croak(
198             'Devel::EvalError: Expect*() not called before checking for success',
199             ) if ! defined $okay;
200 2         10 return $okay;
201             }
202              
203              
204             sub Failed {
205 1     1 1 575 my( $me )= @_;
206 1         4 return ! $me->Succeeded();
207             }
208              
209              
210              
211             sub DESTROY {
212 2     2   4 my( $me )= @_;
213 2         6 $me->Erase();
214             }
215              
216              
217             'Devel::EvalError';
218             __END__