File Coverage

blib/lib/Test/Synopsis/Expectation.pm
Criterion Covered Total %
statement 100 112 89.2
branch 30 36 83.3
condition 6 7 85.7
subroutine 21 23 91.3
pod 4 4 100.0
total 161 182 88.4


line stmt bran cond sub pod time code
1             package Test::Synopsis::Expectation;
2 10     10   22039 use 5.008005;
  10         26  
  10         326  
3 10     10   67 use strict;
  10         14  
  10         243  
4 10     10   42 use warnings;
  10         13  
  10         256  
5 10     10   4939 use parent qw/Test::Builder::Module/;
  10         2671  
  10         45  
6              
7             my @test_more_exports;
8 10     10   88077 BEGIN { @test_more_exports = (qw/done_testing/) }
9 10     10   5596 use PPI::Tokenizer;
  10         842297  
  10         372  
10 10     10   5571 use ExtUtils::Manifest qw/maniread/;
  10         72391  
  10         818  
11 10     10   6663 use Test::More import => \@test_more_exports;
  10         38222  
  10         116  
12 10     10   6610 use Test::Synopsis::Expectation::Pod;
  10         35  
  10         9807  
13              
14             our $VERSION = "0.12";
15             our @EXPORT = (@test_more_exports, qw/all_synopsis_ok synopsis_ok/);
16              
17             my $prepared = '';
18             my $ignorings = [];
19              
20             sub prepare {
21 1     1 1 7 $prepared = shift;
22             }
23              
24             sub set_ignorings {
25 1     1 1 8 $ignorings = shift;
26 1 50       4 $ignorings = [$ignorings] if ref $ignorings ne 'ARRAY';
27              
28 1         2 return $ignorings;
29             }
30              
31             sub all_synopsis_ok {
32 0     0 1 0 my $builder = __PACKAGE__->builder;
33 0         0 my @files = _list_up_files_from_manifest($builder);
34 0         0 for my $file (@files) {
35 0         0 _synopsis_ok($file);
36             }
37             }
38              
39             sub synopsis_ok {
40 9     9 1 318 my ($files) = @_;
41              
42 9 100       52 $files = [$files] if ref $files ne 'ARRAY';
43 9         29 for my $file (@$files) {
44 10         33 _synopsis_ok($file);
45             }
46             }
47              
48             sub _synopsis_ok {
49 10     10   21 my ($file) = @_;
50              
51 10         53 local $Test::Builder::Level = $Test::Builder::Level + 1;
52              
53 10         115 my $parser = Test::Synopsis::Expectation::Pod->new;
54 10         100 $parser->parse_file($file);
55              
56 10         164 my $block_num = 1;
57 10         31 for my $target_code (@{$parser->{target_codes}}) {
  10         32  
58 12         43 my ($expectations, $code) = _analyze_target_code($target_code);
59              
60 12         69 _check_syntax($code, $block_num, $file);
61 12         4542 for my $expectation (@$expectations) {
62 24         3383 _check_with_expectation($expectation, $block_num, $file);
63             }
64              
65 12         3925 $block_num++;
66             }
67             }
68              
69             sub _check_syntax {
70             package Test::Synopsis::Expectation::Sandbox;
71 12     12   944 eval $_[0]; ## no critic
  1     1   6  
  1         1  
  1         43  
72 12 50       49 if ($@) {
73 0         0 Test::More::fail("Syntax OK: $_[2] (SYNOPSIS Block: $_[1])");
74             }
75             else {
76 12         135 Test::More::pass("Syntax OK: $_[2] (SYNOPSIS Block: $_[1])");
77             }
78             }
79              
80             sub _check_with_expectation {
81             package Test::Synopsis::Expectation::Sandbox;
82              
83             # $_[0] is expectation
84 24     24   1481 my $got = eval $_[0]->{code}; ## no critic
  1     1   4  
  1     1   2  
  1     1   29  
  1     1   4  
  1         2  
  1         33  
  1         5  
  1         1  
  1         48  
  1         5  
  1         2  
  1         46  
85 24         939 my $expected = eval $_[0]->{expected}; ## no critic
86 24         68 my $method = $_[0]->{method};
87 24         114 my $test_name = "$_[2] (SYNOPSIS Block: $_[1], Line: $_[0]->{line_num})";
88              
89 24 100       96 if ($method eq 'is') {
    100          
    100          
    100          
    50          
90 19         62 Test::More::is($got, $expected, $test_name);
91             } elsif ($method eq 'isa') {
92 1         6 Test::More::isa_ok($got, $expected, $test_name);
93             } elsif ($method eq 'like') {
94 1         4 Test::More::like($got, $expected, $test_name);
95             } elsif ($method eq 'is_deeply') {
96 1         4 Test::More::is_deeply($got, $expected, $test_name);
97             } elsif ($method eq 'success') {
98 2         11 Test::More::ok($got, $test_name);
99             }
100             }
101              
102             sub _analyze_target_code {
103 12     12   21 my ($target_code) = @_;
104              
105 12         19 my $deficient_brace = 0;
106 12   100     66 my $code = $prepared || ''; # code for test
107 12         19 my @expectations; # store expectations for test
108 12         16 my $line_num = 1;
109 12         77 for my $line (split /\n\r?/, $target_code) {
110 44         522 my $tokens = PPI::Tokenizer->new(\$line)->all_tokens;
111              
112 44 100       22609 if (grep {$_->{content} eq '...' && $_->isa('PPI::Token::Operator')} @$tokens) {
  256 100       1306  
113 1         6 next;
114             }
115              
116 43         80 for my $ignoring (@$ignorings) {
117 4         27 $line =~ s/\Q$ignoring\E//g;
118             }
119              
120 43         84 $code .= "$line\n";
121              
122             # Count the number of left braces to complete deficient right braces
123 43 100       62 $deficient_brace++ if (grep {$_->{content} eq '{' && $_->isa('PPI::Token::Structure')} @$tokens);
  254 100       537  
124 43 100       55 $deficient_brace-- if (grep {$_->{content} eq '}' && $_->isa('PPI::Token::Structure')} @$tokens);
  254 100       507  
125              
126             # Extract comment statement
127             # Tokens contain one comment token on a line, at the most
128 43 100       67 if (my ($comment) = grep {$_->isa('PPI::Token::Comment')} @$tokens) {
  254         925  
129             # Accept special comment for this module
130             # e.g.
131             # # => is 42
132 24         140 my ($expectation) = $comment->{content} =~ /#\s*=>\s*(.+)/;
133 24 50       65 next unless $expectation;
134              
135             # Accept test methods as string
136 24         31 my $method;
137 24 100       76 if ($expectation =~ s/^(?:(is|isa|is_deeply|like)\s|(success))//) {
138 7   66     27 $method = $1 || $2;
139             }
140              
141 24   100     248 push @expectations, +{
142             'method' => $method || 'is',
143             'expected' => $expectation,
144             'code' => $code . ('}' x $deficient_brace),
145             'line_num' => $line_num,
146             };
147             }
148              
149 43         199 $line_num++;
150             }
151              
152 12         251 return (\@expectations, $code);
153             }
154              
155             sub _list_up_files_from_manifest {
156 0     0   0 my ($builder) = @_;
157              
158 0         0 my $manifest = $ExtUtils::Manifest::MANIFEST;
159 0 0       0 if ( not -f $manifest ) {
160 0         0 $builder->plan( skip_all => "$manifest doesn't exist" );
161             }
162 0         0 return grep { m!\Alib/.*\.pm\Z! } keys %{ maniread() };
  0         0  
  0         0  
163             }
164             1;
165             __END__