File Coverage

blib/lib/Test/Module/Used.pm
Criterion Covered Total %
statement 264 265 99.6
branch 37 40 92.5
condition 22 24 91.6
subroutine 62 62 100.0
pod 6 6 100.0
total 391 397 98.4


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