File Coverage

inc/Test/Deep.pm
Criterion Covered Total %
statement 78 173 45.0
branch 21 70 30.0
condition 12 29 41.3
subroutine 16 36 44.4
pod 17 25 68.0
total 144 333 43.2


line stmt bran cond sub pod time code
1 2     2   1612 #line 1
  2         6  
  2         66  
2 2     2   12 use strict;
  2         4  
  2         72  
3             use warnings;
4              
5 2     2   10 package Test::Deep;
  2         3  
  2         129  
6             use Carp qw( confess );
7 2     2   1137  
  2         6  
  2         47  
8 2     2   1018 use Test::Deep::Cache;
  2         7  
  2         48  
9 2     2   920 use Test::Deep::Stack;
  2         5  
  2         52  
10             use Test::Deep::RegexpVersion;
11              
12 2     2   11 require overload;
  2         3  
  2         130  
13             use Scalar::Util;
14              
15             my $Test;
16             unless (defined $Test::Deep::NoTest::NoTest)
17             {
18             # for people who want eq_deeply but not Test::Builder
19             require Test::Builder;
20             $Test = Test::Builder->new;
21             }
22 2     2   10  
  2         5  
  2         129  
23             use Data::Dumper qw(Dumper);
24 2         819  
25             use vars qw(
26             $VERSION @EXPORT @EXPORT_OK @ISA
27             $Stack %Compared $CompareCache %WrapCache
28 2     2   11 $Snobby $Expects $DNE $DNE_ADDR $Shallow
  2         2  
29             );
30              
31             $VERSION = '0.106';
32             $VERSION = eval $VERSION;
33              
34             require Exporter;
35             @ISA = qw( Exporter );
36              
37             @EXPORT = qw( eq_deeply cmp_deeply cmp_set cmp_bag cmp_methods
38             useclass noclass set bag subbagof superbagof subsetof
39             supersetof superhashof subhashof
40             );
41             # plus all the ones generated from %constructors below
42              
43             @EXPORT_OK = qw( descend render_stack class_base cmp_details deep_diag );
44              
45             $Snobby = 1; # should we compare classes?
46             $Expects = 0; # are we comparing got vs expect or expect vs expect
47              
48             $DNE = \"";
49             $DNE_ADDR = Scalar::Util::refaddr($DNE);
50              
51             # if no sub name is supplied then we use the package name in lower case
52             my %constructors = (
53             Number => "num",
54             Methods => "",
55             ListMethods => "",
56             String => "str",
57             Boolean => "bool",
58             ScalarRef => "scalref",
59             ScalarRefOnly => "",
60             Array => "",
61             ArrayEach => "array_each",
62             ArrayElementsOnly => "",
63             Hash => "",
64             HashEach => "hash_each",
65             Regexp => "re",
66             RegexpMatches => "",
67             RegexpOnly => "",
68             RegexpRef => "",
69             Ignore => "",
70             Shallow => "",
71             Any => "",
72             All => "",
73             Isa => "Isa",
74             RegexpRefOnly => "",
75             RefType => "",
76             Blessed => "",
77             ArrayLength => "",
78             ArrayLengthOnly => "",
79             HashKeys => "",
80             HashKeysOnly => "",
81             Code => "",
82             );
83              
84             while (my ($pkg, $name) = each %constructors)
85             {
86             $name = lc($pkg) unless $name;
87             my $full_pkg = "Test::Deep::$pkg";
88             my $file = "$full_pkg.pm";
89             $file =~ s#::#/#g;
90 482     482   12662 my $sub = sub {
91 482         1717 require $file;
92             return $full_pkg->new(@_);
93             };
94 2     2   12 {
  2         3  
  2         3535  
95             no strict 'refs';
96             *{$name} = $sub;
97             }
98             push(@EXPORT, $name);
99             }
100             my %count;
101             foreach my $e (@EXPORT)
102             {
103             $count{$e}++;
104             }
105              
106             # this is ugly, I should never have exported a sub called isa now I
107             # have to try figure out if the recipient wanted my isa or if a class
108             # imported us and UNIVERSAL::isa is being called on that class.
109             # Luckily our isa always expects 1 argument and U::isa always expects
110             # 2, so we can figure out (assuming the caller is no buggy).
111             sub isa
112 0 0   0 1 0 {
113             if (@_ == 1)
114 0         0 {
115             goto &Isa;
116             }
117             else
118 0         0 {
119             goto &UNIVERSAL::isa;
120             }
121             }
122              
123             push(@EXPORT, "isa");
124              
125             sub cmp_deeply
126 47     47 1 74 {
127             my ($d1, $d2, $name) = @_;
128 47         116  
129             my ($ok, $stack) = cmp_details($d1, $d2);
130 47 50       242  
131             if (not $Test->ok($ok, $name))
132 0         0 {
133 0         0 my $diag = deep_diag($stack);
134             $Test->diag($diag);
135             }
136 47         856  
137             return $ok;
138             }
139              
140             sub cmp_details
141 47     47 1 76 {
142             my ($d1, $d2) = @_;
143 47         214  
144 47         211 local $Stack = Test::Deep::Stack->new;
145 47         77 local $CompareCache = Test::Deep::Cache->new;
146             local %WrapCache;
147 47         104  
148             my $ok = descend($d1, $d2);
149 47         748  
150             return ($ok, $Stack);
151             }
152              
153             sub eq_deeply
154 0     0 1 0 {
155             my ($d1, $d2) = @_;
156 0         0  
157             my ($ok) = cmp_details($d1, $d2);
158 0         0  
159             return $ok
160             }
161              
162             sub eq_deeply_cache
163             {
164             # this is like cross between eq_deeply and descend(). It doesn't start
165             # with a new $CompareCache but if the comparison fails it will leave
166             # $CompareCache as if nothing happened. However, if the comparison
167             # succeeds then $CompareCache retains all the new information
168              
169             # this allows Set and Bag to handle circular refs
170 0     0 0 0  
171             my ($d1, $d2, $name) = @_;
172 0         0  
173 0         0 local $Stack = Test::Deep::Stack->new;
174             $CompareCache->local;
175 0         0  
176             my $ok = descend($d1, $d2);
177 0         0  
178             $CompareCache->finish($ok);
179 0         0  
180             return $ok;
181             }
182              
183             sub deep_diag
184 0     0 1 0 {
185             my $stack = shift;
186             # ick! incArrow and other things expect the stack has to be visible
187 0         0 # in a well known place . TODO clean this up
188             local $Stack = $stack;
189 0         0  
190             my $where = render_stack('$data', $stack);
191 0 0       0  
192 0         0 confess "No stack to diagnose" unless $stack;
193             my $last = $stack->getLast;
194 0         0  
195             my $diag;
196 0         0 my $message;
197 0         0 my $got;
198             my $expected;
199 0         0  
200 0 0       0 my $exp = $last->{exp};
201             if (ref $exp)
202 0 0       0 {
203             if ($exp->can("diagnostics"))
204 0         0 {
205 0         0 $diag = $exp->diagnostics($where, $last);
206             $diag =~ s/\n+$/\n/;
207             }
208             else
209 0 0       0 {
210             if ($exp->can("diag_message"))
211 0         0 {
212             $message = $exp->diag_message($where);
213             }
214             }
215             }
216 0 0       0  
217             if (not defined $diag)
218 0 0       0 {
219 0 0       0 $got = $exp->renderGot($last->{got}) unless defined $got;
220 0 0       0 $expected = $exp->renderExp unless defined $expected;
221             $message = "Compared $where" unless defined $message;
222 0         0  
223             $diag = <
224             $message
225             got : $got
226             expect : $expected
227             EOM
228             }
229 0         0  
230             return $diag;
231             }
232              
233             sub render_val
234             {
235 0     0 0 0 # add in Data::Dumper stuff
236             my $val = shift;
237 0         0  
238 0 0       0 my $rendered;
239             if (defined $val)
240 0 0       0 {
    0          
241             $rendered = ref($val) ?
242             (Scalar::Util::refaddr($val) eq $DNE_ADDR ?
243             "Does not exist" :
244             overload::StrVal($val)
245             ) :
246             qq('$val');
247             }
248             else
249 0         0 {
250             $rendered = "undef";
251             }
252 0         0  
253             return $rendered;
254             }
255              
256             sub descend
257 614     614 0 821 {
258             my ($d1, $d2) = @_;
259 614 50 66     3714  
      66        
260             if (! $Expects and ref($d1) and UNIVERSAL::isa($d1, "Test::Deep::Cmp"))
261 0         0 {
262 0         0 my $where = $Stack->render('$data');
263             confess "Found a special comparison in $where\nYou can only the specials in the expects structure";
264             }
265 614 100 66     2030  
266             if (ref $d1 and ref $d2)
267             {
268             # this check is only done when we're comparing 2 expecteds against each
269             # other
270 378 50 33     808  
271             if ($Expects and UNIVERSAL::isa($d1, "Test::Deep::Cmp"))
272             {
273 0 0       0 # check they are the same class
274 0 0       0 return 0 unless Test::Deep::blessed(Scalar::Util::blessed($d2))->descend($d1);
275             if ($d1->can("compare"))
276 0         0 {
277             return $d1->compare($d2);
278             }
279             }
280 378         755  
281 378         538 my $s1 = Scalar::Util::refaddr($d1);
282             my $s2 = Scalar::Util::refaddr($d2);
283 378 50       1055  
284             if ($s1 eq $s2)
285 0         0 {
286             return 1;
287 378 50       975 }
288             if ($CompareCache->cmp($d1, $d2))
289             {
290             # we've tried comparing these already so either they turned out to
291             # be the same or we must be in a loop and we have to assume they're
292             # the same
293 0         0  
294             return 1;
295             }
296             else
297 378         1053 {
298             $CompareCache->add($d1, $d2)
299             }
300             }
301 614         1455  
302             $d2 = wrap($d2);
303 614         2758  
304             $Stack->push({exp => $d2, got => $d1});
305 614 50 66     2447  
306             if (ref($d1) and (Scalar::Util::refaddr($d1) == $DNE_ADDR))
307             {
308             # whatever it was suposed to be, it didn't exist and so it's an
309 0         0 # automatic fail
310             return 0;
311             }
312 614 50       1854  
313             if ($d2->descend($d1))
314             {
315 614         1600 # print "d1 = $d1, d2 = $d2\nok\n";
316             $Stack->pop;
317 614         2469  
318             return 1;
319             }
320             else
321             {
322 0         0 # print "d1 = $d1, d2 = $d2\nnot ok\n";
323             return 0;
324             }
325             }
326              
327             sub wrap
328 614     614 0 635 {
329             my $data = shift;
330 614 100 100     3349  
331             return $data if ref($data) and UNIVERSAL::isa($data, "Test::Deep::Cmp");
332 173         325  
333             my ($class, $base) = class_base($data);
334 173         208  
335             my $cmp;
336 173 100       298  
337             if($base eq '')
338 110         174 {
339             $cmp = shallow($data);
340             }
341             else
342 63         124 {
343             my $addr = Scalar::Util::refaddr($data);
344 63 50       155  
345             return $WrapCache{$addr} if $WrapCache{$addr};
346 63 100 0     176
    50 0        
    0          
    0          
347             if($base eq 'ARRAY')
348 19         62 {
349             $cmp = array($data);
350             }
351             elsif($base eq 'HASH')
352 44         85 {
353             $cmp = hash($data);
354             }
355             elsif($base eq 'SCALAR' or $base eq 'REF')
356 0         0 {
357             $cmp = scalref($data);
358             }
359             elsif(($base eq 'Regexp') or ($base eq 'REGEXP'))
360 0         0 {
361             $cmp = regexpref($data);
362             }
363             else
364 0         0 {
365             $cmp = shallow($data);
366             }
367 63         169  
368             $WrapCache{$addr} = $cmp;
369 173         281 }
370             return $cmp;
371             }
372              
373             sub class_base
374 173     173 0 200 {
375             my $val = shift;
376 173 100       281  
377             if (ref $val)
378 63         135 {
379 63 50       123 my $blessed = Scalar::Util::blessed($val);
380 63         139 $blessed = defined($blessed) ? $blessed : "";
381             my $reftype = Scalar::Util::reftype($val);
382              
383 63 50       128  
384 0 0 0     0 if ($Test::Deep::RegexpVersion::OldStyle) {
385             if ($blessed eq "Regexp" and $reftype eq "SCALAR")
386 0         0 {
387             $reftype = "Regexp"
388             }
389 63         156 }
390             return ($blessed, $reftype);
391             }
392             else
393 110         286 {
394             return ("", "");
395             }
396             }
397              
398             sub render_stack
399 0     0 0   {
400             my ($var, $stack) = @_;
401 0            
402             return $stack->render($var);
403             }
404              
405             sub cmp_methods
406 0     0 1   {
407 0           local $Test::Builder::Level = $Test::Builder::Level + 1;
  0            
408             return cmp_deeply(shift, methods(@{shift()}), shift);
409             }
410              
411             sub requireclass
412 0     0 0   {
413             require Test::Deep::Class;
414 0            
415             my $val = shift;
416 0            
417             return Test::Deep::Class->new(1, $val);
418             }
419              
420             # docs and export say this is call useclass, doh!
421              
422             *useclass = \&requireclass;
423              
424             sub noclass
425 0     0 1   {
426             require Test::Deep::Class;
427 0            
428             my $val = shift;
429 0            
430             return Test::Deep::Class->new(0, $val);
431             }
432              
433             sub set
434 0     0 1   {
435             require Test::Deep::Set;
436 0            
437             return Test::Deep::Set->new(1, "", @_);
438             }
439              
440             sub supersetof
441 0     0 1   {
442             require Test::Deep::Set;
443 0            
444             return Test::Deep::Set->new(1, "sup", @_);
445             }
446              
447             sub subsetof
448 0     0 1   {
449             require Test::Deep::Set;
450 0            
451             return Test::Deep::Set->new(1, "sub", @_);
452             }
453              
454             sub cmp_set
455 0     0 1   {
456 0           local $Test::Builder::Level = $Test::Builder::Level + 1;
  0            
457             return cmp_deeply(shift, set(@{shift()}), shift);
458             }
459              
460             sub bag
461 0     0 1   {
462             require Test::Deep::Set;
463 0            
464             return Test::Deep::Set->new(0, "", @_);
465             }
466              
467             sub superbagof
468 0     0 1   {
469             require Test::Deep::Set;
470 0            
471             return Test::Deep::Set->new(0, "sup", @_);
472             }
473              
474             sub subbagof
475 0     0 1   {
476             require Test::Deep::Set;
477 0            
478             return Test::Deep::Set->new(0, "sub", @_);
479             }
480              
481             sub cmp_bag
482 0     0 1   {
483 0   0       local $Test::Builder::Level = $Test::Builder::Level + 1;
484 0 0         my $ref = ref($_[1]) || "";
485             confess "Argument 2 to cmp_bag is not an ARRAY ref (".render_val($_[1]).")"
486 0           unless $ref eq "ARRAY";
  0            
487             return cmp_deeply(shift, bag(@{shift()}), shift);
488             }
489              
490             sub superhashof
491 0     0 1   {
492             require Test::Deep::Hash;
493 0            
494             my $val = shift;
495 0            
496             return Test::Deep::SuperHash->new($val);
497             }
498              
499             sub subhashof
500 0     0 1   {
501             require Test::Deep::Hash;
502 0            
503             my $val = shift;
504 0            
505             return Test::Deep::SubHash->new($val);
506             }
507              
508             sub builder
509 0 0   0 0   {
510             if (@_)
511 0           {
512             $Test = shift;
513 0           }
514             return $Test;
515             }
516              
517             1;
518