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   2586 use strict;
  2         9  
  2         70  
3 2     2   15 use warnings;
  2         5  
  2         63  
4 2     2   269 use Acme::MetaSyntactic ();
  2         4  
  2         42  
5 2     2   12 use Config ();
  2         3  
  2         39  
6              
7 2     2   10 use base 'Test::Builder::Module';
  2         4  
  2         1354  
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 70 my (@lib) = @_;
18 1 50       5 @lib = _starting_points() if !@lib;
19 1         9 my %source = Acme::MetaSyntactic->_find_themes(@lib);
20              
21 1         20 my $tb = __PACKAGE__->builder;
22 1         27 local $Test::Builder::Level = $Test::Builder::Level + 1;
23 1         10 $tb->plan( tests => scalar keys %source );
24 1         820 my @fail;
25 1   33     12 theme_ok( $_, $source{$_} ) or push @fail, $_ for sort keys %source;
26 1 50       1563 $tb->diag("Test suite failed for the following:") if @fail;
27 1         68 $tb->diag("- $_") for @fail;
28             }
29              
30             sub theme_ok {
31 3     3 1 2897 my @args = @_;
32 3         13 my $tb = __PACKAGE__->builder;
33 3         31 local $Test::Builder::Level = $Test::Builder::Level + 1;
34              
35             # all subtests
36 3         7 my $theme = $args[0];
37             $tb->subtest(
38             $theme,
39             sub {
40 3     3   2123 $tb->subtest( "$theme fixme", sub { subtest_fixme(@args); } );
  3         2079  
41 3         2707 $tb->subtest( "$theme encoding", sub { subtest_encoding(@args); } );
  3         1970  
42 3         2414 $tb->subtest( "$theme load", sub { subtest_load(@args); } )
43 3 50       3182 or return;
44 3         5455 $tb->subtest( "$theme version", sub { subtest_version(@args); } );
  3         2680  
45 3         3772 $tb->subtest( "$theme data", sub { subtest_data(@args); } );
  3         2102  
46 3         2748 $tb->subtest( "$theme format", sub { subtest_format(@args); } );
  3         2082  
47 3         2927 $tb->subtest( "$theme length", sub { subtest_length(@args); } );
  3         2090  
48 3         3424 $tb->subtest( "$theme import", sub { subtest_import(@args); } );
  3         2484  
49 3         3766 $tb->subtest( "$theme noimport", sub { subtest_noimport(@args); } );
  3         2127  
50 3         3666 $tb->subtest( "$theme theme", sub { subtest_theme(@args); } );
  3         2058  
51 3         3599 $tb->subtest( "$theme remote", sub { subtest_remote(@args); } );
  3         2001  
52 3         2578 $tb->done_testing;
53             }
54 3         30 );
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   13 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   24 my ( $theme, $do_import ) = @_;
73 8         26 my $module = "Acme::MetaSyntactic::$theme";
74 8         42 my $pkg = sprintf "Acme::MetaSyntactic::SCRATCH_%04d", $num++;
75 8 100       40 my $code = $do_import
76             ? "package $pkg; use $module; 1;"
77             : "package $pkg; use $module (); 1;";
78 8     1   641 my $ok = eval $code;
  1     1   459  
  1     1   5  
  1     1   7  
  1     1   8  
  1     1   1  
  1     1   8  
  1     1   9  
  1         2  
  1         7  
  1         13  
  1         3  
  1         11  
  1         7  
  1         2  
  1         8  
  1         9  
  1         2  
  1         9  
  1         10  
  1         2  
  1         5  
  1         6  
  1         3  
  1         9  
79 8   33     50 return ( $pkg, !$ok && $@ );
80             }
81             }
82              
83             # return a list of [ AMS object, details ]
84             sub _theme_sublists {
85 9     9   20 my ($theme) = @_;
86 9         14 my @metas;
87              
88             # assume the module has already been loaded
89 2     2   42877 no strict 'refs';
  2         5  
  2         450  
90 9         23 my $class = "Acme::MetaSyntactic::$theme";
91              
92 9 100       96 if( $class->isa('Acme::MetaSyntactic::Locale') ) {
    50          
93 3         23 for my $lang ( "Acme::MetaSyntactic::$theme"->languages() ) {
94 9         35 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         27 push @metas, [ "Acme::MetaSyntactic::$theme"->new(), $theme ];
108             }
109              
110 9         30 return @metas;
111             }
112              
113             # return the list of all theme items
114             sub _theme_items {
115 2     2   6 my ($theme) = @_;
116              
117             # assume the module has already been loaded
118 2     2   15 no strict 'refs';
  2         5  
  2         1676  
119 2         7 my $class = "Acme::MetaSyntactic::$theme";
120             my @items
121             = $class->isa('Acme::MetaSyntactic::List')
122 1         18 ? @{"$class\::List"}
123             : $class->isa('Acme::MetaSyntactic::MultiList')
124 2 50       52 ? map @$_, values %{"$class\::MultiList"}
  1 100       25  
125             : ();
126 2         29 return @items;
127             }
128              
129             sub _check_file_lines {
130 9     9   27 my ($theme, $file, $mesg, $cb ) = @_;
131 9         33 my $tb = __PACKAGE__->builder;
132 9         85 $tb->plan( tests => 1 );
133 9         4712 local $Test::Builder::Level = $Test::Builder::Level + 1;
134              
135             # try to find a source file if none given
136 9   33     36 $file ||= { Acme::MetaSyntactic->_find_themes(_starting_points) }->{$theme};
137              
138             SKIP: {
139 9         23 my ($fh, $skip);
  9         17  
140 9 50       23 if ( $file ) {
141 9 50       404 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       34 if( $skip ) {
147 0         0 $tb->skip($skip);
148 0         0 last SKIP;
149             }
150              
151 9         666 my @lines = $cb->( <$fh> );
152 9         143 $tb->is_num( scalar @lines, 0, sprintf $mesg, $file );
153 9 50       3548 map $tb->diag( $_ ), "Failed lines:\n", map " $_", @lines if @lines;
154 9         115 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 10 my ($theme) = @_;
166 3         16 my $tb = __PACKAGE__->builder;
167              
168 3         37 $tb->plan( tests => 2 );
169              
170             # load in the current process
171 3         1877 my ( $pkg, $error ) = _load( $theme, 1 );
172 3         22 $tb->ok( !$error, "use Acme::MetaSyntactic::$theme;" );
173 3 50       1070 $tb->diag($error) if $error;
174              
175             # load in isolation
176 3   50     97 local $ENV{PERL5LIB} = join $Config::Config{path_sep} || ';', @INC;
177 3         86850 `$^X -MAcme::MetaSyntactic::$theme -e1`;
178 3         138 $tb->is_eq( $? >> 8, 0, "perl -MAcme::MetaSyntactic::$theme -e1" );
179             }
180              
181             # t/02fixme.t
182             sub subtest_fixme {
183 3     3 1 9 my ( $theme, $file ) = @_;
184 3 50       8 $file = '' if !defined $file;
185             _check_file_lines(
186             $theme, $file,
187             "No FIXME found in %s",
188 3     3   62 sub { grep /\bFIXME\b/, @_ }
189 3         21 );
190             }
191              
192             sub subtest_encoding {
193 3     3 1 10 my ( $theme, $file ) = @_;
194 3 50       12 $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   200 my @non_ascii = grep /[^\x00-\x7f]/, @_;
200 3         60 my @encoding = grep /^=encoding \S+/, @_;
201 3 50       15 return @encoding ? () : @non_ascii;
202             }
203 3         27 );
204             }
205              
206             # t/08theme.t
207             sub subtest_theme {
208 3     3 1 8 my ($theme) = @_;
209 3         10 my $tb = __PACKAGE__->builder;
210 3         29 $tb->plan( tests => 2 );
211              
212 3         1526 $tb->is_eq( eval { "Acme::MetaSyntactic::$theme"->theme },
  3         38  
213             $theme, "theme() for Acme::MetaSyntactic::$theme" );
214 3         968 $tb->is_eq( eval { "Acme::MetaSyntactic::$theme"->new->theme },
  3         16  
215             $theme, "theme() for Acme::MetaSyntactic::$theme" );
216             }
217              
218             # t/17import.t
219             sub subtest_import {
220 3     3 1 11 my ($theme) = @_;
221 3         16 my $tb = __PACKAGE__->builder;
222 3         38 $tb->plan( tests => my $tests = 2 );
223              
224             SKIP: {
225 3 100       1799 if ( $theme =~ /^(?:any|random)$/ ) {
  3         29  
226 1         10 $tb->skip("Not testing import for theme $theme") for 1 .. $tests;
227 1         850 last SKIP;
228             }
229             else {
230 2         12 my ($pkg) = _load( $theme, 1 );
231 2         16 my %seen = map { $_ => 1 } _theme_items($theme);
  91         198  
232              
233 2     2   38 no strict 'refs';
  2         5  
  2         1609  
234 2         15 $tb->ok( exists ${"$pkg\::"}{"meta$theme"},
  2         26  
235             "meta$theme exported" );
236              
237             my @names
238 2     1   910 = eval qq{package $pkg; no strict 'refs'; "meta$theme"->();};
  1     1   6  
  1         3  
  1         34  
  1         7  
  1         4  
  1         36  
239 2         17 $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 8 my ($theme) = @_;
247 3         11 my $tb = __PACKAGE__->builder;
248 3         32 $tb->plan( tests => 1 );
249              
250 3         1486 my ($pkg) = _load($theme);
251              
252             # meta$theme should not exist
253 3         153 eval "package $pkg; meta$theme(1);";
254 3         84 $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 12 my ($theme) = @_;
261 3         14 my $tb = __PACKAGE__->builder;
262              
263 3         32 my @metas = _theme_sublists($theme);
264 3         12 $tb->plan( tests => scalar @metas );
265              
266 3         1539 for my $test (@metas) {
267 5         14 my ( $ams, $theme ) = @$test;
268 5         23 my @items = $ams->name(0);
269 5         10 my @failed;
270 5         8 my $ok = 0;
271 5   33     279 ( /^[A-Za-z_]\w*$/ && ++$ok ) || push @failed, $_ for @items;
      33        
272 5         27 $tb->is_num( $ok, scalar @items, "All names correct for $theme" );
273 5 50       1862 $tb->diag("Bad names: @failed") if @failed;
274             }
275             }
276              
277             # t/23length.t
278             sub subtest_length {
279 3     3 1 8 my ($theme) = @_;
280 3         14 my $tb = __PACKAGE__->builder;
281              
282 3         40 my @metas = _theme_sublists($theme);
283 3         16 $tb->plan( tests => 2 * @metas );
284              
285 3         1828 for my $t (@metas) {
286 5         18 my ( $ams, $theme ) = @$t;
287              
288             # no empty themes
289 5         22 my @items = $ams->name(0);
290 5         31 $tb->cmp_ok( 0 + @items, '>=', 1, "$theme has at least one item" );
291              
292             # no empty names
293 5         1890 my @failed;
294 5         13 my $ok = 0;
295             ( length($_) >= 1 && length($_) <= 251 && ++$ok ) || push @failed, $_
296 5   33     325 for @items;
      33        
      33        
297 5         36 $tb->is_num( $ok, scalar @items, "All names correct for $theme" );
298 5 50       1917 $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       16 $file = '' if !defined $file;
306             _check_file_lines(
307             $theme, $file,
308             "__DATA__ section for %s",
309             sub {
310 3     3   9 my @lines;
311             my $in_data;
312 3         9 for my $line (@_) {
313 528 100       914 $in_data++ if $line =~ /^__DATA__$/;
314 528 100       727 next if !$in_data;
315 14 50 33     31 push @lines, $line
316             if /^#/ && !/^# ?(?:names(?: +[-\w]+)*|default)\s*$/;
317             }
318 3         9 return @lines;
319             }
320 3         41 );
321             }
322              
323             sub subtest_version {
324 3     3 1 15 my ($theme) = @_;
325 3         30 my $tb = __PACKAGE__->builder;
326 3         51 $tb->plan( tests => 1 );
327 2     2   17 no strict 'refs';
  2         6  
  2         220  
328 3   50     1983 my $version = "Acme::MetaSyntactic::$theme"->VERSION || '';
329 3         20 $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   7 $has_lwp = eval { require LWP::UserAgent; 1; };
  2         1179  
  2         72163  
336 2   33     23 $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 11 my ($theme) = @_;
344 3         13 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     34 : $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         14 my $tb = __PACKAGE__->builder;
357 3         29 my @metas = _theme_sublists($theme);
358 3         11 $tb->plan( tests => scalar @metas );
359              
360             SKIP: {
361 3 50       1524 if ($why) {
  3         11  
362 3         27 $tb->skip($why) for 1 .. @metas;
363 3         2094 last SKIP;
364             }
365              
366              
367 0         0 for my $test (@metas) {
368 0         0 my ( $ams, $theme ) = @$test;
369              
370 2     2   443029 no warnings 'utf8';
  2         5  
  2         417  
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__