File Coverage

inc/Test/Deep.pm
Criterion Covered Total %
statement 46 171 26.9
branch 5 78 6.4
condition 6 38 15.7
subroutine 14 34 41.1
pod 17 25 68.0
total 88 346 25.4


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