File Coverage

blib/lib/TAP/Parser/SourceHandler/Perl.pm
Criterion Covered Total %
statement 119 120 99.1
branch 39 46 84.7
condition 9 12 75.0
subroutine 25 25 100.0
pod 4 4 100.0
total 196 207 94.6


line stmt bran cond sub pod time code
1             package TAP::Parser::SourceHandler::Perl;
2              
3 38     38   3163 use strict;
  38         49  
  38         927  
4 38     38   127 use warnings;
  38         46  
  38         798  
5 38     38   123 use Config;
  38         42  
  38         2241  
6              
7 38     38   144 use constant IS_WIN32 => ( $^O =~ /^(MS)?Win32$/ );
  38         49  
  38         2018  
8 38     38   142 use constant IS_VMS => ( $^O eq 'VMS' );
  38         46  
  38         1561  
9              
10 38     38   3493 use TAP::Parser::IteratorFactory ();
  38         43  
  38         487  
11 38     38   3794 use TAP::Parser::Iterator::Process ();
  38         53  
  38         844  
12 38     38   15958 use Text::ParseWords qw(shellwords);
  38         38089  
  38         2047  
13              
14 38     38   195 use base 'TAP::Parser::SourceHandler::Executable';
  38         48  
  38         36566  
15              
16             TAP::Parser::IteratorFactory->register_handler(__PACKAGE__);
17              
18             =head1 NAME
19              
20             TAP::Parser::SourceHandler::Perl - Stream TAP from a Perl executable
21              
22             =head1 VERSION
23              
24             Version 3.38
25              
26             =cut
27              
28             our $VERSION = '3.38';
29              
30             =head1 SYNOPSIS
31              
32             use TAP::Parser::Source;
33             use TAP::Parser::SourceHandler::Perl;
34              
35             my $source = TAP::Parser::Source->new->raw( \'script.pl' );
36             $source->assemble_meta;
37              
38             my $class = 'TAP::Parser::SourceHandler::Perl';
39             my $vote = $class->can_handle( $source );
40             my $iter = $class->make_iterator( $source );
41              
42             =head1 DESCRIPTION
43              
44             This is a I L - it has 2 jobs:
45              
46             1. Figure out if the L it's given is actually a Perl
47             script (L).
48              
49             2. Creates an iterator for Perl sources (L).
50              
51             Unless you're writing a plugin or subclassing L, you probably
52             won't need to use this module directly.
53              
54             =head1 METHODS
55              
56             =head2 Class Methods
57              
58             =head3 C
59              
60             my $vote = $class->can_handle( $source );
61              
62             Only votes if $source looks like a file. Casts the following votes:
63              
64             0.9 if it has a shebang ala "#!...perl"
65             0.75 if it has any shebang
66             0.8 if it's a .t file
67             0.9 if it's a .pl file
68             0.75 if it's in a 't' directory
69             0.25 by default (backwards compat)
70              
71             =cut
72              
73             sub can_handle {
74 310     310 1 488 my ( $class, $source ) = @_;
75 310         760 my $meta = $source->meta;
76              
77 310 100       798 return 0 unless $meta->{is_file};
78 224         346 my $file = $meta->{file};
79              
80 224 100       560 if ( my $shebang = $file->{shebang} ) {
81 220 100       1492 return 0.9 if $shebang =~ /^#!.*\bperl/;
82              
83             # We favour Perl as the interpreter for any shebang to preserve
84             # previous semantics: we used to execute everything via Perl and
85             # relied on it to pass the shebang off to the appropriate
86             # interpreter.
87 161         474 return 0.3;
88             }
89              
90 4 100       14 return 0.8 if $file->{lc_ext} eq '.t'; # vote higher than Executable
91 3 100       10 return 0.9 if $file->{lc_ext} eq '.pl';
92              
93 2 100       12 return 0.75 if $file->{dir} =~ /^t\b/; # vote higher than Executable
94              
95             # backwards compat, always vote:
96 1         4 return 0.25;
97             }
98              
99             =head3 C
100              
101             my $iterator = $class->make_iterator( $source );
102              
103             Constructs & returns a new L for the source.
104             Assumes C<$source-Eraw> contains a reference to the perl script. Cs
105             if the file could not be found.
106              
107             The command to run is built as follows:
108              
109             $perl @switches $perl_script @test_args
110              
111             The perl command to use is determined by L. The command generated
112             is guaranteed to preserve:
113              
114             PERL5LIB
115             PERL5OPT
116             Taint Mode, if set in the script's shebang
117              
118             I the command generated will I respect any shebang line defined in
119             your Perl script. This is only a problem if you have compiled a custom version
120             of Perl or if you want to use a specific version of Perl for one test and a
121             different version for another, for example:
122              
123             #!/path/to/a/custom_perl --some --args
124             #!/usr/local/perl-5.6/bin/perl -w
125              
126             Currently you need to write a plugin to get around this.
127              
128             =cut
129              
130             sub _autoflush_stdhandles {
131 210     210   363 my ($class) = @_;
132              
133 210         1150 $class->_autoflush( \*STDOUT );
134 210         612 $class->_autoflush( \*STDERR );
135             }
136              
137             sub make_iterator {
138 210     210 1 313 my ( $class, $source ) = @_;
139 210         507 my $meta = $source->meta;
140 210         424 my $perl_script = ${ $source->raw };
  210         477  
141              
142 210 50       534 $class->_croak("Cannot find ($perl_script)") unless $meta->{is_file};
143              
144             # TODO: does this really need to be done here?
145 210         717 $class->_autoflush_stdhandles;
146              
147 210         965 my ( $libs, $switches )
148             = $class->_mangle_switches(
149             $class->_filter_libs( $class->_switches($source) ) );
150              
151 210         1018 $class->_run( $source, $libs, $switches );
152             }
153              
154              
155             sub _has_taint_switch {
156 361     361   595 my( $class, $switches ) = @_;
157              
158 361 100       358 my $has_taint = grep { $_ eq "-T" || $_ eq "-t" } @{$switches};
  273         979  
  361         628  
159 361 100       1351 return $has_taint ? 1 : 0;
160             }
161              
162             sub _mangle_switches {
163 210     210   392 my ( $class, $libs, $switches ) = @_;
164              
165             # Taint mode ignores environment variables so we must retranslate
166             # PERL5LIB as -I switches and place PERL5OPT on the command line
167             # in order that it be seen.
168 210 100       555 if ( $class->_has_taint_switch($switches) ) {
169 15 50       421 my @perl5lib = defined $ENV{PERL5LIB} ? split /$Config{path_sep}/, $ENV{PERL5LIB} : ();
170             return (
171             $libs,
172 15         114 [ @{$switches},
173             $class->_libs2switches([@$libs, @perl5lib]),
174 15 100       41 defined $ENV{PERL5OPT} ? shellwords( $ENV{PERL5OPT} ) : ()
175             ],
176             );
177             }
178              
179 195         406 return ( $libs, $switches );
180             }
181              
182             sub _filter_libs {
183 210     210   463 my ( $class, @switches ) = @_;
184              
185 210         3919 my $path_sep = $Config{path_sep};
186 210         1314 my $path_re = qr{$path_sep};
187              
188             # Filter out any -I switches to be handled as libs later.
189             #
190             # Nasty kludge. It might be nicer if we got the libs separately
191             # although at least this way we find any -I switches that were
192             # supplied other then as explicit libs.
193             #
194             # We filter out any names containing colons because they will break
195             # PERL5LIB
196 210         304 my @libs;
197             my @filtered_switches;
198 210         449 for (@switches) {
199 616 100 100     5257 if ( !/$path_re/ && m/ ^ ['"]? -I ['"]? (.*?) ['"]? $ /x ) {
200 527         1252 push @libs, $1;
201             }
202             else {
203 89         188 push @filtered_switches, $_;
204             }
205             }
206              
207 210         1292 return \@libs, \@filtered_switches;
208             }
209              
210             sub _iterator_hooks {
211 210     210   345 my ( $class, $source, $libs, $switches ) = @_;
212              
213             my $setup = sub {
214 210 100 100 210   248 if ( @{$libs} and !$class->_has_taint_switch($switches) ) {
  210         809  
215             $ENV{PERL5LIB} = join(
216 614         1604 $Config{path_sep}, grep {defined} @{$libs},
  139         280  
217             $ENV{PERL5LIB}
218 139         976 );
219             }
220 210         1479 };
221              
222             # VMS environment variables aren't guaranteed to reset at the end of
223             # the process, so we need to put PERL5LIB back.
224 210         556 my $previous = $ENV{PERL5LIB};
225             my $teardown = sub {
226 205 50   205   471 if ( defined $previous ) {
227 205         2377 $ENV{PERL5LIB} = $previous;
228             }
229             else {
230 0         0 delete $ENV{PERL5LIB};
231             }
232 210         735 };
233              
234 210         558 return ( $setup, $teardown );
235             }
236              
237             sub _run {
238 210     210   310 my ( $class, $source, $libs, $switches ) = @_;
239              
240 210 50       675 my @command = $class->_get_command_for_switches( $source, $switches )
241             or $class->_croak("No command found!");
242              
243 210         640 my ( $setup, $teardown ) = $class->_iterator_hooks( $source, $libs, $switches );
244              
245 210         626 return $class->_create_iterator( $source, \@command, $setup, $teardown );
246             }
247              
248             sub _create_iterator {
249 210     210   309 my ( $class, $source, $command, $setup, $teardown ) = @_;
250              
251 210         725 return TAP::Parser::Iterator::Process->new(
252             { command => $command,
253             merge => $source->merge,
254             setup => $setup,
255             teardown => $teardown,
256             }
257             );
258             }
259              
260             sub _get_command_for_switches {
261 210     210   353 my ( $class, $source, $switches ) = @_;
262 210         244 my $file = ${ $source->raw };
  210         582  
263 210 100       252 my @args = @{ $source->test_args || [] };
  210         494  
264 210         592 my $command = $class->get_perl;
265              
266             # XXX don't need to quote if we treat the parts as atoms (except maybe vms)
267             #$file = qq["$file"] if ( $file =~ /\s/ ) && ( $file !~ /^".*"$/ );
268 210         320 my @command = ( $command, @{$switches}, $file, @args );
  210         576  
269 210         918 return @command;
270             }
271              
272             sub _libs2switches {
273 15     15   26 my $class = shift;
274 15         25 return map {"-I$_"} grep {$_} @{ $_[0] };
  113         302  
  113         128  
  15         44  
275             }
276              
277             =head3 C
278              
279             Decode any taint switches from a Perl shebang line.
280              
281             # $taint will be 't'
282             my $taint = TAP::Parser::SourceHandler::Perl->get_taint( '#!/usr/bin/perl -t' );
283              
284             # $untaint will be undefined
285             my $untaint = TAP::Parser::SourceHandler::Perl->get_taint( '#!/usr/bin/perl' );
286              
287             =cut
288              
289             sub get_taint {
290 210     210 1 372 my ( $class, $shebang ) = @_;
291             return
292 210 100 66     1817 unless defined $shebang
293             && $shebang =~ /^#!.*\bperl.*\s-\w*([Tt]+)/;
294 15         70 return $1;
295             }
296              
297             sub _switches {
298 210     210   546 my ( $class, $source ) = @_;
299 210         304 my $file = ${ $source->raw };
  210         672  
300 210 100       292 my @switches = @{ $source->switches || [] };
  210         490  
301 210         531 my $shebang = $source->meta->{file}->{shebang};
302 210 50       533 return unless defined $shebang;
303              
304 210         615 my $taint = $class->get_taint($shebang);
305 210 100       1002 push @switches, "-$taint" if defined $taint;
306              
307             # Quote the argument if we're VMS, since VMS will downcase anything
308             # not quoted.
309 210         262 if (IS_VMS) {
310             for (@switches) {
311             $_ = qq["$_"];
312             }
313             }
314              
315 210         945 return @switches;
316             }
317              
318             =head3 C
319              
320             Gets the version of Perl currently running the test suite.
321              
322             =cut
323              
324             sub get_perl {
325 210     210 1 373 my $class = shift;
326 210 50       581 return $ENV{HARNESS_PERL} if defined $ENV{HARNESS_PERL};
327 210 50 33     1234 return qq["$^X"] if IS_WIN32 && ( $^X =~ /[^\w\.\/\\]/ );
328 210         413 return $^X;
329             }
330              
331             1;
332              
333             __END__