xref: /onnv-gate/usr/src/cmd/perl/5.8.4/distrib/lib/Test/Harness/Straps.pm (revision 0:68f95e015346)
1*0Sstevel@tonic-gate# -*- Mode: cperl; cperl-indent-level: 4 -*-
2*0Sstevel@tonic-gate# $Id: Straps.pm,v 1.35 2003/12/31 02:34:22 andy Exp $
3*0Sstevel@tonic-gate
4*0Sstevel@tonic-gatepackage Test::Harness::Straps;
5*0Sstevel@tonic-gate
6*0Sstevel@tonic-gateuse strict;
7*0Sstevel@tonic-gateuse vars qw($VERSION);
8*0Sstevel@tonic-gateuse Config;
9*0Sstevel@tonic-gate$VERSION = '0.19';
10*0Sstevel@tonic-gate
11*0Sstevel@tonic-gateuse Test::Harness::Assert;
12*0Sstevel@tonic-gateuse Test::Harness::Iterator;
13*0Sstevel@tonic-gate
14*0Sstevel@tonic-gate# Flags used as return values from our methods.  Just for internal
15*0Sstevel@tonic-gate# clarification.
16*0Sstevel@tonic-gatemy $TRUE  = (1==1);
17*0Sstevel@tonic-gatemy $FALSE = !$TRUE;
18*0Sstevel@tonic-gatemy $YES   = $TRUE;
19*0Sstevel@tonic-gatemy $NO    = $FALSE;
20*0Sstevel@tonic-gate
21*0Sstevel@tonic-gate
22*0Sstevel@tonic-gate=head1 NAME
23*0Sstevel@tonic-gate
24*0Sstevel@tonic-gateTest::Harness::Straps - detailed analysis of test results
25*0Sstevel@tonic-gate
26*0Sstevel@tonic-gate=head1 SYNOPSIS
27*0Sstevel@tonic-gate
28*0Sstevel@tonic-gate  use Test::Harness::Straps;
29*0Sstevel@tonic-gate
30*0Sstevel@tonic-gate  my $strap = Test::Harness::Straps->new;
31*0Sstevel@tonic-gate
32*0Sstevel@tonic-gate  # Various ways to interpret a test
33*0Sstevel@tonic-gate  my %results = $strap->analyze($name, \@test_output);
34*0Sstevel@tonic-gate  my %results = $strap->analyze_fh($name, $test_filehandle);
35*0Sstevel@tonic-gate  my %results = $strap->analyze_file($test_file);
36*0Sstevel@tonic-gate
37*0Sstevel@tonic-gate  # UNIMPLEMENTED
38*0Sstevel@tonic-gate  my %total = $strap->total_results;
39*0Sstevel@tonic-gate
40*0Sstevel@tonic-gate  # Altering the behavior of the strap  UNIMPLEMENTED
41*0Sstevel@tonic-gate  my $verbose_output = $strap->dump_verbose();
42*0Sstevel@tonic-gate  $strap->dump_verbose_fh($output_filehandle);
43*0Sstevel@tonic-gate
44*0Sstevel@tonic-gate
45*0Sstevel@tonic-gate=head1 DESCRIPTION
46*0Sstevel@tonic-gate
47*0Sstevel@tonic-gateB<THIS IS ALPHA SOFTWARE> in that the interface is subject to change
48*0Sstevel@tonic-gatein incompatible ways.  It is otherwise stable.
49*0Sstevel@tonic-gate
50*0Sstevel@tonic-gateTest::Harness is limited to printing out its results.  This makes
51*0Sstevel@tonic-gateanalysis of the test results difficult for anything but a human.  To
52*0Sstevel@tonic-gatemake it easier for programs to work with test results, we provide
53*0Sstevel@tonic-gateTest::Harness::Straps.  Instead of printing the results, straps
54*0Sstevel@tonic-gateprovide them as raw data.  You can also configure how the tests are to
55*0Sstevel@tonic-gatebe run.
56*0Sstevel@tonic-gate
57*0Sstevel@tonic-gateThe interface is currently incomplete.  I<Please> contact the author
58*0Sstevel@tonic-gateif you'd like a feature added or something change or just have
59*0Sstevel@tonic-gatecomments.
60*0Sstevel@tonic-gate
61*0Sstevel@tonic-gate=head1 Construction
62*0Sstevel@tonic-gate
63*0Sstevel@tonic-gate=head2 C<new>
64*0Sstevel@tonic-gate
65*0Sstevel@tonic-gate  my $strap = Test::Harness::Straps->new;
66*0Sstevel@tonic-gate
67*0Sstevel@tonic-gateInitialize a new strap.
68*0Sstevel@tonic-gate
69*0Sstevel@tonic-gate=cut
70*0Sstevel@tonic-gate
71*0Sstevel@tonic-gatesub new {
72*0Sstevel@tonic-gate    my($proto) = shift;
73*0Sstevel@tonic-gate    my($class) = ref $proto || $proto;
74*0Sstevel@tonic-gate
75*0Sstevel@tonic-gate    my $self = bless {}, $class;
76*0Sstevel@tonic-gate    $self->_init;
77*0Sstevel@tonic-gate
78*0Sstevel@tonic-gate    return $self;
79*0Sstevel@tonic-gate}
80*0Sstevel@tonic-gate
81*0Sstevel@tonic-gate=head2 C<_init>
82*0Sstevel@tonic-gate
83*0Sstevel@tonic-gate  $strap->_init;
84*0Sstevel@tonic-gate
85*0Sstevel@tonic-gateInitialize the internal state of a strap to make it ready for parsing.
86*0Sstevel@tonic-gate
87*0Sstevel@tonic-gate=cut
88*0Sstevel@tonic-gate
89*0Sstevel@tonic-gatesub _init {
90*0Sstevel@tonic-gate    my($self) = shift;
91*0Sstevel@tonic-gate
92*0Sstevel@tonic-gate    $self->{_is_vms}   = ( $^O eq 'VMS' );
93*0Sstevel@tonic-gate    $self->{_is_win32} = ( $^O =~ /^(MS)?Win32$/ );
94*0Sstevel@tonic-gate    $self->{_is_macos} = ( $^O eq 'MacOS' );
95*0Sstevel@tonic-gate}
96*0Sstevel@tonic-gate
97*0Sstevel@tonic-gate=head1 Analysis
98*0Sstevel@tonic-gate
99*0Sstevel@tonic-gate=head2 C<analyze>
100*0Sstevel@tonic-gate
101*0Sstevel@tonic-gate  my %results = $strap->analyze($name, \@test_output);
102*0Sstevel@tonic-gate
103*0Sstevel@tonic-gateAnalyzes the output of a single test, assigning it the given C<$name>
104*0Sstevel@tonic-gatefor use in the total report.  Returns the C<%results> of the test.
105*0Sstevel@tonic-gateSee L<Results>.
106*0Sstevel@tonic-gate
107*0Sstevel@tonic-gateC<@test_output> should be the raw output from the test, including
108*0Sstevel@tonic-gatenewlines.
109*0Sstevel@tonic-gate
110*0Sstevel@tonic-gate=cut
111*0Sstevel@tonic-gate
112*0Sstevel@tonic-gatesub analyze {
113*0Sstevel@tonic-gate    my($self, $name, $test_output) = @_;
114*0Sstevel@tonic-gate
115*0Sstevel@tonic-gate    my $it = Test::Harness::Iterator->new($test_output);
116*0Sstevel@tonic-gate    return $self->_analyze_iterator($name, $it);
117*0Sstevel@tonic-gate}
118*0Sstevel@tonic-gate
119*0Sstevel@tonic-gate
120*0Sstevel@tonic-gatesub _analyze_iterator {
121*0Sstevel@tonic-gate    my($self, $name, $it) = @_;
122*0Sstevel@tonic-gate
123*0Sstevel@tonic-gate    $self->_reset_file_state;
124*0Sstevel@tonic-gate    $self->{file} = $name;
125*0Sstevel@tonic-gate    my %totals  = (
126*0Sstevel@tonic-gate                   max      => 0,
127*0Sstevel@tonic-gate                   seen     => 0,
128*0Sstevel@tonic-gate
129*0Sstevel@tonic-gate                   ok       => 0,
130*0Sstevel@tonic-gate                   todo     => 0,
131*0Sstevel@tonic-gate                   skip     => 0,
132*0Sstevel@tonic-gate                   bonus    => 0,
133*0Sstevel@tonic-gate
134*0Sstevel@tonic-gate                   details  => []
135*0Sstevel@tonic-gate                  );
136*0Sstevel@tonic-gate
137*0Sstevel@tonic-gate    # Set them up here so callbacks can have them.
138*0Sstevel@tonic-gate    $self->{totals}{$name}         = \%totals;
139*0Sstevel@tonic-gate    while( defined(my $line = $it->next) ) {
140*0Sstevel@tonic-gate        $self->_analyze_line($line, \%totals);
141*0Sstevel@tonic-gate        last if $self->{saw_bailout};
142*0Sstevel@tonic-gate    }
143*0Sstevel@tonic-gate
144*0Sstevel@tonic-gate    $totals{skip_all} = $self->{skip_all} if defined $self->{skip_all};
145*0Sstevel@tonic-gate
146*0Sstevel@tonic-gate    my $passed = ($totals{max} == 0 && defined $totals{skip_all}) ||
147*0Sstevel@tonic-gate                 ($totals{max} && $totals{seen} &&
148*0Sstevel@tonic-gate                  $totals{max} == $totals{seen} &&
149*0Sstevel@tonic-gate                  $totals{max} == $totals{ok});
150*0Sstevel@tonic-gate    $totals{passing} = $passed ? 1 : 0;
151*0Sstevel@tonic-gate
152*0Sstevel@tonic-gate    return %totals;
153*0Sstevel@tonic-gate}
154*0Sstevel@tonic-gate
155*0Sstevel@tonic-gate
156*0Sstevel@tonic-gatesub _analyze_line {
157*0Sstevel@tonic-gate    my($self, $line, $totals) = @_;
158*0Sstevel@tonic-gate
159*0Sstevel@tonic-gate    my %result = ();
160*0Sstevel@tonic-gate
161*0Sstevel@tonic-gate    $self->{line}++;
162*0Sstevel@tonic-gate
163*0Sstevel@tonic-gate    my $type;
164*0Sstevel@tonic-gate    if( $self->_is_header($line) ) {
165*0Sstevel@tonic-gate        $type = 'header';
166*0Sstevel@tonic-gate
167*0Sstevel@tonic-gate        $self->{saw_header}++;
168*0Sstevel@tonic-gate
169*0Sstevel@tonic-gate        $totals->{max} += $self->{max};
170*0Sstevel@tonic-gate    }
171*0Sstevel@tonic-gate    elsif( $self->_is_test($line, \%result) ) {
172*0Sstevel@tonic-gate        $type = 'test';
173*0Sstevel@tonic-gate
174*0Sstevel@tonic-gate        $totals->{seen}++;
175*0Sstevel@tonic-gate        $result{number} = $self->{'next'} unless $result{number};
176*0Sstevel@tonic-gate
177*0Sstevel@tonic-gate        # sometimes the 'not ' and the 'ok' are on different lines,
178*0Sstevel@tonic-gate        # happens often on VMS if you do:
179*0Sstevel@tonic-gate        #   print "not " unless $test;
180*0Sstevel@tonic-gate        #   print "ok $num\n";
181*0Sstevel@tonic-gate        if( $self->{saw_lone_not} &&
182*0Sstevel@tonic-gate            ($self->{lone_not_line} == $self->{line} - 1) )
183*0Sstevel@tonic-gate        {
184*0Sstevel@tonic-gate            $result{ok} = 0;
185*0Sstevel@tonic-gate        }
186*0Sstevel@tonic-gate
187*0Sstevel@tonic-gate        my $pass = $result{ok};
188*0Sstevel@tonic-gate        $result{type} = 'todo' if $self->{todo}{$result{number}};
189*0Sstevel@tonic-gate
190*0Sstevel@tonic-gate        if( $result{type} eq 'todo' ) {
191*0Sstevel@tonic-gate            $totals->{todo}++;
192*0Sstevel@tonic-gate            $pass = 1;
193*0Sstevel@tonic-gate            $totals->{bonus}++ if $result{ok}
194*0Sstevel@tonic-gate        }
195*0Sstevel@tonic-gate        elsif( $result{type} eq 'skip' ) {
196*0Sstevel@tonic-gate            $totals->{skip}++;
197*0Sstevel@tonic-gate            $pass = 1;
198*0Sstevel@tonic-gate        }
199*0Sstevel@tonic-gate
200*0Sstevel@tonic-gate        $totals->{ok}++ if $pass;
201*0Sstevel@tonic-gate
202*0Sstevel@tonic-gate        if( $result{number} > 100000 && $result{number} > $self->{max} ) {
203*0Sstevel@tonic-gate            warn "Enormous test number seen [test $result{number}]\n";
204*0Sstevel@tonic-gate            warn "Can't detailize, too big.\n";
205*0Sstevel@tonic-gate        }
206*0Sstevel@tonic-gate        else {
207*0Sstevel@tonic-gate            $totals->{details}[$result{number} - 1] =
208*0Sstevel@tonic-gate                               {$self->_detailize($pass, \%result)};
209*0Sstevel@tonic-gate        }
210*0Sstevel@tonic-gate
211*0Sstevel@tonic-gate        # XXX handle counter mismatch
212*0Sstevel@tonic-gate    }
213*0Sstevel@tonic-gate    elsif ( $self->_is_bail_out($line, \$self->{bailout_reason}) ) {
214*0Sstevel@tonic-gate        $type = 'bailout';
215*0Sstevel@tonic-gate        $self->{saw_bailout} = 1;
216*0Sstevel@tonic-gate    }
217*0Sstevel@tonic-gate    else {
218*0Sstevel@tonic-gate        $type = 'other';
219*0Sstevel@tonic-gate    }
220*0Sstevel@tonic-gate
221*0Sstevel@tonic-gate    $self->{callback}->($self, $line, $type, $totals) if $self->{callback};
222*0Sstevel@tonic-gate
223*0Sstevel@tonic-gate    $self->{'next'} = $result{number} + 1 if $type eq 'test';
224*0Sstevel@tonic-gate}
225*0Sstevel@tonic-gate
226*0Sstevel@tonic-gate=head2 C<analyze_fh>
227*0Sstevel@tonic-gate
228*0Sstevel@tonic-gate  my %results = $strap->analyze_fh($name, $test_filehandle);
229*0Sstevel@tonic-gate
230*0Sstevel@tonic-gateLike C<analyze>, but it reads from the given filehandle.
231*0Sstevel@tonic-gate
232*0Sstevel@tonic-gate=cut
233*0Sstevel@tonic-gate
234*0Sstevel@tonic-gatesub analyze_fh {
235*0Sstevel@tonic-gate    my($self, $name, $fh) = @_;
236*0Sstevel@tonic-gate
237*0Sstevel@tonic-gate    my $it = Test::Harness::Iterator->new($fh);
238*0Sstevel@tonic-gate    $self->_analyze_iterator($name, $it);
239*0Sstevel@tonic-gate}
240*0Sstevel@tonic-gate
241*0Sstevel@tonic-gate=head2 C<analyze_file>
242*0Sstevel@tonic-gate
243*0Sstevel@tonic-gate  my %results = $strap->analyze_file($test_file);
244*0Sstevel@tonic-gate
245*0Sstevel@tonic-gateLike C<analyze>, but it runs the given C<$test_file> and parses its
246*0Sstevel@tonic-gateresults.  It will also use that name for the total report.
247*0Sstevel@tonic-gate
248*0Sstevel@tonic-gate=cut
249*0Sstevel@tonic-gate
250*0Sstevel@tonic-gatesub analyze_file {
251*0Sstevel@tonic-gate    my($self, $file) = @_;
252*0Sstevel@tonic-gate
253*0Sstevel@tonic-gate    unless( -e $file ) {
254*0Sstevel@tonic-gate        $self->{error} = "$file does not exist";
255*0Sstevel@tonic-gate        return;
256*0Sstevel@tonic-gate    }
257*0Sstevel@tonic-gate
258*0Sstevel@tonic-gate    unless( -r $file ) {
259*0Sstevel@tonic-gate        $self->{error} = "$file is not readable";
260*0Sstevel@tonic-gate        return;
261*0Sstevel@tonic-gate    }
262*0Sstevel@tonic-gate
263*0Sstevel@tonic-gate    local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
264*0Sstevel@tonic-gate
265*0Sstevel@tonic-gate    # *sigh* this breaks under taint, but open -| is unportable.
266*0Sstevel@tonic-gate    my $line = $self->_command_line($file);
267*0Sstevel@tonic-gate    unless( open(FILE, "$line|") ) {
268*0Sstevel@tonic-gate        print "can't run $file. $!\n";
269*0Sstevel@tonic-gate        return;
270*0Sstevel@tonic-gate    }
271*0Sstevel@tonic-gate
272*0Sstevel@tonic-gate    my %results = $self->analyze_fh($file, \*FILE);
273*0Sstevel@tonic-gate    my $exit = close FILE;
274*0Sstevel@tonic-gate    $results{'wait'} = $?;
275*0Sstevel@tonic-gate    if( $? && $self->{_is_vms} ) {
276*0Sstevel@tonic-gate        eval q{use vmsish "status"; $results{'exit'} = $?};
277*0Sstevel@tonic-gate    }
278*0Sstevel@tonic-gate    else {
279*0Sstevel@tonic-gate        $results{'exit'} = _wait2exit($?);
280*0Sstevel@tonic-gate    }
281*0Sstevel@tonic-gate    $results{passing} = 0 unless $? == 0;
282*0Sstevel@tonic-gate
283*0Sstevel@tonic-gate    $self->_restore_PERL5LIB();
284*0Sstevel@tonic-gate
285*0Sstevel@tonic-gate    return %results;
286*0Sstevel@tonic-gate}
287*0Sstevel@tonic-gate
288*0Sstevel@tonic-gate
289*0Sstevel@tonic-gateeval { require POSIX; &POSIX::WEXITSTATUS(0) };
290*0Sstevel@tonic-gateif( $@ ) {
291*0Sstevel@tonic-gate    *_wait2exit = sub { $_[0] >> 8 };
292*0Sstevel@tonic-gate}
293*0Sstevel@tonic-gateelse {
294*0Sstevel@tonic-gate    *_wait2exit = sub { POSIX::WEXITSTATUS($_[0]) }
295*0Sstevel@tonic-gate}
296*0Sstevel@tonic-gate
297*0Sstevel@tonic-gate=head2 C<_command_line( $file )>
298*0Sstevel@tonic-gate
299*0Sstevel@tonic-gate  my $command_line = $self->_command_line();
300*0Sstevel@tonic-gate
301*0Sstevel@tonic-gateReturns the full command line that will be run to test I<$file>.
302*0Sstevel@tonic-gate
303*0Sstevel@tonic-gate=cut
304*0Sstevel@tonic-gate
305*0Sstevel@tonic-gatesub _command_line {
306*0Sstevel@tonic-gate    my $self = shift;
307*0Sstevel@tonic-gate    my $file = shift;
308*0Sstevel@tonic-gate
309*0Sstevel@tonic-gate    my $command =  $self->_command();
310*0Sstevel@tonic-gate    my $switches = $self->_switches($file);
311*0Sstevel@tonic-gate
312*0Sstevel@tonic-gate    $file = qq["$file"] if ($file =~ /\s/) && ($file !~ /^".*"$/);
313*0Sstevel@tonic-gate    my $line = "$command $switches $file";
314*0Sstevel@tonic-gate
315*0Sstevel@tonic-gate    return $line;
316*0Sstevel@tonic-gate}
317*0Sstevel@tonic-gate
318*0Sstevel@tonic-gate
319*0Sstevel@tonic-gate=head2 C<_command>
320*0Sstevel@tonic-gate
321*0Sstevel@tonic-gate  my $command = $self->_command();
322*0Sstevel@tonic-gate
323*0Sstevel@tonic-gateReturns the command that runs the test.  Combine this with _switches()
324*0Sstevel@tonic-gateto build a command line.
325*0Sstevel@tonic-gate
326*0Sstevel@tonic-gateTypically this is C<$^X>, but you can set C<$ENV{HARNESS_COMMAND}>
327*0Sstevel@tonic-gateto use a different Perl than what you're running the harness under.
328*0Sstevel@tonic-gateThis might be to run a threaded Perl, for example.
329*0Sstevel@tonic-gate
330*0Sstevel@tonic-gateYou can also overload this method if you've built your own strap subclass,
331*0Sstevel@tonic-gatesuch as a PHP interpreter for a PHP-based strap.
332*0Sstevel@tonic-gate
333*0Sstevel@tonic-gate=cut
334*0Sstevel@tonic-gate
335*0Sstevel@tonic-gatesub _command {
336*0Sstevel@tonic-gate    my $self = shift;
337*0Sstevel@tonic-gate
338*0Sstevel@tonic-gate    return $ENV{HARNESS_PERL}           if defined $ENV{HARNESS_PERL};
339*0Sstevel@tonic-gate    return "MCR $^X"                    if $self->{_is_vms};
340*0Sstevel@tonic-gate    return Win32::GetShortPathName($^X) if $self->{_is_win32};
341*0Sstevel@tonic-gate    return $^X;
342*0Sstevel@tonic-gate}
343*0Sstevel@tonic-gate
344*0Sstevel@tonic-gate
345*0Sstevel@tonic-gate=head2 C<_switches>
346*0Sstevel@tonic-gate
347*0Sstevel@tonic-gate  my $switches = $self->_switches($file);
348*0Sstevel@tonic-gate
349*0Sstevel@tonic-gateFormats and returns the switches necessary to run the test.
350*0Sstevel@tonic-gate
351*0Sstevel@tonic-gate=cut
352*0Sstevel@tonic-gate
353*0Sstevel@tonic-gatesub _switches {
354*0Sstevel@tonic-gate    my($self, $file) = @_;
355*0Sstevel@tonic-gate
356*0Sstevel@tonic-gate    my @existing_switches = $self->_cleaned_switches( $Test::Harness::Switches, $ENV{HARNESS_PERL_SWITCHES} );
357*0Sstevel@tonic-gate    my @derived_switches;
358*0Sstevel@tonic-gate
359*0Sstevel@tonic-gate    local *TEST;
360*0Sstevel@tonic-gate    open(TEST, $file) or print "can't open $file. $!\n";
361*0Sstevel@tonic-gate    my $shebang = <TEST>;
362*0Sstevel@tonic-gate    close(TEST) or print "can't close $file. $!\n";
363*0Sstevel@tonic-gate
364*0Sstevel@tonic-gate    my $taint = ( $shebang =~ /^#!.*\bperl.*\s-\w*([Tt]+)/ );
365*0Sstevel@tonic-gate    push( @derived_switches, "-$1" ) if $taint;
366*0Sstevel@tonic-gate
367*0Sstevel@tonic-gate    # When taint mode is on, PERL5LIB is ignored.  So we need to put
368*0Sstevel@tonic-gate    # all that on the command line as -Is.
369*0Sstevel@tonic-gate    # MacPerl's putenv is broken, so it will not see PERL5LIB, tainted or not.
370*0Sstevel@tonic-gate    if ( $taint || $self->{_is_macos} ) {
371*0Sstevel@tonic-gate	my @inc = $self->_filtered_INC;
372*0Sstevel@tonic-gate	push @derived_switches, map { "-I$_" } @inc;
373*0Sstevel@tonic-gate    }
374*0Sstevel@tonic-gate
375*0Sstevel@tonic-gate    # Quote the argument if there's any whitespace in it, or if
376*0Sstevel@tonic-gate    # we're VMS, since VMS requires all parms quoted.  Also, don't quote
377*0Sstevel@tonic-gate    # it if it's already quoted.
378*0Sstevel@tonic-gate    for ( @derived_switches ) {
379*0Sstevel@tonic-gate	$_ = qq["$_"] if ((/\s/ || $self->{_is_vms}) && !/^".*"$/ );
380*0Sstevel@tonic-gate    }
381*0Sstevel@tonic-gate    return join( " ", @existing_switches, @derived_switches );
382*0Sstevel@tonic-gate}
383*0Sstevel@tonic-gate
384*0Sstevel@tonic-gate=head2 C<_cleaned_switches>
385*0Sstevel@tonic-gate
386*0Sstevel@tonic-gate  my @switches = $self->_cleaned_switches( @switches_from_user );
387*0Sstevel@tonic-gate
388*0Sstevel@tonic-gateReturns only defined, non-blank, trimmed switches from the parms passed.
389*0Sstevel@tonic-gate
390*0Sstevel@tonic-gate=cut
391*0Sstevel@tonic-gate
392*0Sstevel@tonic-gatesub _cleaned_switches {
393*0Sstevel@tonic-gate    my $self = shift;
394*0Sstevel@tonic-gate
395*0Sstevel@tonic-gate    local $_;
396*0Sstevel@tonic-gate
397*0Sstevel@tonic-gate    my @switches;
398*0Sstevel@tonic-gate    for ( @_ ) {
399*0Sstevel@tonic-gate	my $switch = $_;
400*0Sstevel@tonic-gate	next unless defined $switch;
401*0Sstevel@tonic-gate	$switch =~ s/^\s+//;
402*0Sstevel@tonic-gate	$switch =~ s/\s+$//;
403*0Sstevel@tonic-gate	push( @switches, $switch ) if $switch ne "";
404*0Sstevel@tonic-gate    }
405*0Sstevel@tonic-gate
406*0Sstevel@tonic-gate    return @switches;
407*0Sstevel@tonic-gate}
408*0Sstevel@tonic-gate
409*0Sstevel@tonic-gate=head2 C<_INC2PERL5LIB>
410*0Sstevel@tonic-gate
411*0Sstevel@tonic-gate  local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
412*0Sstevel@tonic-gate
413*0Sstevel@tonic-gateTakes the current value of C<@INC> and turns it into something suitable
414*0Sstevel@tonic-gatefor putting onto C<PERL5LIB>.
415*0Sstevel@tonic-gate
416*0Sstevel@tonic-gate=cut
417*0Sstevel@tonic-gate
418*0Sstevel@tonic-gatesub _INC2PERL5LIB {
419*0Sstevel@tonic-gate    my($self) = shift;
420*0Sstevel@tonic-gate
421*0Sstevel@tonic-gate    $self->{_old5lib} = $ENV{PERL5LIB};
422*0Sstevel@tonic-gate
423*0Sstevel@tonic-gate    return join $Config{path_sep}, $self->_filtered_INC;
424*0Sstevel@tonic-gate}
425*0Sstevel@tonic-gate
426*0Sstevel@tonic-gate=head2 C<_filtered_INC>
427*0Sstevel@tonic-gate
428*0Sstevel@tonic-gate  my @filtered_inc = $self->_filtered_INC;
429*0Sstevel@tonic-gate
430*0Sstevel@tonic-gateShortens C<@INC> by removing redundant and unnecessary entries.
431*0Sstevel@tonic-gateNecessary for OSes with limited command line lengths, like VMS.
432*0Sstevel@tonic-gate
433*0Sstevel@tonic-gate=cut
434*0Sstevel@tonic-gate
435*0Sstevel@tonic-gatesub _filtered_INC {
436*0Sstevel@tonic-gate    my($self, @inc) = @_;
437*0Sstevel@tonic-gate    @inc = @INC unless @inc;
438*0Sstevel@tonic-gate
439*0Sstevel@tonic-gate    if( $self->{_is_vms} ) {
440*0Sstevel@tonic-gate	# VMS has a 255-byte limit on the length of %ENV entries, so
441*0Sstevel@tonic-gate	# toss the ones that involve perl_root, the install location
442*0Sstevel@tonic-gate        @inc = grep !/perl_root/i, @inc;
443*0Sstevel@tonic-gate
444*0Sstevel@tonic-gate    } elsif ( $self->{_is_win32} ) {
445*0Sstevel@tonic-gate	# Lose any trailing backslashes in the Win32 paths
446*0Sstevel@tonic-gate	s/[\\\/+]$// foreach @inc;
447*0Sstevel@tonic-gate    }
448*0Sstevel@tonic-gate
449*0Sstevel@tonic-gate    my %dupes;
450*0Sstevel@tonic-gate    @inc = grep !$dupes{$_}++, @inc;
451*0Sstevel@tonic-gate
452*0Sstevel@tonic-gate    return @inc;
453*0Sstevel@tonic-gate}
454*0Sstevel@tonic-gate
455*0Sstevel@tonic-gate
456*0Sstevel@tonic-gate=head2 C<_restore_PERL5LIB>
457*0Sstevel@tonic-gate
458*0Sstevel@tonic-gate  $self->_restore_PERL5LIB;
459*0Sstevel@tonic-gate
460*0Sstevel@tonic-gateThis restores the original value of the C<PERL5LIB> environment variable.
461*0Sstevel@tonic-gateNecessary on VMS, otherwise a no-op.
462*0Sstevel@tonic-gate
463*0Sstevel@tonic-gate=cut
464*0Sstevel@tonic-gate
465*0Sstevel@tonic-gatesub _restore_PERL5LIB {
466*0Sstevel@tonic-gate    my($self) = shift;
467*0Sstevel@tonic-gate
468*0Sstevel@tonic-gate    return unless $self->{_is_vms};
469*0Sstevel@tonic-gate
470*0Sstevel@tonic-gate    if (defined $self->{_old5lib}) {
471*0Sstevel@tonic-gate        $ENV{PERL5LIB} = $self->{_old5lib};
472*0Sstevel@tonic-gate    }
473*0Sstevel@tonic-gate}
474*0Sstevel@tonic-gate
475*0Sstevel@tonic-gate=head1 Parsing
476*0Sstevel@tonic-gate
477*0Sstevel@tonic-gateMethods for identifying what sort of line you're looking at.
478*0Sstevel@tonic-gate
479*0Sstevel@tonic-gate=head2 C<_is_comment>
480*0Sstevel@tonic-gate
481*0Sstevel@tonic-gate  my $is_comment = $strap->_is_comment($line, \$comment);
482*0Sstevel@tonic-gate
483*0Sstevel@tonic-gateChecks if the given line is a comment.  If so, it will place it into
484*0Sstevel@tonic-gateC<$comment> (sans #).
485*0Sstevel@tonic-gate
486*0Sstevel@tonic-gate=cut
487*0Sstevel@tonic-gate
488*0Sstevel@tonic-gatesub _is_comment {
489*0Sstevel@tonic-gate    my($self, $line, $comment) = @_;
490*0Sstevel@tonic-gate
491*0Sstevel@tonic-gate    if( $line =~ /^\s*\#(.*)/ ) {
492*0Sstevel@tonic-gate        $$comment = $1;
493*0Sstevel@tonic-gate        return $YES;
494*0Sstevel@tonic-gate    }
495*0Sstevel@tonic-gate    else {
496*0Sstevel@tonic-gate        return $NO;
497*0Sstevel@tonic-gate    }
498*0Sstevel@tonic-gate}
499*0Sstevel@tonic-gate
500*0Sstevel@tonic-gate=head2 C<_is_header>
501*0Sstevel@tonic-gate
502*0Sstevel@tonic-gate  my $is_header = $strap->_is_header($line);
503*0Sstevel@tonic-gate
504*0Sstevel@tonic-gateChecks if the given line is a header (1..M) line.  If so, it places how
505*0Sstevel@tonic-gatemany tests there will be in C<< $strap->{max} >>, a list of which tests
506*0Sstevel@tonic-gateare todo in C<< $strap->{todo} >> and if the whole test was skipped
507*0Sstevel@tonic-gateC<< $strap->{skip_all} >> contains the reason.
508*0Sstevel@tonic-gate
509*0Sstevel@tonic-gate=cut
510*0Sstevel@tonic-gate
511*0Sstevel@tonic-gate# Regex for parsing a header.  Will be run with /x
512*0Sstevel@tonic-gatemy $Extra_Header_Re = <<'REGEX';
513*0Sstevel@tonic-gate                       ^
514*0Sstevel@tonic-gate                        (?: \s+ todo \s+ ([\d \t]+) )?      # optional todo set
515*0Sstevel@tonic-gate                        (?: \s* \# \s* ([\w:]+\s?) (.*) )?     # optional skip with optional reason
516*0Sstevel@tonic-gateREGEX
517*0Sstevel@tonic-gate
518*0Sstevel@tonic-gatesub _is_header {
519*0Sstevel@tonic-gate    my($self, $line) = @_;
520*0Sstevel@tonic-gate
521*0Sstevel@tonic-gate    if( my($max, $extra) = $line =~ /^1\.\.(\d+)(.*)/ ) {
522*0Sstevel@tonic-gate        $self->{max}  = $max;
523*0Sstevel@tonic-gate        assert( $self->{max} >= 0,  'Max # of tests looks right' );
524*0Sstevel@tonic-gate
525*0Sstevel@tonic-gate        if( defined $extra ) {
526*0Sstevel@tonic-gate            my($todo, $skip, $reason) = $extra =~ /$Extra_Header_Re/xo;
527*0Sstevel@tonic-gate
528*0Sstevel@tonic-gate            $self->{todo} = { map { $_ => 1 } split /\s+/, $todo } if $todo;
529*0Sstevel@tonic-gate
530*0Sstevel@tonic-gate            if( $self->{max} == 0 ) {
531*0Sstevel@tonic-gate                $reason = '' unless defined $skip and $skip =~ /^Skip/i;
532*0Sstevel@tonic-gate            }
533*0Sstevel@tonic-gate
534*0Sstevel@tonic-gate            $self->{skip_all} = $reason;
535*0Sstevel@tonic-gate        }
536*0Sstevel@tonic-gate
537*0Sstevel@tonic-gate        return $YES;
538*0Sstevel@tonic-gate    }
539*0Sstevel@tonic-gate    else {
540*0Sstevel@tonic-gate        return $NO;
541*0Sstevel@tonic-gate    }
542*0Sstevel@tonic-gate}
543*0Sstevel@tonic-gate
544*0Sstevel@tonic-gate=head2 C<_is_test>
545*0Sstevel@tonic-gate
546*0Sstevel@tonic-gate  my $is_test = $strap->_is_test($line, \%test);
547*0Sstevel@tonic-gate
548*0Sstevel@tonic-gateChecks if the $line is a test report (ie. 'ok/not ok').  Reports the
549*0Sstevel@tonic-gateresult back in C<%test> which will contain:
550*0Sstevel@tonic-gate
551*0Sstevel@tonic-gate  ok            did it succeed?  This is the literal 'ok' or 'not ok'.
552*0Sstevel@tonic-gate  name          name of the test (if any)
553*0Sstevel@tonic-gate  number        test number (if any)
554*0Sstevel@tonic-gate
555*0Sstevel@tonic-gate  type          'todo' or 'skip' (if any)
556*0Sstevel@tonic-gate  reason        why is it todo or skip? (if any)
557*0Sstevel@tonic-gate
558*0Sstevel@tonic-gateIf will also catch lone 'not' lines, note it saw them
559*0Sstevel@tonic-gateC<< $strap->{saw_lone_not} >> and the line in C<< $strap->{lone_not_line} >>.
560*0Sstevel@tonic-gate
561*0Sstevel@tonic-gate=cut
562*0Sstevel@tonic-gate
563*0Sstevel@tonic-gatemy $Report_Re = <<'REGEX';
564*0Sstevel@tonic-gate                 ^
565*0Sstevel@tonic-gate                  (not\ )?               # failure?
566*0Sstevel@tonic-gate                  ok\b
567*0Sstevel@tonic-gate                  (?:\s+(\d+))?         # optional test number
568*0Sstevel@tonic-gate                  \s*
569*0Sstevel@tonic-gate                  (.*)                  # and the rest
570*0Sstevel@tonic-gateREGEX
571*0Sstevel@tonic-gate
572*0Sstevel@tonic-gatemy $Extra_Re = <<'REGEX';
573*0Sstevel@tonic-gate                 ^
574*0Sstevel@tonic-gate                  (.*?) (?:(?:[^\\]|^)# (.*))?
575*0Sstevel@tonic-gate                 $
576*0Sstevel@tonic-gateREGEX
577*0Sstevel@tonic-gate
578*0Sstevel@tonic-gatesub _is_test {
579*0Sstevel@tonic-gate    my($self, $line, $test) = @_;
580*0Sstevel@tonic-gate
581*0Sstevel@tonic-gate    # We pulverize the line down into pieces in three parts.
582*0Sstevel@tonic-gate    if( my($not, $num, $extra)    = $line  =~ /$Report_Re/ox ) {
583*0Sstevel@tonic-gate        my ($name, $control) = $extra ? split(/(?:[^\\]|^)#/, $extra) : ();
584*0Sstevel@tonic-gate        my ($type, $reason)  = $control ? $control =~ /^\s*(\S+)(?:\s+(.*))?$/ : ();
585*0Sstevel@tonic-gate
586*0Sstevel@tonic-gate        $test->{number} = $num;
587*0Sstevel@tonic-gate        $test->{ok}     = $not ? 0 : 1;
588*0Sstevel@tonic-gate        $test->{name}   = $name;
589*0Sstevel@tonic-gate
590*0Sstevel@tonic-gate        if( defined $type ) {
591*0Sstevel@tonic-gate            $test->{type}   = $type =~ /^TODO$/i ? 'todo' :
592*0Sstevel@tonic-gate                              $type =~ /^Skip/i  ? 'skip' : 0;
593*0Sstevel@tonic-gate        }
594*0Sstevel@tonic-gate        else {
595*0Sstevel@tonic-gate            $test->{type} = '';
596*0Sstevel@tonic-gate        }
597*0Sstevel@tonic-gate        $test->{reason} = $reason;
598*0Sstevel@tonic-gate
599*0Sstevel@tonic-gate        return $YES;
600*0Sstevel@tonic-gate    }
601*0Sstevel@tonic-gate    else{
602*0Sstevel@tonic-gate        # Sometimes the "not " and "ok" will be on separate lines on VMS.
603*0Sstevel@tonic-gate        # We catch this and remember we saw it.
604*0Sstevel@tonic-gate        if( $line =~ /^not\s+$/ ) {
605*0Sstevel@tonic-gate            $self->{saw_lone_not} = 1;
606*0Sstevel@tonic-gate            $self->{lone_not_line} = $self->{line};
607*0Sstevel@tonic-gate        }
608*0Sstevel@tonic-gate
609*0Sstevel@tonic-gate        return $NO;
610*0Sstevel@tonic-gate    }
611*0Sstevel@tonic-gate}
612*0Sstevel@tonic-gate
613*0Sstevel@tonic-gate=head2 C<_is_bail_out>
614*0Sstevel@tonic-gate
615*0Sstevel@tonic-gate  my $is_bail_out = $strap->_is_bail_out($line, \$reason);
616*0Sstevel@tonic-gate
617*0Sstevel@tonic-gateChecks if the line is a "Bail out!".  Places the reason for bailing
618*0Sstevel@tonic-gate(if any) in $reason.
619*0Sstevel@tonic-gate
620*0Sstevel@tonic-gate=cut
621*0Sstevel@tonic-gate
622*0Sstevel@tonic-gatesub _is_bail_out {
623*0Sstevel@tonic-gate    my($self, $line, $reason) = @_;
624*0Sstevel@tonic-gate
625*0Sstevel@tonic-gate    if( $line =~ /^Bail out!\s*(.*)/i ) {
626*0Sstevel@tonic-gate        $$reason = $1 if $1;
627*0Sstevel@tonic-gate        return $YES;
628*0Sstevel@tonic-gate    }
629*0Sstevel@tonic-gate    else {
630*0Sstevel@tonic-gate        return $NO;
631*0Sstevel@tonic-gate    }
632*0Sstevel@tonic-gate}
633*0Sstevel@tonic-gate
634*0Sstevel@tonic-gate=head2 C<_reset_file_state>
635*0Sstevel@tonic-gate
636*0Sstevel@tonic-gate  $strap->_reset_file_state;
637*0Sstevel@tonic-gate
638*0Sstevel@tonic-gateResets things like C<< $strap->{max} >> , C<< $strap->{skip_all} >>,
639*0Sstevel@tonic-gateetc. so it's ready to parse the next file.
640*0Sstevel@tonic-gate
641*0Sstevel@tonic-gate=cut
642*0Sstevel@tonic-gate
643*0Sstevel@tonic-gatesub _reset_file_state {
644*0Sstevel@tonic-gate    my($self) = shift;
645*0Sstevel@tonic-gate
646*0Sstevel@tonic-gate    delete @{$self}{qw(max skip_all todo)};
647*0Sstevel@tonic-gate    $self->{line}       = 0;
648*0Sstevel@tonic-gate    $self->{saw_header} = 0;
649*0Sstevel@tonic-gate    $self->{saw_bailout}= 0;
650*0Sstevel@tonic-gate    $self->{saw_lone_not} = 0;
651*0Sstevel@tonic-gate    $self->{lone_not_line} = 0;
652*0Sstevel@tonic-gate    $self->{bailout_reason} = '';
653*0Sstevel@tonic-gate    $self->{'next'}       = 1;
654*0Sstevel@tonic-gate}
655*0Sstevel@tonic-gate
656*0Sstevel@tonic-gate=head1 Results
657*0Sstevel@tonic-gate
658*0Sstevel@tonic-gateThe C<%results> returned from C<analyze()> contain the following
659*0Sstevel@tonic-gateinformation:
660*0Sstevel@tonic-gate
661*0Sstevel@tonic-gate  passing           true if the whole test is considered a pass
662*0Sstevel@tonic-gate                    (or skipped), false if its a failure
663*0Sstevel@tonic-gate
664*0Sstevel@tonic-gate  exit              the exit code of the test run, if from a file
665*0Sstevel@tonic-gate  wait              the wait code of the test run, if from a file
666*0Sstevel@tonic-gate
667*0Sstevel@tonic-gate  max               total tests which should have been run
668*0Sstevel@tonic-gate  seen              total tests actually seen
669*0Sstevel@tonic-gate  skip_all          if the whole test was skipped, this will
670*0Sstevel@tonic-gate                      contain the reason.
671*0Sstevel@tonic-gate
672*0Sstevel@tonic-gate  ok                number of tests which passed
673*0Sstevel@tonic-gate                      (including todo and skips)
674*0Sstevel@tonic-gate
675*0Sstevel@tonic-gate  todo              number of todo tests seen
676*0Sstevel@tonic-gate  bonus             number of todo tests which
677*0Sstevel@tonic-gate                      unexpectedly passed
678*0Sstevel@tonic-gate
679*0Sstevel@tonic-gate  skip              number of tests skipped
680*0Sstevel@tonic-gate
681*0Sstevel@tonic-gateSo a successful test should have max == seen == ok.
682*0Sstevel@tonic-gate
683*0Sstevel@tonic-gate
684*0Sstevel@tonic-gateThere is one final item, the details.
685*0Sstevel@tonic-gate
686*0Sstevel@tonic-gate  details           an array ref reporting the result of
687*0Sstevel@tonic-gate                    each test looks like this:
688*0Sstevel@tonic-gate
689*0Sstevel@tonic-gate    $results{details}[$test_num - 1] =
690*0Sstevel@tonic-gate            { ok        => is the test considered ok?
691*0Sstevel@tonic-gate              actual_ok => did it literally say 'ok'?
692*0Sstevel@tonic-gate              name      => name of the test (if any)
693*0Sstevel@tonic-gate              type      => 'skip' or 'todo' (if any)
694*0Sstevel@tonic-gate              reason    => reason for the above (if any)
695*0Sstevel@tonic-gate            };
696*0Sstevel@tonic-gate
697*0Sstevel@tonic-gateElement 0 of the details is test #1.  I tried it with element 1 being
698*0Sstevel@tonic-gate#1 and 0 being empty, this is less awkward.
699*0Sstevel@tonic-gate
700*0Sstevel@tonic-gate=head2 C<_detailize>
701*0Sstevel@tonic-gate
702*0Sstevel@tonic-gate  my %details = $strap->_detailize($pass, \%test);
703*0Sstevel@tonic-gate
704*0Sstevel@tonic-gateGenerates the details based on the last test line seen.  C<$pass> is
705*0Sstevel@tonic-gatetrue if it was considered to be a passed test.  C<%test> is the results
706*0Sstevel@tonic-gateof the test you're summarizing.
707*0Sstevel@tonic-gate
708*0Sstevel@tonic-gate=cut
709*0Sstevel@tonic-gate
710*0Sstevel@tonic-gatesub _detailize {
711*0Sstevel@tonic-gate    my($self, $pass, $test) = @_;
712*0Sstevel@tonic-gate
713*0Sstevel@tonic-gate    my %details = ( ok         => $pass,
714*0Sstevel@tonic-gate                    actual_ok  => $test->{ok}
715*0Sstevel@tonic-gate                  );
716*0Sstevel@tonic-gate
717*0Sstevel@tonic-gate    assert( !(grep !defined $details{$_}, keys %details),
718*0Sstevel@tonic-gate            'test contains the ok and actual_ok info' );
719*0Sstevel@tonic-gate
720*0Sstevel@tonic-gate    # We don't want these to be undef because they are often
721*0Sstevel@tonic-gate    # checked and don't want the checker to have to deal with
722*0Sstevel@tonic-gate    # uninitialized vars.
723*0Sstevel@tonic-gate    foreach my $piece (qw(name type reason)) {
724*0Sstevel@tonic-gate        $details{$piece} = defined $test->{$piece} ? $test->{$piece} : '';
725*0Sstevel@tonic-gate    }
726*0Sstevel@tonic-gate
727*0Sstevel@tonic-gate    return %details;
728*0Sstevel@tonic-gate}
729*0Sstevel@tonic-gate
730*0Sstevel@tonic-gate=head1 EXAMPLES
731*0Sstevel@tonic-gate
732*0Sstevel@tonic-gateSee F<examples/mini_harness.plx> for an example of use.
733*0Sstevel@tonic-gate
734*0Sstevel@tonic-gate=head1 AUTHOR
735*0Sstevel@tonic-gate
736*0Sstevel@tonic-gateMichael G Schwern C<< <schwern@pobox.com> >>, currently maintained by
737*0Sstevel@tonic-gateAndy Lester C<< <andy@petdance.com> >>.
738*0Sstevel@tonic-gate
739*0Sstevel@tonic-gate=head1 SEE ALSO
740*0Sstevel@tonic-gate
741*0Sstevel@tonic-gateL<Test::Harness>
742*0Sstevel@tonic-gate
743*0Sstevel@tonic-gate=cut
744*0Sstevel@tonic-gate
745*0Sstevel@tonic-gate1;
746