File Coverage

inc/Test/Deep.pm
Criterion Covered Total %
statement 79 177 44.6
branch 23 78 29.4
condition 14 35 40.0
subroutine 16 36 44.4
pod 17 25 68.0
total 149 351 42.4


line stmt bran cond sub pod time code
1 13     13   338599 #line 1
  13         32  
  13         472  
2 13     13   64 use strict;
  13         26  
  13         553  
3             use warnings;
4              
5 13     13   70 package Test::Deep;
  13         23  
  13         1194  
6             use Carp qw( confess );
7 13     13   8302  
  13         37  
  13         324  
8 13     13   17474 use Test::Deep::Cache;
  13         39  
  13         451  
9 13     13   8609 use Test::Deep::Stack;
  13         33  
  13         429  
10             use Test::Deep::RegexpVersion;
11              
12 13     13   71 require overload;
  13         22  
  13         1052  
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 13     13   16647  
  13         162337  
  13         1439  
23             use Data::Dumper qw(Dumper);
24 13         6356  
25             use vars qw(
26             $VERSION @EXPORT @EXPORT_OK @ISA
27             $Stack %Compared $CompareCache %WrapCache
28 13     13   118 $Snobby $Expects $DNE $DNE_ADDR $Shallow
  13         23  
29             );
30              
31             $VERSION = '0.108';
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             All => "",
54             Any => "",
55             Array => "",
56             ArrayEach => "array_each",
57             ArrayElementsOnly => "",
58             ArrayLength => "",
59             ArrayLengthOnly => "",
60             Blessed => "",
61             Boolean => "bool",
62             Code => "",
63             Hash => "",
64             HashEach => "hash_each",
65             HashKeys => "",
66             HashKeysOnly => "",
67             Ignore => "",
68             Isa => "Isa",
69             ListMethods => "",
70             Methods => "",
71             Number => "num",
72             RefType => "",
73             Regexp => "re",
74             RegexpMatches => "",
75             RegexpOnly => "",
76             RegexpRef => "",
77             RegexpRefOnly => "",
78             ScalarRef => "scalref",
79             ScalarRefOnly => "",
80             Shallow => "",
81             String => "str",
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 550     550   137555 my $sub = sub {
91 550         2944 require $file;
92             return $full_pkg->new(@_);
93             };
94 13     13   83 {
  13         30  
  13         31454  
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 71     71 1 80729 {
127             my ($d1, $d2, $name) = @_;
128 71         217  
129             my ($ok, $stack) = cmp_details($d1, $d2);
130 71 50       360  
131             if (not $Test->ok($ok, $name))
132 0         0 {
133 0         0 my $diag = deep_diag($stack);
134             $Test->diag($diag);
135             }
136 71         34972  
137             return $ok;
138             }
139              
140             sub cmp_details
141 71     71 1 130 {
142             my ($d1, $d2) = @_;
143 71         531  
144 71         436 local $Stack = Test::Deep::Stack->new;
145 71         138 local $CompareCache = Test::Deep::Cache->new;
146             local %WrapCache;
147 71         192  
148             my $ok = descend($d1, $d2);
149 71         1786  
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 1018     1018 0 1475 {
258             my ($d1, $d2) = @_;
259 1018 100 66     3063  
260             if (!ref $d1 and !ref $d2)
261             {
262 450 50       723 # Shortcut comparison for the non-reference case.
263             if (defined $d1)
264 450 50 33     3839 {
265             return 1 if defined $d2 and $d1 eq $d2;
266             }
267             else
268 0 0       0 {
269             return 1 if !defined $d2;
270             }
271             }
272 568 50 66     4118  
      66        
273             if (! $Expects and ref($d1) and UNIVERSAL::isa($d1, "Test::Deep::Cmp"))
274 0         0 {
275 0         0 my $where = $Stack->render('$data');
276             confess "Found a special comparison in $where\nYou can only the specials in the expects structure";
277             }
278 568 100 66     2523  
279             if (ref $d1 and ref $d2)
280             {
281             # this check is only done when we're comparing 2 expecteds against each
282             # other
283 426 50 33     1037  
284             if ($Expects and UNIVERSAL::isa($d1, "Test::Deep::Cmp"))
285             {
286 0 0       0 # check they are the same class
287 0 0       0 return 0 unless Test::Deep::blessed(Scalar::Util::blessed($d2))->descend($d1);
288             if ($d1->can("compare"))
289 0         0 {
290             return $d1->compare($d2);
291             }
292             }
293 426         957  
294 426         664 my $s1 = Scalar::Util::refaddr($d1);
295             my $s2 = Scalar::Util::refaddr($d2);
296 426 50       1275  
297             if ($s1 eq $s2)
298 0         0 {
299             return 1;
300 426 50       1367 }
301             if ($CompareCache->cmp($d1, $d2))
302             {
303             # we've tried comparing these already so either they turned out to
304             # be the same or we must be in a loop and we have to assume they're
305             # the same
306 0         0  
307             return 1;
308             }
309             else
310 426         1367 {
311             $CompareCache->add($d1, $d2)
312             }
313             }
314 568         1330  
315             $d2 = wrap($d2);
316 568         5296  
317             $Stack->push({exp => $d2, got => $d1});
318 568 50 66     2773  
319             if (ref($d1) and (Scalar::Util::refaddr($d1) == $DNE_ADDR))
320             {
321             # whatever it was suposed to be, it didn't exist and so it's an
322 0         0 # automatic fail
323             return 0;
324             }
325 568 50       1721  
326             if ($d2->descend($d1))
327             {
328 568         1502 # print "d1 = $d1, d2 = $d2\nok\n";
329             $Stack->pop;
330 568         2510  
331             return 1;
332             }
333             else
334             {
335 0         0 # print "d1 = $d1, d2 = $d2\nnot ok\n";
336             return 0;
337             }
338             }
339              
340             sub wrap
341 568     568 0 729 {
342             my $data = shift;
343 568 100 66     12751  
344             return $data if ref($data) and UNIVERSAL::isa($data, "Test::Deep::Cmp");
345 71         193  
346             my ($class, $base) = class_base($data);
347 71         108  
348             my $cmp;
349 71 50       174  
350             if($base eq '')
351 0         0 {
352             $cmp = shallow($data);
353             }
354             else
355 71         177 {
356             my $addr = Scalar::Util::refaddr($data);
357 71 50       253  
358             return $WrapCache{$addr} if $WrapCache{$addr};
359 71 100 0     185
    50 0        
    0          
    0          
360             if($base eq 'ARRAY')
361 65         243 {
362             $cmp = array($data);
363             }
364             elsif($base eq 'HASH')
365 6         22 {
366             $cmp = hash($data);
367             }
368             elsif($base eq 'SCALAR' or $base eq 'REF')
369 0         0 {
370             $cmp = scalref($data);
371             }
372             elsif(($base eq 'Regexp') or ($base eq 'REGEXP'))
373 0         0 {
374             $cmp = regexpref($data);
375             }
376             else
377 0         0 {
378             $cmp = shallow($data);
379             }
380 71         1125  
381             $WrapCache{$addr} = $cmp;
382 71         153 }
383             return $cmp;
384             }
385              
386             sub class_base
387 71     71 0 100 {
388             my $val = shift;
389 71 50       228  
390             if (ref $val)
391 71         184 {
392 71 50       181 my $blessed = Scalar::Util::blessed($val);
393 71         276 $blessed = defined($blessed) ? $blessed : "";
394             my $reftype = Scalar::Util::reftype($val);
395              
396 71 50       200  
397 0 0 0     0 if ($Test::Deep::RegexpVersion::OldStyle) {
398             if ($blessed eq "Regexp" and $reftype eq "SCALAR")
399 0         0 {
400             $reftype = "Regexp"
401             }
402 71         293 }
403             return ($blessed, $reftype);
404             }
405             else
406 0           {
407             return ("", "");
408             }
409             }
410              
411             sub render_stack
412 0     0 0   {
413             my ($var, $stack) = @_;
414 0            
415             return $stack->render($var);
416             }
417              
418             sub cmp_methods
419 0     0 1   {
420 0           local $Test::Builder::Level = $Test::Builder::Level + 1;
  0            
421             return cmp_deeply(shift, methods(@{shift()}), shift);
422             }
423              
424             sub requireclass
425 0     0 0   {
426             require Test::Deep::Class;
427 0            
428             my $val = shift;
429 0            
430             return Test::Deep::Class->new(1, $val);
431             }
432              
433             # docs and export say this is call useclass, doh!
434              
435             *useclass = \&requireclass;
436              
437             sub noclass
438 0     0 1   {
439             require Test::Deep::Class;
440 0            
441             my $val = shift;
442 0            
443             return Test::Deep::Class->new(0, $val);
444             }
445              
446             sub set
447 0     0 1   {
448             require Test::Deep::Set;
449 0            
450             return Test::Deep::Set->new(1, "", @_);
451             }
452              
453             sub supersetof
454 0     0 1   {
455             require Test::Deep::Set;
456 0            
457             return Test::Deep::Set->new(1, "sup", @_);
458             }
459              
460             sub subsetof
461 0     0 1   {
462             require Test::Deep::Set;
463 0            
464             return Test::Deep::Set->new(1, "sub", @_);
465             }
466              
467             sub cmp_set
468 0     0 1   {
469 0           local $Test::Builder::Level = $Test::Builder::Level + 1;
  0            
470             return cmp_deeply(shift, set(@{shift()}), shift);
471             }
472              
473             sub bag
474 0     0 1   {
475             require Test::Deep::Set;
476 0            
477             return Test::Deep::Set->new(0, "", @_);
478             }
479              
480             sub superbagof
481 0     0 1   {
482             require Test::Deep::Set;
483 0            
484             return Test::Deep::Set->new(0, "sup", @_);
485             }
486              
487             sub subbagof
488 0     0 1   {
489             require Test::Deep::Set;
490 0            
491             return Test::Deep::Set->new(0, "sub", @_);
492             }
493              
494             sub cmp_bag
495 0     0 1   {
496 0   0       local $Test::Builder::Level = $Test::Builder::Level + 1;
497 0 0         my $ref = ref($_[1]) || "";
498             confess "Argument 2 to cmp_bag is not an ARRAY ref (".render_val($_[1]).")"
499 0           unless $ref eq "ARRAY";
  0            
500             return cmp_deeply(shift, bag(@{shift()}), shift);
501             }
502              
503             sub superhashof
504 0     0 1   {
505             require Test::Deep::Hash;
506 0            
507             my $val = shift;
508 0            
509             return Test::Deep::SuperHash->new($val);
510             }
511              
512             sub subhashof
513 0     0 1   {
514             require Test::Deep::Hash;
515 0            
516             my $val = shift;
517 0            
518             return Test::Deep::SubHash->new($val);
519             }
520              
521             sub builder
522 0 0   0 0   {
523             if (@_)
524 0           {
525             $Test = shift;
526 0           }
527             return $Test;
528             }
529              
530             1;
531