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   161082 use strict;
  13         19  
  13         308  
4 13     13   41 use warnings;
  13         11  
  13         225  
5 13     13   211 use 5.008_001;
  13         27  
6              
7             our $VERSION = '0.15'; # VERSION
8              
9 13     13   4843 use parent qw( Test::Builder::Module );
  13         3003  
  13         51  
10             our @EXPORT = qw( synopsis_ok all_synopsis_ok );
11              
12 13     13   23906 use ExtUtils::Manifest qw( maniread );
  13         102452  
  13         6140  
13             my %ARGS;
14             # = ( dump_all_code_on_error => 1 ); ### REMOVE THIS FOR PRODUCTION!!!
15             sub all_synopsis_ok {
16 1     1 1 4 %ARGS = @_;
17              
18 1         3 my $manifest = maniread();
19 1 50       381 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         8 __PACKAGE__->builder->no_plan();
23              
24 1         16 synopsis_ok(@files);
25             }
26              
27             sub synopsis_ok {
28 13     13 1 829 my @files = @_;
29              
30 13         28 for my $file (@files) {
31 14         282 my $blocks = _extract_synopsis($file);
32 14 100       51 unless (@$blocks) {
33 1         13 __PACKAGE__->builder->ok(1, "No SYNOPSIS code");
34 1         393 next;
35             }
36              
37 13         18 my $block_num = 0;
38 13         29 for my $block (@$blocks) {
39 15         464 $block_num++;
40 15         44 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         123 $code =~ s/(?=(?:__END__|__DATA__)\s*$)/}\n/m;
47              
48 15         34 $options = join(";", @$options);
49 15         57 my $test = qq($options;\nsub{\n#line $line "$file"\n$code\n;});
50             #use Test::More (); Test::More::note "=========\n$test\n========";
51 15         34 my $ok = _compile($test);
52              
53             # See if the user is trying to skip this test using the =for block
54 15 100 100     330 if ( !$ok and $@=~/^SKIP:.+BEGIN failed--compilation aborted/si ) {
55 1         3 $@ =~ s/^SKIP:\s*//;
56 1         3 $@ =~ s/\nBEGIN failed--compilation aborted at.+//s;
57 1         8 __PACKAGE__->builder->skip($@, 1);
58             } else {
59 14         22 my $block_name = $file;
60             ## Show block number only if more than one block
61 14 100       34 if (@$blocks > 1) {
62 4         10 $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       112 ? "$@\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   2407 eval sprintf "package\nTest::Synopsis::Sandbox%d;\n%s",
80             ++$sandbox, $_[0]; ## no critic
81             }
82              
83             sub _extract_synopsis
84             {
85 14     14   21 my $file = shift;
86              
87 14         81 my $parser = Test::Synopsis::Parser->new;
88 14         59 $parser->parse_file($file);
89             $parser->{tsyn_blocks}
90 14         442 }
91              
92             package
93             Test::Synopsis::Parser; # on new line to avoid indexing
94              
95 13     13   7688 use Pod::Simple 3.09;
  13         268024  
  13         350  
96 13     13   87 use parent 'Pod::Simple';
  13         16  
  13         67  
97              
98             sub new
99             {
100 14     14   119 my $self = shift->SUPER::new(@_);
101 14         362 $self->accept_targets('test_synopsis');
102 14         305 $self->merge_text(1);
103 14         145 $self->no_errata_section(1);
104             $self->strip_verbatim_indent(sub {
105 35     35   7106 my $lines = shift;
106 35         103 my ($indent) = $lines->[0] =~ /^(\s*)/;
107 35         65 $indent
108 14         132 });
109              
110 14         68 $self->{tsyn_stack} = [];
111 14         33 $self->{tsyn_options} = [];
112 14         26 $self->{tsyn_blocks} = [];
113 14         25 $self->{tsyn_in_synopsis} = '';
114              
115 14         20 $self
116             }
117              
118             sub _handle_element_start
119             {
120 302     302   61784 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         226 push @{$self->{tsyn_stack}}, [ $element_name, $attrs ];
  302         646  
125             }
126              
127             sub _handle_element_end
128             {
129 302 100   302   1503 return unless $_[0]->{tsyn_stack};
130 293         173 pop @{ $_[0]->{tsyn_stack} };
  293         426  
131             }
132              
133             sub _handle_text
134             {
135 352 50   352   1801 return unless $_[0]->{tsyn_stack};
136 352         301 my ($self, $text) = @_;
137 352         310 my $elt = $self->{tsyn_stack}[-1][0];
138 352 100 66     1161 if ($elt eq 'head1') {
    100          
    100          
139 48 100       90 if ($self->{tsyn_in_synopsis}) {
140             # Exiting SYNOPSIS => Skip everything to the end
141 9         25 delete $self->{tsyn_stack};
142             }
143 48         146 $self->{tsyn_in_synopsis} = $text =~ /SYNOPSIS\s*$/;
144             } elsif ($elt eq 'Data') {
145             # use Test::More; Test::More::note "XXX";
146 4         7 my $up = $self->{tsyn_stack}[-2];
147 4 50 33     30 if ($up->[0] eq 'for' && $up->[1]->{target} eq 'test_synopsis') {
148 4         6 my $line = $up->[1]{start_line};
149 4         18 my $file = $self->source_filename;
150 4         22 push @{$self->{tsyn_options}}, qq<#line $line "$file"\n$text\n>;
  4         48  
151             }
152             } elsif ($elt eq 'Verbatim' && $self->{tsyn_in_synopsis}) {
153 15         21 my $line = $self->{tsyn_stack}[-1][1]{start_line};
154 15         118 push @{ $self->{tsyn_blocks} }, [
155             $line,
156             $text,
157             $self->{tsyn_options},
158 15         15 ];
159 15         39 $self->{tsyn_options} = [];
160             }
161             }
162              
163              
164             1;
165             __END__
166              
167             =encoding utf-8
168              
169             =for stopwords Goro blogged Znet Zoffix DOHERTY Doherty
170             KRYDE Ryde ZOFFIX Gr nauer Grünauer pm HEREDOC HEREDOCs DROLSKY
171              
172             =for test_synopsis $main::for_checked=1
173              
174             =head1 NAME
175              
176             Test::Synopsis - Test your SYNOPSIS code
177              
178             =head1 SYNOPSIS
179              
180             # xt/synopsis.t (with Module::Install::AuthorTests)
181 2     2   16 use Test::Synopsis;
  2         2  
  2         17  
182             all_synopsis_ok();
183              
184             # Or, run safe without Test::Synopsis
185 2     2   805 use Test::More;
  2         3459  
  2         15  
186             eval "use Test::Synopsis";
187             plan skip_all => "Test::Synopsis required for testing" if $@;
188             all_synopsis_ok();
189              
190             =head1 DESCRIPTION
191              
192             Test::Synopsis is an (author) test module to find .pm or .pod files
193             under your I<lib> directory and then make sure the example snippet
194             code in your I<SYNOPSIS> section passes the perl compile check.
195              
196             Note that this module only checks the perl syntax (by wrapping the
197             code with C<sub>) and doesn't actually run the code, B<UNLESS>
198             that code is a C<BEGIN {}> block or a C<use> statement.
199              
200             Suppose you have the following POD in your module.
201              
202             =head1 NAME
203              
204             Awesome::Template - My awesome template
205              
206             =head1 SYNOPSIS
207              
208             use Awesome::Template;
209              
210             my $template = Awesome::Template->new;
211             $tempalte->render("template.at");
212              
213             =head1 DESCRIPTION
214              
215             An user of your module would try copy-paste this synopsis code and
216             find that this code doesn't compile because there's a typo in your
217             variable name I<$tempalte>. Test::Synopsis will catch that error
218             before you ship it.
219              
220             =head1 VARIABLE DECLARATIONS
221              
222             Sometimes you might want to put some undeclared variables in your
223             synopsis, like:
224              
225             =head1 SYNOPSIS
226              
227             use Data::Dumper::Names;
228             print Dumper($scalar, \@array, \%hash);
229              
230             This assumes these variables like I<$scalar> are defined elsewhere in
231             module user's code, but Test::Synopsis, by default, will complain that
232             these variables are not declared:
233              
234             Global symbol "$scalar" requires explicit package name at ...
235              
236             In this case, you can add the following POD sequence elsewhere in your POD:
237              
238             =for test_synopsis
239             no strict 'vars'
240              
241             Or more explicitly,
242              
243             =for test_synopsis
244             my($scalar, @array, %hash);
245              
246             Test::Synopsis will find these C<=for> blocks and these statements are
247             prepended before your SYNOPSIS code when being evaluated, so those
248             variable name errors will go away, without adding unnecessary bits in
249             SYNOPSIS which might confuse users.
250              
251             =head1 SKIPPING TEST FROM INSIDE THE POD
252              
253             You can use a C<BEGIN{}> block in the C<=for test_synopsis> to check for
254             specific conditions (e.g. if a module is present), and possibly skip
255             the test.
256              
257             If you C<die()> inside the C<BEGIN{}> block and the die message begins
258             with sequence C<SKIP:> (note the colon at the end), the test
259             will be skipped for that document.
260              
261             =head1 SYNOPSIS
262              
263             =for test_synopsis BEGIN { die "SKIP: skip this pod, it's horrible!\n"; }
264              
265             $x; # undeclared variable, but we skipped the test!
266              
267             =end
268              
269             =head1 EXPORTED SUBROUTINES
270              
271             =head2 C<all_synopsis_ok>
272              
273             all_synopsis_ok();
274              
275             all_synopsis_ok( dump_all_code_on_error => 1 );
276              
277             Checks the SYNOPSIS code in all your modules. Takes B<optional>
278             arguments as key/value pairs. Possible arguments are as follows:
279              
280             =head3 C<dump_all_code_on_error>
281              
282             all_synopsis_ok( dump_all_code_on_error => 1 );
283              
284             Takes true or false values as a value. B<Defaults to:> false. When
285             set to a true value, if an error is discovered in the SYNOPSIS code,
286             the test will dump the entire snippet of code it tried to test. Use this
287             if you want to copy/paste and play around with the code until the error
288             is fixed.
289              
290             The dumped code will include any of the C<=for> code you specified (see
291             L<VARIABLE DECLARATIONS> section above) as well as a few internal bits
292             this test module uses to make SYNOPSIS code checking possible.
293              
294             B<Note:> you will likely have to remove the C<#> and a space at the start
295             of each line (C<perl -pi -e 's/^#\s//;' TEMP_FILE_WITH_CODE>)
296              
297             =head2 C<synopsis_ok>
298              
299             use Test::More tests => 1;
300             use Test::Synopsis;
301             synopsis_ok("t/lib/NoPod.pm");
302             synopsis_ok(qw/Pod1.pm Pod2.pm Pod3.pm/);
303              
304             Lets you test a single file. B<Note:> you must setup your own plan if
305             you use this subroutine (e.g. with C<< use Test::More tests => 1; >>).
306             B<Takes> a list of filenames for documents containing SYNOPSIS code to test.
307              
308             =head1 CAVEATS
309              
310             This module will not check code past the C<__END__> or
311             C<__DATA__> tokens, if one is
312             present in the SYNOPSIS code.
313              
314             This module will actually execute C<use> statements and any code
315             you specify in the C<BEGIN {}> blocks in the SYNOPSIS.
316              
317             If you're using HEREDOCs in your SYNOPSIS, you will need to place
318             the ending of the HEREDOC at the same indent as the
319             first line of the code of your SYNOPSIS.
320              
321             Redefinition warnings can be turned off with
322              
323             =for test_synopsis
324             no warnings 'redefine';
325              
326             =head1 REPOSITORY
327              
328             Fork this module on GitHub:
329             L<https://github.com/miyagawa/Test-Synopsis>
330              
331             =head1 BUGS
332              
333             To report bugs or request features, please use
334             L<https://github.com/miyagawa/Test-Synopsis/issues>
335              
336             If you can't access GitHub, you can email your request
337             to C<bug-Test-Synopsis at rt.cpan.org>
338              
339             =head1 AUTHOR
340              
341             Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>
342              
343             Goro Fuji blogged about the original idea at
344             L<http://d.hatena.ne.jp/gfx/20090224/1235449381> based on the testing
345             code taken from L<Test::Weaken>.
346              
347             =head1 MAINTAINER
348              
349             Zoffix Znet <cpan (at) zoffix.com>
350              
351             =head1 CONTRIBUTORS
352              
353             =over 4
354              
355             =item * Dave Rolsky (L<DROLSKY|https://metacpan.org/author/DROLSKY>)
356              
357             =item * Kevin Ryde (L<KRYDE|https://metacpan.org/author/KRYDE>)
358              
359             =item * Marcel Grünauer (L<MARCEL|https://metacpan.org/author/MARCEL>)
360              
361             =item * Mike Doherty (L<DOHERTY|https://metacpan.org/author/DOHERTY>)
362              
363             =item * Patrice Clement (L<monsieurp|https://github.com/monsieurp>)
364              
365             =item * Greg Sabino Mullane (L<TURNSTEP|https://metacpan.org/author/TURNSTEP>)
366              
367             =item * Zoffix Znet (L<ZOFFIX|https://metacpan.org/author/ZOFFIX>)
368              
369             =item * Olivier Mengué (L<DOLMEN|https://metacpan.org/author/DOLMEN>)
370              
371             =back
372              
373             =head1 LICENSE
374              
375             This library is free software; you can redistribute it and/or modify
376             it under the same terms as Perl itself.
377              
378             =head1 COPYRIGHT
379              
380             This library is Copyright (c) Tatsuhiko Miyagawa
381              
382             =head1 SEE ALSO
383              
384             L<Test::Pod>, L<Test::UseAllModules>, L<Test::Inline>
385              
386             =cut