root/branches/release-40/t/lib/Test/Deep.pm @ 2583

Revision 2583, 8.5 kB (checked in by mpaschal, 18 months ago)

Add Test::Deep to t/lib, since we already use it in driver-tests.pl
BugzID: 79953

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