Upgrade to Test::Harness 3.14
[p5sagit/p5-mst-13.2.git] / ext / Test / Harness / lib / TAP / Parser / Source / Perl.pm
CommitLineData
b965d173 1package TAP::Parser::Source::Perl;
2
3use strict;
4use Config;
5use vars qw($VERSION @ISA);
6
7use constant IS_WIN32 => ( $^O =~ /^(MS)?Win32$/ );
8use constant IS_VMS => ( $^O eq 'VMS' );
9
10use TAP::Parser::Source;
27fc0087 11use TAP::Parser::Utils qw( split_shell );
12
b965d173 13@ISA = 'TAP::Parser::Source';
14
15=head1 NAME
16
17TAP::Parser::Source::Perl - Stream Perl output
18
19=head1 VERSION
20
27fc0087 21Version 3.14
b965d173 22
23=cut
24
27fc0087 25$VERSION = '3.14';
f7c69158 26
27=head1 SYNOPSIS
28
29 use TAP::Parser::Source::Perl;
30 my $perl = TAP::Parser::Source::Perl->new;
31 my $stream = $perl->source( [ $filename, @args ] )->get_stream;
b965d173 32
33=head1 DESCRIPTION
34
35Takes a filename and hopefully returns a stream from it. The filename should
36be the name of a Perl program.
37
38Note that this is a subclass of L<TAP::Parser::Source>. See that module for
39more methods.
40
b965d173 41=head1 METHODS
42
43=head2 Class Methods
44
45=head3 C<new>
46
47 my $perl = TAP::Parser::Source::Perl->new;
48
49Returns a new C<TAP::Parser::Source::Perl> object.
50
51=head2 Instance Methods
52
53=head3 C<source>
54
55Getter/setter the name of the test program and any arguments it requires.
56
57 my ($filename, @args) = @{ $perl->source };
58 $perl->source( [ $filename, @args ] );
59
f7c69158 60C<croak>s if C<$filename> could not be found.
61
b965d173 62=cut
63
64sub source {
65 my $self = shift;
66 $self->_croak("Cannot find ($_[0][0])")
67 if @_ && !-f $_[0][0];
68 return $self->SUPER::source(@_);
69}
70
71=head3 C<switches>
72
73 my $switches = $perl->switches;
74 my @switches = $perl->switches;
75 $perl->switches( \@switches );
76
77Getter/setter for the additional switches to pass to the perl executable. One
78common switch would be to set an include directory:
79
80 $perl->switches( ['-Ilib'] );
81
82=cut
83
84sub switches {
85 my $self = shift;
86 unless (@_) {
87 return wantarray ? @{ $self->{switches} } : $self->{switches};
88 }
89 my $switches = shift;
90 $self->{switches} = [@$switches]; # force a copy
91 return $self;
92}
93
94##############################################################################
95
96=head3 C<get_stream>
97
f7c69158 98 my $stream = $source->get_stream($parser);
b965d173 99
f7c69158 100Returns a stream of the output generated by executing C<source>. Must be
101passed an object that implements a C<make_iterator> method. Typically
102this is a TAP::Parser instance.
b965d173 103
104=cut
105
106sub get_stream {
f7c69158 107 my ( $self, $factory ) = @_;
b965d173 108
109 my @extra_libs;
110
111 my @switches = $self->_switches;
112 my $path_sep = $Config{path_sep};
113 my $path_pat = qr{$path_sep};
114
115 # Nasty kludge. It might be nicer if we got the libs separately
116 # although at least this way we find any -I switches that were
117 # supplied other then as explicit libs.
118 # We filter out any names containing colons because they will break
119 # PERL5LIB
120 my @libs;
121 for ( grep { $_ !~ $path_pat } @switches ) {
122 push @libs, $1 if / ^ ['"]? -I (.*?) ['"]? $ /x;
123 }
124
125 my $previous = $ENV{PERL5LIB};
126 if ($previous) {
127 push @libs, split( $path_pat, $previous );
128 }
129
130 my $setup = sub {
131 if (@libs) {
132 $ENV{PERL5LIB} = join( $path_sep, @libs );
133 }
134 };
135
136 # Cargo culted from comments seen elsewhere about VMS / environment
137 # variables. I don't know if this is actually necessary.
138 my $teardown = sub {
139 if ($previous) {
140 $ENV{PERL5LIB} = $previous;
141 }
142 else {
143 delete $ENV{PERL5LIB};
144 }
145 };
146
147 # Taint mode ignores environment variables so we must retranslate
148 # PERL5LIB as -I switches and place PERL5OPT on the command line
149 # in order that it be seen.
27fc0087 150 if ( grep { $_ eq "-T" || $_ eq "-t" } @switches ) {
b965d173 151 push @switches,
152 $self->_libs2switches(
153 split $path_pat,
154 $ENV{PERL5LIB} || $ENV{PERLLIB} || ''
155 );
156
27fc0087 157 push @switches, split_shell( $ENV{PERL5OPT} );
b965d173 158 }
159
160 my @command = $self->_get_command_for_switches(@switches)
161 or $self->_croak("No command found!");
162
f7c69158 163 return $factory->make_iterator(
b965d173 164 { command => \@command,
165 merge => $self->merge,
166 setup => $setup,
167 teardown => $teardown,
168 }
169 );
170}
171
172sub _get_command_for_switches {
173 my $self = shift;
174 my @switches = @_;
175 my ( $file, @args ) = @{ $self->source };
176 my $command = $self->_get_perl;
177
f7c69158 178# XXX we never need to quote if we treat the parts as atoms (except maybe vms)
179#$file = qq["$file"] if ( $file =~ /\s/ ) && ( $file !~ /^".*"$/ );
b965d173 180 my @command = ( $command, @switches, $file, @args );
181 return @command;
182}
183
184sub _get_command {
185 my $self = shift;
186 return $self->_get_command_for_switches( $self->_switches );
187}
188
189sub _libs2switches {
190 my $self = shift;
191 return map {"-I$_"} grep {$_} @_;
192}
193
194=head3 C<shebang>
195
196Get the shebang line for a script file.
197
f7c69158 198 my $shebang = TAP::Parser::Source::Perl->shebang( $some_script );
b965d173 199
200May be called as a class method
201
202=cut
203
204{
205
206 # Global shebang cache.
207 my %shebang_for;
208
209 sub _read_shebang {
210 my $file = shift;
211 local *TEST;
212 my $shebang;
213 if ( open( TEST, $file ) ) {
214 $shebang = <TEST>;
215 close(TEST) or print "Can't close $file. $!\n";
216 }
217 else {
218 print "Can't open $file. $!\n";
219 }
220 return $shebang;
221 }
222
223 sub shebang {
224 my ( $class, $file ) = @_;
225 unless ( exists $shebang_for{$file} ) {
226 $shebang_for{$file} = _read_shebang($file);
227 }
228 return $shebang_for{$file};
229 }
230}
231
232=head3 C<get_taint>
233
234Decode any taint switches from a Perl shebang line.
235
f7c69158 236 # $taint will be 't'
237 my $taint = TAP::Parser::Source::Perl->get_taint( '#!/usr/bin/perl -t' );
b965d173 238
f7c69158 239 # $untaint will be undefined
240 my $untaint = TAP::Parser::Source::Perl->get_taint( '#!/usr/bin/perl' );
b965d173 241
242=cut
243
244sub get_taint {
245 my ( $class, $shebang ) = @_;
246 return
247 unless defined $shebang
248 && $shebang =~ /^#!.*\bperl.*\s-\w*([Tt]+)/;
249 return $1;
250}
251
252sub _switches {
253 my $self = shift;
254 my ( $file, @args ) = @{ $self->source };
255 my @switches = (
256 $self->switches,
257 );
258
259 my $shebang = $self->shebang($file);
260 return unless defined $shebang;
261
262 my $taint = $self->get_taint($shebang);
263 push @switches, "-$taint" if defined $taint;
264
265 # Quote the argument if there's any whitespace in it, or if
266 # we're VMS, since VMS requires all parms quoted. Also, don't quote
267 # it if it's already quoted.
268 for (@switches) {
269 $_ = qq["$_"] if ( ( /\s/ || IS_VMS ) && !/^".*"$/ );
270 }
271
b965d173 272 return @switches;
273}
274
275sub _get_perl {
f7c69158 276 my $self = shift;
69f36734 277 return $ENV{HARNESS_PERL} if defined $ENV{HARNESS_PERL};
b965d173 278 return Win32::GetShortPathName($^X) if IS_WIN32;
279 return $^X;
280}
281
2821;
f7c69158 283
284=head1 SUBCLASSING
285
286Please see L<TAP::Parser/SUBCLASSING> for a subclassing overview.
287
288=head2 Example
289
290 package MyPerlSource;
291
292 use strict;
293 use vars '@ISA';
294
295 use Carp qw( croak );
296 use TAP::Parser::Source::Perl;
297
298 @ISA = qw( TAP::Parser::Source::Perl );
299
300 sub source {
301 my ($self, $args) = @_;
302 if ($args) {
303 $self->{file} = $args->[0];
304 return $self->SUPER::source($args);
305 }
306 return $self->SUPER::source;
307 }
308
309 # use the version of perl from the shebang line in the test file
310 sub _get_perl {
311 my $self = shift;
312 if (my $shebang = $self->shebang( $self->{file} )) {
313 $shebang =~ /^#!(.*\bperl.*?)(?:(?:\s)|(?:$))/;
314 return $1 if $1;
315 }
316 return $self->SUPER::_get_perl(@_);
317 }
318
319=head1 SEE ALSO
320
321L<TAP::Object>,
322L<TAP::Parser>,
323L<TAP::Parser::Source>,
324
325=cut