File Coverage

blib/lib/Test/DistManifest.pm
Criterion Covered Total %
statement 100 104 96.1
branch 24 26 92.3
condition 15 15 100.0
subroutine 13 13 100.0
pod 1 1 100.0
total 153 159 96.2


line stmt bran cond sub pod time code
1             package Test::DistManifest; # git description: v1.013-7-g5a494e0
2             # ABSTRACT: Author test that validates a package MANIFEST
3             # KEYWORDS: test distribution manifest files upload contents author
4              
5             our $VERSION = '1.014';
6              
7 4     4   55515 use strict;
  4         6  
  4         134  
8 4     4   15 use warnings;
  4         4  
  4         101  
9 4     4   1988 use ExtUtils::Manifest;
  4         36206  
  4         211  
10              
11             #pod =head1 EXPORTS
12             #pod
13             #pod By default, this module exports the following functions:
14             #pod
15             #pod =over
16             #pod
17             #pod =item * manifest_ok
18             #pod
19             #pod =back
20             #pod
21             #pod =cut
22              
23             # File management commands
24 4     4   26 use Cwd ();
  4         5  
  4         51  
25 4     4   14 use File::Spec; # Portability
  4         5  
  4         46  
26 4     4   12 use File::Spec::Unix; # To get UNIX-style paths
  4         5  
  4         75  
27 4     4   12 use File::Find (); # Traverse the filesystem tree
  4         6  
  4         53  
28              
29 4     4   2010 use Module::Manifest 0.07;
  4         14520  
  4         111  
30 4     4   24 use Test::Builder;
  4         6  
  4         342  
31              
32             my $test = Test::Builder->new;
33              
34             my @EXPORTS = (
35             'manifest_ok',
36             );
37              
38             # These platforms were copied from File::Spec
39             my %platforms = (
40             MacOS => 1,
41             MSWin32 => 1,
42             os2 => 1,
43             VMS => 1,
44             epoc => 1,
45             NetWare => 1,
46             symbian => 1,
47             dos => 1,
48             cygwin => 1,
49             );
50              
51             # Looking at other Test modules this seems to be an ad-hoc standard
52             sub import {
53 4     4   29 my ($self, @plan) = @_;
54 4         10 my $caller = caller;
55              
56             {
57 4     4   48 no strict 'refs';
  4         5  
  4         2413  
  4         9  
58 4         7 for my $func (@EXPORTS) {
59 4         34 *{$caller . '::' . $func} = \&{$func};
  4         29  
  4         10  
60             }
61             }
62              
63 4         17 $test->exported_to($caller);
64 4         39 $test->plan(@plan);
65 4         1385 return;
66             }
67              
68             #pod =head1 DESCRIPTION
69             #pod
70             #pod This module provides a simple method of testing that a MANIFEST matches the
71             #pod distribution.
72             #pod
73             #pod It tests three things:
74             #pod
75             #pod =for stopwords unsatisfiable
76             #pod
77             #pod =over
78             #pod
79             #pod =item 1
80             #pod
81             #pod Everything in B exists
82             #pod
83             #pod =item 2
84             #pod
85             #pod Everything in the package is listed in B, or subsequently matches
86             #pod a regular expression mask in B
87             #pod
88             #pod =item 3
89             #pod
90             #pod Nothing exists in B that also matches a mask in B,
91             #pod so as to avoid an unsatisfiable dependency conditions
92             #pod
93             #pod =back
94             #pod
95             #pod If there is no B included in your distribution, this module
96             #pod will replicate the toolchain behaviour of using the default system-wide
97             #pod MANIFEST.SKIP file. To view the contents of this file, use the command:
98             #pod
99             #pod $ perldoc -m ExtUtils::MANIFEST.SKIP
100             #pod
101             #pod =head1 SYNOPSIS
102             #pod
103             #pod This is the common idiom for author test modules like this, but see
104             #pod the full example in examples/checkmanifest.t and, more importantly,
105             #pod Adam Kennedy's article: L
106             #pod
107             #pod use Test::More;
108             #pod eval 'use Test::DistManifest';
109             #pod if ($@) {
110             #pod plan skip_all => 'Test::DistManifest required to test MANIFEST';
111             #pod }
112             #pod
113             #pod manifest_ok('MANIFEST', 'MANIFEST.SKIP'); # Default options
114             #pod
115             #pod manifest_ok(); # Functionally equivalent to above
116             #pod
117             #pod =head1 FUNCTIONS
118             #pod
119             #pod =head2 manifest_ok
120             #pod
121             #pod manifest_ok( $manifest, $skipfile )
122             #pod
123             #pod This subroutine checks the manifest list contained in C<$manifest> by using
124             #pod C to determine the list of files and then checking for the
125             #pod existence of all such files. Then, it checks if there are any files in the
126             #pod distribution that were not specified in the C<$manifest> file but do not match
127             #pod any regular expressions provided in the C<$skipfile> exclusion file.
128             #pod
129             #pod If your MANIFEST file is generated by a module installation toolchain system
130             #pod such as L, L or L, then
131             #pod you shouldn't have any problems with these files. It's just a helpful test
132             #pod to remind you to update these files, using:
133             #pod
134             #pod $ make manifest # For ExtUtils::MakeMaker
135             #pod $ ./Build manifest # For Module::Build
136             #pod
137             #pod =head1 NON-FATAL ERRORS
138             #pod
139             #pod By default, errors in the B or B files are treated
140             #pod as fatal, which really is the purpose of using C as part
141             #pod of your author test suite.
142             #pod
143             #pod In some cases this is not desirable behaviour, such as with the Debian Perl
144             #pod Group, which runs all tests - including author tests - as part of its module
145             #pod packaging process. This wreaks havoc because Debian adds its control files
146             #pod in C downstream, and that directory or its files are generally not
147             #pod in B.
148             #pod
149             #pod By setting the environment variable B to a true value,
150             #pod errors will be non-fatal - they show up as diagnostic messages only, but all
151             #pod tests pass from the perspective of C.
152             #pod
153             #pod This can be used in a test script as:
154             #pod
155             #pod $ENV{MANIFEST_WARN_ONLY} = 1;
156             #pod
157             #pod or from other shell scripts as:
158             #pod
159             #pod export MANIFEST_WARN_ONLY=1
160             #pod
161             #pod Note that parsing errors in B and circular dependencies will
162             #pod always be considered fatal. The author is not aware of any cases where
163             #pod other behaviour would be useful.
164             #pod
165             #pod =cut
166              
167             sub manifest_ok {
168 10   100 10 1 4047 my $warn_only = $ENV{MANIFEST_WARN_ONLY} || 0;
169              
170 10   100     28 my $manifile = shift || 'MANIFEST';
171 10   100     35 my $skipfile = shift || 'MANIFEST.SKIP';
172              
173 10         45 my $root = Cwd::getcwd(); # this is Build.PL's Cwd
174 10         68 my $manifest = Module::Manifest->new;
175              
176 10 100       142 unless ($test->has_plan) {
177 1         16 $test->plan(tests => 4);
178             }
179              
180             # Try to parse the MANIFEST and MANIFEST.SKIP files
181 10         620 eval {
182 10         37 $manifest->open(manifest => $manifile);
183             };
184 10 100       2716 if ($@) {
185 2         9 $test->diag($!);
186             }
187 10         188 $test->ok(!$@, 'Parse MANIFEST or equivalent');
188              
189 10         3400 eval {
190 10         35 $manifest->open(skip => $skipfile);
191             };
192 10 100       2721 if ($@) {
193 4         31 $test->diag('Unable to parse MANIFEST.SKIP file:');
194 4         354 $test->diag($!);
195 4         628 $test->diag('Using default skip data from ExtUtils::Manifest ' . ExtUtils::Manifest->VERSION);
196              
197 4 50       527 open my $fh, '<', $ExtUtils::Manifest::DEFAULT_MSKIP
198             or die "Cannot open $ExtUtils::Manifest::DEFAULT_MSKIP: $!";
199 4         145 chomp(my @manifest_content = <$fh>);
200 4         25 $manifest->parse( skip => \@manifest_content );
201             }
202              
203 10         719 my @files;
204             # Callback function called by File::Find
205             my $closure = sub {
206             # Trim off the package root to determine the relative path.
207 148     148   7987 my $path = File::Spec->abs2rel($File::Find::name, $root);
208              
209             # Portably deal with different OSes
210 148 50       531 if ($platforms{$^O}) { # Check if we are on a non-Unix platform
211             # Get path info from File::Spec, split apart
212 0         0 my (undef, $dir, $file) = File::Spec->splitpath($path);
213 0         0 my @dir = File::Spec->splitdir($dir);
214              
215             # Reconstruct the path in Unix-style
216 0         0 $dir = File::Spec::Unix->catdir(@dir);
217 0         0 $path = File::Spec::Unix->catpath(undef, $dir, $file);
218             }
219              
220             # Test that the path is a file and then make sure it's not skipped
221 148 100 100     1842 if (-f $path && !$manifest->skipped($path)) {
222 99         21467 push @files, $path;
223             }
224 148         6031 return;
225 10         50 };
226              
227             # Traverse the directory recursively
228 10         699 File::Find::find({
229             wanted => $closure,
230             untaint => 1,
231             no_chdir => 1,
232             }, $root);
233              
234             # The two arrays have no duplicates. Thus we loop through them and
235             # add the result to a hash.
236 10         21 my %seen;
237             # Allocate buckets for the hash
238 10         46 keys(%seen) = 2 * scalar(@files);
239 10         42 foreach my $path (@files, $manifest->files) {
240 206         295 $seen{$path}++;
241             }
242              
243 10         19 my $flag = 1;
244 10         16 foreach my $path (@files) {
245             # Skip the path if it was seen twice (the expected condition)
246 99 100       697 next if ($seen{$path} == 2);
247              
248             # Oh no, we have files in @files not in $manifest->files
249 16 100       30 if ($flag == 1) {
250 4         15 $test->diag('Distribution files are missing in MANIFEST:');
251 4         365 $flag = 0;
252             }
253 16         27 $test->diag($path);
254             }
255 10   100     240 $test->ok($warn_only || $flag, 'All files are listed in MANIFEST or ' .
256             'skipped');
257              
258             # Reset the flag and test $manifest->files now
259 10         2932 $flag = 1;
260 10         21 my @circular = (); # for detecting circular logic
261 10         27 foreach my $path ($manifest->files) {
262             # Skip the path if it was seen twice (the expected condition)
263 107 100       1299 next if ($seen{$path} == 2);
264              
265             # If the file should exist but is passed by MANIFEST.SKIP, we have
266             # a strange circular logic condition.
267 24 100       106 if ($manifest->skipped($path)) {
268 2         49 push (@circular, $path);
269 2         3 next;
270             }
271              
272             # Oh no, we have files in $manifest->files not in @files
273 22 100       4286 if ($flag == 1) {
274 2         7 $test->diag('MANIFEST lists the following missing files:');
275 2         111 $flag = 0;
276             }
277 22         49 $test->diag($path);
278             }
279 10   100     175 $test->ok($warn_only || $flag, 'All files listed in MANIFEST exist ' .
280             'on disk');
281              
282             # Test for circular dependencies
283 10 100       2423 $flag = (scalar @circular == 0) ? 1 : 0;
284 10 100       28 if (not $flag) {
285 2         10 $test->diag('MANIFEST and MANIFEST.SKIP have circular dependencies:');
286 2         112 foreach my $path (@circular) {
287 2         6 $test->diag($path);
288             }
289             }
290 10         118 $test->ok($flag, 'No files are in both MANIFEST and MANIFEST.SKIP');
291              
292 10         2635 return;
293             }
294              
295             #pod =head1 GUTS
296             #pod
297             #pod This module internally plans four tests:
298             #pod
299             #pod =over
300             #pod
301             #pod =item 1
302             #pod
303             #pod B can be parsed by C
304             #pod
305             #pod =item 2
306             #pod
307             #pod Check which files exist in the distribution directory that do not match an
308             #pod existing regular expression in B and not listed in the
309             #pod B file. These files should either be excluded from the test by
310             #pod addition of a mask in MANIFEST.SKIP (in the case of temporary development
311             #pod or test files) or should be included in the MANIFEST.
312             #pod
313             #pod =item 3
314             #pod
315             #pod Check which files are specified in B but do not exist on the disk.
316             #pod This usually occurs when one deletes a test or similar script from the
317             #pod distribution, or accidentally moves it.
318             #pod
319             #pod =item 4
320             #pod
321             #pod Check which files are specified in both B and B.
322             #pod This is clearly an unsatisfiable condition, since the file in question
323             #pod cannot be expected to be included while also simultaneously ignored.
324             #pod
325             #pod =back
326             #pod
327             #pod If you want to run tests on multiple different MANIFEST files, you can
328             #pod simply pass 'no_plan' to the import function, like so:
329             #pod
330             #pod use Test::DistManifest 'no_plan';
331             #pod
332             #pod # Multiple tests work properly now
333             #pod manifest_ok('MANIFEST', 'MANIFEST.SKIP');
334             #pod manifest_ok();
335             #pod manifest_ok('MANIFEST.OTHER', 'MANIFEST.SKIP');
336             #pod
337             #pod I doubt this will be useful to users of this module. However, this is used
338             #pod internally for testing and it might be helpful to you. You can also plan
339             #pod more tests, but keep in mind that the idea of "3 internal tests" may change
340             #pod in the future.
341             #pod
342             #pod Example code:
343             #pod
344             #pod use Test::DistManifest tests => 5;
345             #pod manifest_ok(); # 4 tests
346             #pod ok(1, 'is 1 true?');
347             #pod
348             #pod =head1 ACKNOWLEDGEMENTS
349             #pod
350             #pod =over
351             #pod
352             #pod =item *
353             #pod
354             #pod Thanks to Adam Kennedy for developing L, which provides
355             #pod much of the core functionality for these tests.
356             #pod
357             #pod =item *
358             #pod
359             #pod Thanks to Apocalypse Eapocal@cpan.orgE, for helping me track down
360             #pod an obscure bug caused by circular dependencies: when files are expected by
361             #pod MANIFEST but explicitly skipped by MANIFEST.SKIP.
362             #pod
363             #pod =back
364             #pod
365             #pod =head1 SEE ALSO
366             #pod
367             #pod =over
368             #pod
369             #pod =item *
370             #pod L, a module providing similar functionality
371             #pod
372             #pod =item *
373             #pod L
374             #pod
375             #pod =item *
376             #pod L
377             #pod
378             #pod =item *
379             #pod L
380             #pod
381             #pod =back
382             #pod
383             #pod =head1 CAVEATS
384             #pod
385             #pod =over
386             #pod
387             #pod =item *
388             #pod
389             #pod There is currently no way to test a MANIFEST/MANIFEST.SKIP without having the
390             #pod files actually exist on disk. I am planning for this to change in the future.
391             #pod
392             #pod =item *
393             #pod
394             #pod This module has not been tested very thoroughly with Unicode.
395             #pod
396             #pod =item *
397             #pod
398             #pod This module does not produce any useful diagnostic messages in terms of how
399             #pod to correct the situation. Hopefully this will be obvious for anybody using
400             #pod the module; the emphasis should be on generating helpful error messages.
401             #pod
402             #pod =back
403             #pod
404             #pod =cut
405              
406             1;
407              
408             __END__