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   35058 use 5.008005;
  10         34  
  10         391  
3 10     10   75 use strict;
  10         16  
  10         272  
4 10     10   56 use warnings;
  10         17  
  10         469  
5 10     10   8681 use parent qw/Test::Builder::Module/;
  10         3405  
  10         49  
6              
7             my @test_more_exports;
8 10     10   136340 BEGIN { @test_more_exports = (qw/done_testing/) }
9 10     10   11198 use PPI::Tokenizer;
  10         1067582  
  10         1576  
10 10     10   16170 use ExtUtils::Manifest qw/maniread/;
  10         108865  
  10         931  
11 10     10   11278 use Test::More import => \@test_more_exports;
  10         53459  
  10         119  
12 10     10   9135 use Test::Synopsis::Expectation::Pod;
  10         43  
  10         11674  
13              
14             our $VERSION = "0.11";
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 9 $prepared = shift;
22             }
23              
24             sub set_ignorings {
25 1     1 1 10 $ignorings = shift;
26 1 50       7 $ignorings = [$ignorings] if ref $ignorings ne 'ARRAY';
27              
28 1         4 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 380 my ($files) = @_;
41              
42 9 100       65 $files = [$files] if ref $files ne 'ARRAY';
43 9         29 for my $file (@$files) {
44 10         45 _synopsis_ok($file);
45             }
46             }
47              
48             sub _synopsis_ok {
49 10     10   25 my ($file) = @_;
50              
51 10         32 local $Test::Builder::Level = $Test::Builder::Level + 1;
52              
53 10         130 my $parser = Test::Synopsis::Expectation::Pod->new;
54 10         125 $parser->parse_file($file);
55              
56 10         191 my $block_num = 1;
57 10         24 for my $target_code (@{$parser->{target_codes}}) {
  10         33  
58 12         74 my ($expectations, $code) = _analyze_target_code($target_code);
59              
60 12         119 _check_syntax($code, $block_num, $file);
61 12         8846 for my $expectation (@$expectations) {
62 24         9038 _check_with_expectation($expectation, $block_num, $file);
63             }
64              
65 12         5635 $block_num++;
66             }
67             }
68              
69             sub _check_syntax {
70             package Test::Synopsis::Expectation::Sandbox;
71 12     12   1158 eval $_[0]; ## no critic
  1     1   8  
  1         2  
  1         57  
72 12 50       56 if ($@) {
73 0         0 Test::More::fail("Syntax OK: $_[2] (SYNOPSIS Block: $_[1])");
74             }
75             else {
76 12         117 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   1959 my $got = eval $_[0]->{code}; ## no critic
  1     1   7  
  1     1   2  
  1     1   36  
  1     1   5  
  1         2  
  1         39  
  1         6  
  1         2  
  1         124  
  1         5  
  1         2  
  1         52  
85 24         1137 my $expected = eval $_[0]->{expected}; ## no critic
86 24         81 my $method = $_[0]->{method};
87 24         125 my $test_name = "$_[2] (SYNOPSIS Block: $_[1], Line: $_[0]->{line_num})";
88              
89 24 100       107 if ($method eq 'is') {
    100          
    100          
    100          
    50          
90 19         77 Test::More::is($got, $expected, $test_name);
91             } elsif ($method eq 'isa') {
92 1         5 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         5 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   33 my ($target_code) = @_;
104              
105 12         19 my $deficient_brace = 0;
106 12   100     72 my $code = $prepared || ''; # code for test
107 12         21 my @expectations; # store expectations for test
108 12         20 my $line_num = 1;
109 12         151 for my $line (split /\n\r?/, $target_code) {
110 44         763 my $tokens = PPI::Tokenizer->new(\$line)->all_tokens;
111              
112 44 100       29245 if (grep {$_->{content} eq '...' && $_->isa('PPI::Token::Operator')} @$tokens) {
  256 100       1764  
113 1         6 next;
114             }
115              
116 43         104 for my $ignoring (@$ignorings) {
117 4         27 $line =~ s/\Q$ignoring\E//g;
118             }
119              
120 43         97 $code .= "$line\n";
121              
122             # Count the number of left braces to complete deficient right braces
123 43 100       79 $deficient_brace++ if (grep {$_->{content} eq '{' && $_->isa('PPI::Token::Structure')} @$tokens);
  254 100       830  
124 43 100       71 $deficient_brace-- if (grep {$_->{content} eq '}' && $_->isa('PPI::Token::Structure')} @$tokens);
  254 100       685  
125              
126             # Extract comment statement
127             # Tokens contain one comment token on a line, at the most
128 43 100       74 if (my ($comment) = grep {$_->isa('PPI::Token::Comment')} @$tokens) {
  254         1388  
129             # Accept special comment for this module
130             # e.g.
131             # # => is 42
132 24         147 my ($expectation) = $comment->{content} =~ /#\s*=>\s*(.+)/;
133 24 50       83 next unless $expectation;
134              
135             # Accept test methods as string
136 24         34 my $method;
137 24 100       110 if ($expectation =~ s/^(?:(is|isa|is_deeply|like)\s|(success))//) {
138 7   66     35 $method = $1 || $2;
139             }
140              
141 24   100     289 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         215 $line_num++;
150             }
151              
152 12         314 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__