File Coverage

blib/lib/Test/Synopsis.pm
Criterion Covered Total %
statement 94 94 100.0
branch 22 26 84.6
condition 6 9 66.6
subroutine 18 18 100.0
pod 2 2 100.0
total 142 149 95.3


line stmt bran cond sub pod time code
1             package Test::Synopsis;
2              
3 13     13   760339 use strict;
  13         118  
  13         399  
4 13     13   68 use warnings;
  13         27  
  13         399  
5 13     13   298 use 5.008_001;
  13         44  
6              
7             our $VERSION = '0.16'; # VERSION
8              
9 13     13   5691 use parent qw( Test::Builder::Module );
  13         4133  
  13         70  
10             our @EXPORT = qw( synopsis_ok all_synopsis_ok );
11              
12 13     13   127408 use ExtUtils::Manifest qw( maniread );
  13         142170  
  13         7356  
13             my %ARGS;
14             # = ( dump_all_code_on_error => 1 ); ### REMOVE THIS FOR PRODUCTION!!!
15             sub all_synopsis_ok {
16 1     1 1 98 %ARGS = @_;
17              
18 1         8 my $manifest = maniread();
19 1 50       767 my @files = grep m!^lib/.*\.p(od|m)$!, keys %$manifest
20             or __PACKAGE__->builder->skip_all('No files in lib to test');
21              
22 1         15 __PACKAGE__->builder->no_plan();
23              
24 1         219 synopsis_ok(@files);
25             }
26              
27             sub synopsis_ok {
28 13     13 1 7945 my @files = @_;
29              
30 13         46 for my $file (@files) {
31 14         555 my $blocks = _extract_synopsis($file);
32 14 100       76 unless (@$blocks) {
33 1         12 __PACKAGE__->builder->ok(1, "No SYNOPSIS code");
34 1         576 next;
35             }
36              
37 13         32 my $block_num = 0;
38 13         45 for my $block (@$blocks) {
39 15         832 $block_num++;
40 15         47 my ($line, $code, $options) = @$block;
41              
42             # don't want __END__ blocks in SYNOPSIS chopping our '}' in wrapper sub
43             # same goes for __DATA__ and although we'll be sticking an extra '}'
44             # into its contents; it shouldn't matter since the code shouldn't be
45             # run anyways.
46 15         152 $code =~ s/(?=(?:__END__|__DATA__)\s*$)/}\n/m;
47              
48 15         54 $options = join(";", @$options);
49 15         93 my $test = qq($options;\nsub{\n#line $line "$file"\n$code\n;});
50             #use Test::More (); Test::More::note "=========\n$test\n========";
51 15         56 my $ok = _compile($test);
52              
53             # See if the user is trying to skip this test using the =for block
54 15 100 100     458 if ( !$ok and $@=~/^SKIP:.+BEGIN failed--compilation aborted/si ) {
55 1         6 $@ =~ s/^SKIP:\s*//;
56 1         6 $@ =~ s/\nBEGIN failed--compilation aborted at.+//s;
57 1         11 __PACKAGE__->builder->skip($@, 1);
58             } else {
59 14         37 my $block_name = $file;
60             ## Show block number only if more than one block
61 14 100       52 if (@$blocks > 1) {
62 4         13 $block_name .= " (section $block_num)";
63             }
64             __PACKAGE__->builder->ok($ok, $block_name)
65             or __PACKAGE__->builder->diag(
66             $ARGS{dump_all_code_on_error}
67 14 50       129 ? "$@\nEVALED CODE:\n$test"
    100          
68             : $@
69             );
70             }
71             }
72             }
73             }
74              
75             my $sandbox = 0;
76             sub _compile {
77             package
78             Test::Synopsis::Sandbox;
79 15     15   3293 eval sprintf "package\nTest::Synopsis::Sandbox%d;\n%s",
80             ++$sandbox, $_[0]; ## no critic
81             }
82              
83             sub _extract_synopsis
84             {
85 14     14   40 my $file = shift;
86              
87 14         105 my $parser = Test::Synopsis::Parser->new;
88 14         92 $parser->parse_file($file);
89             $parser->{tsyn_blocks}
90 14         617 }
91              
92             package
93             Test::Synopsis::Parser; # on new line to avoid indexing
94              
95 13     13   8860 use Pod::Simple 3.09;
  13         379656  
  13         513  
96 13     13   117 use parent 'Pod::Simple';
  13         27  
  13         105  
97              
98             sub new
99             {
100 14     14   187 my $self = shift->SUPER::new(@_);
101 14         539 $self->accept_targets('test_synopsis');
102 14         458 $self->merge_text(1);
103 14         196 $self->no_errata_section(1);
104             $self->strip_verbatim_indent(sub {
105 35     35   13287 my $lines = shift;
106 35         149 my ($indent) = $lines->[0] =~ /^(\s*)/;
107 35         92 $indent
108 14         194 });
109              
110 14         103 $self->{tsyn_stack} = [];
111 14         169 $self->{tsyn_options} = [];
112 14         46 $self->{tsyn_blocks} = [];
113 14         37 $self->{tsyn_in_synopsis} = '';
114              
115 14         32 $self
116             }
117              
118             sub _handle_element_start
119             {
120 302     302   100245 my ($self, $element_name, $attrs) = @_;
121              
122             #Test::More::note Test::More::explain($element_name);
123             #Test::More::note Test::More::explain($attrs);
124 302         415 push @{$self->{tsyn_stack}}, [ $element_name, $attrs ];
  302         844  
125             }
126              
127             sub _handle_element_end
128             {
129 302 100   302   2266 return unless $_[0]->{tsyn_stack};
130 293         365 pop @{ $_[0]->{tsyn_stack} };
  293         575  
131             }
132              
133             sub _handle_text
134             {
135 352 50   352   3115 return unless $_[0]->{tsyn_stack};
136 352         597 my ($self, $text) = @_;
137 352         541 my $elt = $self->{tsyn_stack}[-1][0];
138 352 100 66     1262 if ($elt eq 'head1') {
    100          
    100          
139 48 100       119 if ($self->{tsyn_in_synopsis}) {
140             # Exiting SYNOPSIS => Skip everything to the end
141 9         35 delete $self->{tsyn_stack};
142             }
143 48         201 $self->{tsyn_in_synopsis} = $text =~ /SYNOPSIS\s*$/;
144             } elsif ($elt eq 'Data') {
145             # use Test::More; Test::More::note "XXX";
146 4         10 my $up = $self->{tsyn_stack}[-2];
147 4 50 33     32 if ($up->[0] eq 'for' && $up->[1]->{target} eq 'test_synopsis') {
148 4         9 my $line = $up->[1]{start_line};
149 4         25 my $file = $self->source_filename;
150 4         32 push @{$self->{tsyn_options}}, qq<#line $line "$file"\n$text\n>;
  4         28  
151             }
152             } elsif ($elt eq 'Verbatim' && $self->{tsyn_in_synopsis}) {
153 15         40 my $line = $self->{tsyn_stack}[-1][1]{start_line};
154 15         48 push @{ $self->{tsyn_blocks} }, [
155             $line,
156             $text,
157             $self->{tsyn_options},
158 15         29 ];
159 15         50 $self->{tsyn_options} = [];
160             }
161             }
162              
163              
164             1;
165             __END__