File Coverage

inc/Test/Deep.pm
Criterion Covered Total %
statement 95 173 54.9
branch 24 70 34.2
condition 15 29 51.7
subroutine 20 36 55.5
pod 17 25 68.0
total 171 333 51.3


line stmt bran cond sub pod time code
1 1     1   720 #line 1
  1         2  
  1         52  
2 1     1   4 use strict;
  1         3  
  1         40  
3             use warnings;
4              
5 1     1   5 package Test::Deep;
  1         2  
  1         80  
6             use Carp qw( confess );
7 1     1   752  
  1         3  
  1         30  
8 1     1   1432 use Test::Deep::Cache;
  1         5  
  1         33  
9 1     1   733 use Test::Deep::Stack;
  1         3  
  1         40  
10             use Test::Deep::RegexpVersion;
11              
12 1     1   7 require overload;
  1         1  
  1         98  
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 1     1   1491  
  1         12813  
  1         106  
23             use Data::Dumper qw(Dumper);
24 1         790  
25             use vars qw(
26             $VERSION @EXPORT @EXPORT_OK @ISA
27             $Stack %Compared $CompareCache %WrapCache
28 1     1   11 $Snobby $Expects $DNE $DNE_ADDR $Shallow
  1         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 30     30   2650 my $sub = sub {
91 30         140 require $file;
92             return $full_pkg->new(@_);
93             };
94 1     1   7 {
  1         3  
  1         2729  
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 4     4 1 7 {
127             my ($d1, $d2, $name) = @_;
128 4         12  
129             my ($ok, $stack) = cmp_details($d1, $d2);
130 4 50       20  
131             if (not $Test->ok($ok, $name))
132 0         0 {
133 0         0 my $diag = deep_diag($stack);
134             $Test->diag($diag);
135             }
136 4         1362  
137             return $ok;
138             }
139              
140             sub cmp_details
141 6     6 1 8 {
142             my ($d1, $d2) = @_;
143 6         31  
144 6         32 local $Stack = Test::Deep::Stack->new;
145 6         8 local $CompareCache = Test::Deep::Cache->new;
146             local %WrapCache;
147 6         14  
148             my $ok = descend($d1, $d2);
149 6         96  
150             return ($ok, $Stack);
151             }
152              
153             sub eq_deeply
154 2     2 1 5 {
155             my ($d1, $d2) = @_;
156 2         5  
157             my ($ok) = cmp_details($d1, $d2);
158 2         30  
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 8     8 0 12  
171             my ($d1, $d2, $name) = @_;
172 8         22  
173 8         21 local $Stack = Test::Deep::Stack->new;
174             $CompareCache->local;
175 8         23  
176             my $ok = descend($d1, $d2);
177 8         31  
178             $CompareCache->finish($ok);
179 8         47  
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 50     50 0 77 {
258             my ($d1, $d2) = @_;
259 50 50 100     269  
      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 50 100 66     170  
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 32 50 66     119  
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 32         67  
281 32         49 my $s1 = Scalar::Util::refaddr($d1);
282             my $s2 = Scalar::Util::refaddr($d2);
283 32 100       121  
284             if ($s1 eq $s2)
285 4         9 {
286             return 1;
287 28 50       82 }
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 28         75 {
298             $CompareCache->add($d1, $d2)
299             }
300             }
301 46         95  
302             $d2 = wrap($d2);
303 46         188  
304             $Stack->push({exp => $d2, got => $d1});
305 46 50 66     228  
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 46 100       124  
313             if ($d2->descend($d1))
314             {
315 38         93 # print "d1 = $d1, d2 = $d2\nok\n";
316             $Stack->pop;
317 38         152  
318             return 1;
319             }
320             else
321             {
322 8         21 # print "d1 = $d1, d2 = $d2\nnot ok\n";
323             return 0;
324             }
325             }
326              
327             sub wrap
328 46     46 0 52 {
329             my $data = shift;
330 46 100 100     244  
331             return $data if ref($data) and UNIVERSAL::isa($data, "Test::Deep::Cmp");
332 14         27  
333             my ($class, $base) = class_base($data);
334 14         14  
335             my $cmp;
336 14 100       25  
337             if($base eq '')
338 10         19 {
339             $cmp = shallow($data);
340             }
341             else
342 4         9 {
343             my $addr = Scalar::Util::refaddr($data);
344 4 50       11  
345             return $WrapCache{$addr} if $WrapCache{$addr};
346 4 50 0     13
    50 0        
    0          
    0          
347             if($base eq 'ARRAY')
348 0         0 {
349             $cmp = array($data);
350             }
351             elsif($base eq 'HASH')
352 4         9 {
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 4         10  
368             $WrapCache{$addr} = $cmp;
369 14         28 }
370             return $cmp;
371             }
372              
373             sub class_base
374 14     14 0 15 {
375             my $val = shift;
376 14 100       25  
377             if (ref $val)
378 4         10 {
379 4 100       10 my $blessed = Scalar::Util::blessed($val);
380 4         10 $blessed = defined($blessed) ? $blessed : "";
381             my $reftype = Scalar::Util::reftype($val);
382              
383 4 50       19  
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 4         9 }
390             return ($blessed, $reftype);
391             }
392             else
393 10         24 {
394             return ("", "");
395             }
396             }
397              
398             sub render_stack
399 0     0 0 0 {
400             my ($var, $stack) = @_;
401 0         0  
402             return $stack->render($var);
403             }
404              
405             sub cmp_methods
406 0     0 1 0 {
407 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
  0         0  
408             return cmp_deeply(shift, methods(@{shift()}), shift);
409             }
410              
411             sub requireclass
412 0     0 0 0 {
413             require Test::Deep::Class;
414 0         0  
415             my $val = shift;
416 0         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 0 {
426             require Test::Deep::Class;
427 0         0  
428             my $val = shift;
429 0         0  
430             return Test::Deep::Class->new(0, $val);
431             }
432              
433             sub set
434 0     0 1 0 {
435             require Test::Deep::Set;
436 0         0  
437             return Test::Deep::Set->new(1, "", @_);
438             }
439              
440             sub supersetof
441 0     0 1 0 {
442             require Test::Deep::Set;
443 0         0  
444             return Test::Deep::Set->new(1, "sup", @_);
445             }
446              
447             sub subsetof
448 0     0 1 0 {
449             require Test::Deep::Set;
450 0         0  
451             return Test::Deep::Set->new(1, "sub", @_);
452             }
453              
454             sub cmp_set
455 0     0 1 0 {
456 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
  0         0  
457             return cmp_deeply(shift, set(@{shift()}), shift);
458             }
459              
460             sub bag
461 4     4 1 702 {
462             require Test::Deep::Set;
463 4         24  
464             return Test::Deep::Set->new(0, "", @_);
465             }
466              
467             sub superbagof
468 0     0 1 0 {
469             require Test::Deep::Set;
470 0         0  
471             return Test::Deep::Set->new(0, "sup", @_);
472             }
473              
474             sub subbagof
475 0     0 1 0 {
476             require Test::Deep::Set;
477 0         0  
478             return Test::Deep::Set->new(0, "sub", @_);
479             }
480              
481             sub cmp_bag
482 4     4 1 49 {
483 4   50     13 local $Test::Builder::Level = $Test::Builder::Level + 1;
484 4 50       12 my $ref = ref($_[1]) || "";
485             confess "Argument 2 to cmp_bag is not an ARRAY ref (".render_val($_[1]).")"
486 4         5 unless $ref eq "ARRAY";
  4         12  
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