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   170262 use base qw(Exporter);
  17         31  
  17         1789  
3 17     17   91 use strict;
  17         31  
  17         559  
4 17     17   327 use warnings;
  17         29  
  17         603  
5 17     17   82 use File::Find;
  17         21  
  17         1266  
6 17     17   8733 use File::Spec::Functions qw(catfile);
  17         12026  
  17         1375  
7 17     17   8758 use Module::Used qw(modules_used_in_document);
  17         2103365  
  17         1390  
8 17     17   37112 use Module::CoreList;
  17         570928  
  17         203  
9 17     17   17254 use Test::Builder;
  17         71558  
  17         656  
10 17     17   107 use List::MoreUtils qw(any uniq all);
  17         24  
  17         1938  
11 17     17   221 use PPI::Document;
  17         20  
  17         445  
12 17     17   70 use version;
  17         19  
  17         105  
13 17     17   12066 use CPAN::Meta;
  17         394122  
  17         706  
14 17     17   162 use Carp;
  17         31  
  17         1259  
15 17     17   331 use 5.008001;
  17         43  
  17         44755  
16             our $VERSION = '0.2.6';
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 393 my $class = shift;
96 22         109 my (%opt) = @_;
97 22   100     421 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         76 bless $self, $class;
109 22         99 $self->_get_packages();
110 22         118 return $self;
111             }
112              
113             sub _find_meta_file {
114 22     22   52 my ($opt_meta_file) = @_;
115 22 100       303 return $opt_meta_file if ( defined $opt_meta_file );
116 6         18 for my $file ( qw(META.json META.yml META.yaml) ) {
117 6 50       231 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   126 return shift->{test_dir};
125             }
126              
127             sub _lib_dir {
128 24     24   332 return shift->{lib_dir};
129             }
130              
131             sub _test_lib_dir {
132 22     22   160 return shift->{test_lib_dir};
133             }
134              
135             sub _meta_file {
136 16     16   190 return shift->{meta_file};
137             }
138              
139             sub _perl_version {
140 16     16   134 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 501 my $self = shift;
161 6         39 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 327 my $self = shift;
179 2         12 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 333 my $self = shift;
197 2         15 return $self->_ok(\&_num_tests_requires_ok, \&_requires_ok);
198             }
199              
200             sub _ok {
201 10     10   26 my $self = shift;
202 10         28 my ($num_tests_subref, @ok_subrefs) = @_;
203              
204 10 50       44 croak('Already tested. Calling ok(), used_ok() and requires_ok() in same test file is not allowed') if ( !!$self->{tested} );
205              
206 10         40 my $num_tests = $num_tests_subref->($self);
207 10         117 return $self->_do_test($num_tests, @ok_subrefs);
208             }
209              
210             sub _do_test {
211 10     10   26 my $self = shift;
212 10         32 my ($num_tests, @ok_subrefs) = @_;
213              
214 10         111 my $test = Test::Builder->new();
215 10 100       225 my $test_status = $num_tests > 0 ? $self->_do_test_normal($num_tests, @ok_subrefs) :
216             $self->_do_test_no_tests();
217 10         47 $self->{tested} = 1;
218 10         396 return !!$test_status;
219             }
220              
221             sub _do_test_normal {
222 9     9   23 my $self = shift;
223 9         29 my ($num_tests, @ok_subrefs) = @_;
224              
225 9         35 my $test = Test::Builder->new();
226 9         80 $test->plan(tests => $num_tests);
227 9         2013 my @status;
228 9         47 for my $ok_subref ( @ok_subrefs ) {
229 14         161 push(@status, $ok_subref->($self, $test));
230             }
231 9     13   178 my $test_status = all { $_ } @status;
  13         53  
232 9         55 return !!$test_status;
233             }
234              
235             sub _do_test_no_tests {
236 1     1   2 my $self = shift;
237              
238 1         4 my $test = Test::Builder->new();
239 1         7 $test->plan(tests => 1);
240 1         192 $test->ok(1, "no tests run");
241 1         292 return 1;
242             }
243              
244             sub _used_ok {
245 7     7   17 my $self = shift;
246 7         15 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         60 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     82 return $status_lib && $status_test;
256             }
257              
258             sub _requires_ok {
259 7     7   17 my $self = shift;
260 7         17 my ($test) = @_;
261 7         39 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         59 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     110 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 40 my $self = shift;
288 22         45 my @exclude_module_names = @_;
289 22         108 push @{$self->{exclude_in_libdir}},@exclude_module_names;
  22         83  
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 36 my $self = shift;
309 19         42 my @exclude_module_names = @_;
310 19         34 push @{$self->{exclude_in_testdir}},@exclude_module_names;
  19         67  
311             }
312              
313             sub _version {
314 213     213   486 my $self = shift;
315 213 100       828 if ( !defined $self->{version} ) {
316 15   100     70 $self->{version} = $self->_perl_version || $self->_version_from_meta || "5.008000";
317             }
318 213         2243 return $self->{version};
319             }
320              
321             sub _num_tests {
322 6     6   14 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   12 my $self = shift;
328 8         33 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   21 my $self = shift;
334 8         47 return scalar($self->_remove_core($self->_requires,
335             $self->_build_requires));
336              
337             }
338              
339             sub _check_required_but_not_used {
340 14     14   36 my $self = shift;
341 14         38 my ($test, $used_aref, $requires_aref, $place) = @_;
342 14         34 my @requires = @{$requires_aref};
  14         44  
343 14         25 my @used = @{$used_aref};
  14         42  
344              
345 14         26 my $result = 1;
346 14         50 for my $requires ( @requires ) {
347 29     66   263 my $status = any { $_ eq $requires } @used;
  66         105  
348 29         251 $test->ok( $status, "check required module: $requires" );
349 29 100       9949 if ( !$status ) {
350 2         15 $test->diag("module $requires is required in META file but not used in $place");
351 2         135 $result = 0;
352             }
353             }
354 14         50 return $result;
355             }
356              
357             sub _check_used_but_not_required {
358 14     14   39 my $self = shift;
359 14         38 my ($test, $used_aref, $requires_aref, $place) = @_;
360 14         28 my @requires = @{$requires_aref};
  14         45  
361 14         25 my @used = @{$used_aref};
  14         39  
362              
363 14         25 my $result = 1;
364 14         46 for my $used ( @used ) {
365 29     64   282 my $status = any { $_ eq $used } @requires;
  64         149  
366 29         237 $test->ok( $status, "check used module: $used" );
367 29 100       9512 if ( !$status ) {
368 2         13 $test->diag("module $used is used in $place but not required");
369 2         132 $result = 0;
370             }
371             }
372 14         49 return $result;
373             }
374              
375             sub _pm_files {
376 40     40   84 my $self = shift;
377 40 100       239 if ( !defined $self->{pm_files} ) {
378 22         99 my @files = $self->_find_files_by_ext($self->_lib_dir, qr/\.pm$/);
379 22         112 $self->{pm_files} = \@files;
380             }
381 40         55 return @{$self->{pm_files}};
  40         173  
382             }
383              
384             sub _pm_files_in_test {
385 41     41   74 my $self = shift;
386 41 100       171 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         100 $self->{pm_files_in_test} = \@files;
389             }
390 41         61 return @{$self->{pm_files_in_test}};
  41         185  
391             }
392              
393             sub _test_files {
394 16     16   37 my $self = shift;
395             return (
396 16         61 $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   95 my $self = shift;
403 60         350 my ($start_dirs_aref, $ext_qr) = @_;
404 60         74 my @result;
405             find( sub {
406 505 100   505   11608 push @result, catfile($File::Find::dir, $_) if ( $_ =~ $ext_qr );
407             },
408 60         331 @{$start_dirs_aref});
  60         5593  
409 60         418 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         48 my @used = map { modules_used_in_document($self->_ppi_for($_)) } $self->_pm_files;
  13         45  
416 13         213350 my @result = uniq _array_difference(\@used, $self->{exclude_in_libdir});
417 13         62 $self->{used_modules} = \@result;
418             }
419 25         42 return @{$self->{used_modules}};
  25         152  
420             }
421              
422             sub _used_modules_in_test {
423 26     26   55 my $self = shift;
424 26 100       118 if ( !defined $self->{used_modules_in_test} ) {
425 14         56 my @used = map { modules_used_in_document($self->_ppi_for($_)) } $self->_test_files;
  47         151144  
426 14         32713 my @result = uniq _array_difference(\@used, $self->{exclude_in_testdir});
427 14         70 $self->{used_modules_in_test} = \@result;
428             }
429 26         46 return @{$self->{used_modules_in_test}};
  26         157  
430             }
431              
432             sub _array_difference {
433 79     79   173 my ( $aref1, $aref2 ) = @_;
434 79         116 my @a1 = @{$aref1};
  79         211  
435 79         123 my @a2 = @{$aref2};
  79         157  
436              
437 79         206 for my $a2 ( @a2 ) {
438 44         73 @a1 = grep { $_ ne $a2 } @a1;
  372         473  
439             }
440 79         311 my @result = sort @a1;
441 79         651 return @result;
442             }
443              
444             sub _version_from_meta {
445 14     14   27 my $self = shift;
446 14         149 return $self->{version_from_meta};
447             }
448              
449             sub _remove_core {
450 74     74   145 my $self = shift;
451 74         178 my( @module_names ) = @_;
452 74         152 my @result = grep { !$self->_is_core_module($_) } @module_names;
  338         1127  
453 74         738 return @result;
454             }
455              
456             sub _is_core_module {
457 338     338   552 my $self = shift;
458 338         493 my($module_name) = @_;
459 338         1301 my $first_release = Module::CoreList->first_release($module_name);
460 338   100     3364490 return defined $first_release && $first_release <= $self->_version;
461             }
462              
463             sub _read_meta {
464 14     14   46 my $self = shift;
465 14         115 my $meta = CPAN::Meta->load_file( $self->_meta_file );
466 14         330446 my $prereqs = $meta->prereqs();
467 14         31361 $self->{build_requires} = $prereqs->{build}->{requires};
468 14         138 my $requires = $prereqs->{runtime}->{requires};
469 14 100       297 $self->{version_from_meta} = version->parse($requires->{perl})->numify() if defined $requires->{perl};
470 14         186 delete $requires->{perl};
471 14         263 $self->{requires} = $requires;
472             }
473              
474             sub _build_requires {
475 26     26   73 my $self = shift;
476              
477 26 50       119 $self->_read_meta if !defined $self->{build_requires};
478 26         46 my @result = sort keys %{$self->{build_requires}};
  26         171  
479 26         132 return _array_difference(\@result, $self->{exclude_in_build_requires});
480             }
481              
482             sub _requires {
483 26     26   64 my $self = shift;
484              
485 26 100       194 $self->_read_meta if !defined $self->{requires};
486 26         47 my @result = sort keys %{$self->{requires}};
  26         197  
487 26         146 return _array_difference(\@result, $self->{exclude_in_requires});
488             }
489              
490             # find package statements in lib
491             sub _get_packages {
492 24     24   50 my $self = shift;
493 24         107 my @packages = $self->_packages_in( $self->_pm_files );
494 24         122 my @exclude_in_testdir = (__PACKAGE__, @packages, $self->_packages_in($self->_pm_files_in_test));
495 24 100       163 $self->push_exclude_in_testdir(@exclude_in_testdir) if ( !defined $self->{exclude_in_testdir} );
496 24 100       144 $self->push_exclude_in_libdir(@packages) if ( !defined $self->{exclude_in_libdir} );
497             }
498              
499             sub _packages_in {
500 51     51   86 my $self = shift;
501 51         89 my ( @filenames ) = @_;
502              
503 51         63 my @result;
504 51         101 for my $filename ( @filenames ) {
505 31         97 my @packages = $self->_packages_in_file($filename);
506 31         80 push @result, @packages;
507             }
508 51         150 return @result;
509             }
510              
511             sub _packages_in_file {
512 31     31   49 my $self = shift;
513 31         54 my ( $filename ) = @_;
514 31         93 my @ppi_package_statements = $self->_ppi_package_statements($filename);
515 31         49 my @result;
516 31         76 for my $ppi_package_statement ( @ppi_package_statements ) {
517 31         126 push @result, $self->_package_names_in($ppi_package_statement);
518             }
519 31         95 return @result;
520             }
521              
522             sub _ppi_package_statements {
523 31     31   54 my $self = shift;
524 31         45 my ($filename) = @_;
525              
526 31         102 my $doc = $self->_ppi_for($filename);
527 31         183 my $packages = $doc->find('PPI::Statement::Package');
528 31 50       809911 return if ( $packages eq '' );
529 31         67 return @{ $packages };
  31         119  
530             }
531              
532             sub _package_names_in {
533 31     31   47 my $self = shift;
534 31         53 my ($ppi_package_statement) = @_;
535 31         46 my @result;
536 31         40 for my $token ( @{$ppi_package_statement->{children}} ) {
  31         91  
537 124 100       470 next if ( !$self->_is_package_name($token) );
538 31         196 push @result, $token->content;
539             }
540 31         98 return @result;
541             }
542              
543             sub _is_package_name {
544 124     124   120 my $self = shift;
545 124         114 my ($ppi_token) = @_;
546 124   100     650 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   162 my $self = shift;
552 91         144 my ($filename) = @_;
553 91 100       371 if ( !defined $self->{ppi_for}->{$filename} ) {
554 71         488 my $doc = PPI::Document->new($filename);
555 71         3075540 $self->{ppi_for}->{$filename} = $doc;
556             }
557 91         488 return $self->{ppi_for}->{$filename};
558             }
559              
560              
561             1;
562             __END__