File Coverage

blib/lib/Test/Portability/Files.pm
Criterion Covered Total %
statement 80 99 80.8
branch 44 68 64.7
condition 10 18 55.5
subroutine 13 13 100.0
pod 3 3 100.0
total 150 201 74.6


line stmt bran cond sub pod time code
1             package Test::Portability::Files;
2             $Test::Portability::Files::VERSION = '0.09';
3             # ABSTRACT: Check file names portability
4 3     3   14497 use strict;
  3         9  
  3         103  
5 3     3   20 use warnings;
  3         6  
  3         114  
6 3     3   1787 use ExtUtils::Manifest qw(maniread);
  3         31182  
  3         270  
7 3     3   29 use File::Basename;
  3         8  
  3         193  
8 3     3   22 use File::Find;
  3         7  
  3         122  
9 3     3   19 use File::Spec;
  3         7  
  3         53  
10 3     3   16 use Test::Builder;
  3         7  
  3         277  
11             require Exporter;
12              
13             our @ISA = qw(Exporter);
14             our @EXPORT = qw(&options &run_tests);
15             our @EXPORT_OK = @EXPORT;
16              
17             my $Test = Test::Builder->new;
18              
19             sub import {
20 1     1   10 my $self = shift;
21 1         17 my $caller = caller;
22              
23             {
24             ## no critic
25 3     3   19 no strict 'refs';
  3         6  
  3         3864  
  1         3  
26 1         2 *{ $caller . '::options' } = \&options;
  1         7  
27 1         3 *{ $caller . '::run_tests' } = \&run_tests;
  1         3  
28             ## use critic
29             }
30              
31 1         7 $Test->exported_to($caller);
32 1 50       14 $Test->plan( tests => 1 ) unless $Test->has_plan;
33             }
34              
35             my %options = ( use_file_find => 0, );
36              
37             my %tests = (
38             ansi_chars => 1,
39             one_dot => 1,
40             dir_noext => 1,
41             special_chars => 1,
42             space => 1,
43             mac_length => 0,
44             amiga_length => 0,
45             vms_length => 1,
46             dos_length => 0,
47             case => 1,
48             'symlink' => 1,
49             windows_reserved => 1,
50             );
51              
52             my %errors_text =
53             ( # wrap the text at this column --------------------------------> |
54             ansi_chars =>
55             "These files does not respect the portable filename characters\n"
56             . "as defined by ANSI C and perlport:\n",
57              
58             one_dot => "These files contain more than one dot in their name:\n",
59              
60             dir_noext => "These directories have an extension in their name:\n",
61              
62             special_chars =>
63             "These files contain special characters that may break on\n"
64             . "several systems, please correct:\n",
65              
66             space => "These files contain space in their name, which is not well\n"
67             . "handled on several systems:\n",
68              
69             mac_length =>
70             "These files have a name more than 31 characters long, which\n"
71             . "will be truncated on Mac OS Classic and old AmigaOS:\n",
72              
73             amiga_length =>
74             "These files have a name more than 107 characters long, which\n"
75             . "will be truncated on recent AmigaOS:\n",
76              
77             vms_length =>
78             "These files have a name or extension too long for VMS (both\n"
79             . "are limited to 39 characters):\n",
80              
81             dos_length =>
82             "These files have a name too long for MS-DOS and compatible\n"
83             . "systems:\n",
84              
85             case => "The name of these files differ only by the case, which can\n"
86             . "cause real problems on case-insensitive filesystems:",
87              
88             'symlink' => "The following files are symbolic links, which are not\n"
89             . "supported on several operating systems:",
90             );
91              
92             my %bad_names = ();
93             my %lc_names = ();
94              
95              
96             sub options {
97 1     1 1 7 my %opts = @_;
98 1         21 for my $test ( keys %tests ) {
99 12 50       27 $tests{$test} = $opts{"test_$test"} if exists $opts{"test_$test"};
100             }
101 1         4 for my $opt ( keys %options ) {
102 1 50       3 $options{$opt} = $opts{$opt} if exists $opts{$opt};
103             }
104             @tests{ keys %tests } = ( $opts{all_tests} ) x ( keys %tests )
105 1 50       9 if exists $opts{all_tests};
106             }
107              
108              
109             sub test_name_portability {
110 45     45 1 5209 my ( $file, $file_name, $file_path, $file_ext );
111              
112             # extract path, base name and extension
113 45 50       99 if ( $options{use_file_find} ) { # using Find::File
114             # skip generated files
115 0 0 0     0 return if $_ eq File::Spec->curdir or $_ eq 'pm_to_blib';
116 0         0 my $firstdir = (
117             File::Spec->splitdir( File::Spec->canonpath($File::Find::name) ) )
118             [0];
119 0 0 0     0 return if $firstdir eq 'blib' or $firstdir eq '_build';
120              
121 0         0 $file = $File::Find::name;
122 0         0 ( $file_name, $file_path, $file_ext ) =
123             fileparse( $file, '\\.[^.]+?' );
124              
125             }
126             else { # only check against MANIFEST
127 45         78 $file = shift;
128 45         1059 ( $file_name, $file_path, $file_ext ) =
129             fileparse( $file, '\\.[^.]+?' );
130              
131             #for my $dir (File::Spec->splitdir(File::Spec->canonpath($file_path))) {
132             # test_name_portability($dir)
133             #}
134              
135 45         138 $_ = $file_name . $file_ext;
136             }
137              
138             #print STDERR "file $file\t=> path='$file_path', name='$file_name', ext='$file_ext'\n";
139              
140             # After this point, the following variables are expected to hold these semantics
141             # $file must contain the path to the file (t/00load.t)
142             # $_ must contain the full name of the file (00load.t)
143             # $file_name must contain the base name of the file (00load)
144             # $file_path must contain the path to the directory containing the file (t/)
145             # $file_ext must contain the extension (if any) of the file (.t)
146              
147             # check if the name only uses portable filename characters, as defined by ANSI C
148 45 50       115 if ( $tests{ansi_chars} ) {
149 45 100       171 /^[A-Za-z0-9._][A-Za-z0-9._-]*$/ or $bad_names{$file} .= 'ansi_chars,';
150             }
151              
152             # check if the name is a Windows Reserved Filename
153 45 50       95 if ( $tests{'windows_reserved'} ) {
154             $file_name =~ /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i
155 45 50       148 and $bad_names{$file} .= 'windows_reserved,';
156             }
157              
158             # check if the name contains more than one dot
159 45 50       93 if ( $tests{one_dot} ) {
160 45 100       115 tr/.// > 1 and $bad_names{$file} .= 'one_dot,';
161             }
162              
163             # check if the name contains special chars
164 45 50       96 if ( $tests{special_chars} ) {
165             m-[!"#\$%&'\(\)\*\+/:;<>\?@\[\\\]^`\{\|\}~]-
166 45 100       102 and $bad_names{$file} .= 'special_chars,';
167             }
168              
169             # check if the name contains a space char
170 45 50       87 if ( $tests{space} ) {
171 45 100       98 m/ / and $bad_names{$file} .= 'space,';
172             }
173              
174             # check the length of the name, compared to Mac OS Classic max length
175 45 100       83 if ( $tests{mac_length} ) {
176 14 100       41 length > 31 and $bad_names{$file} .= 'mac_length,';
177             }
178              
179             # check the length of the name, compared to AmigaOS max length
180 45 100       83 if ( $tests{amiga_length} ) {
181 14 100       30 length > 107 and $bad_names{$file} .= 'amiga_length,';
182             }
183              
184             # check the length of the name, compared to VMS max length
185 45 50       82 if ( $tests{vms_length} ) {
186             ( length($file_name) <= 39 and length($file_ext) <= 40 )
187 45 100 100     168 or $bad_names{$file} .= 'vms_length,';
188             }
189              
190             # check the length of the name, compared to DOS max length
191 45 100       84 if ( $tests{dos_length} ) {
192             ( length($file_name) <= 8 and length($file_ext) <= 4 )
193 14 100 100     52 or $bad_names{$file} .= 'dos_length,';
194             }
195              
196             # check if the name is unique on case-insensitive filesystems
197 45 50       86 if ( $tests{case} ) {
198 45 100 66     172 if ( not $lc_names{$file} and $lc_names{ lc $file } ) {
199 1         4 $bad_names{$file} .= 'case,';
200 1         4 $bad_names{ $lc_names{ lc $file } } .= 'case,';
201             }
202             else {
203 44         107 $lc_names{ lc $file } = $file;
204             }
205             }
206              
207             # check if the file is a symbolic link
208 45 50       89 if ( $tests{'symlink'} ) {
209 45 50       688 -l $file and $bad_names{$file} .= 'symlink,';
210             }
211              
212             # if it's a directory, check that it has no extension
213 45 50       163 if ( $tests{'dir_noext'} ) {
214 45 100 66     630 -d $file and tr/.// > 0 and $bad_names{$file} .= 'dir_noext,';
215             }
216             }
217              
218              
219             sub run_tests {
220 1     1 1 999 fileparse_set_fstype('Unix');
221              
222 1 50       6 if ( $options{use_file_find} ) {
223              
224             # check all files found using File::Find
225 0         0 find( \&test_name_portability, File::Spec->curdir );
226              
227             }
228             else {
229             # check only against files listed in MANIFEST
230 1         4 my $manifest = maniread();
231 1         525 map { test_name_portability($_) } keys %$manifest;
  31         89  
232             }
233              
234             # check the results
235 1         6 my $bad_names = _bad_names();
236 1 50       5 if ( keys %$bad_names ) {
237 0         0 $Test->ok( 0, "File names portability" );
238              
239 0         0 my %errors_list = ();
240 0         0 for my $file ( keys %$bad_names ) {
241 0         0 for my $error ( split ',', $bad_names->{$file} ) {
242 0 0       0 $errors_list{$error} = [] if not ref $errors_list{$error};
243 0         0 push @{ $errors_list{$error} }, $file;
  0         0  
244             }
245             }
246              
247 0         0 for my $error ( sort keys %errors_list ) {
248 0         0 $Test->diag( $errors_text{$error} );
249              
250 0         0 for my $file ( sort @{ $errors_list{$error} } ) {
  0         0  
251 0         0 $Test->diag(" $file");
252             }
253              
254 0         0 $Test->diag(' ');
255             }
256             }
257             else {
258 1         7 $Test->ok( 1, "File names portability" );
259             }
260             }
261              
262              
263             sub _bad_names {
264 15     15   78 return \%bad_names;
265             }
266              
267              
268             1;
269              
270             __END__