File Coverage

blib/lib/Test/Module/Used.pm
Criterion Covered Total %
statement 266 267 99.6
branch 36 40 90.0
condition 22 24 91.6
subroutine 62 62 100.0
pod 6 6 100.0
total 392 399 98.2


line stmt bran cond sub pod time code
1             package Test::Module::Used;
2 17     17   155896 use base qw(Exporter);
  17         28  
  17         1726  
3 17     17   91 use strict;
  17         25  
  17         631  
4 17     17   72 use warnings;
  17         28  
  17         659  
5 17     17   78 use File::Find;
  17         26  
  17         1252  
6 17     17   8794 use File::Spec::Functions qw(catfile);
  17         12010  
  17         1328  
7 17     17   9308 use Module::Used qw(modules_used_in_document);
  17         2101380  
  17         1405  
8 17     17   42083 use Module::CoreList;
  17         589889  
  17         190  
9 17     17   17175 use Test::Builder;
  17         82426  
  17         627  
10 17     17   107 use List::MoreUtils qw(any uniq all);
  17         19  
  17         1939  
11 17     17   226 use PPI::Document;
  17         18  
  17         541  
12 17     17   71 use version;
  17         15  
  17         112  
13 17     17   12408 use CPAN::Meta;
  17         408156  
  17         639  
14 17     17   166 use Carp;
  17         27  
  17         1378  
15 17     17   324 use 5.008001;
  17         47  
  17         43720  
16             our $VERSION = '0.2.5';
17              
18             =for stopwords versa
19              
20             =head1 NAME
21              
22             Test::Module::Used - Test required module is really used and vice versa between lib/t and META.yml
23              
24             =head1 SYNOPSIS
25              
26             #!/usr/bin/perl -w
27             use strict;
28             use warnings;
29             use Test::Module::Used;
30             my $used = Test::Module::Used->new();
31             $used->ok;
32              
33              
34             =head1 DESCRIPTION
35              
36             Test dependency between module and META.yml.
37              
38             This module reads I and get I and I. It compares required module is really used and used module is really required.
39              
40             =cut
41              
42             =head1 Important changes
43              
44             Some behavior changed since 0.1.3_01.
45              
46             =over 4
47              
48             =item * perl_version set in constructor is prior to use, and read version from META.yml(not read from use statement in *.pm)
49              
50             =item * deprecated interfaces are deleted. (module_dir, test_module_dir, exclude_in_moduledir and push_exclude_in_moduledir)
51              
52             =back
53              
54             =cut
55              
56             =head1 methods
57              
58             =cut
59              
60             =head2 new
61              
62             create new instance
63              
64             all parameters are passed by hash-style, and optional.
65              
66             in ordinary use.
67              
68             my $used = Test::Module::Used->new();
69             $used->ok();
70              
71             all parameters are as follows.(specified values are default, except I)
72              
73             my $used = Test::Module::Used->new(
74             test_dir => ['t'], # directory(ies) which contains test scripts.
75             lib_dir => ['lib'], # directory(ies) which contains module libs.
76             test_lib_dir => ['t'], # directory(ies) which contains libs used ONLY in test (ex. MockObject for test)
77             meta_file => 'META.json' or
78             'META.yml' or
79             'META.yaml', # META file (YAML or JSON which contains module requirement information)
80             perl_version => '5.008', # expected perl version which is used for ignore core-modules in testing
81             exclude_in_testdir => [], # ignored module(s) for test even if it is used.
82             exclude_in_libdir => [], # ignored module(s) for your lib even if it is used.
83             exclude_in_build_requires => [], # ignored module(s) even if it is written in build_requires of META.yml.
84             exclude_in_requires => [], # ignored module(s) even if it is written in requires of META.yml.
85             );
86              
87             if perl_version is not passed in constructor, this modules reads I and get perl version.
88              
89             I is automatically set by default. This module reads I and parse "package" statement, then found "package" statements and myself(Test::Module::Used) is set.
90             I is also automatically set by default. This module reads I and parse "package" statement, found "package" statement are set.(Test::Module::Used isn't included)
91              
92             =cut
93              
94             sub new {
95 22     22 1 340 my $class = shift;
96 22         110 my (%opt) = @_;
97 22   100     428 my $self = {
      100        
      100        
      100        
      100        
98             test_dir => $opt{test_dir} || ['t'],
99             lib_dir => $opt{lib_dir} || ['lib'],
100             test_lib_dir => $opt{test_lib_dir} || ['t'],
101             meta_file => _find_meta_file($opt{meta_file}),
102             perl_version => $opt{perl_version},
103             exclude_in_testdir => $opt{exclude_in_testdir},
104             exclude_in_libdir => $opt{exclude_in_libdir},
105             exclude_in_build_requires => $opt{exclude_in_build_requires} || [],
106             exclude_in_requires => $opt{exclude_in_requires} || [],
107             };
108 22         74 bless $self, $class;
109 22         105 $self->_get_packages();
110 22         127 return $self;
111             }
112              
113             sub _find_meta_file {
114 22     22   53 my ($opt_meta_file) = @_;
115 22 100       322 return $opt_meta_file if ( defined $opt_meta_file );
116 6         17 for my $file ( qw(META.json META.yml META.yaml) ) {
117 6 50       248 return $file if ( -e $file );
118             }
119 0         0 croak "META file not found\n";
120             }
121              
122              
123             sub _test_dir {
124 18     18   132 return shift->{test_dir};
125             }
126              
127             sub _lib_dir {
128 24     24   277 return shift->{lib_dir};
129             }
130              
131             sub _test_lib_dir {
132 22     22   170 return shift->{test_lib_dir};
133             }
134              
135             sub _meta_file {
136 16     16   176 return shift->{meta_file};
137             }
138              
139             sub _perl_version {
140 16     16   142 return shift->{perl_version};
141             }
142              
143             =head2 ok()
144              
145             check used modules are required in META file and required modules in META files are used.
146              
147             my $used = Test::Module::Used->new(
148             exclude_in_testdir => ['Test::Module::Used', 'My::Module'],
149             );
150             $used->ok;
151              
152              
153             First, This module reads I and get I and I. Next, reads module directory (by default I) and test directory(by default I), and compare required module is really used and used module is really required. If all these requirement information is OK, test will success.
154              
155             It is NOT allowed to call ok(), used_ok() and requires_ok() in same test file.
156              
157             =cut
158              
159             sub ok {
160 6     6 1 552 my $self = shift;
161 6         45 return $self->_ok(\&_num_tests, \&_used_ok, \&_requires_ok);
162             }
163              
164             =head2 used_ok()
165              
166             Only check used modules are required in META file.
167             Test will success if unused I or I are defined.
168              
169             my $used = Test::Module::Used->new();
170             $used->used_ok;
171              
172              
173             It is NOT allowed to call ok(), used_ok() and requires_ok() in same test file.
174              
175             =cut
176              
177             sub used_ok {
178 2     2 1 225 my $self = shift;
179 2         17 return $self->_ok(\&_num_tests_used_ok, \&_used_ok);
180             }
181              
182             =head2 requires_ok()
183              
184             Only check required modules in META file is used.
185             Test will success if used modules are not defined in META file.
186              
187             my $used = Test::Module::Used->new();
188             $used->requires_ok;
189              
190              
191             It is NOT allowed to call ok(), used_ok() and requires_ok() in same test file.
192              
193             =cut
194              
195             sub requires_ok {
196 2     2 1 259 my $self = shift;
197 2         13 return $self->_ok(\&_num_tests_requires_ok, \&_requires_ok);
198             }
199              
200             sub _ok {
201 10     10   21 my $self = shift;
202 10         24 my ($num_tests_subref, @ok_subrefs) = @_;
203              
204 10 50       46 croak('Already tested. Calling ok(), used_ok() and requires_ok() in same test file is not allowed') if ( !!$self->{tested} );
205              
206 10         41 my $num_tests = $num_tests_subref->($self);
207 10         129 return $self->_do_test($num_tests, @ok_subrefs);
208             }
209              
210             sub _do_test {
211 10     10   25 my $self = shift;
212 10         33 my ($num_tests, @ok_subrefs) = @_;
213              
214 10         113 my $test = Test::Builder->new();
215 10 100       214 my $test_status = $num_tests > 0 ? $self->_do_test_normal($num_tests, @ok_subrefs) :
216             $self->_do_test_no_tests();
217 10         38 $self->{tested} = 1;
218 10         356 return !!$test_status;
219             }
220              
221             sub _do_test_normal {
222 9     9   20 my $self = shift;
223 9         26 my ($num_tests, @ok_subrefs) = @_;
224              
225 9         34 my $test = Test::Builder->new();
226 9         82 $test->plan(tests => $num_tests);
227 9         2355 my @status;
228 9         44 for my $ok_subref ( @ok_subrefs ) {
229 14         153 push(@status, $ok_subref->($self, $test));
230             }
231 9     13   160 my $test_status = all { $_ } @status;
  13         28  
232 9         50 return !!$test_status;
233             }
234              
235             sub _do_test_no_tests {
236 1     1   2 my $self = shift;
237              
238 1         3 my $test = Test::Builder->new();
239 1         8 $test->plan(tests => 1);
240 1         318 $test->ok(1, "no tests run");
241 1         268 return 1;
242             }
243              
244             sub _used_ok {
245 7     7   14 my $self = shift;
246 7         14 my ($test) = @_;
247 7         37 my $status_lib = $self->_check_used_but_not_required($test,
248             [$self->_remove_core($self->_used_modules)],
249             [$self->_remove_core($self->_requires)],
250             "lib");
251 7         62 my $status_test = $self->_check_used_but_not_required($test,
252             [$self->_remove_core($self->_used_modules_in_test)],
253             [$self->_remove_core($self->_build_requires)],
254             "test");
255 7   66     77 return $status_lib && $status_test;
256             }
257              
258             sub _requires_ok {
259 7     7   17 my $self = shift;
260 7         16 my ($test) = @_;
261 7         49 my $status_lib = $self->_check_required_but_not_used($test,
262             [$self->_remove_core($self->_used_modules)],
263             [$self->_remove_core($self->_requires)],
264             "lib");
265 7         57 my $status_test = $self->_check_required_but_not_used($test,
266             [$self->_remove_core($self->_used_modules_in_test)],
267             [$self->_remove_core($self->_build_requires)],
268             "test");
269 7   66     103 return $status_lib && $status_test;
270             }
271              
272              
273             =head2 push_exclude_in_libdir( @exclude_module_names )
274              
275             add ignored module(s) for your module(lib) even if it is used after new()'ed.
276             this is usable if you want to use auto set feature for I but manually specify exclude modules.
277              
278             For example,
279              
280             my $used = Test::Module::Used->new(); #automatically set exclude_in_libdir
281             $used->push_exclude_in_libdir( qw(Some::Module::Which::You::Want::To::Exclude) );#module(s) which you want to exclude
282             $used->ok(); #do test
283              
284             =cut
285              
286             sub push_exclude_in_libdir {
287 22     22 1 47 my $self = shift;
288 22         40 my @exclude_module_names = @_;
289 22         39 push @{$self->{exclude_in_libdir}},@exclude_module_names;
  22         80  
290             }
291              
292              
293              
294             =head2 push_exclude_in_testdir( @exclude_module_names )
295              
296             add ignored module(s) for test even if it is used after new()'ed.
297             this is usable if you want to use auto set feature for I but manually specify exclude modules.
298              
299             For example,
300              
301             my $used = Test::Module::Used->new(); #automatically set exclude_in_testdir
302             $used->push_exclude_in_testdir( qw(Some::Module::Which::You::Want::To::Exclude) );#module(s) which you want to exclude
303             $used->ok(); #do test
304              
305             =cut
306              
307             sub push_exclude_in_testdir {
308 19     19 1 38 my $self = shift;
309 19         42 my @exclude_module_names = @_;
310 19         36 push @{$self->{exclude_in_testdir}},@exclude_module_names;
  19         69  
311             }
312              
313             sub _version {
314 213     213   421 my $self = shift;
315 213 100       686 if ( !defined $self->{version} ) {
316 15   100     75 $self->{version} = $self->_perl_version || $self->_version_from_meta || "5.008000";
317             }
318 213         1914 return $self->{version};
319             }
320              
321             sub _num_tests {
322 6     6   10 my $self = shift;
323 6         22 return $self->_num_tests_used_ok() + $self->_num_tests_requires_ok();
324             }
325              
326             sub _num_tests_used_ok {
327 8     8   13 my $self = shift;
328 8         36 return scalar($self->_remove_core($self->_used_modules,
329             $self->_used_modules_in_test));
330             }
331              
332             sub _num_tests_requires_ok {
333 8     8   18 my $self = shift;
334 8         44 return scalar($self->_remove_core($self->_requires,
335             $self->_build_requires));
336              
337             }
338              
339             sub _check_required_but_not_used {
340 14     14   33 my $self = shift;
341 14         33 my ($test, $used_aref, $requires_aref, $place) = @_;
342 14         23 my @requires = @{$requires_aref};
  14         37  
343 14         28 my @used = @{$used_aref};
  14         46  
344              
345 14         25 my $result = 1;
346 14         40 for my $requires ( @requires ) {
347 29     66   231 my $status = any { $_ eq $requires } @used;
  66         96  
348 29         213 $test->ok( $status, "check required module: $requires" );
349 29 100       9518 if ( !$status ) {
350 2         14 $test->diag("module $requires is required in META file but not used in $place");
351 2         107 $result = 0;
352             }
353             }
354 14         49 return $result;
355             }
356              
357             sub _check_used_but_not_required {
358 14     14   40 my $self = shift;
359 14         40 my ($test, $used_aref, $requires_aref, $place) = @_;
360 14         28 my @requires = @{$requires_aref};
  14         46  
361 14         33 my @used = @{$used_aref};
  14         43  
362              
363 14         31 my $result = 1;
364 14         45 for my $used ( @used ) {
365 29     64   245 my $status = any { $_ eq $used } @requires;
  64         121  
366 29         231 $test->ok( $status, "check used module: $used" );
367 29 100       10822 if ( !$status ) {
368 2         16 $test->diag("module $used is used in $place but not required");
369 2         138 $result = 0;
370             }
371             }
372 14         54 return $result;
373             }
374              
375             sub _pm_files {
376 40     40   93 my $self = shift;
377 40 100       249 if ( !defined $self->{pm_files} ) {
378 22         97 my @files = $self->_find_files_by_ext($self->_lib_dir, qr/\.pm$/);
379 22         117 $self->{pm_files} = \@files;
380             }
381 40         53 return @{$self->{pm_files}};
  40         189  
382             }
383              
384             sub _pm_files_in_test {
385 41     41   79 my $self = shift;
386 41 100       163 if ( !defined $self->{pm_files_in_test} ) {
387 22         101 my @files = $self->_find_files_by_ext($self->_test_lib_dir, qr/\.pm$/);
388 22         107 $self->{pm_files_in_test} = \@files;
389             }
390 41         62 return @{$self->{pm_files_in_test}};
  41         190  
391             }
392              
393             sub _test_files {
394 16     16   38 my $self = shift;
395             return (
396 16         66 $self->_find_files_by_ext($self->_test_dir, qr/\.t$/),
397             $self->_pm_files_in_test()
398             );
399             }
400              
401             sub _find_files_by_ext {
402 60     60   101 my $self = shift;
403 60         337 my ($start_dirs_aref, $ext_qr) = @_;
404 60         87 my @result;
405             find( sub {
406 505 100   505   10157 push @result, catfile($File::Find::dir, $_) if ( $_ =~ $ext_qr );
407             },
408 60         334 @{$start_dirs_aref});
  60         5691  
409 60         414 return @result;
410             }
411              
412             sub _used_modules {
413 25     25   57 my $self = shift;
414 25 100       105 if ( !defined $self->{used_modules} ) {
415 13         55 my @used = map { modules_used_in_document($self->_ppi_for($_)) } $self->_pm_files;
  13         48  
416 13         211418 my @result = uniq _array_difference(\@used, $self->{exclude_in_libdir});
417 13         66 $self->{used_modules} = \@result;
418             }
419 25         46 return @{$self->{used_modules}};
  25         167  
420             }
421              
422             sub _used_modules_in_test {
423 26     26   62 my $self = shift;
424 26 100       126 if ( !defined $self->{used_modules_in_test} ) {
425 14         64 my @used = map { modules_used_in_document($self->_ppi_for($_)) } $self->_test_files;
  47         166648  
426 14         32692 my @result = uniq _array_difference(\@used, $self->{exclude_in_testdir});
427 14         72 $self->{used_modules_in_test} = \@result;
428             }
429 26         48 return @{$self->{used_modules_in_test}};
  26         167  
430             }
431              
432             sub _array_difference {
433 79     79   179 my ( $aref1, $aref2 ) = @_;
434 79         126 my @a1 = @{$aref1};
  79         240  
435 79         119 my @a2 = @{$aref2};
  79         237  
436              
437 79         204 for my $a2 ( @a2 ) {
438 44         75 @a1 = grep { $_ ne $a2 } @a1;
  372         498  
439             }
440 79         341 my @result = sort @a1;
441 79         638 return @result;
442             }
443              
444             sub _version_from_meta {
445 14     14   31 my $self = shift;
446 14         146 return $self->{version_from_meta};
447             }
448              
449             sub _remove_core {
450 74     74   148 my $self = shift;
451 74         186 my( @module_names ) = @_;
452 74         204 my @result = grep { !$self->_is_core_module($_) } @module_names;
  338         1048  
453 74         635 return @result;
454             }
455              
456             sub _is_core_module {
457 338     338   463 my $self = shift;
458 338         494 my($module_name) = @_;
459 338         1258 my $first_release = Module::CoreList->first_release($module_name);
460 338   100     3379574 return defined $first_release && $first_release <= $self->_version;
461             }
462              
463             sub _read_meta {
464 14     14   42 my $self = shift;
465 14         103 my $meta = CPAN::Meta->load_file( $self->_meta_file );
466 14         313889 my $prereqs = $meta->prereqs();
467 14         30408 $self->{build_requires} = $prereqs->{build}->{requires};
468 14         91 my $requires = $prereqs->{runtime}->{requires};
469 14 100       323 $self->{version_from_meta} = version->parse($requires->{perl})->numify() if defined $requires->{perl};
470 14         240 delete $requires->{perl};
471 14         266 $self->{requires} = $requires;
472             }
473              
474             sub _build_requires {
475 26     26   67 my $self = shift;
476              
477 26 50       121 $self->_read_meta if !defined $self->{build_requires};
478 26         44 my @result = sort keys %{$self->{build_requires}};
  26         156  
479 26         124 return _array_difference(\@result, $self->{exclude_in_build_requires});
480             }
481              
482             sub _requires {
483 26     26   59 my $self = shift;
484              
485 26 100       157 $self->_read_meta if !defined $self->{requires};
486 26         44 my @result = sort keys %{$self->{requires}};
  26         200  
487 26         145 return _array_difference(\@result, $self->{exclude_in_requires});
488             }
489              
490             # find package statements in lib
491             sub _get_packages {
492 24     24   41 my $self = shift;
493 24         115 my @packages = $self->_packages_in( $self->_pm_files );
494 24         132 my @exclude_in_testdir = (__PACKAGE__, @packages, $self->_packages_in($self->_pm_files_in_test));
495 24 100       166 $self->push_exclude_in_testdir(@exclude_in_testdir) if ( !defined $self->{exclude_in_testdir} );
496 24 100       160 $self->push_exclude_in_libdir(@packages) if ( !defined $self->{exclude_in_libdir} );
497             }
498              
499             sub _packages_in {
500 51     51   87 my $self = shift;
501 51         96 my ( @filenames ) = @_;
502              
503 51         77 my @result;
504 51         95 for my $filename ( @filenames ) {
505 31         103 my @packages = $self->_packages_in_file($filename);
506 31         92 push @result, @packages;
507             }
508 51         150 return @result;
509             }
510              
511             sub _packages_in_file {
512 31     31   53 my $self = shift;
513 31         55 my ( $filename ) = @_;
514 31         93 my @ppi_package_statements = $self->_ppi_package_statements($filename);
515 31         57 my @result;
516 31         79 for my $ppi_package_statement ( @ppi_package_statements ) {
517 31         130 push @result, $self->_package_names_in($ppi_package_statement);
518             }
519 31         89 return @result;
520             }
521              
522             sub _ppi_package_statements {
523 31     31   54 my $self = shift;
524 31         50 my ($filename) = @_;
525              
526 31         102 my $doc = $self->_ppi_for($filename);
527 31         203 my $packages = $doc->find('PPI::Statement::Package');
528 31 50       781474 return if ( $packages eq '' );
529 31         69 return @{ $packages };
  31         136  
530             }
531              
532             sub _package_names_in {
533 31     31   57 my $self = shift;
534 31         58 my ($ppi_package_statement) = @_;
535 31         42 my @result;
536 31         50 for my $token ( @{$ppi_package_statement->{children}} ) {
  31         98  
537 124 100       504 next if ( !$self->_is_package_name($token) );
538 31         195 push @result, $token->content;
539             }
540 31         94 return @result;
541             }
542              
543             sub _is_package_name {
544 124     124   119 my $self = shift;
545 124         130 my ($ppi_token) = @_;
546 124   100     769 return $ppi_token->isa('PPI::Token::Word') && $ppi_token->content ne 'package';
547             }
548              
549             # PPI::Document object for $filename
550             sub _ppi_for {
551 91     91   163 my $self = shift;
552 91         145 my ($filename) = @_;
553 91 100       400 if ( !defined $self->{ppi_for}->{$filename} ) {
554 71         503 my $doc = PPI::Document->new($filename);
555 71         3101201 $self->{ppi_for}->{$filename} = $doc;
556             }
557 91         537 return $self->{ppi_for}->{$filename};
558             }
559              
560              
561             1;
562             __END__