File Coverage

blib/lib/Sub/Assert.pm
Criterion Covered Total %
statement 132 151 87.4
branch 66 106 62.2
condition 3 9 33.3
subroutine 14 14 100.0
pod 1 1 100.0
total 216 281 76.8


line stmt bran cond sub pod time code
1             package Sub::Assert;
2            
3 2     2   59280 use 5.006;
  2         7  
  2         76  
4 2     2   10 use strict;
  2         5  
  2         210  
5 2     2   10 use warnings;
  2         3  
  2         172  
6            
7             require Exporter;
8            
9             our @ISA = qw(Exporter);
10            
11             our @EXPORT = qw(
12             assert
13             );
14             our $VERSION = '1.23';
15            
16 2     2   10 use Carp qw/croak carp/;
  2         3  
  2         482  
17            
18             sub assert {
19 6     6 1 1760 my %params = @_;
20 6         14 my $sub = $params{sub};
21 6 50       19 defined $sub or croak("assert missing the subroutine to work with");
22            
23 6         10 my $package;
24             my $subref;
25 6 50       27 if (ref $sub eq 'CODE') {
    50          
26 0         0 $subref = $sub;
27             }
28             elsif (ref $sub eq '') {
29 6         20 ($package, undef, undef) = caller();
30 6 50       18 defined $package
31             or croak("assert could not determine caller package");
32 2     2   11 no strict 'refs';
  2         4  
  2         85  
33 6         8 $subref = *{"${package}::$sub"}{CODE};
  6         32  
34 2     2   9 use strict 'refs';
  2         2  
  2         2026  
35 6 50 33     46 defined $subref and ref($subref) eq 'CODE'
36             or croak("assert finds that there is no '$sub' subroutine in package '$package'");
37             }
38             else {
39 0         0 croak("Subroutine argument to assert is invalid");
40             }
41            
42 6 50       17 $params{action} = 'croak' unless defined $params{action};
43 6         16 my $action = $package . '::' . $params{action};
44            
45 6         21 my $precond = _normalize_conditions($params{pre}, 'precondition');
46            
47 6         37 my $postcond = _normalize_conditions($params{post}, 'postcondition');
48            
49 6         8 my $context;
50 6 100       17 if (exists $params{context}) {
51 2 50 33     53 unless (defined $params{context} and
      33        
52             $params{context} eq 'list' ||
53             $params{context} eq 'scalar' ||
54             $params{context} eq 'void' ||
55             $params{context} eq 'novoid' ||
56             $params{context} eq 'any'
57             ) {
58 0         0 croak("Invalid context specified for assertion");
59             }
60 2         4 $context = $params{context};
61             }
62             else {
63 4         6 $context = 'any';
64             }
65            
66 6         10 my $new_sub_text = "sub {\nmy \@PARAM = \@_;\n";
67            
68 6 50       35 if ($context eq 'list') {
    50          
    100          
    50          
69 0 0       0 $new_sub_text .= "unless (wantarray()) {\n" .
70             "my \$context = (defined wantarray() ?\n" .
71             " 'scalar' : 'void');\n" .
72             "$action(\"" .
73             (ref($sub) eq 'CODE' ?
74             'C' :
75             "${package}::$sub c"
76             ) .
77             'alled in $context context.")' .
78             "}\n";
79             }
80             elsif ($context eq 'scalar') {
81 0 0       0 $new_sub_text .= "unless (defined(wantarray()) and not " .
82             "wantarray()) {\n" .
83             "my \$context = (wantarray() ?\n" .
84             " 'list' : 'void');\n" .
85             "$action(\"" .
86             (ref($sub) eq 'CODE' ?
87             'C' :
88             "${package}::$sub c"
89             ) .
90             'alled in $context context.")' .
91             "}\n";
92             }
93             elsif ($context eq 'novoid') {
94 2 50       18 $new_sub_text .= "unless (defined wantarray()) {\n" .
95             "$action(\"" .
96             (ref($sub) eq 'CODE' ?
97             'C' :
98             "${package}::$sub c"
99             ) .
100             'alled in void context.")' .
101             "}\n";
102             }
103             elsif ($context eq 'void') {
104 0 0       0 $new_sub_text .= "unless (not defined wantarray()) {\n" .
105             "my \$context = (wantarray() ?\n" .
106             " 'list' : 'scalar');\n" .
107             "$action(\"" .
108             (ref($sub) eq 'CODE' ?
109             'C' :
110             "${package}::$sub c"
111             ) .
112             'alled in $context context.")' .
113             "}\n";
114             }
115            
116 6         14 foreach my $pre_name (keys %$precond) {
117 6 100       13 if ($pre_name eq '_') {
118 3         5 my $pre_array = $precond->{'_'};
119 3         9 foreach my $pre_no (1..@$pre_array) {
120 3 50       24 $new_sub_text .=
121             "do{\n".$pre_array->[$pre_no-1]
122             . "\n}\nor $action(\"Precondition "
123             . "$pre_no "
124             . (ref($sub) eq 'CODE' ? '' : "for ${package}::$sub ")
125             . "failed.\");\n\n";
126             }
127             }
128             else {
129 3 50       24 $new_sub_text .=
130             "do{\n".$precond->{$pre_name}
131             . "\n}\nor $action(\"Precondition "
132             . "'$pre_name' "
133             . (ref($sub) eq 'CODE' ? '' : "for ${package}::$sub ")
134             . "failed.\");\n\n";
135             }
136             }
137 6         15 $new_sub_text .= <<'HERE';
138             my @RETURN;
139             my $RETURN;
140             my $VOID;
141             if (wantarray()) {
142             @RETURN = $SUBROUTINEREF->(@PARAM);
143             $RETURN = $RETURN[0] if @RETURN;
144             }
145             elsif (defined wantarray()) {
146             $RETURN = $SUBROUTINEREF->(@PARAM);
147             @RETURN = ($RETURN);
148             }
149             else {
150             $VOID = 1;
151             $SUBROUTINEREF->(@PARAM);
152             }
153             HERE
154            
155 6         14 foreach my $post_name (keys %$postcond) {
156 9 100       20 if ($post_name eq '_') {
157 5         9 my $post_array = $postcond->{'_'};
158 5         10 foreach my $post_no (1..@$post_array) {
159 8 50       63 $new_sub_text .=
160             "do{\n".$post_array->[$post_no-1]
161             . "\n}\nor $action(\"Postcondition "
162             . "$post_no "
163             . (ref($sub) eq 'CODE' ? '' : "for ${package}::$sub ")
164             . "failed.\");\n\n";
165             }
166             }
167             else {
168 4 50       25 $new_sub_text .=
169             "do{\n".$postcond->{$post_name}
170             . "\n}\nor $action(\"Postcondition "
171             . "'$post_name' "
172             . (ref($sub) eq 'CODE' ? '' : "for ${package}::$sub ")
173             . "failed.\");\n\n";
174             }
175             }
176            
177 6 50       20 $new_sub_text .= ($context eq 'list' ?
178             "return \@RETURN;\n}\n" :
179             "return \$RETURN;\n}\n"
180             );
181 6         16 my ($new_sub_ref, $error) =
182             _generate_assertion_subroutine($subref, $new_sub_text);
183            
184 6 50       19 if ($error) {
185 0         0 croak("Compilation of assertions failed: $error.\n$new_sub_text");
186             }
187 6 50       16 if (ref($sub) eq 'CODE') {
188 0         0 return $new_sub_ref;
189             }
190             else {
191 2     2   10 no strict;
  2         3  
  2         56  
192 2     2   9 no warnings;
  2         4  
  2         90  
193 6         540 *{"${package}::$sub"} = $new_sub_ref;
  6         30  
194 2     2   9 use strict;
  2         9  
  2         54  
195 2     2   29 use warnings;
  2         3  
  2         945  
196             }
197 6         41 return $new_sub_ref;
198             }
199            
200             sub _generate_assertion_subroutine {
201 6     6   8 local $@;
202 6         9 my $SUBROUTINEREF = $_[0];
203 6 100   4   2060 return eval($_[1]), "$@";
  4 0       1194  
  4 50       6  
  4 50       18  
  4 50       93  
  0 50       0  
  4 50       13  
  0 50       0  
  0 100       0  
  4 100       11  
  4 50       111  
  0 100       0  
  0 100       0  
  4 100       6  
  4 100       16  
  4 100       5  
  4 0       10  
  4 50       6  
  4 100       14  
  4 100       5  
  4 100       16  
  4         29  
  4         1072  
  4         12  
  2         9  
  4         28  
  3         13  
  4         31  
  0         0  
  3         8  
  1         7  
  1         32  
  2         6  
  2         51  
  2         3  
  2         15  
  4         61  
  6         971  
  6         52  
  2         51  
  4         27  
  4         18  
  4         30  
  0         0  
  4         12  
  0         0  
  0         0  
  3         10  
  3         75  
  1         2  
  1         4  
  4         29  
  4         31  
  4         73  
204             }
205            
206             sub _normalize_conditions {
207 12     12   16 my $conditions = shift;
208 12         18 my $type = shift;
209            
210 12 50       55 if (not defined $conditions) {
    100          
    100          
    50          
211             # no conditions
212 0         0 $conditions = {};
213             }
214             elsif (ref($conditions) eq '') {
215             # a single, unnamed condition
216 5         18 $conditions = {'_' =>[$conditions]};
217             }
218             elsif (ref($conditions) eq 'ARRAY') {
219             # an array of unnamed conditions
220 3         5 my $ary = $conditions;
221 3         11 $conditions = {'_' => [@$ary]};
222             }
223             elsif (ref($conditions) eq 'HASH') {}
224             else {
225 0         0 croak("Invalid type of $type");
226             }
227            
228 12         32 foreach my $name (keys %$conditions) {
229 15 100       30 if ($name eq '_') {
230 8         12 foreach my $cond (@{$conditions->{'_'}}) {
  8         36  
231 11 50       40 croak("Invalid unnamed $type")
232             if ref($cond) ne '';
233             }
234             }
235             else {
236 7 50       22 croak("Invalid $type '$name'")
237             if ref($conditions->{$name}) ne '';
238             }
239             }
240            
241 12         32 return $conditions;
242             }
243            
244             1;
245             __END__