File Coverage

blib/lib/Test/MetaSyntactic.pm
Criterion Covered Total %
statement 232 251 92.4
branch 32 66 48.4
condition 13 37 35.1
subroutine 43 43 100.0
pod 13 13 100.0
total 333 410 81.2


line stmt bran cond sub pod time code
1             package Test::MetaSyntactic;
2 2     2   2422 use strict;
  2         11  
  2         59  
3 2     2   10 use warnings;
  2         4  
  2         46  
4 2     2   476 use Acme::MetaSyntactic ();
  2         5  
  2         47  
5 2     2   13 use Config ();
  2         3  
  2         37  
6              
7 2     2   9 use base 'Test::Builder::Module';
  2         5  
  2         1546  
8              
9             our @EXPORT = qw( all_themes_ok theme_ok );
10             our $VERSION = '1.005';
11              
12             #
13             # exported functions
14             #
15              
16             sub all_themes_ok {
17 1     1 1 90 my (@lib) = @_;
18 1 50       7 @lib = _starting_points() if !@lib;
19 1         12 my %source = Acme::MetaSyntactic->_find_themes(@lib);
20              
21 1         15 my $tb = __PACKAGE__->builder;
22 1         21 local $Test::Builder::Level = $Test::Builder::Level + 1;
23 1         8 $tb->plan( tests => scalar keys %source );
24 1         771 my @fail;
25 1   33     9 theme_ok( $_, $source{$_} ) or push @fail, $_ for sort keys %source;
26 1 50       2168 $tb->diag("Test suite failed for the following:") if @fail;
27 1         186 $tb->diag("- $_") for @fail;
28             }
29              
30             sub theme_ok {
31 3     3 1 4323 my @args = @_;
32 3         15 my $tb = __PACKAGE__->builder;
33 3         33 local $Test::Builder::Level = $Test::Builder::Level + 1;
34              
35             # all subtests
36 3         9 my $theme = $args[0];
37             $tb->subtest(
38             $theme,
39             sub {
40 3     3   2519 $tb->subtest( "$theme fixme", sub { subtest_fixme(@args); } );
  3         2626  
41 3         3314 $tb->subtest( "$theme encoding", sub { subtest_encoding(@args); } );
  3         2581  
42 3         2546 $tb->subtest( "$theme load", sub { subtest_load(@args); } )
43 3 50       3209 or return;
44 3         8589 $tb->subtest( "$theme version", sub { subtest_version(@args); } );
  3         3839  
45 3         4766 $tb->subtest( "$theme data", sub { subtest_data(@args); } );
  3         2704  
46 3         3875 $tb->subtest( "$theme format", sub { subtest_format(@args); } );
  3         2764  
47 3         3434 $tb->subtest( "$theme length", sub { subtest_length(@args); } );
  3         2743  
48 3         3313 $tb->subtest( "$theme import", sub { subtest_import(@args); } );
  3         2756  
49 3         4252 $tb->subtest( "$theme noimport", sub { subtest_noimport(@args); } );
  3         2652  
50 3         4707 $tb->subtest( "$theme theme", sub { subtest_theme(@args); } );
  3         2748  
51 3         4812 $tb->subtest( "$theme remote", sub { subtest_remote(@args); } );
  3         2806  
52 3         3407 $tb->done_testing;
53             }
54 3         32 );
55             }
56              
57             #
58             # useful internal functions
59             #
60              
61             # some starting points to look for theme modules
62             sub _starting_points {
63 1 50   1   24 return 'blib/lib' if -e 'blib/lib';
64 0         0 return 'lib';
65             }
66              
67             # load the theme in a random namespace
68             {
69             my $num = 0;
70              
71             sub _load {
72 8     8   41 my ( $theme, $do_import ) = @_;
73 8         30 my $module = "Acme::MetaSyntactic::$theme";
74 8         43 my $pkg = sprintf "Acme::MetaSyntactic::SCRATCH_%04d", $num++;
75 8 100       57 my $code = $do_import
76             ? "package $pkg; use $module; 1;"
77             : "package $pkg; use $module (); 1;";
78 8     1   782 my $ok = eval $code;
  1     1   720  
  1     1   3  
  1     1   8  
  1     1   9  
  1     1   2  
  1     1   11  
  1     1   9  
  1         2  
  1         7  
  1         23  
  1         8  
  1         15  
  1         8  
  1         4  
  1         11  
  1         11  
  1         3  
  1         23  
  1         19  
  1         4  
  1         6  
  1         8  
  1         2  
  1         11  
79 8   33     54 return ( $pkg, !$ok && $@ );
80             }
81             }
82              
83             # return a list of [ AMS object, details ]
84             sub _theme_sublists {
85 9     9   26 my ($theme) = @_;
86 9         19 my @metas;
87              
88             # assume the module has already been loaded
89 2     2   63490 no strict 'refs';
  2         5  
  2         533  
90 9         23 my $class = "Acme::MetaSyntactic::$theme";
91              
92 9 100       129 if( $class->isa('Acme::MetaSyntactic::Locale') ) {
    50          
93 3         52 for my $lang ( "Acme::MetaSyntactic::$theme"->languages() ) {
94 9         47 push @metas,
95             [ "Acme::MetaSyntactic::$theme"->new( lang => $lang ),
96             "$theme, $lang locale" ];
97             }
98             }
99             elsif( $class->isa('Acme::MetaSyntactic::MultiList') ) {
100 0         0 for my $cat ( "Acme::MetaSyntactic::$theme"->categories() ) {
101 0         0 push @metas,
102             [ "Acme::MetaSyntactic::$theme"->new( category => $cat ),
103             "$theme, $cat category" ];
104             }
105             }
106             else {
107 6         36 push @metas, [ "Acme::MetaSyntactic::$theme"->new(), $theme ];
108             }
109              
110 9         35 return @metas;
111             }
112              
113             # return the list of all theme items
114             sub _theme_items {
115 2     2   13 my ($theme) = @_;
116              
117             # assume the module has already been loaded
118 2     2   17 no strict 'refs';
  2         5  
  2         2065  
119 2         12 my $class = "Acme::MetaSyntactic::$theme";
120             my @items
121             = $class->isa('Acme::MetaSyntactic::List')
122 1         11 ? @{"$class\::List"}
123             : $class->isa('Acme::MetaSyntactic::MultiList')
124 2 50       29 ? map @$_, values %{"$class\::MultiList"}
  1 100       18  
125             : ();
126 2         21 return @items;
127             }
128              
129             sub _check_file_lines {
130 9     9   45 my ($theme, $file, $mesg, $cb ) = @_;
131 9         45 my $tb = __PACKAGE__->builder;
132 9         201 $tb->plan( tests => 1 );
133 9         6257 local $Test::Builder::Level = $Test::Builder::Level + 1;
134              
135             # try to find a source file if none given
136 9   33     33 $file ||= { Acme::MetaSyntactic->_find_themes(_starting_points) }->{$theme};
137              
138             SKIP: {
139 9         21 my ($fh, $skip);
  9         18  
140 9 50       25 if ( $file ) {
141 9 50       685 open $fh, $file or do { $skip = "Can't open $file: $!"; };
  0         0  
142             }
143             else {
144 0         0 $skip = "This test needs the source file for $theme";
145             }
146 9 50       53 if( $skip ) {
147 0         0 $tb->skip($skip);
148 0         0 last SKIP;
149             }
150              
151 9         962 my @lines = $cb->( <$fh> );
152 9         164 $tb->is_num( scalar @lines, 0, sprintf $mesg, $file );
153 9 50       4361 map $tb->diag( $_ ), "Failed lines:\n", map " $_", @lines if @lines;
154 9         172 close $fh;
155             }
156             }
157              
158             #
159             # individual subtest functions
160             #
161              
162             # t/01load.t
163             # t/51useall.t
164             sub subtest_load {
165 3     3 1 8 my ($theme) = @_;
166 3         16 my $tb = __PACKAGE__->builder;
167              
168 3         34 $tb->plan( tests => 2 );
169              
170             # load in the current process
171 3         1931 my ( $pkg, $error ) = _load( $theme, 1 );
172 3         19 $tb->ok( !$error, "use Acme::MetaSyntactic::$theme;" );
173 3 50       1368 $tb->diag($error) if $error;
174              
175             # load in isolation
176 3   50     157 local $ENV{PERL5LIB} = join $Config::Config{path_sep} || ';', @INC;
177 3         99114 `$^X -MAcme::MetaSyntactic::$theme -e1`;
178 3         317 $tb->is_eq( $? >> 8, 0, "perl -MAcme::MetaSyntactic::$theme -e1" );
179             }
180              
181             # t/02fixme.t
182             sub subtest_fixme {
183 3     3 1 10 my ( $theme, $file ) = @_;
184 3 50       10 $file = '' if !defined $file;
185             _check_file_lines(
186             $theme, $file,
187             "No FIXME found in %s",
188 3     3   97 sub { grep /\bFIXME\b/, @_ }
189 3         36 );
190             }
191              
192             sub subtest_encoding {
193 3     3 1 10 my ( $theme, $file ) = @_;
194 3 50       11 $file = '' if !defined $file;
195             _check_file_lines(
196             $theme, $file,
197             "%s should have an =encoding line if it contains non-us-ascii characters",
198             sub {
199 3     3   221 my @non_ascii = grep /[^\x00-\x7f]/, @_;
200 3         60 my @encoding = grep /^=encoding \S+/, @_;
201 3 50       16 return @encoding ? () : @non_ascii;
202             }
203 3         39 );
204             }
205              
206             # t/08theme.t
207             sub subtest_theme {
208 3     3 1 15 my ($theme) = @_;
209 3         14 my $tb = __PACKAGE__->builder;
210 3         36 $tb->plan( tests => 2 );
211              
212 3         2076 $tb->is_eq( eval { "Acme::MetaSyntactic::$theme"->theme },
  3         42  
213             $theme, "theme() for Acme::MetaSyntactic::$theme" );
214 3         1417 $tb->is_eq( eval { "Acme::MetaSyntactic::$theme"->new->theme },
  3         19  
215             $theme, "theme() for Acme::MetaSyntactic::$theme" );
216             }
217              
218             # t/17import.t
219             sub subtest_import {
220 3     3 1 13 my ($theme) = @_;
221 3         17 my $tb = __PACKAGE__->builder;
222 3         37 $tb->plan( tests => my $tests = 2 );
223              
224             SKIP: {
225 3 100       2055 if ( $theme =~ /^(?:any|random)$/ ) {
  3         45  
226 1         17 $tb->skip("Not testing import for theme $theme") for 1 .. $tests;
227 1         1295 last SKIP;
228             }
229             else {
230 2         18 my ($pkg) = _load( $theme, 1 );
231 2         20 my %seen = map { $_ => 1 } _theme_items($theme);
  91         221  
232              
233 2     2   41 no strict 'refs';
  2         5  
  2         1851  
234 2         16 $tb->ok( exists ${"$pkg\::"}{"meta$theme"},
  2         24  
235             "meta$theme exported" );
236              
237             my @names
238 2     1   1173 = eval qq{package $pkg; no strict 'refs'; "meta$theme"->();};
  1     1   8  
  1         3  
  1         62  
  1         10  
  1         2  
  1         63  
239 2         15 $tb->ok( exists $seen{ $names[0] }, "meta$theme -> $names[0]" );
240             }
241             }
242             }
243              
244             # t/18import.t
245             sub subtest_noimport {
246 3     3 1 10 my ($theme) = @_;
247 3         12 my $tb = __PACKAGE__->builder;
248 3         33 $tb->plan( tests => 1 );
249              
250 3         1971 my ($pkg) = _load($theme);
251              
252             # meta$theme should not exist
253 3         188 eval "package $pkg; meta$theme(1);";
254 3         123 $tb->ok( $@ =~ /^Undefined subroutine &$pkg\::meta$theme called/,
255             "meta$theme function not exported" );
256             }
257              
258             # t/21format.t
259             sub subtest_format {
260 3     3 1 14 my ($theme) = @_;
261 3         32 my $tb = __PACKAGE__->builder;
262              
263 3         53 my @metas = _theme_sublists($theme);
264 3         25 $tb->plan( tests => scalar @metas );
265              
266 3         2062 for my $test (@metas) {
267 5         18 my ( $ams, $theme ) = @$test;
268 5         39 my @items = $ams->name(0);
269 5         50 my @failed;
270 5         10 my $ok = 0;
271 5   33     246 ( /^[A-Za-z_]\w*$/ && ++$ok ) || push @failed, $_ for @items;
      33        
272 5         29 $tb->is_num( $ok, scalar @items, "All names correct for $theme" );
273 5 50       2626 $tb->diag("Bad names: @failed") if @failed;
274             }
275             }
276              
277             # t/23length.t
278             sub subtest_length {
279 3     3 1 9 my ($theme) = @_;
280 3         14 my $tb = __PACKAGE__->builder;
281              
282 3         32 my @metas = _theme_sublists($theme);
283 3         16 $tb->plan( tests => 2 * @metas );
284              
285 3         2064 for my $t (@metas) {
286 5         20 my ( $ams, $theme ) = @$t;
287              
288             # no empty themes
289 5         19 my @items = $ams->name(0);
290 5         28 $tb->cmp_ok( 0 + @items, '>=', 1, "$theme has at least one item" );
291              
292             # no empty names
293 5         2474 my @failed;
294 5         12 my $ok = 0;
295             ( length($_) >= 1 && length($_) <= 251 && ++$ok ) || push @failed, $_
296 5   33     416 for @items;
      33        
      33        
297 5         31 $tb->is_num( $ok, scalar @items, "All names correct for $theme" );
298 5 50       2577 $tb->diag("Names too long: @failed") if @failed;
299             }
300             }
301              
302             # t/24data.t
303             sub subtest_data {
304 3     3 1 17 my ( $theme, $file ) = @_;
305 3 50       19 $file = '' if !defined $file;
306             _check_file_lines(
307             $theme, $file,
308             "__DATA__ section for %s",
309             sub {
310 3     3   12 my @lines;
311             my $in_data;
312 3         13 for my $line (@_) {
313 528 100       858 $in_data++ if $line =~ /^__DATA__$/;
314 528 100       917 next if !$in_data;
315 14 50 33     43 push @lines, $line
316             if /^#/ && !/^# ?(?:names(?: +[-\w]+)*|default)\s*$/;
317             }
318 3         15 return @lines;
319             }
320 3         114 );
321             }
322              
323             sub subtest_version {
324 3     3 1 24 my ($theme) = @_;
325 3         44 my $tb = __PACKAGE__->builder;
326 3         72 $tb->plan( tests => 1 );
327 2     2   18 no strict 'refs';
  2         5  
  2         241  
328 3   50     2648 my $version = "Acme::MetaSyntactic::$theme"->VERSION || '';
329 3         29 $tb->ok( $version, "$theme version $version" );
330             }
331              
332             # t/90up2date.t
333             my ($has_lwp, $has_test_diff, $has_network);
334             BEGIN {
335 2     2   8 $has_lwp = eval { require LWP::UserAgent; 1; };
  2         1527  
  2         93760  
336 2   33     22 $has_network = $has_lwp
337             && LWP::UserAgent->new( timeout => 5, env_proxy => 1 )
338             ->get('http://www.google.com/intl/en/')
339             ->is_success;
340             };
341              
342             sub subtest_remote {
343 3     3 1 26 my ($theme) = @_;
344 3         42 my $class = "Acme::MetaSyntactic::$theme";
345              
346             # find out if we're in one of the many cases for skipping
347             my $why
348             = !$ENV{RELEASE_TESTING}
349             && !$ENV{AUTHOR_TESTING} ? 'Remote list test is RELEASE_TESTING'
350 3 0 33     53 : $ENV{AUTOMATED_TESTING} ? "Remote list test isn't AUTOMATED_TESTING"
    0          
    0          
    0          
    50          
351             : !$class->has_remotelist ? "Theme $theme does not have a remote list"
352             : !$has_lwp ? 'Remote list test needs LWP::UserAgent'
353             : !$has_network ? 'Remote list test needs network'
354             : '';
355              
356 3         35 my $tb = __PACKAGE__->builder;
357 3         39 my @metas = _theme_sublists($theme);
358 3         33 $tb->plan( tests => scalar @metas );
359              
360             SKIP: {
361 3 50       2077 if ($why) {
  3         11  
362 3         43 $tb->skip($why) for 1 .. @metas;
363 3         2871 last SKIP;
364             }
365              
366              
367 0         0 for my $test (@metas) {
368 0         0 my ( $ams, $theme ) = @$test;
369              
370 2     2   501602 no warnings 'utf8';
  2         6  
  2         561  
371 0         0 my $current = [ sort $ams->name(0) ];
372 0         0 my $remote = [ sort $ams->remote_list() ];
373              
374 0 0       0 if ( !@$remote ) {
375 0         0 $tb->skip("Fetching remote items for $theme probably failed");
376 0         0 next;
377             }
378              
379             # compare both lists
380 0         0 my %seen;
381 0         0 $seen{$_}++ for @$remote;
382 0         0 $seen{$_}-- for @$current;
383 0 0       0 $tb->ok( !grep( $_, values %seen ),
384             "Local and remote lists are identical for $theme" )
385             or $tb->diag("Differences between local and remote list:");
386             $tb->diag( $seen{$_} > 0 ? "+ $_" : "- $_" )
387 0 0       0 for grep $seen{$_}, sort keys %seen;
388             }
389             }
390             }
391              
392             1;
393              
394             __END__