use strict; use warnings; use Test::More; use Data::Dumper; use vars qw/%Has/; BEGIN { $Has{diff}=!!eval "use Algorithm::Diff qw(sdiff diff); 1"; $Has{sortkeys}=!!eval "Data::Dumper->new([1])->Sortkeys(1)->Dump()"; } #$Id: test_helper.pl 26 2006-04-16 15:18:52Z demerphq $# # all of this is acumulated junk used for making the various test easier. # as a close inspection shows, this all derives from different periods of # the module and is pretty nasty/hacky to look at. Slowly id like to convert # everything over to test_dump() and get rid of same(). sub string_diff { my ( $str1, $str2, $title1, $title2 ) = @_; $title1 ||= "Got"; $title2 ||= "Expected"; my $line = ( caller(2) )[2]; #print $str1,"\n---\n",$str2; my $seq1 = ( ref $str1 ) ? $str1 : [ split /\n/, $str1 ]; my $seq2 = ( ref $str2 ) ? $str2 : [ split /\n/, $str2 ]; # im sure theres a more elegant way to do all this as well my @array; my $are_diff; Algorithm::Diff::traverse_sequences( $seq1, $seq2, { MATCH => sub { my ( $t, $u ) = @_; push @array, [ '=', $seq1->[$t], $t, $u ]; }, DISCARD_A => sub { my ( $t, $u ) = @_; push @array, [ '-', $seq1->[$t], $t, $u ]; $are_diff++; }, DISCARD_B => sub { my ( $t, $u ) = @_; push @array, [ '+', $seq2->[$u], $t, $u ]; $are_diff++; }, } ); return "" unless $are_diff; my $return = "-$title1\n+$title2\n"; #especially this bit. my ( $last, $skipped ) = ( "=", 1 ); foreach ( 0 .. $#array ) { my $elem = $array[$_]; my ( $do, $str, $pos, $eq ) = @$elem; if ( $do eq $last && $do eq '=' && ( $_ < $#array && $array[ $_ + 1 ][0] eq "=" || $_ == $#array ) ) { $skipped = 1; next; } $str .= "\n" unless $str =~ /\n\z/; if ($skipped) { $return .= sprintf( "\@%d,%d (%d)\n", $eq + 1, $pos + 1, $line + $eq + 1 ); $skipped = 0; } $last = $do; $return .= join ( "", $do, " ", $str ); } return $return; } sub capture { \@_ } sub _same { my ( $str1, $str2, $name, $obj ) = @_; s/\s+$//gm for $str1, $str2; s/\r\n/\n/g for $str1, $str2; s/\(0x[0-9a-xA-X]+\)/(0xdeadbeef)/g for $str1, $str2; my @vars = $str2 =~ m/^(?:my\s*)?(\$\w+)\s*=/gm; #warn "@vars"; unless ( ok( "\n" . $str1 eq "\n" . $str2, $name ) ) { if ( $str2 =~ /\S/ ) { eval { print string_diff( "\n" . $str2, "\n" . $str1, "Expected", "Result" ); print "Got:\n" . $str1 . "\n"; } or do { print "Expected:\n$str2\nGot:\n$str1\n"; } } else { print $str1, "\n"; } $obj->diag; } } { my $version=""; my %errors; my @errors=(''); sub _dumper { my ($todump)=@_; my ($dump,$error); foreach my $use_perl (1) { my $warned=""; local $SIG{__WARN__}=sub { my $err=join ('',@_); $warned.=$err unless $err=~/^Subroutine|Encountered/}; $dump=eval { scalar Data::Dumper->new( $todump )->Purity(1)->Sortkeys(1)->Quotekeys(1)->Useperl($use_perl)->Dump() }; unless ($@) { normalize($dump); return ($dump,$error.$warned); }else { unless ($version) { $version="\tSomething is wrong with Data::Dumper v" . Data::Dumper->VERSION . "\n"; $error=$version; } my $msg=$@.$warned; unless ($errors{$msg}) { (my $err=$msg)=~s/^/\t/g; push @errors,$msg; $errors{$msg}=$#errors; $error.=sprintf "\tData::Dumper (Useperl==$use_perl) Error(%#d):\n\t%s", $#errors,$err; } else { $error.=sprintf "\tData::Dumper (Useperl==$use_perl) Error %#d\n",$errors{$msg}; } next } } #warn $error; return ($dump,$error); } } sub vstr {Data::Dump::Streamer::__vstr(@_)} our $Clean; sub normalize { my @x=@_; foreach (@x) { #warn "\n$_\n"; s/^\s*(no|use).*\n//gm; s/^\s*BEGIN\s*\{.*\}\n//gm; s/\A(?:\s*(?:#\*\.*)?\n)+//g; if (/^\s+(#\s*)/) { my $ind=$1; s/^\s+$ind//gm; } s/\(0x[0-9a-fA-F]+\)/(0xdeadbeef)/g; s/\r\n/\n/g; s/\s+$//gm; $_.="\n"; #warn "\n$_\n"; } unless (defined wantarray) { $_[$_-1]=$x[$_-1] for 1..@_; } wantarray ? @x : $x[0] } sub same { goto &_same unless ref( $_[1] ); my $name = shift; my $obj = shift; my ($expect,$result) = normalize(shift, scalar $obj->Data(@_)->Out()); my $main_pass; { my $r=$result; my $e=$expect; #warn "@vars"; $main_pass="\n" . $r eq "\n" . $e; unless ( $main_pass ) { if ( $e =~ /\S/ ) { eval { print string_diff( "\n" . $e, "\n" . $r, "Expected", "Result" ); print "$name Got:\n" . $r . "\nEXPECT\n"; } or do { print "$name Expected:\n$e\nGot:\n$r\n"; } } else { print $r, "\n"; } $obj->diag; } } my @declare=grep { /^[\$\@\%]/ } @{$obj->{declare}}; my @dump =map { /^[\@\%\&]/ ? "\\$_" : $_ } @{$obj->{out_names}}; my $dumpvars=join ( ",", @dump ); print $result,"\n" if $name=~/Test/; my ($dumper,$error) = _dumper(\@_); if ($error) { diag( "$name\n$error" ) if $ENV{TEST_VERBOSE}; } if ($dumper) { my $result2_eval = $result . "\n" . 'scalar( $obj->Data(' . $dumpvars . ")->Out())\n"; my $dd_result_eval = $result . "\nscalar(Data::Dumper->new(" . 'sub{\@_}->(' . $dumpvars . ")" . ")->Purity(1)->Sortkeys(1)->Quotekeys(1)->" . "Useperl(1)->Dump())\n"; unless ( $obj->Declare ) { $dd_result_eval = "my(" . join ( ",", @declare ) . ");\n" . $dd_result_eval; $result2_eval = "my(" . join ( ",", @declare ) . ");\n" . $result2_eval; } foreach my $test ( [ "Data::Dumper", $dd_result_eval, $dumper ], [ "Data::Dump::Streamer", $result2_eval, $result ] ) { my ( $test_name, $eval, $orig ) = @$test; my ($warned,$res); { local $SIG{__WARN__}=sub { my $err=join ('',@_); $warned.=$err unless $err=~/^Subroutine|Encountered/}; $res = eval $eval; if ($warned) { print "Eval $test_name produced warnings:$warned\n$eval" }; } normalize($res); my $fail = 0; if ($@) { print join "\n", "Failed $test_name eval()", $eval, $@, ""; $fail = 1; } elsif ( $res ne $orig ) { print "Failed $test_name second time\n"; eval { print string_diff( $orig, $res, "Orig", "Result" ) }; print "Orig:\n$orig\nResult:\n$res\nEval:\n$eval\n"; $fail = 1; } $obj->diag if $fail; return fail($name) if $fail; } #print join "\n",$result,$result2,$dumper,$dd_result,""; } ok( $main_pass, $name ) } =pod test_dump( "Name", $obj, @vars, $expect ) =cut my %Methods=( 'Data::Dumper'=>'->new(sub{\\@_}->(@_))'. '->Purity(1)'. '->Sortkeys(1)'. '->Quotekeys(1)'. '->Useperl(1)'. '->Dump()', 'Data::Dump::Streamer'=>'->Data(@_)->Out()', ); use constant NO_EVAL=>''; sub _dmp { my $obj=shift; my $eval=shift; my $class=ref($obj) || $obj; my $objname=ref($obj) ? '$obj' : $obj; my @lines; my $method=$Methods{$class}; if ($eval) { return @$eval if @$eval!=1; my ($names,$declare,%arg)=@_; my @declare= grep { /^[\$\@\%]/ } @$declare; my @to_dump= map { /^[\@\%\&]/ ? "\\$_" : $_ } @$names; my $decl=@$declare ? "my(" . join ( ",", @declare ) . ");" : ""; push @lines,$decl,$arg{pre_eval},$eval->[0],$arg{post_eval}; $method=~s/\(\@_\)/"(".join (", ",@to_dump).")"/ge; } push @lines,"normalize ( scalar $objname$method )"; my $eval_str=join ";\n",map { !$_ ? () : (s/[\s;]+\z//g || 1) && $_ } @lines; #print "\n---\n",$eval_str,"\n---\n"; my $res; { my @w; { local $SIG{__WARN__}=sub { push @w,join "",@_; ""}; $res=eval $eval_str; } warn "Test $class$method produced warnings. Code:\n$eval_str\nWarnings:\n".join("\n",@w)."\n" if @w; return ($res,"$class$method failed dump:\n$eval_str\n$@") if $@; } return ($res); } my %ldchar=(u=>'=','+'=>'+','-'=>'-','c'=>'!'); my %mdchar=(u=>'|','+'=>'>','-'=>'<','c'=>'*'); sub _my_diff { my ($e,$g,$mode)=@_; unless ($Has{diff}) { if ($e ne $g) { return join "\n","Expected:",$e,"Got:",$g,"" } else { return } } my @exp=split /\n/,$e; my @got=split /\n/,$g; my $line=0; my $diff=0; my $lw=length('Expected'); my $u=3; my @buff; my @lines=map{ if ($_->[0]ne'u') { $diff=1; $u=0; } else { $u++; } $lw=length $_->[1] if $lw < length $_->[1]; unshift @$_,$line++; if ($u<3) { my @r=$u==0 && @buff ? (@buff,$_) : ($_); @buff=() unless $u; @r } else { shift @buff if @buff>=2; push @buff,$_; (); } } sdiff(\@exp,\@got); my $as_str=join("\n", sprintf("%7s%*s%3s%s",'',-$lw,'Expected','','Result'), map { sprintf "%4d %1s %*s %1s %s", $_->[0],$ldchar{$_->[1]}, -$lw,$_->[2]||'',$mdchar{$_->[1]}, $_->[3]||'' } @lines)."\n"; return $diff ? $as_str : ''; } sub _eq { my ($exp,$res,$test,$name)=@_; my ($exp_err,$res_err); # if they are arrays then they from tests involving _dmp # but if they are empty then the test isnt performed and # we can forget it return 1 if ref $exp and !@$exp or ref($res) and !@$res; ($exp,$exp_err)=@$exp if ref $exp; ($res,$res_err)=@$res if ref $res; # the thing we are trying to compare against was a failure # so assume we suceed. (or rather the test cant be counted) return 1 if $exp_err; # result was a failure if ($res_err) { if ($test->{verbose}) { diag "Error:\n$test->{name} subtest $name:\n",$res_err; } return 0 } # finally both $exp and $res should hold results my $diff=_my_diff($exp,$res); if ($diff && $test->{verbose}) { diag "Error:\n$test->{name} subtest $name failed to return the expected result:\n", $diff } return !$diff; } # eventually id like to move everything over to this. # test_dump( {name=>"merlyns test 2", # verbose=>1}, $o, ( \\@a ), # <<'EXPECT', ); $::Pre_Eval = ""; $::Post_Eval = ""; $::No_Dumper = 0; $::No_Redump = 0; sub test_dump { my $test = shift; my $obj = shift; my $exp = normalize(pop @_); # vars are now left in @_ $test = { name => $test, } unless ref $test; $test->{pre_eval}= $::Pre_Eval unless exists $test->{pre_eval}; $test->{post_eval}= $::Post_Eval unless exists $test->{post_eval}; $test->{no_dumper}= $::No_Dumper unless exists $test->{no_dumper}; $test->{no_redump}= $::No_Redump unless exists $test->{no_redump}; $test->{verbose} = 1 if not exists $test->{verbose} and $ENV{TEST_VERBOSE}; $test->{no_dumper} = 1 if !$Has{sortkeys}; my @res=_dmp($obj,NO_EVAL,@_); if (@res==2) { diag "Error:\n",$res[1]; fail($test->{name}); return } my $to_dump=$obj->{out_names}; my $to_decl=$obj->Declare ? [] : $obj->{declare}||[]; my @dmp =!$test->{no_dumper} ? _dmp('Data::Dumper',NO_EVAL,@_) : (); if (@dmp==2 and $test->{verbose}) { diag "Error:\n",$dmp[1]; } my @reres=!$test->{no_redump} ? _dmp($obj,\@res,$to_dump,$to_decl,pre_eval=>$test->{pre_eval},post_eval=>$test->{post_eval}) : (); my @redmp=!$test->{no_redump} && !$test->{no_dumper} ? _dmp('Data::Dumper',\@res,$to_dump,$to_decl,pre_eval=>$test->{pre_eval},post_eval=>$test->{post_eval}) : (); my $ok= @dmp<2 && _eq($exp, \@res,$test,"Expected") && _eq($exp, \@reres,$test,"Second time") && _eq(\@dmp,\@redmp,$test,"Both Dumper's same "); unless ($ok) { warn "Got <<'EXPECT';\n$res[0]\nEXPECT\n"; } ok( $ok, $test->{name} ); } 1;