File Coverage

lib/Test/Trivial.pm
Criterion Covered Total %
statement 228 275 82.9
branch 90 124 72.5
condition 50 111 45.0
subroutine 40 42 95.2
pod 10 19 52.6
total 418 571 73.2


line stmt bran cond sub pod time code
1             # Copyright (c) 2008 Yahoo! Inc. All rights reserved.
2             # The copyrights to the contents of this file are licensed
3             # under the Perl Artistic License (ver. 15 Aug 1997)
4             ##########################################################
5             package Test::Trivial;
6             ##########################################################
7 12     12   123223 use strict;
  12         29  
  12         912  
8 12     12   67 use warnings;
  12         23  
  12         1499  
9 12     12   58743 use IO::Handle;
  12         250553  
  12         875  
10 12     12   58626 use POSIX qw(strftime);
  12         199839  
  12         113  
11 12     12   62267 use Regexp::Common qw(balanced comment);
  12         140906  
  12         93  
12 12     12   359864 use Text::Diff;
  12         226605  
  12         1173  
13 12     12   29408 use Filter::Simple;
  12         439492  
  12         245  
14 12     12   777 use File::Basename;
  12         29  
  12         1670  
15 12     12   69 use constant IFS => $/;
  12         28  
  12         1023  
16              
17 12     12   17116 use version;
  12         46522  
  12         94  
18             our $VERSION = version->declare("1.901.2");
19              
20             FILTER {
21             my @grps;
22             my @comments;
23             my $group_marker = '****Test::Trivial::Group****';
24             while( s/$RE{balanced}{-parens=>'(){}[]'}{-keep}/$group_marker/s ) {
25             push @grps, $1;
26             }
27             my $comment_marker = '****Test::Trivial::Comment****';
28             while( s/$RE{comment}{Perl}{-keep}/$comment_marker/s ) {
29             push @comments, $1;
30             }
31              
32             s/TODO\s+(.*?);/do { local \$Test::Trivial::TODO = "Test Know to fail"; $1; };/gs;
33              
34             while( my $comment = shift @comments ) {
35             s/\Q$comment_marker\E/$comment/;
36             }
37             while( my $grp = shift @grps ) {
38             s/\Q$group_marker\E/$grp/;
39             }
40             };
41              
42 12     12   29811 use Getopt::Long;
  12         334965  
  12         92  
43             Getopt::Long::Configure(
44             "pass_through"
45             );
46              
47             our $FATAL = 0;
48             our $VERBOSE = 0;
49             our $LEVEL = 0;
50             our $DIFF = "Unified";
51             our $TODO = "";
52             our $LOG = $ENV{TEST_TRIVIAL_LOG};
53              
54             GetOptions(
55             'fatal' => \$FATAL,
56             'verbose' => \$VERBOSE,
57             'diff=s' => \$DIFF,
58             'log:s' => \$LOG,
59             );
60              
61             # rebless the singleton so we can intercept
62             # the _is_diag function
63             BEGIN {
64 12     12   25807 require Test::More;
65              
66             # forgive me, for I have sinned ...
67 12     12   10078 no warnings qw(redefine);
  12         35  
  12         1076  
68              
69             # replace Test::More _format_stack so
70             # we can call Text::Diff when needed
71 12         518372 *Test::More::_format_stack = \&format_stack;
72             }
73              
74             bless Test::More->builder, 'Test::Trivial::Builder';
75              
76             sub import {
77             my $package = shift;
78              
79             if ( !@_ ) {
80             eval "use Test::More qw( no_plan )";
81             if ( $@ ) {
82             die "Failed to load Test::More: $@";
83             }
84             }
85             elsif ( @_ == 1 ) {
86             eval "use Test::More qw( $_[0] )";
87             if ( $@ ) {
88             die "Failed to load Test::More: $@";
89             }
90             }
91             else {
92             my %args = @_;
93             if( my $tests = delete $args{tests} ) {
94 12     12   120 eval "use Test::More tests => \"$tests\"";
  12         24  
  12         163  
95             }
96             elsif( my $skip = delete $args{skip_all} ) {
97             eval "use Test::More skip_all => \"$skip\"";
98             }
99             if ( $@ ) {
100             die "Failed to load Test::More: $@";
101             }
102             if ( $args{diff} ) {
103             $DIFF = $args{diff};
104             }
105             }
106              
107             # crude Exporter
108             my ($pkg) = caller();
109             for my $func ( qw(ERR OK NOK EQ ID ISA IS ISNT LIKE UNLIKE) ) {
110 12     12   132 no strict 'refs';
  12         43  
  12         32588  
111             *{"${pkg}::$func"} = \&{$func};
112             }
113              
114             if ( defined $LOG ) {
115             my $logfile = $LOG;
116             if( !$logfile ) {
117             my ($name, $dir) = File::Basename::fileparse($0);
118             $logfile = "$dir/$name.log";
119             }
120             open my $log, ">>$logfile" or die "Could not open $logfile: $!";
121             my $tee = tie( *STDOUT, "Test::Trivial::IO::Tee", $log, \*STDOUT);
122             tie( *STDERR, "Test::Trivial::IO::Tee", $log, \*STDERR);
123             if( $VERBOSE ) {
124             $SIG{__WARN__} = sub { print STDERR @_ };
125             }
126             else {
127             $VERBOSE++;
128             $SIG{__WARN__} = sub { $tee->log(@_) }
129             }
130             $SIG{__DIE__} = sub { print STDOUT @_ };
131             my $tb = Test::Builder->new();
132             $tb->output(\*STDOUT);
133             $tb->failure_output(\*STDERR);
134             warn "#"x50, "\n";
135             warn "#\n";
136             warn "# Test: $0\n";
137             warn "# Time: ", POSIX::strftime("%Y-%m-%d %X", localtime()), "\n";
138             warn "#\n";
139             warn "#"x50, "\n";
140             }
141             }
142              
143             sub ERR (&) {
144 6     6 1 79 my $code = shift;
145 6         10 local $@;
146 6         15 my $ret = eval {
147 6         18 &$code;
148             };
149 6 100       123 return $@ if $@;
150 2         309 return $ret;
151             }
152              
153             sub OK ($;$) {
154 19     19 1 3160 my ($test, $msg) = @_;
155 19   33     96 $msg ||= line_to_text();
156 19 50       53 if( $VERBOSE ) {
157 0         0 require Data::Dumper;
158 0         0 warn "--------------------------------------------------------\n";
159 0         0 warn Data::Dumper->Dump([$test], ["OK"]);
160 0         0 warn "--------------------------------------------------------\n";
161             }
162 19 100       89 check($test) || warn_line_failure(1);
163 19 100 33     148 ok($test, $msg) || ($FATAL && !$TODO && die "All errors Fatal\n");
      50        
164            
165             }
166              
167             sub NOK ($;$) {
168 5     5 1 687 my ($test, $msg) = @_;
169 5   33     39 $msg ||= line_to_text();
170 5 50       17 if( $VERBOSE ) {
171 0         0 require Data::Dumper;
172 0         0 warn "--------------------------------------------------------\n";
173 0         0 warn Data::Dumper->Dump([$test], ["NOK"]);
174 0         0 warn "--------------------------------------------------------\n";
175             }
176 5 100       18 check(!$test) || warn_line_failure(1);
177 5 100 33     30 ok(!$test, "not [$msg]") || ($FATAL && !$TODO && die "All errors Fatal\n");
      50        
178            
179             }
180              
181             sub EQ ($$;$) {
182 18     18 1 5705 my ($lhs, $rhs, $msg) = @_;
183 18   33     105 $msg ||= line_to_text();
184 18 50       63 if( $VERBOSE ) {
185 0         0 require Data::Dumper;
186 0         0 warn "--------------------------------------------------------\n";
187 0         0 warn Data::Dumper->Dump([[$lhs, $rhs]], ["EQ"]);
188 0         0 warn "--------------------------------------------------------\n";
189             }
190 12     12   140 no warnings qw(numeric);
  12         28  
  12         10009  
191 18 100       71 check_is(0+$lhs,0+$rhs) || warn_line_failure(1);
192 18 100 33     87 is(0+$lhs,0+$rhs, $msg) || ($FATAL && !$TODO && die "All errors Fatal\n");
      50        
193             }
194              
195             sub ID ($$;$) {
196 8     8 1 2152 my ($lhs, $rhs, $msg) = @_;
197 8   33     46 $msg ||= line_to_text();
198 8 50       24 if( $VERBOSE ) {
199 0         0 require Data::Dumper;
200 0         0 warn "--------------------------------------------------------\n";
201 0         0 warn Data::Dumper->Dump([[$lhs,$rhs]], ["ID"]);
202 0         0 warn "--------------------------------------------------------\n";
203             }
204 8 100       24 check_is($lhs,$rhs) || warn_line_failure(1);
205 8 100 33     63 is($lhs,$rhs, $msg) || ($FATAL && !$TODO && die "All errors Fatal\n");
      50        
206             }
207              
208             my ($OFH, $FFH, $TFH);
209             sub capture_io {
210 28     28 0 50 my $data = shift;
211 28         212 my $io = IO::Scalar->new($data);
212 28         1880 my $tb = Test::Builder->new();
213 28         258 ($OFH, $FFH, $TFH) = (
214             $tb->output(),
215             $tb->failure_output,
216             $tb->todo_output,
217             );
218 28         500 $tb->output($io);
219 28         745 $tb->failure_output($io);
220 28         684 $tb->todo_output($io);
221             }
222              
223             sub reset_io {
224 28     28 0 117 my $tb = Test::Builder->new();
225 28 50       248 $tb->output($OFH) if defined $OFH;
226 28 50       552 $tb->failure_output($FFH) if defined $FFH;
227 28 50       568 $tb->todo_output($TFH) if defined $TFH;
228             }
229              
230             sub ISA ($$;$) {
231 11     11 1 4033 local $LEVEL += 1;
232 11         71 return OK(UNIVERSAL::isa($_[0],$_[1]),$_[2]);
233             }
234              
235             sub IS ($$;$) {
236 28     28 1 763 my ($lhs, $rhs, $msg) = @_;
237 28   33     150 $msg ||= line_to_text();
238 12     12   18227 use IO::Scalar;
  12         84867  
  12         65146  
239 28         59 my $output = "";
240 28 50       75 if( $VERBOSE ) {
241 0         0 require Data::Dumper;
242 0         0 warn "--------------------------------------------------------\n";
243 0         0 warn Data::Dumper->Dump([[$lhs, $rhs]], ["IS"]);
244 0         0 warn "--------------------------------------------------------\n";
245             }
246 28         100 capture_io(\$output);
247 28         709 my $ok = is_deeply($lhs, $rhs, $msg);
248 28         17913 reset_io();
249 28 100       882 warn_line_failure() unless $ok;
250 28         13127 print $output;
251 28 100 33     720 $ok || ($FATAL && !$TODO && die "All errors Fatal\n");
      50        
252             }
253              
254             # Test::More does not have an isnt_deeply
255             # so hacking one in here.
256             sub isnt_deeply {
257 8     8 0 69 my $tb = Test::More->builder;
258 8         74 my($got, $expected, $name) = @_;
259              
260 8         42 $tb->_unoverload_str(\$expected, \$got);
261              
262 8         1094 my $ok;
263 8 100 66     77 if ( !ref $got and !ref $expected ) {
    50 25        
264             # no references, simple comparison
265 6         45 $ok = $tb->isnt_eq($got, $expected, $name);
266             } elsif ( !ref $got xor !ref $expected ) {
267             # not same type, so they are definately different
268 0         0 $ok = $tb->ok(1, $name);
269             } else { # both references
270 2         7 local @Test::More::Data_Stack = ();
271 2 100       11 if ( Test::More::_deep_check($got, $expected) ) {
272             # deep check passed, so they are the same
273 1         1342 $ok = $tb->ok(0, $name);
274             } else {
275 1         5207 $ok = $tb->ok(1, $name);
276             }
277             }
278              
279 8         5963 return $ok;
280             }
281              
282             sub ISNT ($$;$) {
283 8     8 1 698 my ($lhs, $rhs, $msg) = @_;
284 8   33     61 $msg ||= line_to_text();
285 8 50       28 if( $VERBOSE ) {
286 0         0 require Data::Dumper;
287 0         0 warn "--------------------------------------------------------\n";
288 0         0 warn Data::Dumper->Dump([[$lhs, $rhs]], ["ISNT"]);
289 0         0 warn "--------------------------------------------------------\n";
290             }
291 8 100       31 check_is($lhs,$rhs) && warn_line_failure(1);
292 8 100 33     33 isnt_deeply($lhs, $rhs, $msg) || ($FATAL && !$TODO && die "All errors Fatal\n");
      50        
293             }
294              
295             sub LIKE ($$;$) {
296 7     7 1 8225 my ($lhs, $rhs, $msg) = @_;
297 7   33     47 $msg ||= line_to_text();
298 7 50       27 if( $VERBOSE ) {
299 0         0 require Data::Dumper;
300 0         0 warn "--------------------------------------------------------\n";
301 0         0 warn Data::Dumper->Dump([[$lhs, $rhs]], ["LIKE"]);
302 0         0 warn "--------------------------------------------------------\n";
303             }
304 7 100       23 check_like($lhs,$rhs) || warn_line_failure(1);
305 7 100 33     31 like($lhs, $rhs, $msg) || ($FATAL && !$TODO && die "All errors Fatal\n");
      50        
306             }
307              
308             sub UNLIKE ($$;$) {
309 7     7 1 2256 my ($lhs, $rhs, $msg) = @_;
310 7   33     45 $msg ||= line_to_text();
311 7 50       23 if( $VERBOSE ) {
312 0         0 require Data::Dumper;
313 0         0 warn "--------------------------------------------------------\n";
314 0         0 warn Data::Dumper->Dump([[$lhs, $rhs]], ["UNLIKE"]);
315 0         0 warn "--------------------------------------------------------\n";
316             }
317 7 100       21 check_like($lhs,$rhs) && warn_line_failure(1);
318 7 100 33     33 unlike($lhs, $rhs, $msg) || ($FATAL && !$TODO && die "All errors Fatal\n");
      50        
319             }
320              
321             sub check {
322 24 100   24 0 61 if( !$_[0] ) {
323 10         53 return 0;
324             }
325 14         35 return 1;
326             }
327              
328             sub check_is {
329 34     34 0 70 my $data = shift;
330 34         62 my $expected = shift;
331 34 0 33     84 return 1 if (not defined $data) && (not defined $expected);
332 34 50 33     380 return 0 if (not defined $data) && (defined $expected);
333 34 50 33     168 return 0 if (defined $data) && (not defined $expected);
334 34         301 return $data eq $expected;
335             }
336              
337             sub check_like {
338 14     14 0 22 my $data = shift;
339 14         22 my $match = shift;
340 14 50       37 return 0 unless defined $match;
341            
342 14 100 33     176 if ( ((not defined $data) && (defined $match))
      66        
343             || ($data !~ $match) ) {
344 4         22 return 0;
345             }
346 10         47 return 1;
347             }
348              
349             my %file_cache = ();
350              
351             sub warn_line_failure {
352 40   100 40 0 176 my $count_offset = shift || 0;
353 40         368 my ($pkg, $file, $line, $sub) = caller($LEVEL + 1);
354 40 50       181 print STDERR POSIX::strftime("# Time: %Y-%m-%d %X\n", localtime())
355             unless $ENV{HARNESS_ACTIVE};
356 40         310 $sub =~ s/^.*?::(\w+)$/$1/;
357 40         135 my $source = $file_cache{$file}->[$line-1];
358 40         97 my $col = index($source,$sub);
359             # index -1 on error, else add 1 (editors start at 1, not 0)
360 40 50       143 $col = $col == -1 ? 0 : $col + 1;
361 40         201 my $tb = Test::Builder->new();
362 40 50       1412 print "$file:$line:$col: Test ", $tb->current_test()+$count_offset, " Failed\n"
363             unless $ENV{HARNESS_ACTIVE};
364             }
365              
366              
367             my %OPS = (
368             'OK' => "",
369             'NOK' => "",
370             'EQ' => "==",
371             'ID' => "==",
372             'IS' => "==",
373             'ISA' => "ISA",
374             'ISNT' => "!=",
375             'LIKE' => "=~",
376             'UNLIKE' => "!~",
377             );
378              
379             sub line_to_text {
380 100     100 0 896 my ($pkg, $file, $line, $sub) = caller($LEVEL + 1);
381              
382 100         969 $sub =~ s/^.*::(\w+)$/$1/;
383              
384 100         171 my $source;
385 100 100 66     402 unless( $file_cache{$file} && @{$file_cache{$file}}) {
  88         525  
386             # reset input line seperator in case some
387             # is trying to screw with us
388 12         72 local $/ = IFS;
389 12         146 my $io = IO::Handle->new();
390 12 50       414 my $fn = $file eq '-e' ? "/proc/$$/cmdline" : $file;
391 12 50       5504 $fn = $0 unless -e $fn;
392 12 50       277 $fn = "$ENV{PWD}/$0" unless -e $fn;
393 12 50       256 $fn = "$ENV{PWD}/$ENV{_}" unless -e $fn;
394 12 50       688 open($io, "$fn") or die "Could not open $file: $!";
395 12         863 my @source = <$io>;
396 12         260 $file_cache{$file} = \@source;
397             }
398              
399             # sometimes caller returns the line number of the end
400             # of the statement insted of the beginning, so backtrack
401             # to find the calling sub if the current line does not
402             # have sub in it.
403 100   33     1418 $line-- while defined $file_cache{$file}->[$line-1] && $file_cache{$file}->[$line-1] !~ /$sub/;
404 100         185 my $offset = $line-1;
405 100         239 $source = $file_cache{$file}->[$offset];
406 100   100     780 while ($source !~ /;/ && $offset+1 != @{$file_cache{$file}} ){
  12         49  
407 11         17 $offset++;
408 11         38 $source .= $file_cache{$file}->[$offset];
409             }
410              
411 100         241 my $msg = "Unknown";
412 100 50       896 if( $source =~ /$sub$RE{balanced}{-parens=>'()'}{-keep}/s ) {
    100          
    50          
413 0         0 $msg = substr($1,1,-1);
414             }
415             elsif( $source =~ /$sub(.*?)\s(or|and)\b/s ) {
416 1         236 $msg = $1;
417             }
418             elsif( $source =~ /$sub(.*?)(;|$)/s ) {
419 99         27959 $msg = $1;
420             }
421              
422 100         1344 $msg =~ s/^\s+//;
423 100         365 $msg =~ s/\s+$//;
424              
425 100 100       371 if( my $op = $OPS{$sub} ) {
426             # multiple args
427 87         1232 my @parens;
428 87         454 while( $msg =~ s/$RE{balanced}{-parens=>'(){}[]'}{-keep}/#####GRP#####/s ) {
429 48         12307 push @parens, $1;
430             }
431 87         18735 my @parts = split /\s*(?:,|=>)\s*/s, $msg;
432 87   33     854 s/^\s+// || s/\s+$// for @parts;
433 87         279 $msg = "$parts[0] $op $parts[1]";
434              
435 87         450 while( my $paren = shift @parens ) {
436 48         293 $msg =~ s/#####GRP#####/$paren/;
437             }
438            
439             }
440 100         522 return $msg;
441             }
442              
443             #
444             # this routing is basically copied from
445             #
446             # Test::More::_format_stack.
447             # Original Author: Michael G Schwern
448             # Copyright: Copyright 2001-2008 by Michael G Schwern
449             #
450             # It has been modified to wedge in the Text::Diff call
451             #
452              
453             sub format_stack {
454 3     3 0 148 my(@Stack) = @_;
455            
456 3         10 my $var = '$FOO';
457 3         6 my $did_arrow = 0;
458 3         8 foreach my $entry (@Stack) {
459 6   100     29 my $type = $entry->{type} || '';
460 6         17 my $idx = $entry->{'idx'};
461 6 100       33 if ( $type eq 'HASH' ) {
    100          
    50          
462 2 50       10 $var .= "->" unless $did_arrow++;
463 2         10 $var .= "{$idx}";
464             } elsif ( $type eq 'ARRAY' ) {
465 3 50       14 $var .= "->" unless $did_arrow++;
466 3         13 $var .= "[$idx]";
467             } elsif ( $type eq 'REF' ) {
468 0         0 $var = "\${$var}";
469             }
470             }
471              
472 3         68 my @vals = @{$Stack[-1]{vals}}[0,1];
  3         15  
473 3         8 my @vars = ();
474              
475 3         7 my $out = "Structures begin differing at:\n";
476 3 100 66     33 if ( $vals[0] =~ /\n/ || $vals[1] =~ /\n/ ) {
477 1         5 ($vars[0] = $var) =~ s/\$FOO/\$got/;
478 1         5 ($vars[1] = $var) =~ s/\$FOO/\$expected/;
479 1         11 $out .= Text::Diff::diff(\$vals[0], \$vals[1], {
480             STYLE => $DIFF,
481             FILENAME_A => $vars[0],
482             FILENAME_B => $vars[1],
483             })
484             } else {
485 2         8 foreach my $idx (0..$#vals) {
486 4         63 my $val = $vals[$idx];
487 4 50       21 $vals[$idx] = !defined $val ? 'undef' :
    100          
    50          
488             Test::More::_dne($val) ? "Does not exist" :
489             ref $val ? "$val" :
490             "'$val'";
491             }
492 2         22 ($vars[0] = $var) =~ s/\$FOO/ \$got/;
493 2         10 ($vars[1] = $var) =~ s/\$FOO/\$expected/;
494 2         8 $out .= "$vars[0] = $vals[0]\n";
495 2         7 $out .= "$vars[1] = $vals[1]\n";
496 2         15 $out =~ s/^/ /msg;
497             }
498 3         428 return $out;
499             }
500              
501             package Test::Trivial::Builder;
502 12     12   167 use base qw(Test::Builder);
  12         29  
  12         10058  
503              
504             #
505             # Overload the base Test::Builder _is_diag function
506             # so we can call Text::Diff on multiline statements.
507             #
508             sub _is_diag {
509 16     16   2469 my($self, $got, $type, $expect) = @_;
510 16 100 66     134 return $self->SUPER::_is_diag($got,$type,$expect)
511             unless defined $got && defined $expect;
512              
513 14 100 66     110 if( $got =~ /\n/ || $expect =~ /\n/ ) {
514 2         27 return $self->diag(
515             Text::Diff::diff(\$got, \$expect, {
516             STYLE => $DIFF,
517             FILENAME_A => "got",
518             FILENAME_B => "expected",
519             })
520             );
521             }
522 12         76 return $self->SUPER::_is_diag($got,$type,$expect);
523             }
524              
525             #
526             # chop out the "at tests.t line 32" stuff since
527             # we add that above with warn_line_failure().
528             # I prefer ours since it prints out before
529             # the test header so emacs next-error will
530             # let me see what just ran
531             #
532             sub diag{
533 113     113   66867 my ($self, @msgs) = @_;
534 113         888 $self->SUPER::diag(
535 113         239 grep { !/\s+at\s+\S+\s+line\s+\d+[.]\n/ } @msgs
536             );
537             }
538              
539             package Test::Trivial::IO::Tee;
540 12     12   266 use base qw(IO::Tee);
  12         26  
  12         16392  
541              
542             sub TIEHANDLE {
543 0     0     my $class = shift;
544 0           my @handles = ();
545 0           for my $handle ( @_ ) {
546 0 0         unless( UNIVERSAL::isa($handle, "IO::Handle") ) {
547 0           my $io = IO::Handle->new();
548 0           $io->fdopen($handle->fileno(), "w");
549 0           $io->autoflush(1);
550 0           push @handles, $io;
551             }
552             else {
553 0           $handle->autoflush(1);
554 0           push @handles, $handle;
555             }
556             }
557 0           return bless [@handles], $class;
558             }
559              
560             sub log {
561 0     0     shift->[0]->print(@_);
562             }
563              
564             1;
565              
566             __END__