File Coverage

File:local/lib/perl5/Test/Deep.pm
Coverage:37.5%

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