File Coverage

blib/lib/Test/Lazy/Tester.pm
Criterion Covered Total %
statement 90 100 90.0
branch 25 40 62.5
condition 5 15 33.3
subroutine 15 15 100.0
pod 6 6 100.0
total 141 176 80.1


line stmt bran cond sub pod time code
1             package Test::Lazy::Tester;
2              
3 2     2   11 use warnings;
  2         3  
  2         65  
4 2     2   10 use strict;
  2         4  
  2         93  
5              
6             =head1 NAME
7              
8             Test::Lazy::Tester
9              
10             =head1 SYNOPSIS
11              
12             use Test::Lazy::Tester;
13              
14             $tester = Test::Lazy::Tester->new;
15              
16             # Will evaluate the code and check it:
17             $tester->try('qw/a/' => eq => 'a');
18             $tester->try('qw/a/' => ne => 'b');
19             $tester->try('qw/a/' => is => ['a']);
20              
21             # Don't evaluate, but still compare:
22             $tester->check(1 => is => 1);
23             $tester->check(0 => isnt => 1);
24             $tester->check(a => like => qr/[a-zA-Z]/);
25             $tester->check(0 => unlike => qr/a-zA-Z]/);
26             $tester->check(1 => '>' => 0);
27             $tester->check(0 => '<' => 1);
28              
29             # A failure example:
30              
31             $tester->check([qw/a b/] => is => [qw/a b c/]);
32              
33             # Failed test '['a','b'] is ['a','b','c']'
34             # Compared array length of $data
35             # got : array with 2 element(s)
36             # expect : array with 3 element(s)
37              
38              
39             # Custom test explanation:
40              
41             $tester->try('2 + 2' => '==' => 5, "Math is hard: %?");
42              
43             # Failed test 'Math is hard: 2 + 2 == 5'
44             # got: 4
45             # expected: 5
46              
47             =head1 DESCRIPTION
48              
49             See L for more information.
50              
51             =head1 METHODS
52              
53             =head2 Test::Lazy::Tester->new( cmp_scalar => ?, cmp_structure => ?, render => ? )
54              
55             Create a new Test::Lazy::Tester object, optionally amending the scalar comparison, structure comparison, and render subroutines
56             using the supplied hashes.
57              
58             For now, more information on customization can be gotten by:
59              
60             perldoc -m Test::Lazy::Tester
61              
62             =head2 $tester->check( , , , [ ] )
63              
64             See L for details.
65              
66             =head2 $tester->try( , , , [ ] )
67              
68             See L for details.
69              
70             =head2 $tester->template()
71              
72             Creates a C using $tester as the basis.
73              
74             See L for more details.
75              
76             Returns a new L object.
77              
78             =head2 $tester->render_value( )
79              
80             Render a gotten or expected value to a form suitable for the test notice/explanation.
81              
82             This method will consult the $tester->render hash to see what if should do based on 'ref '.
83             By default, ARRAY and HASH are handled by Data::Dumper using the following:
84              
85             local $Data::Dumper::Indent = 0;
86             local $Data::Dumper::Varname = 0;
87             local $Data::Dumper::Terse = 1;
88              
89             An undef value is a special case, handled by the $tester->render->{undef} subroutine.
90             By default, the subroutine returns the string "undef"
91              
92             =head2 $tester->render_notice( , , , )
93              
94             Render the text explantaion message. You don't need to mess with this.
95              
96             =cut
97              
98 2     2   11 use base qw/Class::Accessor::Fast/;
  2         3  
  2         1903  
99              
100             __PACKAGE__->mk_accessors(qw/render cmp_scalar cmp_structure/);
101              
102 2     2   9293 use Data::Dumper qw/Dumper/;
  2         19645  
  2         174  
103 2     2   20 use Carp;
  2         5  
  2         116  
104 2     2   967 use Test::Deep;
  2         11202  
  2         502  
105 2     2   17 use Test::Builder();
  2         3  
  2         409  
106              
107             my $deparser;
108             eval {
109             require B::Deparse;
110             $deparser = B::Deparse->new;
111             $deparser->ambient_pragmas(strict => 'all', warnings => 'all');
112             };
113             undef $deparser if $@;
114              
115             my %base_cmp_scalar = (
116             ok => sub {
117             Test::More::ok($_[0], $_[2])
118             },
119              
120             not_ok => sub {
121             Test::More::ok(! $_[0], $_[2])
122             },
123              
124             (map { my $mtd = $_; $_ => sub {
125             Test::More::cmp_ok($_[0] => $mtd => $_[1], $_[2])
126             } }
127             qw/< > <= >= lt gt le ge == != eq ne/),
128              
129             (map { my $method = $_; $_ => sub {
130 2     2   13 no strict 'refs';
  2         4  
  2         3108  
131             "Test::More::$method"->($_[0], $_[1], $_[2])
132             } }
133             qw/is isnt like unlike/),
134             );
135              
136             my %base_cmp_structure = (
137             ok => sub {
138             Test::More::ok($_[0], $_[2])
139             },
140              
141             not_ok => sub {
142             Test::More::ok(! $_[0], $_[2])
143             },
144              
145             (map { $_ => sub {
146             Test::Deep::cmp_bag($_[0], $_[1], $_[2]);
147             } }
148             qw/bag same_bag samebag/),
149              
150             (map { $_ => sub {
151             Test::Deep::cmp_set($_[0], $_[1], $_[2]);
152             } }
153             qw/set same_set sameset/),
154              
155             (map { $_ => sub {
156             Test::Deep::cmp_deeply($_[0], $_[1], $_[2]);
157             } }
158             qw/same is like eq ==/),
159              
160             (map { $_ => sub {
161             Test::More::ok(!Test::Deep::eq_deeply($_[0], $_[1]), $_[2]);
162             } }
163             qw/isnt unlink ne !=/),
164             );
165              
166             my %base_render = (
167             ARRAY => sub {
168             local $Data::Dumper::Indent = 0;
169             local $Data::Dumper::Varname = 0;
170             local $Data::Dumper::Terse = 1;
171             my $self = shift;
172             my $value = shift;
173             return Dumper($value);
174             },
175              
176             HASH => sub {
177             local $Data::Dumper::Indent = 0;
178             local $Data::Dumper::Varname = 0;
179             local $Data::Dumper::Terse = 1;
180             my $self = shift;
181             my $value = shift;
182             return Dumper($value);
183             },
184              
185             undef => sub {
186             return "undef";
187             },
188             );
189              
190             sub new {
191 2     2 1 7 my $self = bless {}, shift;
192 2         7 local %_ = @_;
193 2 50       19 $self->{cmp_scalar} = { %base_cmp_scalar, %{ $_{cmp_scalar} || {} } };
  2         52  
194 2 50       18 $self->{cmp_structure} = { %base_cmp_structure, %{ $_{cmp_structure} || {} } };
  2         39  
195 2 50       10 $self->{render} = { %base_render, %{ $_{base_render} || {} } };
  2         18  
196 2         12 return $self;
197             }
198              
199             sub render_notice {
200 114     114 1 13550 my $self = shift;
201 114         223 my ($left, $compare, $right, $notice, $length) = @_;
202              
203             # my $_notice = $length == 4 ? "$left $compare $right" : "$left $compare";
204 114         221 my $_notice = "$left $compare $right";
205 114 100       184 if (defined $notice) {
206 70 50       125 if ($notice =~ m/%\?/) {
207 0         0 $notice =~ s/%\?/$_notice/g;
208             }
209             else { # Old version, deprecated.
210 70         269 $notice =~ s/%(?!%)/%?/g;
211 70         5840 $notice =~ s/%%/%/g;
212 70         186 $notice =~ s/%\?/$_notice/g;
213             }
214             }
215             else {
216 44         56 $notice = $_notice;
217             }
218              
219 114         248 return $notice;
220             }
221              
222             sub render_value {
223 136     136 1 158 my $self = shift;
224 136         140 my $value = shift;
225              
226 136         148 my $type = ref $value;
227 136 100       302 $type = "undef" unless defined $value;
228              
229 136 100       300 return $value unless $type;
230 38 100       112 return $value unless my $renderer = $self->render->{$type};
231 34         208 return $renderer->($self, $value);
232             }
233              
234             sub _test {
235 114     114   128 my $self = shift;
236 114         163 my ($compare, $got, $expect, $notice) = @_;
237              
238 114         132 local $Test::Builder::Level = $Test::Builder::Level + 1;
239              
240 114         144 my $cmp = $compare;
241 114 50       192 if (ref $cmp eq "CODE") {
242 0         0 Test::More::ok($cmp->($got, $expect), $notice);
243             }
244             else {
245 114   66     408 my $structure = ref $expect eq "ARRAY" || ref $expect eq "HASH";
246 114         127 my $scalar = ! $structure;
247              
248 114 100       343 my $cmp_source = $scalar ? $self->cmp_scalar : $self->cmp_structure;
249              
250 114 50       627 die "Don't know how to compare via ($compare)" unless $cmp = $cmp_source->{$cmp};
251 114         131 local $Test::Builder::Level = $Test::Builder::Level + 1;
252 114         218 $cmp->($got, $expect, $notice);
253             }
254             }
255              
256             sub check {
257 22     22 1 27 my $self = shift;
258 22         39 my ($got, $compare, $expect, $notice) = @_;
259 22         26 my $length = @_;
260              
261 22         50 my $left = $self->render_value($got);
262 22         38 my $right = $self->render_value($expect);
263 22         59 $notice = $self->render_notice($left, $compare, $right, $notice, $length);
264              
265 22         29 local $Test::Builder::Level = $Test::Builder::Level + 1;
266              
267 22         48 return $self->_test($compare, $got, $expect, $notice);
268             }
269              
270             sub try {
271 92     92 1 453 my $self = shift;
272 92         156 my ($statement, $compare, $expect, $notice) = @_;
273 92         102 my $length = @_;
274              
275 92 50       4290 my @got = ref $statement eq "CODE" ? $statement->() : eval $statement;
276 92 50       306 die "$statement: $@" if $@;
277 92         97 my $got;
278 92 100       162 if (@got > 1) {
279 2 50       12 if (ref $expect eq "ARRAY") {
    50          
280 0         0 $got = \@got;
281             }
282             elsif (ref $expect eq "HASH") {
283 2         10 $got = { @got };
284             }
285             else {
286 0         0 $got = scalar @got;
287             }
288             }
289             else {
290 90 50 0     316 if (ref $expect eq "ARRAY" && (! @got || ref $got[0] ne "ARRAY")) {
    50 33        
      33        
291 0         0 $got = \@got;
292             }
293             elsif (ref $expect eq "HASH" && ! @got) {
294 0         0 $got = { };
295             }
296             else {
297 90         127 $got = $got[0];
298             }
299             }
300            
301 92         99 my $left;
302 92 50 33     274 if (ref $statement eq "CODE" && $deparser) {
303 0         0 my $deparse = $deparser->coderef2text($statement);
304 0         0 my @deparse = split m/\n\s*/, $deparse;
305 0 0       0 $deparse = join ' ', "sub", @deparse if 3 == @deparse;
306 0         0 $left = $deparse;
307             }
308             else {
309 92         119 $left = $statement;
310             }
311 92         181 my $right = $self->render_value($expect);
312 92         432 $notice = $self->render_notice($left, $compare, $right, $notice, $length);
313              
314 92         178 local $Test::Builder::Level = $Test::Builder::Level + 1;
315              
316 92         202 return $self->_test($compare, $got, $expect, $notice);
317             }
318              
319             sub template {
320 1     1 1 2 my $self = shift;
321 1         719 require Test::Lazy::Template;
322 1         6 return Test::Lazy::Template->new($self, @_);
323             }
324              
325             1;