]> git.saurik.com Git - apple/security.git/blob - SecurityTests/regressions/inc/Test/Harness/Straps.pm
Security-57740.1.18.tar.gz
[apple/security.git] / SecurityTests / regressions / inc / Test / Harness / Straps.pm
1 # -*- Mode: cperl; cperl-indent-level: 4 -*-
2 package Test::Harness::Straps;
3
4 use strict;
5 use vars qw($VERSION);
6 $VERSION = '0.26_01';
7
8 use Config;
9 use Test::Harness::Assert;
10 use Test::Harness::Iterator;
11 use Test::Harness::Point;
12 use Test::Harness::Results;
13
14 # Flags used as return values from our methods. Just for internal
15 # clarification.
16 my $YES = (1==1);
17 my $NO = !$YES;
18
19 =head1 NAME
20
21 Test::Harness::Straps - detailed analysis of test results
22
23 =head1 SYNOPSIS
24
25 use Test::Harness::Straps;
26
27 my $strap = Test::Harness::Straps->new;
28
29 # Various ways to interpret a test
30 my $results = $strap->analyze($name, \@test_output);
31 my $results = $strap->analyze_fh($name, $test_filehandle);
32 my $results = $strap->analyze_file($test_file);
33
34 # UNIMPLEMENTED
35 my %total = $strap->total_results;
36
37 # Altering the behavior of the strap UNIMPLEMENTED
38 my $verbose_output = $strap->dump_verbose();
39 $strap->dump_verbose_fh($output_filehandle);
40
41
42 =head1 DESCRIPTION
43
44 B<THIS IS ALPHA SOFTWARE> in that the interface is subject to change
45 in incompatible ways. It is otherwise stable.
46
47 Test::Harness is limited to printing out its results. This makes
48 analysis of the test results difficult for anything but a human. To
49 make it easier for programs to work with test results, we provide
50 Test::Harness::Straps. Instead of printing the results, straps
51 provide them as raw data. You can also configure how the tests are to
52 be run.
53
54 The interface is currently incomplete. I<Please> contact the author
55 if you'd like a feature added or something change or just have
56 comments.
57
58 =head1 CONSTRUCTION
59
60 =head2 new()
61
62 my $strap = Test::Harness::Straps->new;
63
64 Initialize a new strap.
65
66 =cut
67
68 sub new {
69 my $class = shift;
70 my $self = bless {}, $class;
71
72 $self->_init;
73
74 return $self;
75 }
76
77 =for private $strap->_init
78
79 $strap->_init;
80
81 Initialize the internal state of a strap to make it ready for parsing.
82
83 =cut
84
85 sub _init {
86 my($self) = shift;
87
88 $self->{_is_vms} = ( $^O eq 'VMS' );
89 $self->{_is_win32} = ( $^O =~ /^(MS)?Win32$/ );
90 $self->{_is_macos} = ( $^O eq 'MacOS' );
91 }
92
93 =head1 ANALYSIS
94
95 =head2 $strap->analyze( $name, \@output_lines )
96
97 my $results = $strap->analyze($name, \@test_output);
98
99 Analyzes the output of a single test, assigning it the given C<$name>
100 for use in the total report. Returns the C<$results> of the test.
101 See L<Results>.
102
103 C<@test_output> should be the raw output from the test, including
104 newlines.
105
106 =cut
107
108 sub analyze {
109 my($self, $name, $test_output) = @_;
110
111 my $it = Test::Harness::Iterator->new($test_output);
112 return $self->_analyze_iterator($name, $it);
113 }
114
115
116 sub _analyze_iterator {
117 my($self, $name, $it) = @_;
118
119 $self->_reset_file_state;
120 $self->{file} = $name;
121
122 my $results = Test::Harness::Results->new;
123
124 # Set them up here so callbacks can have them.
125 $self->{totals}{$name} = $results;
126 while( defined(my $line = $it->next) ) {
127 $self->_analyze_line($line, $results);
128 last if $self->{saw_bailout};
129 }
130
131 $results->set_skip_all( $self->{skip_all} ) if defined $self->{skip_all};
132
133 my $passed =
134 (($results->max == 0) && defined $results->skip_all) ||
135 ($results->max &&
136 $results->seen &&
137 $results->max == $results->seen &&
138 $results->max == $results->ok);
139
140 $results->set_passing( $passed ? 1 : 0 );
141
142 return $results;
143 }
144
145
146 sub _analyze_line {
147 my $self = shift;
148 my $line = shift;
149 my $results = shift;
150
151 $self->{line}++;
152
153 my $linetype;
154 my $point = Test::Harness::Point->from_test_line( $line );
155 if ( $point ) {
156 $linetype = 'test';
157
158 $results->inc_seen;
159 $point->set_number( $self->{'next'} ) unless $point->number;
160
161 # sometimes the 'not ' and the 'ok' are on different lines,
162 # happens often on VMS if you do:
163 # print "not " unless $test;
164 # print "ok $num\n";
165 if ( $self->{lone_not_line} && ($self->{lone_not_line} == $self->{line} - 1) ) {
166 $point->set_ok( 0 );
167 }
168
169 if ( $self->{todo}{$point->number} ) {
170 $point->set_directive_type( 'todo' );
171 }
172
173 if ( $point->is_todo ) {
174 $results->inc_todo;
175 $results->inc_bonus if $point->ok;
176 }
177 elsif ( $point->is_skip ) {
178 $results->inc_skip;
179 }
180
181 $results->inc_ok if $point->pass;
182
183 if ( ($point->number > 100_000) && ($point->number > ($self->{max}||100_000)) ) {
184 if ( !$self->{too_many_tests}++ ) {
185 warn "Enormous test number seen [test ", $point->number, "]\n";
186 warn "Can't detailize, too big.\n";
187 }
188 }
189 else {
190 my $details = {
191 ok => $point->pass,
192 actual_ok => $point->ok,
193 name => _def_or_blank( $point->description ),
194 type => _def_or_blank( $point->directive_type ),
195 reason => _def_or_blank( $point->directive_reason ),
196 };
197
198 assert( defined( $details->{ok} ) && defined( $details->{actual_ok} ) );
199 $results->set_details( $point->number, $details );
200 }
201 } # test point
202 elsif ( $line =~ /^not\s+$/ ) {
203 $linetype = 'other';
204 # Sometimes the "not " and "ok" will be on separate lines on VMS.
205 # We catch this and remember we saw it.
206 $self->{lone_not_line} = $self->{line};
207 }
208 elsif ( $self->_is_header($line) ) {
209 $linetype = 'header';
210
211 $self->{saw_header}++;
212
213 $results->inc_max( $self->{max} );
214 }
215 elsif ( $self->_is_bail_out($line, \$self->{bailout_reason}) ) {
216 $linetype = 'bailout';
217 $self->{saw_bailout} = 1;
218 }
219 elsif (my $diagnostics = $self->_is_diagnostic_line( $line )) {
220 $linetype = 'other';
221 # XXX We can throw this away, really.
222 my $test = $results->details->[-1];
223 $test->{diagnostics} ||= '';
224 $test->{diagnostics} .= $diagnostics;
225 }
226 else {
227 $linetype = 'other';
228 }
229
230 $self->callback->($self, $line, $linetype, $results) if $self->callback;
231
232 $self->{'next'} = $point->number + 1 if $point;
233 } # _analyze_line
234
235
236 sub _is_diagnostic_line {
237 my ($self, $line) = @_;
238 return if index( $line, '# Looks like you failed' ) == 0;
239 $line =~ s/^#\s//;
240 return $line;
241 }
242
243 =for private $strap->analyze_fh( $name, $test_filehandle )
244
245 my $results = $strap->analyze_fh($name, $test_filehandle);
246
247 Like C<analyze>, but it reads from the given filehandle.
248
249 =cut
250
251 sub analyze_fh {
252 my($self, $name, $fh) = @_;
253
254 my $it = Test::Harness::Iterator->new($fh);
255 return $self->_analyze_iterator($name, $it);
256 }
257
258 =head2 $strap->analyze_file( $test_file )
259
260 my $results = $strap->analyze_file($test_file);
261
262 Like C<analyze>, but it runs the given C<$test_file> and parses its
263 results. It will also use that name for the total report.
264
265 =cut
266
267 sub analyze_file {
268 my($self, $file) = @_;
269
270 unless( -e $file ) {
271 $self->{error} = "$file does not exist";
272 return;
273 }
274
275 unless( -r $file ) {
276 $self->{error} = "$file is not readable";
277 return;
278 }
279
280 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
281 if ( $Test::Harness::Debug ) {
282 local $^W=0; # ignore undef warnings
283 print "# PERL5LIB=$ENV{PERL5LIB}\n";
284 }
285
286 # *sigh* this breaks under taint, but open -| is unportable.
287 my $line = $self->_command_line($file);
288
289 unless ( open(FILE, "$line|" )) {
290 print "can't run $file. $!\n";
291 return;
292 }
293
294 my $results = $self->analyze_fh($file, \*FILE);
295 my $exit = close FILE;
296
297 $results->set_wait($?);
298 if ( $? && $self->{_is_vms} ) {
299 $results->set_exit($?);
300 }
301 else {
302 $results->set_exit( _wait2exit($?) );
303 }
304 $results->set_passing(0) unless $? == 0;
305
306 $self->_restore_PERL5LIB();
307
308 return $results;
309 }
310
311
312 eval { require POSIX; &POSIX::WEXITSTATUS(0) };
313 if( $@ ) {
314 *_wait2exit = sub { $_[0] >> 8 };
315 }
316 else {
317 *_wait2exit = sub { POSIX::WEXITSTATUS($_[0]) }
318 }
319
320 =for private $strap->_command_line( $file )
321
322 Returns the full command line that will be run to test I<$file>.
323
324 =cut
325
326 sub _command_line {
327 my $self = shift;
328 my $file = shift;
329
330 my $command = $self->_command();
331 my $switches = $self->_switches($file);
332
333 $file = qq["$file"] if ($file =~ /\s/) && ($file !~ /^".*"$/);
334 my $line = "$command $switches $file";
335
336 return $line;
337 }
338
339
340 =for private $strap->_command()
341
342 Returns the command that runs the test. Combine this with C<_switches()>
343 to build a command line.
344
345 Typically this is C<$^X>, but you can set C<$ENV{HARNESS_PERL}>
346 to use a different Perl than what you're running the harness under.
347 This might be to run a threaded Perl, for example.
348
349 You can also overload this method if you've built your own strap subclass,
350 such as a PHP interpreter for a PHP-based strap.
351
352 =cut
353
354 sub _command {
355 my $self = shift;
356
357 return $ENV{HARNESS_PERL} if defined $ENV{HARNESS_PERL};
358 #return qq["$^X"] if $self->{_is_win32} && ($^X =~ /[^\w\.\/\\]/);
359 return qq["$^X"] if $^X =~ /\s/ and $^X !~ /^["']/;
360 return $^X;
361 }
362
363
364 =for private $strap->_switches( $file )
365
366 Formats and returns the switches necessary to run the test.
367
368 =cut
369
370 sub _switches {
371 my($self, $file) = @_;
372
373 my @existing_switches = $self->_cleaned_switches( $Test::Harness::Switches, $ENV{HARNESS_PERL_SWITCHES} );
374 my @derived_switches;
375
376 local *TEST;
377 open(TEST, $file) or print "can't open $file. $!\n";
378 my $shebang = <TEST>;
379 close(TEST) or print "can't close $file. $!\n";
380
381 my $taint = ( $shebang =~ /^#!.*\bperl.*\s-\w*([Tt]+)/ );
382 push( @derived_switches, "-$1" ) if $taint;
383
384 # When taint mode is on, PERL5LIB is ignored. So we need to put
385 # all that on the command line as -Is.
386 # MacPerl's putenv is broken, so it will not see PERL5LIB, tainted or not.
387 if ( $taint || $self->{_is_macos} ) {
388 my @inc = $self->_filtered_INC;
389 push @derived_switches, map { "-I$_" } @inc;
390 }
391
392 # Quote the argument if there's any whitespace in it, or if
393 # we're VMS, since VMS requires all parms quoted. Also, don't quote
394 # it if it's already quoted.
395 for ( @derived_switches ) {
396 $_ = qq["$_"] if ((/\s/ || $self->{_is_vms}) && !/^".*"$/ );
397 }
398 return join( " ", @existing_switches, @derived_switches );
399 }
400
401 =for private $strap->_cleaned_switches( @switches_from_user )
402
403 Returns only defined, non-blank, trimmed switches from the parms passed.
404
405 =cut
406
407 sub _cleaned_switches {
408 my $self = shift;
409
410 local $_;
411
412 my @switches;
413 for ( @_ ) {
414 my $switch = $_;
415 next unless defined $switch;
416 $switch =~ s/^\s+//;
417 $switch =~ s/\s+$//;
418 push( @switches, $switch ) if $switch ne "";
419 }
420
421 return @switches;
422 }
423
424 =for private $strap->_INC2PERL5LIB
425
426 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
427
428 Takes the current value of C<@INC> and turns it into something suitable
429 for putting onto C<PERL5LIB>.
430
431 =cut
432
433 sub _INC2PERL5LIB {
434 my($self) = shift;
435
436 $self->{_old5lib} = $ENV{PERL5LIB};
437
438 return join $Config{path_sep}, $self->_filtered_INC;
439 }
440
441 =for private $strap->_filtered_INC()
442
443 my @filtered_inc = $self->_filtered_INC;
444
445 Shortens C<@INC> by removing redundant and unnecessary entries.
446 Necessary for OSes with limited command line lengths, like VMS.
447
448 =cut
449
450 sub _filtered_INC {
451 my($self, @inc) = @_;
452 @inc = @INC unless @inc;
453
454 if( $self->{_is_vms} ) {
455 # VMS has a 255-byte limit on the length of %ENV entries, so
456 # toss the ones that involve perl_root, the install location
457 @inc = grep !/perl_root/i, @inc;
458
459 }
460 elsif ( $self->{_is_win32} ) {
461 # Lose any trailing backslashes in the Win32 paths
462 s/[\\\/+]$// foreach @inc;
463 }
464
465 my %seen;
466 $seen{$_}++ foreach $self->_default_inc();
467 @inc = grep !$seen{$_}++, @inc;
468
469 return @inc;
470 }
471
472
473 { # Without caching, _default_inc() takes a huge amount of time
474 my %cache;
475 sub _default_inc {
476 my $self = shift;
477 my $perl = $self->_command;
478 $cache{$perl} ||= [do {
479 local $ENV{PERL5LIB};
480 my @inc =`$perl -le "print join qq[\\n], \@INC"`;
481 chomp @inc;
482 }];
483 return @{$cache{$perl}};
484 }
485 }
486
487
488 =for private $strap->_restore_PERL5LIB()
489
490 $self->_restore_PERL5LIB;
491
492 This restores the original value of the C<PERL5LIB> environment variable.
493 Necessary on VMS, otherwise a no-op.
494
495 =cut
496
497 sub _restore_PERL5LIB {
498 my($self) = shift;
499
500 return unless $self->{_is_vms};
501
502 if (defined $self->{_old5lib}) {
503 $ENV{PERL5LIB} = $self->{_old5lib};
504 }
505 }
506
507 =head1 Parsing
508
509 Methods for identifying what sort of line you're looking at.
510
511 =for private _is_diagnostic
512
513 my $is_diagnostic = $strap->_is_diagnostic($line, \$comment);
514
515 Checks if the given line is a comment. If so, it will place it into
516 C<$comment> (sans #).
517
518 =cut
519
520 sub _is_diagnostic {
521 my($self, $line, $comment) = @_;
522
523 if( $line =~ /^\s*\#(.*)/ ) {
524 $$comment = $1;
525 return $YES;
526 }
527 else {
528 return $NO;
529 }
530 }
531
532 =for private _is_header
533
534 my $is_header = $strap->_is_header($line);
535
536 Checks if the given line is a header (1..M) line. If so, it places how
537 many tests there will be in C<< $strap->{max} >>, a list of which tests
538 are todo in C<< $strap->{todo} >> and if the whole test was skipped
539 C<< $strap->{skip_all} >> contains the reason.
540
541 =cut
542
543 # Regex for parsing a header. Will be run with /x
544 my $Extra_Header_Re = <<'REGEX';
545 ^
546 (?: \s+ todo \s+ ([\d \t]+) )? # optional todo set
547 (?: \s* \# \s* ([\w:]+\s?) (.*) )? # optional skip with optional reason
548 REGEX
549
550 sub _is_header {
551 my($self, $line) = @_;
552
553 if( my($max, $extra) = $line =~ /^1\.\.(\d+)(.*)/ ) {
554 $self->{max} = $max;
555 assert( $self->{max} >= 0, 'Max # of tests looks right' );
556
557 if( defined $extra ) {
558 my($todo, $skip, $reason) = $extra =~ /$Extra_Header_Re/xo;
559
560 $self->{todo} = { map { $_ => 1 } split /\s+/, $todo } if $todo;
561
562 if( $self->{max} == 0 ) {
563 $reason = '' unless defined $skip and $skip =~ /^Skip/i;
564 }
565
566 $self->{skip_all} = $reason;
567 }
568
569 return $YES;
570 }
571 else {
572 return $NO;
573 }
574 }
575
576 =for private _is_bail_out
577
578 my $is_bail_out = $strap->_is_bail_out($line, \$reason);
579
580 Checks if the line is a "Bail out!". Places the reason for bailing
581 (if any) in $reason.
582
583 =cut
584
585 sub _is_bail_out {
586 my($self, $line, $reason) = @_;
587
588 if( $line =~ /^Bail out!\s*(.*)/i ) {
589 $$reason = $1 if $1;
590 return $YES;
591 }
592 else {
593 return $NO;
594 }
595 }
596
597 =for private _reset_file_state
598
599 $strap->_reset_file_state;
600
601 Resets things like C<< $strap->{max} >> , C<< $strap->{skip_all} >>,
602 etc. so it's ready to parse the next file.
603
604 =cut
605
606 sub _reset_file_state {
607 my($self) = shift;
608
609 delete @{$self}{qw(max skip_all todo too_many_tests)};
610 $self->{line} = 0;
611 $self->{saw_header} = 0;
612 $self->{saw_bailout}= 0;
613 $self->{lone_not_line} = 0;
614 $self->{bailout_reason} = '';
615 $self->{'next'} = 1;
616 }
617
618 =head1 EXAMPLES
619
620 See F<examples/mini_harness.plx> for an example of use.
621
622 =head1 AUTHOR
623
624 Michael G Schwern C<< <schwern at pobox.com> >>, currently maintained by
625 Andy Lester C<< <andy at petdance.com> >>.
626
627 =head1 SEE ALSO
628
629 L<Test::Harness>
630
631 =cut
632
633 sub _def_or_blank {
634 return $_[0] if defined $_[0];
635 return "";
636 }
637
638 sub set_callback {
639 my $self = shift;
640 $self->{callback} = shift;
641 }
642
643 sub callback {
644 my $self = shift;
645 return $self->{callback};
646 }
647
648 1;