This patch lets Test::More compare circular data structures.

Get the latest version (0.47) of Test::More, apply fixes.patch to fix some 
issues, then apply circular.patch. Test with circular.t,

F

-- 
Do you need someone with lots of Unix sysadmin and/or lots of OO software 
development experience? Go on, giz a job.
My CV - http://www.fergaldaly.com/cv.html
--- More.pm.orig	2002-08-26 17:20:41.000000000 +0100
+++ More.pm	2003-03-04 02:03:29.000000000 +0000
@@ -25,7 +25,7 @@
              cmp_ok
              skip todo todo_skip
              pass fail
-             eq_array eq_hash eq_set
+             eq_array eq_hash eq_set eq_deeply
              $TODO
              plan
              can_ok  isa_ok
@@ -937,7 +937,7 @@
     my($this, $that, $name) = @_;
 
     my $ok;
-    if( !ref $this || !ref $that ) {
+    if( !ref $this && !ref $that ) {
         $ok = $Test->is_eq($this, $that, $name);
     }
     else {
@@ -984,8 +984,9 @@
     foreach my $idx (0..$#vals) {
         my $val = $vals[$idx];
         $vals[$idx] = !defined $val ? 'undef' : 
-                      $val eq $DNE  ? "Does not exist"
-                                    : "'$val'";
+                                    ref $val ? $val eq $DNE  ? "Does not exist"
+                                                             : $val
+                                             : "'$val'"
     }
 
     $out .= "$vars[0] = $vals[0]\n";
@@ -995,6 +996,12 @@
     return $out;
 }
 
+sub eq_deeply {
+    my ($a1, $a2) = @_;
+
+    local @Data_Stack = ();
+    return _deep_check($a1, $a2);
+}
 
 =item B<eq_array>
 
@@ -1006,7 +1013,14 @@
 =cut
 
 #'#
-sub eq_array  {
+
+sub eq_array {
+    my ($a1, $a2) = @_;
+
+    return UNIVERSAL::isa($a2, "ARRAY") ? eq_deeply($a1, $a2) : 0;
+}
+
+sub _eq_array  {
     my($a1, $a2) = @_;
     return 1 if $a1 eq $a2;
 
@@ -1034,19 +1048,24 @@
         # Quiet uninitialized value warnings when comparing undefs.
         local $^W = 0; 
 
-        if( $e1 eq $e2 ) {
+        if( ! (ref $e1 xor ref $e2) and $e1 eq $e2 ) {
             $ok = 1;
         }
         else {
-            if( UNIVERSAL::isa($e1, 'ARRAY') and
+            if ( (ref $e1 and $e1 eq $DNE) or
+            	(ref $e2 and $e2 eq $DNE) )
+            {
+            	$ok = 0;
+            }
+            elsif( UNIVERSAL::isa($e1, 'ARRAY') and
                 UNIVERSAL::isa($e2, 'ARRAY') )
             {
-                $ok = eq_array($e1, $e2);
+                $ok = _eq_array($e1, $e2);
             }
             elsif( UNIVERSAL::isa($e1, 'HASH') and
                    UNIVERSAL::isa($e2, 'HASH') )
             {
-                $ok = eq_hash($e1, $e2);
+                $ok = _eq_hash($e1, $e2);
             }
             elsif( UNIVERSAL::isa($e1, 'REF') and
                    UNIVERSAL::isa($e2, 'REF') )
@@ -1060,6 +1079,7 @@
             {
                 push @Data_Stack, { type => 'REF', vals => [$e1, $e2] };
                 $ok = _deep_check($$e1, $$e2);
+                pop @Data_Stack if $ok;
             }
             else {
                 push @Data_Stack, { vals => [$e1, $e2] };
@@ -1082,6 +1102,12 @@
 =cut
 
 sub eq_hash {
+    my ($a1, $a2) = @_;
+
+    return UNIVERSAL::isa($a2, "HASH") ? eq_deeply($a1, $a2) : 0;
+}
+
+sub _eq_hash {
     my($a1, $a2) = @_;
     return 1 if $a1 eq $a2;
 
@@ -1156,6 +1182,10 @@
     return Test::Builder->new;
 }
 
+sub set_builder {
+    $Test = shift;
+}
+    
 =back
 
 
--- More.pm.dne	2003-03-04 02:03:29.000000000 +0000
+++ More.pm	2003-03-04 02:14:01.000000000 +0000
@@ -931,7 +931,7 @@
 
 =cut
 
-use vars qw(@Data_Stack);
+use vars qw(@Data_Stack %Compared );
 my $DNE = bless [], 'Does::Not::Exist';
 sub is_deeply {
     my($this, $that, $name) = @_;
@@ -942,6 +942,7 @@
     }
     else {
         local @Data_Stack = ();
+        local %Compared = ();
         if( _deep_check($this, $that) ) {
             $ok = $Test->ok(1, $name);
         }
@@ -1000,6 +1001,7 @@
     my ($a1, $a2) = @_;
 
     local @Data_Stack = ();
+    local %Compared = ();
     return _deep_check($a1, $a2);
 }
 
@@ -1040,6 +1042,16 @@
 }
 
 sub _deep_check {
+
+    # This handles circular references by remembering which structures we
+    # are already in the process of comapring so that if we need to compare
+    # them again after descending further into the structures, we know there
+    # is no point. We just assume they are equal and look for differences
+    # elsewhere. 
+
+    # We also gain a speed boost from this by remembering the results of
+    # earlier deep comparions
+    
     my($e1, $e2) = @_;
     my $ok = 0;
 
@@ -1052,38 +1064,58 @@
             $ok = 1;
         }
         else {
-            if ( (ref $e1 and $e1 eq $DNE) or
-            	(ref $e2 and $e2 eq $DNE) )
-            {
-            	$ok = 0;
-            }
-            elsif( UNIVERSAL::isa($e1, 'ARRAY') and
-                UNIVERSAL::isa($e2, 'ARRAY') )
-            {
-                $ok = _eq_array($e1, $e2);
-            }
-            elsif( UNIVERSAL::isa($e1, 'HASH') and
-                   UNIVERSAL::isa($e2, 'HASH') )
-            {
-                $ok = _eq_hash($e1, $e2);
-            }
-            elsif( UNIVERSAL::isa($e1, 'REF') and
-                   UNIVERSAL::isa($e2, 'REF') )
-            {
-                push @Data_Stack, { type => 'REF', vals => [$e1, $e2] };
-                $ok = _deep_check($$e1, $$e2);
-                pop @Data_Stack if $ok;
-            }
-            elsif( UNIVERSAL::isa($e1, 'SCALAR') and
-                   UNIVERSAL::isa($e2, 'SCALAR') )
-            {
-                push @Data_Stack, { type => 'REF', vals => [$e1, $e2] };
-                $ok = _deep_check($$e1, $$e2);
-                pop @Data_Stack if $ok;
+            if ( ref $e1 and ref $e2 and $Compared{$e1}->{$e2} ) {
+                # we've either:
+
+                # - compared these two before, in which case we know they're
+                # equal already or
+
+                # - we're in the process of comparing them and we've bumped
+                # into them again because one of them is a circular data
+                # structure, in which case we assume they're equal and go
+                # look for differences elsewhere
+
+                $ok = 1;
             }
             else {
-                push @Data_Stack, { vals => [$e1, $e2] };
-                $ok = 0;
+            	if ( ref $e1 and ref $e2 ) {
+                    $Compared{$e1}->{$e2} = 1;
+                    $Compared{$e2}->{$e1} = 1;
+            	}
+
+                if ( (ref $e1 and $e1 eq $DNE) or
+                	(ref $e2 and $e2 eq $DNE) )
+                {
+                	$ok = 0;
+                }
+                elsif( UNIVERSAL::isa($e1, 'ARRAY') and
+                    UNIVERSAL::isa($e2, 'ARRAY') )
+                {
+                    $ok = _eq_array($e1, $e2);
+                }
+                elsif( UNIVERSAL::isa($e1, 'HASH') and
+                       UNIVERSAL::isa($e2, 'HASH') )
+                {
+                    $ok = _eq_hash($e1, $e2);
+                }
+                elsif( UNIVERSAL::isa($e1, 'REF') and
+                       UNIVERSAL::isa($e2, 'REF') )
+                {
+                    push @Data_Stack, { type => 'REF', vals => [$e1, $e2] };
+                    $ok = _deep_check($$e1, $$e2);
+                    pop @Data_Stack if $ok;
+                }
+                elsif( UNIVERSAL::isa($e1, 'SCALAR') and
+                       UNIVERSAL::isa($e2, 'SCALAR') )
+                {
+                    push @Data_Stack, { type => 'REF', vals => [$e1, $e2] };
+                    $ok = _deep_check($$e1, $$e2);
+                    pop @Data_Stack if $ok;
+                }
+                else {
+                    push @Data_Stack, { vals => [$e1, $e2] };
+                    $ok = 0;
+                }
             }
         }
     }
# -*-perl-*-
use strict;
use Test::More (tests => 10);

if(1)
{
	my $a1 = gen_layers(2);
	my $a2 = gen_layers(2);

	is_deeply($a1, $a2, "2 layers");
	push(@$a1, "break");
	ok(! eq_deeply($a1, $a2), "2 layers broken");
	push(@$a2, "break");
	is_deeply($a1, $a2, "2 layers fixed");
}

if(1)
{
	my $a1 = gen_layers(2);
	my $a2 = gen_layers(3);

	is_deeply($a1, $a2, "2 and 3 layers");
	push(@$a1, "break");
	ok(! eq_deeply($a1, $a2), "2 and 3 layers broken");
	push(@$a2, "break");
	ok(! eq_deeply($a1, $a2), "2 and 3 layers not fixed");
}

if(1)
{
	my $a1 = gen_interleave();
	my $a2 = gen_interleave();

	is_deeply($a1, $a2, "interleave");
}

sub gen_layers
{
	my $num = shift;

	my $first = ['text', gen_circle()];
	$num--;
	my $last = $first;
	while ($num--)
	{
		my $next = ['text', gen_circle()];
		push(@$last, $next);
		$last = $next;
	}

	push(@$last, $first);
	return $first
}

sub gen_circle
{
	my $a = ['circle'];
	push(@$a, $a);
	return $a
}

sub gen_interleave
{
	my $a = [];
	my $b = [];
	
	push(@$a, $b, $a);
	push(@$b, $a, $b);
	
	return $a;
}

Reply via email to