File Coverage

blib/lib/TAP/Tree.pm
Criterion Covered Total %
statement 166 184 90.2
branch 70 92 76.0
condition 9 18 50.0
subroutine 21 21 100.0
pod 4 7 57.1
total 270 322 83.8


line stmt bran cond sub pod time code
1             package TAP::Tree;
2              
3 7     7   5616 use strict;
  7         14  
  7         259  
4 7     7   34 use warnings;
  7         12  
  7         219  
5 7     7   101 use v5.10.1;
  7         23  
  7         320  
6 7     7   4963 use utf8;
  7         49  
  7         47  
7              
8             our $VERSION = 'v0.0.5';
9              
10 7     7   366 use Carp;
  7         13  
  7         498  
11 7     7   6763 use autodie;
  7         111716  
  7         47  
12 7     7   51269 use Encode qw[decode];
  7         77230  
  7         24653  
13              
14             sub new {
15 26     26 1 788588 my $class = shift;
16 26         365 my %params = @_;
17              
18 26         1360 my $self = {
19             tap_file => $params{tap_file},
20             tap_ref => $params{tap_ref},
21             tap_tree => $params{tap_tree},
22              
23             utf8 => $params{utf8},
24              
25             is_parsed => undef,
26              
27             result => {
28             version => undef,
29             plan => undef,
30             testline => [],
31             bailout => undef,
32             },
33             };
34              
35 26         209 bless $self, $class;
36              
37 26         296 $self->_validate;
38 25         223 $self->_initialize;
39              
40 25         222 return $self;
41             }
42              
43 2     2 0 2055 sub is_utf8 { return $_[0]->{utf8} }
44 9     9 0 35 sub is_parsed { return $_[0]->{is_parsed} }
45              
46 9 50   9   32 sub _check_for_parsed { croak "not parsed" unless $_[0]->is_parsed }
47              
48             sub summary {
49 5     5 1 817 my $self = shift;
50              
51 5         16 $self->_check_for_parsed;
52              
53 5         7 my $failed_tests = 0;
54 5         7 for my $testline ( @{ $self->{result}{testline} } ) {
  5         15  
55 10 100 100     40 $failed_tests++ if ( $testline->{result} == 0 && ! $testline->{todo} );
56             }
57              
58 5 100       17 my $is_bailout = $self->{result}{bailout} ? 1 : 0;
59 5         7 my $ran_tests = scalar @{ $self->{result}{testline} };
  5         10  
60 5 50       16 my $is_good_plan = ( defined $self->{result}{plan}{number} ) ? 1 : 0;
61              
62 5 100 66     43 my $is_ran_all_tests = ( $is_good_plan and $ran_tests != 0 and $self->{result}{plan}{number} == $ran_tests ) ? 1 : 0;
63              
64 5         50 my $summary = {
65             version => $self->{result}{version},
66              
67             is_skipped_all => defined $self->{result}{plan}{skip_all} ? 1 : 0,
68             skip_all_msg => $self->{result}{plan}{skip_all} ?
69             $self->{result}{plan}{directive} : undef,
70              
71             is_bailout => defined $self->{result}{bailout} ? 1 : 0,
72             bailout_msg => $self->{result}{bailout} ?
73             $self->{result}{bailout}{message} : undef,
74              
75             planned_tests => $self->{result}{plan}{number},
76             ran_tests => $ran_tests,
77             failed_tests => $failed_tests,
78              
79             is_good_plan => $is_good_plan,
80             is_ran_all_tests => $is_ran_all_tests,
81              
82             # for backward compatibility
83             bailout => $self->{result}{bailout},
84             plan => $self->{result}{plan},
85 5 50       46 tests => scalar @{ $self->{result}{testline} },
    50          
    100          
    100          
86             fail => $failed_tests,
87             };
88              
89 5         16 return $summary;
90             }
91              
92             sub tap_tree {
93 4     4 0 6 my $self = shift;
94              
95 4         14 $self->_check_for_parsed;
96              
97 4         33 return $self->{result};
98             }
99              
100             sub create_tap_tree_iterator {
101 4     4 1 6433 my $self = shift;
102 4         12 my %params = @_;
103              
104 4         848 require TAP::Tree::Iterator;
105 4         18 my $iterator = TAP::Tree::Iterator->new( tap_tree => $self->tap_tree, %params );
106              
107 4         12 return $iterator;
108             }
109              
110             sub _validate {
111 26     26   197 my $self = shift;
112              
113 26 100       378 if ( $self->{tap_ref} ) {
114 23 50 33     592 if ( $self->{tap_file} or $self->{tap_tree} ) {
115 0         0 croak "Excessive parameter";
116             }
117              
118 23 50       284 if ( ref( $self->{tap_ref} ) ne 'SCALAR' ) {
119 0         0 croak "Parameter 'tap_ref' is not scalar reference";
120             }
121              
122 23         132 return $self;
123             }
124              
125 3 100       14 if ( $self->{tap_file} ) {
126 2 50 33     21 if ( $self->{tap_ref} or $self->{tap_tree} ) {
127 0         0 croak "Excessive parameter";
128             }
129              
130 2 50       189 if ( ! -e -f -r -T $self->{tap_file} ) {
131 0         0 croak "Paramter 'tap_file' is invalid:$self->{tap_file}";
132             }
133              
134 2         6 return $self;
135             }
136              
137 1 50       6 if ( $self->{tap_tree} ) {
138 0 0 0     0 if ( $self->{tap_file} or $self->{tap_ref} ) {
139 0         0 croak "Excessive parameter";
140             }
141              
142 0 0       0 if ( ref( $self->{tap_tree} ) ne 'HASH' ) {
143 0         0 croak "Parameter 'tap_tree' is not hash reference";
144             }
145              
146 0         0 my @keys = qw[version plan testline];
147 0         0 for my $key ( @keys ) {
148 0 0       0 if ( ! defined $self->{tap_tree}{$key} ) {
149 0         0 croak "Parameter 'tap_tree' is invalid tap tree:$key";
150             }
151             }
152              
153 0         0 return $self;
154             }
155              
156 1         29 croak "No required parameter ( tap_ref or tap_file ot tap_tree )";
157             }
158              
159             sub _initialize {
160 25     25   99 my $self = shift;
161              
162 25 50       218 if ( $self->{tap_tree} ) {
163 0         0 $self->{result} = $self->{tap_tree}; # Not deep copy.
164 0         0 $self->{is_parsed}++;
165              
166 0         0 return $self;
167             }
168              
169             }
170              
171             sub parse {
172 25     25 1 123 my $self = shift;
173              
174 25 50       225 if ( $self->{is_parsed} ) {
175 0         0 croak "TAP is already parsed.";
176             }
177              
178 25 100       132 my $path = ( $self->{tap_file} ) ? $self->{tap_file} : $self->{tap_ref};
179              
180 25         320 open my $fh, '<', $path;
181 25         32771 $self->{result} = $self->_parse( $fh );
182 25         194 close $fh;
183              
184 25         5901 $self->{is_parsed}++;
185              
186 25         148 return $self->{result};
187             }
188              
189             sub _parse {
190 25     25   91 my ( $self, $fh ) = @_;
191              
192 25         201 my $result = {
193             version => undef,
194             plan => undef,
195             testline => [],
196             bailout => undef,
197             parse_error => [],
198             };
199              
200 25         184 my @subtest_lines;
201 25         209 while ( my $line_raw = <$fh> ) {
202              
203 138 100       432 my $line = ( $self->{utf8} ) ? decode( 'UTF-8', $line_raw ) : $line_raw;
204              
205 138         454 chomp $line;
206              
207 138 50       381 next if ( $line =~ /!\s*#/ ); # skip all comments.
208              
209             # Bail Out!
210             # NOTE
211             # 'Test-Simple < 0.98_01' can't handle BAIL_OUT in subtest correctly.
212             # Since TAP-Tree requires 'Test-Simple >= 1.001002'.
213 138 100       354 if ( $line =~ /^Bail out!\s+(.*)/ ) {
214 3         22 $result->{bailout} = {
215             str => $line,
216             message => $1,
217             };
218              
219 3         12 last;
220             }
221              
222             # tap version
223              
224             # Deleted the parsing code for the version of tha TAP.
225             # Since a specified of a version is unnecessary
226             # for the version lower than 12
227             # It is due to add when supporting version 13.
228              
229             # plan
230 135 100       800 if ( $line =~ /^(\s*)1\.\.\d+(\s#.*)?$/ ) {
231              
232 37 100       131 if ( $1 ) { # subtest
233 13         40 push @subtest_lines, $line;
234             } else {
235 24 50       96 if ( $result->{plan}{number} ) {
236 0         0 croak "Invalid TAP sequence. Plan is already specified.";
237             }
238              
239 24         137 $result->{plan} = $self->_parse_plan( $line );
240             }
241              
242 37         154 next;
243             }
244              
245             # testline
246 98 100       431 if ( $line =~ /^(\s*)(not )?ok/ ) {
247              
248 71 100       226 if ( $1 ) { # subtest
249 22         68 push @subtest_lines, $line;
250             } else {
251 49         253 my $subtest = $self->_parse_subtest( \@subtest_lines );
252 49         73 push @{ $result->{testline} },
  49         171  
253             $self->_parse_testline( $line, $subtest );
254             }
255              
256 71         299 next;
257             }
258              
259             # 'unknown' line.
260 27         39 push @{ $self->{result}{parse_error} }, $line;
  27         194  
261             }
262              
263 25 50       73 if ( ! $result->{version} ) {
264 25         72 $result->{version}{number} = 12; # Default tap version is '12' now.
265             }
266              
267 25 100       89 if ( ! $result->{plan} ) {
268 1         12 $result->{plan}{number} = undef;
269             }
270              
271 25         80 return $result;
272             }
273              
274             sub _parse_plan {
275 37     37   60 my $self = shift;
276 37         73 my $line = shift;
277              
278 37         402 my $plan = {
279             str => $line,
280             number => undef,
281             skip_all => undef,
282             directive => undef,
283             };
284              
285             {
286 37         85 $line =~ /^1\.\.(\d+)\s*(# .*)?/;
  37         247  
287              
288 37         105 $plan->{number} = $1;
289 37 100       142 $plan->{skip_all}++ if ( $plan->{number} == 0 );
290              
291 37 100       162 if ( $2 ) {
292 2         29 $plan->{directive} = $2;
293 2         26 $plan->{directive} =~ s/^#\s+//;
294             }
295             }
296              
297 37         99 return $plan;
298             }
299              
300             sub _parse_testline {
301 70     70   84 my $self = shift;
302 70         121 my $line = shift;
303 70         73 my $subtest = shift;
304              
305 70         724 my $testline = {
306             str => $line,
307             result => undef, # 1 (ok) or 0 (not ok)
308             test_number => undef,
309             description => undef,
310             directive => undef,
311             todo => undef, # is todo test?
312             skip => undef, # is skipped?
313             subtest => $subtest,
314             };
315              
316             {
317 70         106 $line =~ /(not )?ok\s*(\d+)?(.*)?/;
  70         367  
318              
319 70 100       212 $testline->{result} = $1 ? 0 : 1;
320 70 100       299 $testline->{test_number} = $2 if $2; # test number is optional
321              
322 70         186 my $msg = $3;
323              
324 70 100 66     1148 if ( $msg && $msg =~ /^\s?(-\s.+?)?\s*(#\s.+?)?\s*$/ ) {
325 68 100       219 if ( $1 ) { # matched description
326 64         149 $testline->{description} = $1;
327 64         281 $testline->{description} =~ s/^-\s//;
328             }
329              
330 68 100       293 if ( $2 ) { # matched directive
331 5         17 $testline->{directive} = $2;
332 5         89 $testline->{directive} =~ s/^#\s//;
333 5 100       58 $testline->{todo}++ if ( $testline->{directive} =~ /TODO/i );
334 5 100       42 $testline->{skip}++ if ( $testline->{directive} =~ /skip/i );
335             }
336             }
337             }
338              
339 70         378 return $testline;
340             }
341              
342             sub _parse_subtest {
343 70     70   8434 my $self = shift;
344 70         84 my $subtest_ref = shift;
345              
346 70 50       165 return unless $subtest_ref;
347 70 100       83 return unless @{ $subtest_ref };
  70         230  
348              
349 13         82 my $subtest_result = {
350             plan => undef,
351             testline => [],
352             subtest => undef,
353             };
354              
355 13         24 my $indent;
356             {
357 13         19 $subtest_ref->[-1] =~ /^(\s+).*/;
  13         60  
358 13         32 $indent = length( $1 );
359             }
360              
361 13         23 my @subtest_more;
362 13         27 while( @{ $subtest_ref } ) {
  61         142  
363 48         49 my $subtest_line = shift @{ $subtest_ref };
  48         71  
364              
365 48         70 my ( $indent_current, $line );
366             {
367 48         48 $subtest_line =~ /^(\s+)(.*)/;
  48         175  
368 48         70 $indent_current = length( $1 );
369 48         84 $line = $2;
370             }
371              
372 48 100       101 if ( $indent_current > $indent ) {
373 14         25 push @subtest_more, $subtest_line;
374 14         20 next;
375             }
376              
377             # parse plan
378 34 100       107 if ( $line =~ /^1\.\.\d+/ ) {
379 13         40 $subtest_result->{plan} = $self->_parse_plan( $line );
380 13         29 next;
381             }
382              
383             # parse testline
384 21 50       93 if ( $line =~ /^(not )?ok/ ) {
385 21         85 my $subtest = $self->_parse_subtest( \@subtest_more );
386 21         27 push @{ $subtest_result->{testline} },
  21         65  
387             $self->_parse_testline( $line, $subtest );
388 21         42 next;
389             }
390             }
391              
392 13         32 return $subtest_result;
393             }
394              
395             1;
396              
397             __END__