File Coverage

inc/Test/Deep.pm
Criterion Covered Total %
statement 80 173 46.2
branch 20 70 28.5
condition 12 29 41.3
subroutine 17 36 47.2
pod 17 25 68.0
total 146 333 43.8


line stmt bran cond sub pod time code
1 16     16   2331 #line 1
  16         28  
  16         514  
2 16     16   75 use strict;
  16         24  
  16         532  
3             use warnings;
4              
5 16     16   97 package Test::Deep;
  16         42  
  16         1084  
6             use Carp qw( confess );
7 16     16   10859  
  16         47  
  16         396  
8 16     16   8565 use Test::Deep::Cache;
  16         50  
  16         368  
9 16     16   9008 use Test::Deep::Stack;
  16         43  
  16         464  
10             use Test::Deep::RegexpVersion;
11              
12 16     16   77 require overload;
  16         29  
  16         1201  
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 16     16   27517  
  16         151010  
  16         2981  
23             use Data::Dumper qw(Dumper);
24 16         10886  
25             use vars qw(
26             $VERSION @EXPORT @EXPORT_OK @ISA
27             $Stack %Compared $CompareCache %WrapCache
28 16     16   317 $Snobby $Expects $DNE $DNE_ADDR $Shallow
  16         29  
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 508     508   47317 my $sub = sub {
91 508         3027 require $file;
92             return $full_pkg->new(@_);
93             };
94 16     16   104 {
  16         35  
  16         37393  
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 9     9 1 20 {
127             my ($d1, $d2, $name) = @_;
128 9         53  
129             my ($ok, $stack) = cmp_details($d1, $d2);
130 9 50       61  
131             if (not $Test->ok($ok, $name))
132 0         0 {
133 0         0 my $diag = deep_diag($stack);
134             $Test->diag($diag);
135             }
136 9         198  
137             return $ok;
138             }
139              
140             sub cmp_details
141 67     67 1 108 {
142             my ($d1, $d2) = @_;
143 67         572  
144 67         458 local $Stack = Test::Deep::Stack->new;
145 67         132 local $CompareCache = Test::Deep::Cache->new;
146             local %WrapCache;
147 67         213  
148             my $ok = descend($d1, $d2);
149 67         1554  
150             return ($ok, $Stack);
151             }
152              
153             sub eq_deeply
154 58     58 1 1050 {
155             my ($d1, $d2) = @_;
156 58         186  
157             my ($ok) = cmp_details($d1, $d2);
158 58         548  
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 709     709 0 1099 {
258             my ($d1, $d2) = @_;
259 709 50 66     4534  
      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 709 100 66     4273  
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 402 50 33     961  
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 402         1578  
281 402         646 my $s1 = Scalar::Util::refaddr($d1);
282             my $s2 = Scalar::Util::refaddr($d2);
283 402 50       1230  
284             if ($s1 eq $s2)
285 0         0 {
286             return 1;
287 402 50       1319 }
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 402         1118 {
298             $CompareCache->add($d1, $d2)
299             }
300             }
301 709         1738  
302             $d2 = wrap($d2);
303 709         5985  
304             $Stack->push({exp => $d2, got => $d1});
305 709 50 66     2946  
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 709 50       2134  
313             if ($d2->descend($d1))
314             {
315 709         2469 # print "d1 = $d1, d2 = $d2\nok\n";
316             $Stack->pop;
317 709         4627  
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 709     709 0 1769 {
329             my $data = shift;
330 709 100 100     2440  
331             return $data if ref($data) and UNIVERSAL::isa($data, "Test::Deep::Cmp");
332 240         488  
333             my ($class, $base) = class_base($data);
334 240         335  
335             my $cmp;
336 240 100       481  
337             if($base eq '')
338 173         391 {
339             $cmp = shallow($data);
340             }
341             else
342 67         174 {
343             my $addr = Scalar::Util::refaddr($data);
344 67 50       204  
345             return $WrapCache{$addr} if $WrapCache{$addr};
346 67 50 0     253
    50 0        
    0          
    0          
347             if($base eq 'ARRAY')
348 0         0 {
349             $cmp = array($data);
350             }
351             elsif($base eq 'HASH')
352 67         332 {
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 67         1504  
368             $WrapCache{$addr} = $cmp;
369 240         467 }
370             return $cmp;
371             }
372              
373             sub class_base
374 240     240 0 306 {
375             my $val = shift;
376 240 100       427  
377             if (ref $val)
378 67         170 {
379 67 50       238 my $blessed = Scalar::Util::blessed($val);
380 67         186 $blessed = defined($blessed) ? $blessed : "";
381             my $reftype = Scalar::Util::reftype($val);
382              
383 67 50       229  
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 67         202 }
390             return ($blessed, $reftype);
391             }
392             else
393 173         553 {
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