File Coverage

inc/Test/Deep.pm
Criterion Covered Total %
statement 86 173 49.7
branch 22 70 31.4
condition 12 29 41.3
subroutine 19 36 52.7
pod 17 25 68.0
total 156 333 46.8


line stmt bran cond sub pod time code
1 2     2   3039 #line 1
  2         4  
  2         76  
2 2     2   9 use strict;
  2         3  
  2         80  
3             use warnings;
4              
5 2     2   11 package Test::Deep;
  2         3  
  2         146  
6             use Carp qw( confess );
7 2     2   2246  
  2         4290  
  2         47  
8 2     2   1554 use Test::Deep::Cache;
  2         3713  
  2         54  
9 2     2   1574 use Test::Deep::Stack;
  2         203  
  2         68  
10             use Test::Deep::RegexpVersion;
11              
12 2     2   12 require overload;
  2         4  
  2         208  
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   1673258  
  2         36591  
  2         203  
23             use Data::Dumper qw(Dumper);
24 2         942  
25             use vars qw(
26             $VERSION @EXPORT @EXPORT_OK @ISA
27             $Stack %Compared $CompareCache %WrapCache
28 2     2   17 $Snobby $Expects $DNE $DNE_ADDR $Shallow
  2         3  
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 280     280   18915 my $sub = sub {
91 280         11277 require $file;
92             return $full_pkg->new(@_);
93             };
94 2     2   12 {
  2         5  
  2         11908  
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 20 50   20 1 174 {
113             if (@_ == 1)
114 20         163 {
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 21     21 1 113 {
127             my ($d1, $d2, $name) = @_;
128 21         48  
129             my ($ok, $stack) = cmp_details($d1, $d2);
130 21 50       114  
131             if (not $Test->ok($ok, $name))
132 0         0 {
133 0         0 my $diag = deep_diag($stack);
134             $Test->diag($diag);
135             }
136 21         11481  
137             return $ok;
138             }
139              
140             sub cmp_details
141 21     21 1 31 {
142             my ($d1, $d2) = @_;
143 21         103  
144 21         518 local $Stack = Test::Deep::Stack->new;
145 21         832 local $CompareCache = Test::Deep::Cache->new;
146             local %WrapCache;
147 21         44  
148             my $ok = descend($d1, $d2);
149 21         498  
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 461     461 0 18094 {
258             my ($d1, $d2) = @_;
259 461 50 66     3185  
      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 461 100 66     1575  
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 338 50 33     672  
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 338         577  
281 338         441 my $s1 = Scalar::Util::refaddr($d1);
282             my $s2 = Scalar::Util::refaddr($d2);
283 338 50       736  
284             if ($s1 eq $s2)
285 0         0 {
286             return 1;
287 338 50       788 }
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 338         11750 {
298             $CompareCache->add($d1, $d2)
299             }
300             }
301 461         7842  
302             $d2 = wrap($d2);
303 461         1759  
304             $Stack->push({exp => $d2, got => $d1});
305 461 50 66     4021  
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 461 50       1172  
313             if ($d2->descend($d1))
314             {
315 461         4305 # print "d1 = $d1, d2 = $d2\nok\n";
316             $Stack->pop;
317 461         3125  
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 540     540 0 1296 {
329             my $data = shift;
330 540 100 100     3119  
331             return $data if ref($data) and UNIVERSAL::isa($data, "Test::Deep::Cmp");
332 35         70  
333             my ($class, $base) = class_base($data);
334 35         43  
335             my $cmp;
336 35 100       61  
337             if($base eq '')
338 33         55 {
339             $cmp = shallow($data);
340             }
341             else
342 2         7 {
343             my $addr = Scalar::Util::refaddr($data);
344 2 50       8  
345             return $WrapCache{$addr} if $WrapCache{$addr};
346 2 100 0     11
    50 0        
    0          
    0          
347             if($base eq 'ARRAY')
348 1         5 {
349             $cmp = array($data);
350             }
351             elsif($base eq 'HASH')
352 1         3 {
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 2         235  
368             $WrapCache{$addr} = $cmp;
369 35         418 }
370             return $cmp;
371             }
372              
373             sub class_base
374 35     35 0 48 {
375             my $val = shift;
376 35 100       57  
377             if (ref $val)
378 2         33 {
379 2 50       9 my $blessed = Scalar::Util::blessed($val);
380 2         6 $blessed = defined($blessed) ? $blessed : "";
381             my $reftype = Scalar::Util::reftype($val);
382              
383 2 50       8  
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 2         6 }
390             return ($blessed, $reftype);
391             }
392             else
393 33         79 {
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 20     20 1 1948 {
426             require Test::Deep::Class;
427 20         471  
428             my $val = shift;
429 20         73  
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 0     0 1 0 {
462             require Test::Deep::Set;
463 0         0  
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 0     0 1 0 {
483 0   0     0 local $Test::Builder::Level = $Test::Builder::Level + 1;
484 0 0       0 my $ref = ref($_[1]) || "";
485             confess "Argument 2 to cmp_bag is not an ARRAY ref (".render_val($_[1]).")"
486 0         0 unless $ref eq "ARRAY";
  0         0  
487             return cmp_deeply(shift, bag(@{shift()}), shift);
488             }
489              
490             sub superhashof
491 49     49 1 2611 {
492             require Test::Deep::Hash;
493 49         3731  
494             my $val = shift;
495 49         145  
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