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   2011 use strict;
  2         2  
  2         48  
3 2     2   6 use warnings;
  2         2  
  2         39  
4 2     2   319 use Acme::MetaSyntactic ();
  2         3  
  2         32  
5 2     2   7 use Config ();
  2         2  
  2         30  
6              
7 2     2   5 use base 'Test::Builder::Module';
  2         2  
  2         1137  
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 9 my (@lib) = @_;
18 1 50       9 @lib = _starting_points() if !@lib;
19 1         13 my %source = Acme::MetaSyntactic->_find_themes(@lib);
20              
21 1         20 my $tb = __PACKAGE__->builder;
22 1         20 local $Test::Builder::Level = $Test::Builder::Level + 1;
23 1         9 $tb->plan( tests => scalar keys %source );
24 1         788 my @fail;
25 1   33     12 theme_ok( $_, $source{$_} ) or push @fail, $_ for sort keys %source;
26 1 50       738 $tb->diag("Test suite failed for the following:") if @fail;
27 1         54 $tb->diag("- $_") for @fail;
28             }
29              
30             sub theme_ok {
31 3     3 1 1352 my @args = @_;
32 3         12 my $tb = __PACKAGE__->builder;
33 3         21 local $Test::Builder::Level = $Test::Builder::Level + 1;
34              
35             # all subtests
36 3         6 my $theme = $args[0];
37             $tb->subtest(
38             $theme,
39             sub {
40 3     3   1613 $tb->subtest( "$theme fixme", sub { subtest_fixme(@args); } );
  3         1451  
41 3         1555 $tb->subtest( "$theme encoding", sub { subtest_encoding(@args); } );
  3         1505  
42 3         1158 $tb->subtest( "$theme load", sub { subtest_load(@args); } )
43 3 50       1337 or return;
44 3         3608 $tb->subtest( "$theme version", sub { subtest_version(@args); } );
  3         1747  
45 3         2254 $tb->subtest( "$theme data", sub { subtest_data(@args); } );
  3         1389  
46 3         1537 $tb->subtest( "$theme format", sub { subtest_format(@args); } );
  3         1450  
47 3         1448 $tb->subtest( "$theme length", sub { subtest_length(@args); } );
  3         1480  
48 3         1796 $tb->subtest( "$theme import", sub { subtest_import(@args); } );
  3         1937  
49 3         2633 $tb->subtest( "$theme noimport", sub { subtest_noimport(@args); } );
  3         1779  
50 3         2894 $tb->subtest( "$theme theme", sub { subtest_theme(@args); } );
  3         1651  
51 3         2172 $tb->subtest( "$theme remote", sub { subtest_remote(@args); } );
  3         1327  
52 3         1325 $tb->done_testing;
53             }
54 3         31 );
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   32 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   19 my ( $theme, $do_import ) = @_;
73 8         24 my $module = "Acme::MetaSyntactic::$theme";
74 8         46 my $pkg = sprintf "Acme::MetaSyntactic::SCRATCH_%04d", $num++;
75 8 100       50 my $code = $do_import
76             ? "package $pkg; use $module; 1;"
77             : "package $pkg; use $module (); 1;";
78 8     1   886 my $ok = eval $code;
  1     1   451  
  1     1   2  
  1     1   6  
  1     1   5  
  1     1   2  
  1     1   9  
  1     1   5  
  1         2  
  1         7  
  1         14  
  1         2  
  1         8  
  1         5  
  1         1  
  1         8  
  1         504  
  1         5  
  1         5  
  1         14  
  1         2  
  1         7  
  1         41  
  1         4  
  1         17  
79 8   33     55 return ( $pkg, !$ok && $@ );
80             }
81             }
82              
83             # return a list of [ AMS object, details ]
84             sub _theme_sublists {
85 9     9   12 my ($theme) = @_;
86 9         14 my @metas;
87              
88             # assume the module has already been loaded
89 2     2   14785 no strict 'refs';
  2         5  
  2         350  
90 9         22 my $class = "Acme::MetaSyntactic::$theme";
91              
92 9 100       138 if( $class->isa('Acme::MetaSyntactic::Locale') ) {
    50          
93 3         32 for my $lang ( "Acme::MetaSyntactic::$theme"->languages() ) {
94 9         38 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         35 push @metas, [ "Acme::MetaSyntactic::$theme"->new(), $theme ];
108             }
109              
110 9         40 return @metas;
111             }
112              
113             # return the list of all theme items
114             sub _theme_items {
115 2     2   5 my ($theme) = @_;
116              
117             # assume the module has already been loaded
118 2     2   8 no strict 'refs';
  2         2  
  2         1463  
119 2         7 my $class = "Acme::MetaSyntactic::$theme";
120             my @items
121             = $class->isa('Acme::MetaSyntactic::List')
122 1         9 ? @{"$class\::List"}
123             : $class->isa('Acme::MetaSyntactic::MultiList')
124 2 50       30 ? map @$_, values %{"$class\::MultiList"}
  1 100       32  
125             : ();
126 2         32 return @items;
127             }
128              
129             sub _check_file_lines {
130 9     9   25 my ($theme, $file, $mesg, $cb ) = @_;
131 9         43 my $tb = __PACKAGE__->builder;
132 9         86 $tb->plan( tests => 1 );
133 9         1294 local $Test::Builder::Level = $Test::Builder::Level + 1;
134              
135             # try to find a source file if none given
136 9   33     28 $file ||= { Acme::MetaSyntactic->_find_themes(_starting_points) }->{$theme};
137              
138             SKIP: {
139 9         13 my ($fh, $skip);
  9         12  
140 9 50       20 if ( $file ) {
141 9 50       468 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       29 if( $skip ) {
147 0         0 $tb->skip($skip);
148 0         0 last SKIP;
149             }
150              
151 9         783 my @lines = $cb->( <$fh> );
152 9         168 $tb->is_num( scalar @lines, 0, sprintf $mesg, $file );
153 9 50       3262 map $tb->diag( $_ ), "Failed lines:\n", map " $_", @lines if @lines;
154 9         142 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         17 my $tb = __PACKAGE__->builder;
167              
168 3         22 $tb->plan( tests => 2 );
169              
170             # load in the current process
171 3         368 my ( $pkg, $error ) = _load( $theme, 1 );
172 3         19 $tb->ok( !$error, "use Acme::MetaSyntactic::$theme;" );
173 3 50       1154 $tb->diag($error) if $error;
174              
175             # load in isolation
176 3   50     101 local $ENV{PERL5LIB} = join $Config::Config{path_sep} || ';', @INC;
177 3         119656 `$^X -MAcme::MetaSyntactic::$theme -e1`;
178 3         154 $tb->is_eq( $? >> 8, 0, "perl -MAcme::MetaSyntactic::$theme -e1" );
179             }
180              
181             # t/02fixme.t
182             sub subtest_fixme {
183 3     3 1 8 my ( $theme, $file ) = @_;
184 3 50       11 $file = '' if !defined $file;
185             _check_file_lines(
186             $theme, $file,
187             "No FIXME found in %s",
188 3     3   71 sub { grep /\bFIXME\b/, @_ }
189 3         25 );
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   260 my @non_ascii = grep /[^\x00-\x7f]/, @_;
200 3         66 my @encoding = grep /^=encoding \S+/, @_;
201 3 50       17 return @encoding ? () : @non_ascii;
202             }
203 3         28 );
204             }
205              
206             # t/08theme.t
207             sub subtest_theme {
208 3     3 1 102 my ($theme) = @_;
209 3         16 my $tb = __PACKAGE__->builder;
210 3         31 $tb->plan( tests => 2 );
211              
212 3         518 $tb->is_eq( eval { "Acme::MetaSyntactic::$theme"->theme },
  3         45  
213             $theme, "theme() for Acme::MetaSyntactic::$theme" );
214 3         1250 $tb->is_eq( eval { "Acme::MetaSyntactic::$theme"->new->theme },
  3         20  
215             $theme, "theme() for Acme::MetaSyntactic::$theme" );
216             }
217              
218             # t/17import.t
219             sub subtest_import {
220 3     3 1 10 my ($theme) = @_;
221 3         17 my $tb = __PACKAGE__->builder;
222 3         33 $tb->plan( tests => my $tests = 2 );
223              
224             SKIP: {
225 3 100       553 if ( $theme =~ /^(?:any|random)$/ ) {
  3         28  
226 1         9 $tb->skip("Not testing import for theme $theme") for 1 .. $tests;
227 1         343 last SKIP;
228             }
229             else {
230 2         13 my ($pkg) = _load( $theme, 1 );
231 2         99 my %seen = map { $_ => 1 } _theme_items($theme);
  91         278  
232              
233 2     2   11 no strict 'refs';
  2         6  
  2         1181  
234 2         16 $tb->ok( exists ${"$pkg\::"}{"meta$theme"},
  2         29  
235             "meta$theme exported" );
236              
237             my @names
238 2     1   1272 = eval qq{package $pkg; no strict 'refs'; "meta$theme"->();};
  1     1   7  
  1         2  
  1         52  
  1         8  
  1         8  
  1         55  
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 7 my ($theme) = @_;
247 3         17 my $tb = __PACKAGE__->builder;
248 3         29 $tb->plan( tests => 1 );
249              
250 3         648 my ($pkg) = _load($theme);
251              
252             # meta$theme should not exist
253 3         278 eval "package $pkg; meta$theme(1);";
254 3         399 $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 10 my ($theme) = @_;
261 3         21 my $tb = __PACKAGE__->builder;
262              
263 3         34 my @metas = _theme_sublists($theme);
264 3         16 $tb->plan( tests => scalar @metas );
265              
266 3         394 for my $test (@metas) {
267 5         17 my ( $ams, $theme ) = @$test;
268 5         28 my @items = $ams->name(0);
269 5         13 my @failed;
270 5         6 my $ok = 0;
271 5   33     444 ( /^[A-Za-z_]\w*$/ && ++$ok ) || push @failed, $_ for @items;
      33        
272 5         33 $tb->is_num( $ok, scalar @items, "All names correct for $theme" );
273 5 50       1943 $tb->diag("Bad names: @failed") if @failed;
274             }
275             }
276              
277             # t/23length.t
278             sub subtest_length {
279 3     3 1 7 my ($theme) = @_;
280 3         16 my $tb = __PACKAGE__->builder;
281              
282 3         28 my @metas = _theme_sublists($theme);
283 3         16 $tb->plan( tests => 2 * @metas );
284              
285 3         380 for my $t (@metas) {
286 5         12 my ( $ams, $theme ) = @$t;
287              
288             # no empty themes
289 5         23 my @items = $ams->name(0);
290 5         35 $tb->cmp_ok( 0 + @items, '>=', 1, "$theme has at least one item" );
291              
292             # no empty names
293 5         1759 my @failed;
294 5         7 my $ok = 0;
295             ( length($_) >= 1 && length($_) <= 251 && ++$ok ) || push @failed, $_
296 5   33     505 for @items;
      33        
      33        
297 5         38 $tb->is_num( $ok, scalar @items, "All names correct for $theme" );
298 5 50       1937 $tb->diag("Names too long: @failed") if @failed;
299             }
300             }
301              
302             # t/24data.t
303             sub subtest_data {
304 3     3 1 16 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   47 my @lines;
311             my $in_data;
312 3         9 for my $line (@_) {
313 528 100       635 $in_data++ if $line =~ /^__DATA__$/;
314 528 100       677 next if !$in_data;
315 14 50 33     41 push @lines, $line
316             if /^#/ && !/^# ?(?:names(?: +[-\w]+)*|default)\s*$/;
317             }
318 3         12 return @lines;
319             }
320 3         39 );
321             }
322              
323             sub subtest_version {
324 3     3 1 9 my ($theme) = @_;
325 3         24 my $tb = __PACKAGE__->builder;
326 3         42 $tb->plan( tests => 1 );
327 2     2   10 no strict 'refs';
  2         2  
  2         182  
328 3   50     593 my $version = "Acme::MetaSyntactic::$theme"->VERSION || '';
329 3         26 $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   2 $has_lwp = eval { require LWP::UserAgent; 1; };
  2         1275  
  2         83317  
336 2   33     25 $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 7 my ($theme) = @_;
344 3         14 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     40 : $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         15 my $tb = __PACKAGE__->builder;
357 3         28 my @metas = _theme_sublists($theme);
358 3         13 $tb->plan( tests => scalar @metas );
359              
360             SKIP: {
361 3 50       429 if ($why) {
  3         10  
362 3         25 $tb->skip($why) for 1 .. @metas;
363 3         1025 last SKIP;
364             }
365              
366              
367 0         0 for my $test (@metas) {
368 0         0 my ( $ams, $theme ) = @$test;
369              
370 2     2   549668 no warnings 'utf8';
  2         4  
  2         650  
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__