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   794390 use strict;
  13         123  
  13         395  
4 13     13   69 use warnings;
  13         25  
  13         349  
5 13     13   287 use 5.008_001;
  13         42  
6              
7             our $VERSION = '0.17'; # VERSION
8              
9 13     13   5888 use parent qw( Test::Builder::Module );
  13         4217  
  13         86  
10             our @EXPORT = qw( synopsis_ok all_synopsis_ok );
11              
12 13     13   133045 use ExtUtils::Manifest qw( maniread );
  13         145316  
  13         7916  
13             my %ARGS;
14             # = ( dump_all_code_on_error => 1 ); ### REMOVE THIS FOR PRODUCTION!!!
15             sub all_synopsis_ok {
16 1     1 1 96 %ARGS = @_;
17              
18 1         7 my $manifest = maniread();
19 1 50       754 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         12 __PACKAGE__->builder->no_plan();
23              
24 1         223 synopsis_ok(@files);
25             }
26              
27             sub synopsis_ok {
28 13     13 1 7837 my @files = @_;
29              
30 13         50 for my $file (@files) {
31 14         576 my $blocks = _extract_synopsis($file);
32 14 100       75 unless (@$blocks) {
33 1         13 __PACKAGE__->builder->ok(1, "No SYNOPSIS code");
34 1         563 next;
35             }
36              
37 13         31 my $block_num = 0;
38 13         44 for my $block (@$blocks) {
39 15         959 $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         155 $code =~ s/(?=(?:__END__|__DATA__)\s*$)/}\n/m;
47              
48 15         64 $options = join(";", @$options);
49 15         88 my $test = qq($options;\nsub{\n#line $line "$file"\n$code\n;});
50             #use Test::More (); Test::More::note "=========\n$test\n========";
51 15         53 my $ok = _compile($test);
52              
53             # See if the user is trying to skip this test using the =for block
54 15 100 100     464 if ( !$ok and $@=~/^SKIP:.+BEGIN failed--compilation aborted/si ) {
55 1         5 $@ =~ s/^SKIP:\s*//;
56 1         5 $@ =~ s/\nBEGIN failed--compilation aborted at.+//s;
57 1         10 __PACKAGE__->builder->skip($@, 1);
58             } else {
59 14         39 my $block_name = $file;
60             ## Show block number only if more than one block
61 14 100       51 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       115 ? "$@\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   3478 eval sprintf "package\nTest::Synopsis::Sandbox%d;\n%s",
80             ++$sandbox, $_[0]; ## no critic
81             }
82              
83             sub _extract_synopsis
84             {
85 14     14   39 my $file = shift;
86              
87 14         99 my $parser = Test::Synopsis::Parser->new;
88 14         82 $parser->parse_file($file);
89             $parser->{tsyn_blocks}
90 14         695 }
91              
92             package
93             Test::Synopsis::Parser; # on new line to avoid indexing
94              
95 13     13   8784 use Pod::Simple 3.09;
  13         396014  
  13         467  
96 13     13   101 use parent 'Pod::Simple';
  13         29  
  13         93  
97              
98             sub new
99             {
100 14     14   173 my $self = shift->SUPER::new(@_);
101 14         545 $self->accept_targets('test_synopsis');
102 14         422 $self->merge_text(1);
103 14         194 $self->no_errata_section(1);
104             $self->strip_verbatim_indent(sub {
105 35     35   13161 my $lines = shift;
106 35         148 my ($indent) = $lines->[0] =~ /^(\s*)/;
107 35         93 $indent
108 14         189 });
109              
110 14         95 $self->{tsyn_stack} = [];
111 14         159 $self->{tsyn_options} = [];
112 14         40 $self->{tsyn_blocks} = [];
113 14         35 $self->{tsyn_in_synopsis} = '';
114              
115 14         30 $self
116             }
117              
118             sub _handle_element_start
119             {
120 304     304   113977 my ($self, $element_name, $attrs) = @_;
121              
122             #Test::More::note Test::More::explain($element_name);
123             #Test::More::note Test::More::explain($attrs);
124 304         414 push @{$self->{tsyn_stack}}, [ $element_name, $attrs ];
  304         861  
125             }
126              
127             sub _handle_element_end
128             {
129 304 100   304   2418 return unless $_[0]->{tsyn_stack};
130 295         372 pop @{ $_[0]->{tsyn_stack} };
  295         642  
131             }
132              
133             sub _handle_text
134             {
135 356 50   356   3285 return unless $_[0]->{tsyn_stack};
136 356         611 my ($self, $text) = @_;
137 356         563 my $elt = $self->{tsyn_stack}[-1][0];
138 356 100 66     1356 if ($elt eq 'head1') {
    100          
    100          
139 48 100       121 if ($self->{tsyn_in_synopsis}) {
140             # Exiting SYNOPSIS => Skip everything to the end
141 9         31 delete $self->{tsyn_stack};
142             }
143 48         191 $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         24 my $file = $self->source_filename;
150 4         31 push @{$self->{tsyn_options}}, qq<#line $line "$file"\n$text\n>;
  4         35  
151             }
152             } elsif ($elt eq 'Verbatim' && $self->{tsyn_in_synopsis}) {
153 15         39 my $line = $self->{tsyn_stack}[-1][1]{start_line};
154 15         56 push @{ $self->{tsyn_blocks} }, [
155             $line,
156             $text,
157             $self->{tsyn_options},
158 15         23 ];
159 15         44 $self->{tsyn_options} = [];
160             }
161             }
162              
163              
164             1;
165             __END__