File Coverage

blib/lib/Test/Portability/Files.pm
Criterion Covered Total %
statement 80 99 80.8
branch 45 68 66.1
condition 10 18 55.5
subroutine 13 13 100.0
pod 3 3 100.0
total 151 201 75.1


line stmt bran cond sub pod time code
1             package Test::Portability::Files;
2             $Test::Portability::Files::VERSION = '0.10';
3             # ABSTRACT: Check file names portability
4 3     3   13258 use strict;
  3         6  
  3         90  
5 3     3   16 use warnings;
  3         6  
  3         87  
6 3     3   1420 use ExtUtils::Manifest qw(maniread);
  3         25107  
  3         204  
7 3     3   22 use File::Basename;
  3         6  
  3         164  
8 3     3   18 use File::Find;
  3         6  
  3         123  
9 3     3   17 use File::Spec;
  3         6  
  3         54  
10 3     3   15 use Test::Builder;
  3         6  
  3         269  
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         14 my $caller = caller;
22              
23             {
24             ## no critic
25 3     3   19 no strict 'refs';
  3         6  
  3         3706  
  1         3  
26 1         2 *{ $caller . '::options' } = \&options;
  1         7  
27 1         2 *{ $caller . '::run_tests' } = \&run_tests;
  1         3  
28             ## use critic
29             }
30              
31 1         6 $Test->exported_to($caller);
32 1 50       13 $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             windows_reserved =>
89             "These files have names that correspond to reserved character devices\n"
90             . "on Windows, and can cause unexpected problems:",
91              
92             'symlink' => "The following files are symbolic links, which are not\n"
93             . "supported on several operating systems:",
94             );
95              
96             my %bad_names = ();
97             my %lc_names = ();
98              
99              
100             sub options {
101 1     1 1 7 my %opts = @_;
102 1         18 for my $test ( keys %tests ) {
103 12 50       28 $tests{$test} = $opts{"test_$test"} if exists $opts{"test_$test"};
104             }
105 1         3 for my $opt ( keys %options ) {
106 1 50       4 $options{$opt} = $opts{$opt} if exists $opts{$opt};
107             }
108             @tests{ keys %tests } = ( $opts{all_tests} ) x ( keys %tests )
109 1 50       9 if exists $opts{all_tests};
110             }
111              
112              
113             sub test_name_portability {
114 50     50 1 6045 my ( $file, $file_name, $file_path, $file_ext );
115              
116             # extract path, base name and extension
117 50 50       109 if ( $options{use_file_find} ) { # using Find::File
118             # skip generated files
119 0 0 0     0 return if $_ eq File::Spec->curdir or $_ eq 'pm_to_blib';
120 0         0 my $firstdir = (
121             File::Spec->splitdir( File::Spec->canonpath($File::Find::name) ) )
122             [0];
123 0 0 0     0 return if $firstdir eq 'blib' or $firstdir eq '_build';
124              
125 0         0 $file = $File::Find::name;
126 0         0 ( $file_name, $file_path, $file_ext ) =
127             fileparse( $file, '\\.[^.]+?' );
128              
129             }
130             else { # only check against MANIFEST
131 50         92 $file = shift;
132 50         1104 ( $file_name, $file_path, $file_ext ) =
133             fileparse( $file, '\\.[^.]+?' );
134              
135             #for my $dir (File::Spec->splitdir(File::Spec->canonpath($file_path))) {
136             # test_name_portability($dir)
137             #}
138              
139 50         144 $_ = $file_name . $file_ext;
140             }
141              
142             #print STDERR "file $file\t=> path='$file_path', name='$file_name', ext='$file_ext'\n";
143              
144             # After this point, the following variables are expected to hold these semantics
145             # $file must contain the path to the file (t/00load.t)
146             # $_ must contain the full name of the file (00load.t)
147             # $file_name must contain the base name of the file (00load)
148             # $file_path must contain the path to the directory containing the file (t/)
149             # $file_ext must contain the extension (if any) of the file (.t)
150              
151             # check if the name only uses portable filename characters, as defined by ANSI C
152 50 50       129 if ( $tests{ansi_chars} ) {
153 50 100       178 /^[A-Za-z0-9._][A-Za-z0-9._-]*$/ or $bad_names{$file} .= 'ansi_chars,';
154             }
155              
156             # check if the name is a Windows Reserved Filename
157 50 50       109 if ( $tests{'windows_reserved'} ) {
158             $file_name =~ /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\..*)*$/i
159 50 100       153 and $bad_names{$file} .= 'windows_reserved,';
160             }
161              
162             # check if the name contains more than one dot
163 50 50       87 if ( $tests{one_dot} ) {
164 50 100       135 tr/.// > 1 and $bad_names{$file} .= 'one_dot,';
165             }
166              
167             # check if the name contains special chars
168 50 50       99 if ( $tests{special_chars} ) {
169             m-[!"#\$%&'\(\)\*\+/:;<>\?@\[\\\]^`\{\|\}~]-
170 50 100       105 and $bad_names{$file} .= 'special_chars,';
171             }
172              
173             # check if the name contains a space char
174 50 50       88 if ( $tests{space} ) {
175 50 100       108 m/ / and $bad_names{$file} .= 'space,';
176             }
177              
178             # check the length of the name, compared to Mac OS Classic max length
179 50 100       95 if ( $tests{mac_length} ) {
180 18 100       44 length > 31 and $bad_names{$file} .= 'mac_length,';
181             }
182              
183             # check the length of the name, compared to AmigaOS max length
184 50 100       94 if ( $tests{amiga_length} ) {
185 18 100       34 length > 107 and $bad_names{$file} .= 'amiga_length,';
186             }
187              
188             # check the length of the name, compared to VMS max length
189 50 50       84 if ( $tests{vms_length} ) {
190             ( length($file_name) <= 39 and length($file_ext) <= 40 )
191 50 100 100     183 or $bad_names{$file} .= 'vms_length,';
192             }
193              
194             # check the length of the name, compared to DOS max length
195 50 100       93 if ( $tests{dos_length} ) {
196             ( length($file_name) <= 8 and length($file_ext) <= 4 )
197 18 100 100     58 or $bad_names{$file} .= 'dos_length,';
198             }
199              
200             # check if the name is unique on case-insensitive filesystems
201 50 50       82 if ( $tests{case} ) {
202 50 100 66     189 if ( not $lc_names{$file} and $lc_names{ lc $file } ) {
203 1         3 $bad_names{$file} .= 'case,';
204 1         3 $bad_names{ $lc_names{ lc $file } } .= 'case,';
205             }
206             else {
207 49         128 $lc_names{ lc $file } = $file;
208             }
209             }
210              
211             # check if the file is a symbolic link
212 50 50       99 if ( $tests{'symlink'} ) {
213 50 50       692 -l $file and $bad_names{$file} .= 'symlink,';
214             }
215              
216             # if it's a directory, check that it has no extension
217 50 50       154 if ( $tests{'dir_noext'} ) {
218 50 100 66     588 -d $file and tr/.// > 0 and $bad_names{$file} .= 'dir_noext,';
219             }
220             }
221              
222              
223             sub run_tests {
224 1     1 1 925 fileparse_set_fstype('Unix');
225              
226 1 50       6 if ( $options{use_file_find} ) {
227              
228             # check all files found using File::Find
229 0         0 find( \&test_name_portability, File::Spec->curdir );
230              
231             }
232             else {
233             # check only against files listed in MANIFEST
234 1         4 my $manifest = maniread();
235 1         515 map { test_name_portability($_) } keys %$manifest;
  32         83  
236             }
237              
238             # check the results
239 1         6 my $bad_names = _bad_names();
240 1 50       4 if ( keys %$bad_names ) {
241 0         0 $Test->ok( 0, "File names portability" );
242              
243 0         0 my %errors_list = ();
244 0         0 for my $file ( keys %$bad_names ) {
245 0         0 for my $error ( split ',', $bad_names->{$file} ) {
246 0 0       0 $errors_list{$error} = [] if not ref $errors_list{$error};
247 0         0 push @{ $errors_list{$error} }, $file;
  0         0  
248             }
249             }
250              
251 0         0 for my $error ( sort keys %errors_list ) {
252 0         0 $Test->diag( $errors_text{$error} );
253              
254 0         0 for my $file ( sort @{ $errors_list{$error} } ) {
  0         0  
255 0         0 $Test->diag(" $file");
256             }
257              
258 0         0 $Test->diag(' ');
259             }
260             }
261             else {
262 1         6 $Test->ok( 1, "File names portability" );
263             }
264             }
265              
266              
267             sub _bad_names {
268 19     19   93 return \%bad_names;
269             }
270              
271              
272             1;
273              
274             __END__