File Coverage

blib/lib/Test/Struct.pm
Criterion Covered Total %
statement 97 138 70.2
branch 23 66 34.8
condition 16 37 43.2
subroutine 18 22 81.8
pod 1 3 33.3
total 155 266 58.2


line stmt bran cond sub pod time code
1             package Test::Struct;
2 1     1   34413 use strict;
  1         2  
  1         38  
3 1     1   5 use warnings;
  1         2  
  1         48  
4             require overload;
5             require Exporter;
6 1     1   5 use vars qw(@ISA @EXPORT_OK @EXPORT $VERSION);
  1         6  
  1         94  
7 1     1   5 use Scalar::Util qw(refaddr reftype blessed isweak readonly tainted);
  1         2  
  1         148  
8 1     1   6 use List::Util qw(max min);
  1         1  
  1         105  
9 1     1   1006 use Data::Dumper;
  1         13723  
  1         73  
10 1     1   9 use Test::Builder;
  1         1  
  1         22  
11 1     1   1045 use Test::More;
  1         7593  
  1         9  
12            
13             my $Test = Test::Builder->new;
14             @ISA = qw(Exporter);
15             @EXPORT_OK = ('deep_eq');
16             @EXPORT = ('deep_eq');
17            
18             $VERSION = '0.01';
19            
20             BEGIN {
21             # no-op regex if we can't find DDS
22 1 50   1 0 421 eval "sub regex { return }"
  1     1   2739  
  0     2   0  
  0         0  
  2         5  
23             unless eval "use Data::Dump::Streamer qw(regex); 1"
24             }
25              
26             sub import {
27 1     1   9 my($self) = shift;
28 1         3 my $pack = caller;
29            
30 1         2 my (@plan,@import);
31 1         2 my $i=0;
32 1         6 while ($i<@_) {
33 0         0 my $arg=$_[$i++];
34 0 0       0 if ($arg=~/^(no_plan|skip_all)$/) {
    0          
    0          
35 0         0 push @plan,$arg;
36             } elsif($arg eq 'tests') {
37 0         0 push @plan,$arg,$_[$i++];
38             } elsif($arg eq 'import') {
39 0         0 push @import,@{$_[$i++]};
  0         0  
40             } else {
41 0         0 push @import,$arg;
42             }
43             }
44 1         19 $Test->exported_to($pack);
45 1         10 $Test->plan(@plan);
46 1         110 $self->export_to_level(1, $self,@import);
47             }
48            
49             # private utility subs
50             sub _qquote {
51 2 50   2   11 return defined $_[0] ? Data::Dumper::qquote($_[0]) : 'undef';
52             }
53            
54             sub _safe {
55 0 0   0   0 return defined $_[0] ? $_[0] : 'undef';
56             }
57            
58             sub _msg {
59 0     0   0 my $msg=shift;
60 0         0 my $noqquote;
61 0 0       0 if (@_>2) { $noqquote=pop; }
  0         0  
62 0 0       0 return "$msg: ".
63             join " ne ",
64 0         0 map { $noqquote ? _safe($_)
65             : _qquote($_) } @_
66             }
67 0     0   0 sub _a { my $str=shift; $str=~s/\$o/\$got/g; $str }
  0         0  
  0         0  
68 0     0   0 sub _b { my $str=shift; $str=~s/\$o/\$expected/g; $str }
  0         0  
  0         0  
69            
70             sub _subscr {
71 1     1   13 my ($v,$script)=@_;
72 1 50 33     11 if ($v=~/^\$\{.*\}$/ or $v=~/\w$/) {
73 0         0 $v.="->".$script;
74             } else {
75 1         2 $v.=$script
76             }
77 1         15 return $v
78             }
79            
80            
81            
82             sub _bool_ne {
83 26     26   85 my ($t1,$t2,$name,$n,$error)=@_;
84 26         32 my $nok = !$t1 != !$t2;
85 26 50       48 if ($nok) {
86 0 0       0 push @$error,"at "._a($n)
87             .($t1 ? ' not expecting ' : ' expecting ')
88             ."$name.";
89             }
90 26         48 return $nok;
91             }
92            
93             sub _ne {
94 6     6   11 my ($t1,$t2,$name,$n,$error)=@_;
95 6         9 my $nok = $t1 ne $t2;
96 6 50       12 if ($nok) {
97 0         0 push @$error,"at "._a($n)
98             ." expecting $name "._qquote($t2)
99             ." but got "._qquote($t1).".";
100             }
101 6         14 return $nok;
102             }
103            
104            
105             # main worker sub
106             sub deep_ne_list {
107 4     4 0 8 my ($o1,$o2,$n,$state)=@_;
108 4   100     14 $state||={};
109 4   100     12 $n||='$o';
110            
111 4         5 my @error;
112            
113             # make sure they are both defined
114 4 50       11 _bool_ne(defined($o1),defined($o2),"defined value",$n,\@error)
115             and return @error;
116            
117 4 50       12 return if !defined($o1); # return if they are undef
118            
119 4         12 my $ra1=refaddr \$_[0];
120 4         6 my $ra2=refaddr \$_[1];
121            
122 4 0 33     19 return if $state->{sv_seen1}{$ra1} &&
      33        
123             $state->{sv_seen2}{$ra2} &&
124             $state->{sv_seen1}{$ra1} eq
125             $state->{sv_seen2}{$ra2};
126            
127 4   33     20 my $t1=($state->{sv_seen1}{$ra1}||=$n);
128 4   33     20 my $t2=($state->{sv_seen2}{$ra2}||=$n);
129            
130            
131 4 50       9 if ($t1 ne $t2) {
132 0 0       0 if ($t1 eq $n) {
    0          
133 0         0 return "expected to have seen "._a($n)
134             ." before at "._a($t2).".";
135             } elsif ($t2 eq $n) {
136 0         0 return "not expected to have seen "._a($n)
137             ." before at "._a($t1).".";
138             } else {
139 0         0 return "expected to have seen "._a($n)
140             ." before at ".a_($t2)
141             ." but saw it in "._a($t1)
142             ." instead.";
143             }
144             }
145            
146            
147 4         19 _bool_ne(readonly($_[0]),readonly($_[1]),"a readonly value",$n,\@error);
148 4         21 _bool_ne(tainted($_[0]),tainted($_[1]),"a tainted value",$n,\@error);
149 4 50       14 _bool_ne(!!ref($o1),!!ref($o2),"value isa reference",$n,\@error)
150             and return @error;
151            
152            
153 4 50       28 if (!ref($o1)) {
154 0         0 _ne($o1,$o2,"value of",$n,\@error);
155 0         0 return @error;
156             }
157            
158             #################################################################
159             # We are dealing with a ref.
160 4         10 $ra1=refaddr($o1);
161 4         7 $ra2=refaddr($o2);
162 4 0 33     13 return if $state->{rv_seen1}{$ra1} &&
      33        
163             $state->{rv_seen2}{$ra2} &&
164             $state->{rv_seen1}{$ra1} eq
165             $state->{rv_seen2}{$ra2};
166            
167 4   33     21 $t1=($state->{rv_seen1}{$ra1}||=$n);
168 4   33     35 $t2=($state->{rv_seen2}{$ra2}||=$n);
169            
170 4 50       31 if ($t1 ne $t2) {
171 0 0       0 if ($t1 eq $n) {
    0          
172 0         0 return "expected to have seen reference in "._a($n)
173             ." before at "._a($t2).".";
174             } elsif ($t2 eq $n) {
175 0         0 return "not expected to have seen reference in "._a($n)
176             ." before at "._a($t1).".";
177             } else {
178 0         0 return "expected to have seen reference in "._a($n)
179             ." before at ".a_($t2)." but saw it in "._a($t1)
180             ." instead."
181             }
182             }
183            
184            
185 4         8 $t1=blessed($o1);
186 4         8 $t2=blessed($o2);
187            
188 4         9 _bool_ne(defined($t1),defined($t2),"a blessed ref",$n,\@error);
189 4 100       11 _ne($t1,$t2," object of class ",$n,\@error)
190             if defined $t1;
191 4         16 _bool_ne(isweak($_[0]),isweak($_[1]),"weak ref",$n,\@error);
192            
193 4         13 my $rt=reftype($o1);
194 4         8 $t2=reftype($o2);
195            
196             # Can't procede further if they are different reftypes
197             # No point in comparing arrays to hashes.
198 4 50       9 _ne($rt,$t2,"reftype",$n,\@error)
199             and return @error;
200            
201            
202 4 100 66     29 if ($rt eq 'ARRAY') {
    100          
    100          
203 1         17 my $min=min(0+@$o1,0+@$o2);
204 1         5 _ne(0+@$o1,0+@$o2,"element count of","\@{".$n."}",\@error);
205 1         4 for my $idx (0..$min-1) {
206 1         18 push @error, deep_ne_list($o1->[$idx],$o2->[$idx],
207             $n."->[$idx]",$state);
208             }
209             } elsif ($rt eq 'HASH') {
210             # Its a hash. get a list of all the keys in both
211             # hashes, and then cycle through them checking
212             # for equivelence.
213 1         2 my %dupe;
214 1         15 my @all=grep !$dupe{$_}++,keys %$o1,keys %$o2;
215 1         3 foreach my $key (@all) {
216            
217 1 50       5 _bool_ne(exists($o1->{$key}),exists($o2->{$key}),
218             "key "._qquote($key),"%{".$n."}",\@error)
219             and next;
220 1         5 push @error,deep_ne_list($o1->{$key},$o2->{$key},
221             _subscr($n,"{"._qquote($key)."}"),$state);
222             }
223             } elsif ($rt eq 'REF' or $rt eq 'SCALAR') {
224 1         36 $t1=regex($_[0]);
225 1         26 $t2=regex($_[1]);
226 1         10 _bool_ne($t1,$t2,"regex",$n,\@error);
227 1 50 33     6 if($t1 && $t2) {
228 0         0 _ne($t1,$t2,"pattern",$n,\@error);
229             } else {
230 1         9 push @error, deep_ne_list($$o1,$$o2,"\${".$n."}",$state);
231             }
232             } else {
233 1         178 die "Whoah nelly! This is just a prototype. Can't handle reftype '$rt'";
234             }
235 0         0 return @error;
236             }
237            
238             sub deep_eq($$;$) {
239 1     1 1 224 my $name;
240             # strip off the name if there is one
241 1 50       4 $name = pop @_ if @_==3;
242 1         6 my @errors=deep_ne_list(@_);
243 0           local $Test::Builder::Level=2;
244 0           ok(!@errors,$name);
245 0 0         diag(join "\n",@errors) if @errors;
246 0           return !@errors;
247             }
248            
249            
250             1;
251             __END__