use 5.005; use strict; # SAMPLE HIERARCHY TO TEST... package Base1; sub new { bless {}, ref($_[0])||$_[0] } package Base2; sub new { bless {}, ref($_[0])||$_[0] } package Der1; @Der1::ISA = qw( Base1 ); package Der2; @Der2::ISA = qw( Base1 ); package Der3; @Der3::ISA = qw( Base2 ); package DerDer1; @DerDer1::ISA = qw( Der1 ); package DerDer2; @DerDer2::ISA = qw( Der2 ); package DerDer3; @DerDer3::ISA = qw( Der3 ); package DerDer4; @DerDer4::ISA = qw( Der3 ); # LOAD AND SHOOT... package main; BEGIN { $| = 1; print "1..350\n"; } END {print "not ok 1\n" unless $::loaded;} use Class::Multimethods; $::loaded = 1; print "ok 1\n"; # DEFINE SOME MULTIMETHODS ON THE ABOVE HIERARCHY... multimethod mm => ('Base1', 'Base2') => sub { 1 }; multimethod mm => ('Base1', 'Der3') => sub { 2 }; multimethod mm => ('Base1', 'DerDer3') => sub { 3 }; multimethod mm => ('Der1', 'Base2') => sub { 4 }; multimethod mm => ('Base1', 'Base2', 'Base2') => sub { 11 }; multimethod mm => ('Base1', 'Der3', 'Der3') => sub { 12 }; # RESET EXPECTATIONS FOR EVERY POSSIBLE COMBINATION... my @type1 = qw{Base1 Der1 Der2 DerDer1 DerDer2}; my @type2 = qw{Base2 Der3 DerDer3 DerDer4}; foreach my $type1 ( @type1, @type2 ) { foreach my $type2 ( @type2, @type1 ) { $::expect{$type1}{$type2} = 0; } } # GIVEN THE ABOVE MULTIMETHODS, ONLY THESE TYPE COMBINATIONS SHOULD BE VIABLE... $::expect{Base1}{Base2} = 1; $::expect{Base1}{Der3} = 2; $::expect{Base1}{DerDer3} = 3; $::expect{Base1}{DerDer4} = 2; $::expect{Der1}{Base2} = 4; $::expect{Der1}{DerDer3} = 3; $::expect{Der2}{Base2} = 1; $::expect{Der2}{Der3} = 2; $::expect{Der2}{DerDer3} = 3; $::expect{Der2}{DerDer4} = 2; $::expect{DerDer1}{Base2} = 4; $::expect{DerDer1}{DerDer3} = 3; $::expect{DerDer2}{Base2} = 1; $::expect{DerDer2}{Der3} = 2; $::expect{DerDer2}{DerDer3} = 3; $::expect{DerDer2}{DerDer4} = 2; # LOOP AND TEST EVERY COMBINATION (3 TIMES)... $::n = 1; for my $rep (1..3) { foreach my $type1 ( @type1, @type2 ) { foreach my $type2 ( @type2, @type1 ) { $::n++; try($type1,$type2, $::expect{$type1}{$type2}) or print "not "; print "ok $::n\n" } } # ON THE LAST TIME THROUGH, ADD A NEW CASE THAT CHANGES SOME EXPECTATIONS... if ($rep == 2) { multimethod mm => ('Der2', 'DerDer4') => sub { 5 }; $::expect{Der2}{DerDer4} = 5; $::expect{DerDer2}{DerDer4} = 5; # mm(new DerDer2, new DerDer4); } } # TEST MULTIMETHODS ON NON-CLASS TYPES multimethod mm => ('Der2', 'ARRAY') => sub { 6 }; multimethod mm => ('Der2', 'Regexp') => sub { 7 }; multimethod mm => ('Der2', '#') => sub { 8 }; multimethod mm => ('Der2', '$') => sub { 9 }; $::n++; eval { mm(new Der2, [1,2,3]) == 6 } or print "\n$@\n" and print "not "; print "ok $::n\n"; $::n++; eval { mm(new Der2, qr/\w*/) == 7 } or print "\n$@\n" and print "not "; print "ok $::n\n"; $::n++; eval { mm(new Der2, 3) == 8 } or print "\n$@\n" and print "not "; print "ok $::n\n"; $::n++; eval { mm(new Der2, "three") == 9 } or print "\n$@\n" and print "not "; print "ok $::n\n"; $::n++; eval { mm(new Der2, "1a") == 9 } or print "\n$@\n" and print "not "; print "ok $::n\n"; $::n++; eval { mm(new Base1, new Base2, new Base2) == 11 } or print "\n$@\n" and print "not "; print "ok $::n\n"; $::n++; eval { mm(new DerDer1, new Der3, new Base2) == 11 } or print "\n$@\n" and print "not "; print "ok $::n\n"; $::n++; eval { mm(new Base1, new Der3, new Der3) == 12 } or print "\n$@\n" and print "not "; print "ok $::n\n"; $::n++; eval { mm(new Base1, new DerDer3, new DerDer3) == 12 } or print "\n$@\n" and print "not "; print "ok $::n\n"; # HERE'S THE SUBROUTINE THAT POWERS THE DOUBLE LOOP ABOVE sub try { # print "for: $_[0], $_[1]\n"; my $obj1 = eval "new $_[0]"; my $obj2 = eval "new $_[1]"; my $err = ''; my $res = 0; eval { $res = mm($obj1, $obj2) } or $err = $@; # print "\texpecting: $_[2], got: $res\n"; return $res == $_[2] || do {print "\n$err\n"; 0}; } # TRY "CROSS-PACKAGE" MULTIMETHODS... package elsewhere; use Class::Multimethods; multimethod 'mm'; $::n++; eval { mm(new Der2, 1) == 8 } or print "\n$@\n" and print "not "; print "ok $::n\n"; multimethod mm => ('Der2', 'HASH') => sub { 10 }; $::n++; eval { mm(new Der2, {a=>1}) == 10 } or print "\n$@\n" and print "not "; print "ok $::n\n"; # TEST ALTERNATE NAME INTRODUCING SYNTAX... package otherwhere; use Class::Multimethods 'mm'; $::n++; eval { mm(new Der2, 1) == 8 } or print "\n$@\n" and print "not "; print "ok $::n\n"; # TRY MULTIMETHODS AS CLASS METHODS... package OtherClass; use Class::Multimethods; multimethod new => ('$','#') => sub { bless { num=>$_[1] }, $_[0] }; multimethod new => ('$','$') => sub { bless { str=>$_[1] }, $_[0] }; multimethod set => ('OtherClass','#') => sub { $_[0]->{num} = $_[1] }; multimethod set => ('OtherClass','$') => sub { $_[0]->{str} = $_[1] }; sub hasvals { for (keys %{$_[1]}) { return undef unless $_[0]->{$_} eq $_[1]->{$_}; } return 1; return $_[0] } sub print { print "=====\n"; print "num: $_[0]->{num}\n" if $_[0]->{num}; print "str: $_[0]->{str}\n" if $_[0]->{str}; print "=====\n"; } package main; my $obj; $obj = new OtherClass (42); # $obj->print(); $::n++; $obj->hasvals({num=>42}) or print "not "; print "ok $::n\n"; $obj = new OtherClass ("cat"); # $obj->print(); $::n++; $obj->hasvals({str=>"cat"}) or print "not "; print "ok $::n\n"; $obj->set("dog"); # $obj->print(); $::n++; $obj->hasvals({str=>"dog"}) or print "not "; print "ok $::n\n"; $obj->set(99); # $obj->print(); $::n++; $obj->hasvals({num=>99, str=>"dog"}) or print "not "; print "ok $::n\n"; # TEST INHERITANCE OF MULTIMETHOD CLASS METHODS... package SonOfOtherClass; @SonOfOtherClass::ISA = qw(OtherClass); use Class::Multimethods; multimethod set => ('OtherClass','ARRAY') => sub { $_[0]->{nums} = $_[1] }; sub print { print "=========\n"; $_[0]->SUPER::print(); print "nums: ", join(',', @{$_[0]->{nums}}), "\n" if $_[0]->{nums}; print "=========\n"; } package main; $obj = new SonOfOtherClass (42); # $obj->print(); $::n++; $obj->hasvals({num=>42}) or print "not "; print "ok $::n\n"; $obj = new SonOfOtherClass ("cat"); # $obj->print(); $::n++; $obj->hasvals({str=>"cat"}) or print "not "; print "ok $::n\n"; $obj->set("dog"); # $obj->print(); $::n++; $obj->hasvals({str=>"dog"}) or print "not "; print "ok $::n\n"; $obj->set(99); # $obj->print(); $::n++; $obj->hasvals({num=>99, str=>"dog"}) or print "not "; print "ok $::n\n"; my $arr = [1,2,3,4,5]; $obj->set($arr); # $obj->print(); $::n++; $obj->hasvals({num=>99, str=>"dog", nums=>"$arr"}) or print "not "; print "ok $::n\n"; # TEST WILDCARDS... multimethod wild => ('Base1', 'Base2') => sub { 1 }; multimethod wild => ('Der1', 'Der3' ) => sub { 2 }; multimethod wild => ('Base1', '*' ) => sub { 3 }; multimethod wild => ('Base2', '*' ) => sub { 4 }; multimethod wild => ('*', 'Der3' ) => sub { 5 }; multimethod wild => ('*', '*' ) => sub { 6 }; # RESET EXPECTATIONS FOR EVERY POSSIBLE COMBINATION... # CONSEQUENCES OF $::expect{'*'}{'*'} = 6; foreach my $type1 ( @type1, @type2 ) { foreach my $type2 ( @type2, @type1 ) { $::expect{$type1}{$type2} = 6; } } # CONSEQUENCES OF $::expect{Base1}{Base2} = 1; foreach my $type1 ( @type1 ) { foreach my $type2 ( @type2 ) { $::expect{$type1}{$type2} = 1; } } # CONSEQUENCES OF $::expect{Der1}{Der3} = 2; foreach my $type1 (qw( Der1 DerDer1 )) { foreach my $type2 (qw( Der3 DerDer3 DerDer4 )) { $::expect{$type1}{$type2} = 2; } } # CONSEQUENCES OF $::expect{Base1}{'*'} = 3; foreach my $type1 ( @type1 ) { foreach my $type2 ( @type1, @type2 ) { $::expect{$type1}{$type2} = 3 if $::expect{$type1}{$type2} == 6 ; } } # CONSEQUENCES OF $::expect{Base2}{'*'} = 4; foreach my $type1 ( @type2 ) { foreach my $type2 ( @type1, @type2 ) { $::expect{$type1}{$type2} = 4 if $::expect{$type1}{$type2} == 6 ; } } # CONSEQUENCES OF $::expect{'*'}{Der3} = 5; foreach my $type1 ( @type1, @type2 ) { foreach my $type2 (qw( Der3 DerDer3 DerDer4 )) { $::expect{$type1}{$type2} = 5 if $::expect{$type1}{$type2} == 6; $::expect{$type1}{$type2} = 0 if $::expect{$type1}{$type2} == 3 || $::expect{$type1}{$type2} == 4; } } # CASES WHICH AREN'T AMBIGOUS, DESPITE THE PREVIOUS RULE $::expect{Base2}{DerDer3} = 4; # 0 -> #4, 1 -> #5 $::expect{Base2}{DerDer4} = 4; # 0 -> #4, 1 -> #5 $::expect{Der3}{Der3} = 5; # 0 -> #5, 1 -> #4 $::expect{DerDer3}{Der3} = 5; # 0 -> #5, 2 -> #4 $::expect{DerDer4}{Der3} = 5; # 0 -> #5, 2 -> #4 $::expect{DerDer3}{DerDer3} = 5; # 1 -> #5, 2 -> #4 $::expect{DerDer4}{DerDer3} = 5; # 1 -> #5, 2 -> #4 $::expect{DerDer3}{DerDer4} = 5; # 1 -> #5, 2 -> #4 $::expect{DerDer4}{DerDer4} = 5; # 1 -> #5, 2 -> #4 # LOOP AND TEST EVERY COMBINATION... foreach my $type1 ( @type1, @type2 ) { foreach my $type2 ( @type2, @type1 ) { $::n++; wildtry($type1,$type2, $::expect{$type1}{$type2}) or print "not "; print "ok $::n\n" } } sub wildtry { # print "for: $_[0], $_[1]\n"; my $obj1 = eval "new $_[0]"; my $obj2 = eval "new $_[1]"; my $err = ''; my $res = 0; eval { $res = wild($obj1, $obj2) } or $err = $@; # print "\texpecting: $_[2], got: $res\n"; return $res == $_[2] || do {print "\n$err\n"; 0}; } # TEST "INHERITANCE" OF '#' FROM '$' multimethod val => ('$', '$') => sub { return '$$'; }; multimethod val => ('$', '#') => sub { return '$#'; }; multimethod val => ('#', '#') => sub { return '##'; }; $::n++; val(1,2) eq '##' or print "not "; print "ok $::n\n"; $::n++; val('a',1) eq '$#' or print "not "; print "ok $::n\n"; $::n++; val('a','b') eq '$$' or print "not "; print "ok $::n\n"; $::n++; val(1,'a') eq '$$' or print "not "; print "ok $::n\n";