File Coverage

blib/lib/Test/DistManifest.pm
Criterion Covered Total %
statement 103 107 96.2
branch 24 26 92.3
condition 15 15 100.0
subroutine 14 14 100.0
pod 1 1 100.0
total 157 163 96.3


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