File Coverage

lib/Test/Trivial.pm
Criterion Covered Total %
statement 42 145 28.9
branch 0 56 0.0
condition 0 32 0.0
subroutine 15 27 55.5
pod n/a
total 57 260 21.9


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