package autohashUtil; use lib qw(t); use strict; use Carp; use List::MoreUtils qw(uniq); #use Test::More qw/no_plan/; use Test::More; use Test::Deep; use Exporter(); use Hash::AutoHash; our @ISA=qw(Exporter); our @EXPORT=qw(as_bool ordinal report _report cmp_types cmp_autohash _cmp_autohash keys_obj values_obj each_obj @KEYS @UNDEFS @VALUES_SV @VALUES_MV $VERBOSE is_autohash is_hash is_object is_self is_tie is_autohash_hash is_autohash_object is_autohash_self is_autohash_tie test_special_keys test_class_methods @COMMON_SPECIAL_KEYS $autohash %hash %tie $object); # globals used in all tests our($autohash,%hash,%tie,$object); our $VERBOSE=0; our @KEYS=qw(key0 key1 key2); our @UNDEFS=(undef) x @KEYS; our @VALUES_SV=([undef,undef,undef], [undef,'value11','value21'], [undef,'value12','value22'], [undef,'value13','value23'],); our @VALUES_MV=([undef,undef,undef], [undef,['value11'],['value21']], [undef,[qw(value11 value12)],[qw(value21 value22)]], [undef,[qw(value11 value12 value13)],[qw(value21 value22 value23)]]); # sub report (*\@\@) { # my($label,$ok,$fail)=@_; # unless (@$fail) { # pass($label) if $VERBOSE; # return 1; # } # fail($label); # diag(scalar(@$ok)." cases have correct values: @$ok"); # diag(scalar(@$fail)." cases have wrong values: @$fail"); # } sub _report (*\@\@) { my($label,$ok,$fail)=@_; unless (@$fail) { pass($label) if $VERBOSE; return 1; } fail($label); diag(scalar(@$ok)." cases have correct values: @$ok"); diag(scalar(@$fail)." cases have wrong values: @$fail"); return 0; } sub report (*\@\@) { my($label,$ok,$fail)=@_; my $pass=_report($label,@$ok,@$fail); pass($_[0]) if $pass && !$VERBOSE; # print if all tests passed and tests didn't print passes $pass; } sub as_bool {$_[0]? 1: undef;} sub ordinal { my $i=shift; return 'initial' unless $i--; return '1st' unless $i--; return '2nd' unless $i--; return '3rd' unless $i--; return $i.'-th'; } # test type of $autohash, whatever $autohash is tied to, %hash, $object, whatever %hash is tied to sub cmp_types { my($label,$correct_tied,$correct_object,$correct_tiedhash)=@_; my $correct_autohash='Hash::AutoHash'; my $correct_hash='HASH'; my(@ok,@fail); my $actual_autohash=ref $autohash; my $actual_tied=ref tied %$autohash; my $actual_hash=ref \%hash; my $actual_object=ref $object; my $actual_tiedhash=ref tied %hash; $actual_autohash eq $correct_autohash? push(@ok,'autohash'): push(@fail,'autohash'); $actual_tied eq $correct_tied? push(@ok,'tied'): push(@fail,'tied'); $actual_hash eq $correct_hash? push(@ok,'hash'): push(@fail,'hash'); $actual_object eq $correct_object? push(@ok,'object'): push(@fail,'object'); $actual_tiedhash eq $correct_tiedhash? push(@ok,'tiedhash'): push(@fail,'tiedhash'); $label.=': types'; pass($label),return unless @fail; fail($label); diag(scalar(@ok)." items have correct types: @ok"); diag(scalar(@fail)." items have wrong types: @fail"); } # test contents of wrapper and external hash or object # NG 09-07-29: generalize to allow any actual autohash or correct value sub cmp_autohash { my $pass=_cmp_autohash(@_); my $label=$_[0]; pass($_[0]) if $pass && !$VERBOSE; # print if all tests passed and tests didn't print passes $pass; } # _cmp_autohash does the work but does not print passes sub _cmp_autohash { my($label,$values,$actual,$correct,$ok_hash,$ok_object,$hash,$obj); if ('ARRAY' eq ref $_[1]) { # old form ($label,$values,$ok_hash,$ok_object)=@_; my @values=@$values; # NG 09-07-29: added computation of %correct. added _cmp_contents. # changed othered to use %correct $actual=$autohash; %$correct=map {defined($values[$_])? ($KEYS[$_]=>$values[$_]): ()} 0..$#values; $hash=\%hash; $obj=$object; } else { ($label,$actual,$correct,$ok_hash,$ok_object,$hash,$obj)=@_; } my $pass=1; # assume success $pass&&=_cmp_contents($label,$actual,$correct); $pass&&=_cmp_autohash_methods($label,$actual,$correct); $pass&&=_cmp_autohash_hash($label,$actual,$correct); $pass&&=_cmp_hash($label,$ok_hash,$hash,$correct); $pass&&=_cmp_object($label,$ok_object,$obj,$correct); $pass; # _cmp_autohash_methods($label,@values); # _cmp_autohash_hash($label,@values); # _cmp_hash($label,$ok_hash,@values); # _cmp_object($label,$ok_object,@values); # } else { # new form # ($label,$actual,$correct,$ok_hash,$ok_object,$hash,$object)=@_; # %correct=%$correct; # _cmp_contents($label,$actual,%correct); # _cmp_autohash_methods($label,$actual,%correct); # _cmp_autohash_hash($label,$actual,%correct); # _cmp_hash($label,$ok_hash,$hash,%correct); # _cmp_object($label,$ok_object,$object,%correct); # } } # NG 09-07-29: added _cmp_contents sub _cmp_contents { my($label,$actual,$correct)=@_; $label.=' contents'; my %actual=%$actual; return 1 if !$VERBOSE && eq_deeply(\%actual,$correct); # else let cmp_deeply print its results cmp_deeply(\%actual,$correct,$label); } sub _cmp_autohash_methods { my($label,$actual,$correct)=@_; $label.=' via methods'; my(@ok,@fail); for my $key (keys %$correct) { my $actual_val=$actual->$key; my $correct_val=$correct->{$key}; eq_deeply($actual_val,$correct_val)? push(@ok,$key): push(@fail,$key); } _report($label,@ok,@fail); } sub _cmp_autohash_hash { my($label,$actual,$correct)=@_; $label.=' as hash'; my(@ok,@fail); for my $key (keys %$correct) { my $actual_val=$actual->{$key}; my $correct_val=$correct->{$key}; eq_deeply($actual_val,$correct_val)? push(@ok,$key): push(@fail,$key); } _report($label,@ok,@fail); } sub _cmp_hash { my($label,$ok_hash,$actual,$correct)=@_; my %actual=defined $actual? %$actual: (); $label.=' external hash'; unless ($ok_hash) { # %actual (aka %hash) should be empty $label.=' empty'; fail($label), return if %actual; pass($label) if $VERBOSE; return 1; } my(@ok,@fail); for my $key (keys %$correct) { my $actual_val=$actual{$key}; my $correct_val=$correct->{$key}; eq_deeply($actual_val,$correct_val)? push(@ok,$key): push(@fail,$key); } _report($label,@ok,@fail); } sub _cmp_object { my($label,$ok_object,$actual,$correct)=@_; $label.=' tied object'; unless ($ok_object) { # $object should be undef $label.=' empty'; fail($label), return if defined $object; pass($label) if $VERBOSE; return 1; } my(@ok,@fail); for my $key (keys %$correct) { my $actual_val=$actual->FETCH($key); my $correct_val=$correct->{$key}; eq_deeply($actual_val,$correct_val)? push(@ok,$key): push(@fail,$key); } _report($label,@ok,@fail); } # sub _cmp_autohash_methods { # my($label,@values)=@_; # $label.=' via methods'; # my(@ok,@fail); # for my $key (@KEYS) { # my $actual=$autohash->$key; # my $correct=shift @values; # eq_deeply($actual,$correct)? push(@ok,$key): push(@fail,$key); # } # report($label,@ok,@fail); # } # sub _cmp_autohash_hash { # my($label,@values)=@_; # $label.=' as hash'; # my(@ok,@fail); # for my $key (@KEYS) { # my $value=shift @values; # eq_deeply($autohash->{$key},$value)? push(@ok,$key): push(@fail,$key); # } # report($label,@ok,@fail); # } # sub _cmp_hash { # my($label,$ok_hash,@values)=@_; # $label.=' external hash'; # unless ($ok_hash) { # %hash should be empty # ok(!%hash,"$label empty"); # return; # } # my(@ok,@fail); # for my $key (@KEYS) { # my $value=shift @values; # eq_deeply($hash{$key},$value)? push(@ok,$key): push(@fail,$key); # } # report($label,@ok,@fail); # } # sub _cmp_object { # my($label,$ok_object,@values)=@_; # $label.=' external object'; # unless ($ok_object) { # $object should be undef # is($object,undef,"$label empty"); # return; # } # my(@ok,@fail); # for my $key (@KEYS) { # my $actual=$object->FETCH($key); # my $correct=shift @values; # eq_deeply($actual,$correct)? push(@ok,$key): push(@fail,$key); # } # report($label,@ok,@fail); # } sub keys_obj { # based on code from now-obsolete version of Hash my $obj=@_? shift: $object; my($key,$value)=$obj->FIRSTKEY() or return (); my @keys=($key); while(($key,$value)=$obj->NEXTKEY()) { push(@keys,$key); } @keys; } sub values_obj { # based on code from now-obsolete version of Hash my $obj=@_? shift: $object; my($key,$value)=$obj->FIRSTKEY() or return (); my @values=($value); while(($key,$value)=$obj->NEXTKEY()) { push(@values,$value); } @values; } my $each; # controls iterator sub each_obj { my $obj=@_? shift: $object; if (wantarray) { my @result=!$each? $object->FIRSTKEY(): $object->NEXTKEY(); $each=scalar @result; return @result; } else { my $result=!$each? $object->FIRSTKEY(): $object->NEXTKEY(); return $each=$result; } } # sub is_object (*@) { # my($label,@values)=@_; # my(@ok,@fail); # for my $key (@KEYS) { # my $value=shift @values; # eq_deeply($object->FETCH($key),$value)? push(@ok,$key): push(@fail,$key); # } # $label.=": object"; # report($label,@ok,@fail); # } # sub is_self (*@) { # my($label,@values)=@_; # my(@ok,@fail); # for my $key (@KEYS) { # my $value=shift @values; # eq_deeply($autohash->{$key},$value)? push(@ok,$key): push(@fail,$key); # } # $label.=": self"; # report($label,@ok,@fail); # } # sub is_tie (*@) { # my($label,@values)=@_; # my(@ok,@fail); # for my $key (@KEYS) { # my $value=shift @values; # eq_deeply($tie{$key},$value)? push(@ok,$key): push(@fail,$key); # } # $label.=": tie"; # report($label,@ok,@fail); # } # sub is_autohash_hash (*@) { # my($label,@values)=@_; # is_autohash($label,@values); # is_hash($label,@values); # } # sub is_autohash_object (*@) { # my($label,@values)=@_; # is_autohash($label,@values); # is_object($label,@values); # is_tie($label,@values); # } # sub is_autohash_self (*@) { # my($label,@values)=@_; # is_autohash($label,@values); # is_self($label,@values); # } # sub is_autohash_tie (*@) { # my($label,@values)=@_; # is_autohash($label,@values); # is_tie($label,@values); # } # NG 12-09-02: no longer possible to override methods inheritted from UNIVERSAL # used by xxx.020.special_keys.t for many (probably all) subclasses our @COMMON_SPECIAL_KEYS=qw(import new AUTOLOAD DESTROY); our @FORMER_SPECIAL_KEYS=qw(can isa DOES VERSION); sub test_special_keys { my($autohash,$repeat,$fixer,$case)=@_; my $class=ref $autohash; defined($repeat) or $repeat=1; defined($fixer) or $fixer=sub {$_[0]}; my $label=length($case)? "$class $case special keys": "$class special keys"; my @keys; { no strict 'refs'; @keys=uniq(@COMMON_SPECIAL_KEYS, # qw(import new can isa DOES VERSION AUTOLOAD DESTROY), @Hash::AutoHash::EXPORT_OK,@{$class.'::EXPORT_OK'}); } my(@ok,@fail); for my $key (@keys) { my $value="value_$key"; for(my $i=0; $i<$repeat; $i++) {$autohash->$key($value);} # set value my $actual=$autohash->$key; # get value my $correct=&$fixer($value); eq_deeply($actual,$correct)? push(@ok,$key): push(@fail,$key); # $actual eq $correct? push(@ok,$key): push(@fail,$key); } # like '_report' # my $label="$class special keys"; # pass("$label. keys=@keys"),return unless @fail; pass($label),return unless @fail; fail($label); diag(scalar(@ok)." keys have correct values: @ok"); diag(scalar(@fail)." keys have wrong values: @fail"); } # # used for Child, Grandchild tests. not for main special_keys test # sub test_subclass_special_keys (*) { # my($class)=@_; # my @keys; # { # no strict 'refs'; # @keys=uniq(@COMMON_SPECIAL_KEYS, # # qw(import new can isa DOES VERSION AUTOLOAD DESTROY), # @Hash::AutoHash::EXPORT_OK,@{$class.'::EXPORT_OK'}); # } # $autohash=new $class; # my(@ok,@fail); # for my $key (@keys) { # my $value="value_$key"; # $autohash->$key($value); # set value # my $actual=$autohash->$key; # get value # my $correct=$value; # # eq_deeply($actual,$correct)? push(@ok,$key): push(@fail,$key); # $actual eq $correct? push(@ok,$key): push(@fail,$key); # } # # like '_report' # my $label="$class special keys"; # # pass("$label. keys=@keys"),return unless @fail; # pass($label),return unless @fail; # fail($label); # diag(scalar(@ok)." keys have correct values: @ok"); # diag(scalar(@fail)." keys have wrong values: @fail"); # } # used by xxx.020.class_methods.t for many (probably all) subclasses sub test_class_methods { my $class=shift; my $import=@_? shift: undef; # an importable function # It's kinda silly to test new and import, since we'd have failed miserably long ago # if these were broken :) Included here for completeness. $autohash=new $class; ok($autohash,'new'); is(ref $autohash,$class,"new returned $class object - sanity check"); if ($import) { eval {import $class ($import)}; ok(!$@,'import: success'); } eval {import $class qw(import);}; ok($@=~/not exported/,'import: not exported'); eval {import $class qw(not_defined);}; ok($@=~/not exported/,'import: not defined'); my $can=can $class('can'); is(ref $can,'CODE','can: can'); my $can=can $class('not_defined'); ok(!$can,'can: can\'t'); if ($class ne 'Hash::AutoHash') { my $isa=$class->isa($class); is($isa,1,"isa: is $class"); } my $isa=$class->isa('Hash::AutoHash'); is($isa,1,'isa: is Hash::AutoHash'); my $isa=$class->isa('UNIVERSAL'); is($isa,1,'isa: is UNIVERSAL'); my $isa=$class->isa('not_defined'); ok(!$isa,'isa: isn\'t'); # Test DOES in perls > 5.10. # Note: $^V returns real string in perls > 5.10, and v-string in earlier perls # regexp below fails in earlier perls. this is okay my($perl_main,$perl_minor)=$^V=~/^v(\d+)\.(\d+)/; # perl version if ($perl_main==5 && $perl_minor>=10) { my $does=DOES $class('Hash::AutoHash'); is($does,1,'DOES: is Hash::AutoHash'); my $does=DOES $class('UNIVERSAL'); is($does,1,'DOES: is UNIVERSAL'); my $does=DOES $class('not_defined'); ok(!$does,'DOES: doesn\'t'); } my $version=VERSION $class; my $correct=eval "\$$class"."::VERSION"; is($version,$correct,'VERSION'); my @imports=eval "\@$class"."::EXPORT_OK"; import $class (@imports); pass('import all functions'); } 1;