File Coverage

blib/lib/Test/Module/Runnable/Base.pm
Criterion Covered Total %
statement 78 152 51.3
branch 20 64 31.2
condition 5 23 21.7
subroutine 13 22 59.0
pod 10 10 100.0
total 126 271 46.4


line stmt bran cond sub pod time code
1             # Module test framework
2             # Copyright (c) 2015-2019, Duncan Ross Palmer (2E0EOL) and others,
3             # All rights reserved.
4             #
5             # Redistribution and use in source and binary forms, with or without
6             # modification, are permitted provided that the following conditions are met:
7             #
8             # * Redistributions of source code must retain the above copyright notice,
9             # this list of conditions and the following disclaimer.
10             #
11             # * Redistributions in binary form must reproduce the above copyright
12             # notice, this list of conditions and the following disclaimer in the
13             # documentation and/or other materials provided with the distribution.
14             #
15             # * Neither the name of the Daybo Logic nor the names of its contributors
16             # may be used to endorse or promote products derived from this software
17             # without specific prior written permission.
18             #
19             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29             # POSSIBILITY OF SUCH DAMAGE.
30              
31             =head1 NAME
32              
33             Test::Module::Runnable::Base - See L<Test::Module::Runnable>
34              
35             =head1 DESCRIPTION
36              
37             This is the base class for L<Test::Module::Runnable>, and all user-documentation
38             must be sought there.
39              
40             A few internal-only methods are documented here for project maintainers.
41              
42             =cut
43              
44             package Test::Module::Runnable::Base;
45 4     4   2761 use Moose;
  4         12  
  4         33  
46              
47 4     4   31499 use Data::Dumper;
  4         27775  
  4         270  
48 4     4   33 use POSIX qw/EXIT_SUCCESS/;
  4         12  
  4         27  
49 4     4   2385 use Test::MockModule;
  4         14528  
  4         150  
50 4     4   28 use Test::More 0.96;
  4         103  
  4         44  
51              
52             BEGIN {
53 4     4   8681 our $VERSION = '0.4.2';
54             }
55              
56             =head1 ATTRIBUTES
57              
58             =over
59              
60             =item C<sut>
61              
62             See L<Test::Module::Runnable/sut>
63              
64             =cut
65              
66             has 'sut' => (is => 'rw', required => 0);
67              
68             =item C<pattern>
69              
70             See L<Test::Module::Runnable/pattern>
71              
72             =cut
73              
74             has 'pattern' => (is => 'ro', isa => 'Regexp', default => sub { qr/^test/ });
75              
76             =item C<logger>
77              
78             See L<Test::Module::Runnable/logger>
79              
80             =cut
81              
82             has 'logger' => (is => 'rw', required => 0);
83              
84             =item C<mocker>
85              
86             See L<Test::Module::Runnable/mocker>
87              
88             =cut
89              
90             has 'mocker' => (
91             is => 'rw',
92             isa => 'Maybe[Test::MockModule]',
93             required => 0,
94             default => undef,
95             );
96              
97             =back
98              
99             =head1 PRIVATE ATTRIBUTES
100              
101             =over
102              
103             =item C<__unique_default_domain>
104              
105             The internal default domain value. This is used when C<unique>
106             is called without a domain, because a key cannot be C<undef> in Perl.
107              
108             =cut
109              
110             has '__unique_default_domain' => (
111             isa => 'Str',
112             is => 'ro',
113             default => 'db3eb5cf-a597-4038-aea8-fd06faea6eed'
114             );
115              
116             =item C<__unique>
117              
118             Tracks the counter returned by C<unique>.
119             Always contains the previous value returned, or zero before any calls.
120             A hash is used to support multiple domains.
121              
122             =cut
123              
124             has '__unique' => (
125             is => 'ro',
126             isa => 'HashRef[Int]',
127             default => sub {
128             { }
129             },
130             );
131              
132             =item C<__random>
133              
134             Hash of random numbers already given out.
135              
136             =cut
137              
138             has '__random' => (
139             is => 'ro',
140             isa => 'HashRef[Int]',
141             default => sub {
142             { }
143             },
144             );
145              
146             =back
147              
148             =head1 METHODS
149              
150             =over
151              
152             =item C<unique>
153              
154             See L<Test::Module::Runnable/unique>
155              
156             =cut
157              
158             sub unique {
159 507     507 1 1258 my ($self, $domain) = @_;
160 507         808 my $useRandomDomain = 0;
161 507         761 my $result;
162              
163 507 100 100     2271 if (defined($domain) && length($domain)) {
164 503 100       1320 $useRandomDomain++ if ('rand' eq $domain);
165             } else {
166 4         136 $domain = $self->__unique_default_domain;
167             }
168              
169 507 100       979 if ($useRandomDomain) {
170             do {
171 500         16833 $result = int(rand(999_999_999));
172 500         705 } while ($self->__random->{$result});
173 500         15110 $self->__random->{$result}++;
174             } else {
175 7         211 $result = ++($self->__unique->{$domain});
176             }
177              
178 507         1590 return $result;
179             }
180              
181             =item C<methodNames>
182              
183             See L<Test::Module::Runnable/methodNames>
184              
185             =cut
186              
187             sub methodNames {
188 6     6 1 2294 my @ret = ( );
189 6         11 my $self = shift;
190 6         33 my @methodList = $self->meta->get_all_methods();
191              
192 6         28675 foreach my $method (@methodList) {
193 292         725 $method = $method->name;
194 292 50       949 next unless ($self->can($method)); # Skip stuff we cannot do
195 292 100       8225 next if ($method !~ $self->pattern); # Skip our own helpers
196 11         32 push(@ret, $method);
197             }
198              
199 6         45 return @ret;
200             }
201              
202             =item C<methodCount>
203              
204             See L<Test::Module::Runnable/methodCount>
205              
206             =cut
207              
208             sub methodCount {
209 2     2 1 410 my $self = shift;
210 2         6 return scalar($self->methodNames());
211             }
212              
213             =item C<run>
214              
215             See L<Test::Module::Runnable/run>
216              
217             =cut
218              
219             sub run {
220 3     3 1 6104 my ($self, %params) = @_;
221 3         9 my ($fail, @tests) = (0);
222              
223 3 100       19 $params{n} = 1 unless ($params{n});
224              
225 3 50       24 if (ref($params{tests}) eq 'ARRAY') { # User specified
226 0         0 @tests = @{ $params{tests} };
  0         0  
227             } else {
228 3         15 @tests = $self->methodNames();
229 3 100       12 if (@ARGV) {
230 1         3 my @userRunTests = ( );
231 1         2 foreach my $testName (@tests) {
232 1         3 foreach my $arg (@ARGV) {
233 2 50       6 next if ($arg ne $testName);
234 0         0 push(@userRunTests, $testName);
235             }
236             }
237              
238 1 50       5 if (scalar(@userRunTests) > 0) {
239 0         0 @tests = @userRunTests;
240             }
241             }
242             }
243              
244 3         23 plan tests => scalar(@tests) * $params{n};
245              
246 3         2507 $fail = $self->setUpBeforeClass(); # Call any registered pre-suite routine
247 3         114 $self->__wrapFail('setUpBeforeClass', undef, $fail);
248 3         17 for (my $i = 0; $i < $params{n}; $i++) {
249 18         50 foreach my $method (@tests) {
250 35         61 my $printableMethodName;
251              
252             # Run correct test (or all)
253 35         79 $printableMethodName = $self->__generateMethodName($method);
254              
255 35         91 $fail = 0;
256              
257             # Check if user specified just one test, and this isn't it
258 35 50       149 confess(sprintf('Test \'%s\' does not exist', $method))
259             unless $self->can($method);
260              
261 35         102 $fail = $self->setUp(method => $method); # Call any registered pre-test routine
262 35         932 $self->__wrapFail('setUp', $method, $fail);
263              
264             subtest $printableMethodName => sub {
265 35     35   28748 $fail = $self->$method(
266             method => $method,
267             printableMethodName => $printableMethodName,
268             );
269 35         264 };
270              
271 35         91293 $self->__wrapFail('method', $method, $fail);
272 35 50       1343 $self->mocker->unmock_all() if ($self->mocker);
273 35         67 $fail = 0;
274 35         116 $fail = $self->tearDown(method => $method); # Call any registered post-test routine
275 35         2232 $self->__wrapFail('tearDown', $method, $fail);
276             }
277 18         68 $fail = $self->modeSwitch($i);
278 18         561 $self->__wrapFail('modeSwitch', $self->sut, $fail);
279             }
280 3         14 $fail = $self->tearDownAfterClass(); # Call any registered post-suite routine
281 3         12 $self->__wrapFail('tearDownAfterClass', undef, $fail);
282              
283 3         27 return EXIT_SUCCESS;
284             }
285              
286             =item C<debug>
287              
288             See L<Test::Module::Runnable/debug>
289              
290             =cut
291              
292             sub debug {
293 0     0 1 0 my (undef, $format, @params) = @_;
294 0 0       0 return unless ($ENV{'TEST_VERBOSE'});
295 0         0 diag(sprintf($format, @params));
296 0         0 return;
297             }
298              
299             =item C<mock($class, $method, $return)>
300              
301             See L<mock($class, $method, $return)>
302              
303             =cut
304              
305             sub mock {
306 0     0 1 0 my ($self, $class, $method, $return) = @_;
307              
308 0 0 0     0 unless ($class->can($method) || $class->can('AUTOLOAD')) {
309 0         0 BAIL_OUT("Cannot mock $class->$method because it doesn't exist and $class has no AUTOLOAD")
310             }
311              
312 0 0 0     0 die('$return must be CODE or ARRAY ref') if defined($return) && ref($return) ne 'CODE' && ref($return) ne 'ARRAY';
      0        
313              
314 0 0       0 unless ($self->{mock_module}->{$class}) {
315 0         0 $self->{mock_module}->{$class} = Test::MockModule->new($class);
316             }
317              
318             $self->{mock_module}->{$class}->mock($method, sub {
319 0     0   0 my @ret;
320 0         0 my @args = @_;
321              
322 0         0 push @{$self->{mock_args}->{$class}->{$method}}, [@args];
  0         0  
323              
324 0 0       0 if ($return) {
325 0         0 my ($val, $empty);
326 0 0       0 if (ref($return) eq 'ARRAY') {
327             # $return is an array ref, so shift the next value
328 0 0       0 if (@$return) {
329 0         0 $val = shift @$return;
330             } else {
331 0         0 $empty = 1;
332             }
333             } else {
334             # here $return must be a CODE ref, so just set $val
335             # and carry on.
336 0         0 $val = $return;
337             }
338              
339 0 0       0 if (ref($val) eq 'CODE') {
340 0 0       0 if (wantarray) {
341 0         0 @ret = $val->(@_);
342             } else {
343 0         0 $ret[0] = scalar $val->(@_);
344             }
345             } else {
346             # just return this value, unless we're in the case
347             # where we exhausted the array, in which case we
348             # don't set this - it would make us return (undef)
349             # rather than empty list in list context.
350 0 0       0 $ret[0] = $val unless $empty;
351             }
352             }
353              
354             # TODO: When running the CODE ref above, we should catch any fatal error,
355             # log them here, and then re-throw the error.
356 0         0 shift @args;
357 0         0 $self->debug(sprintf('%s::%s(%s) returning (%s)',
358             $class, $method, _mockdump(\@args), _mockdump(\@ret)));
359 0 0       0 return (wantarray ? @ret : $ret[0]);
360 0         0 });
361              
362 0         0 return;
363             }
364              
365             =item unmock([class], [$method])
366              
367             See L<Test::Module::Runnable/unmock([class], [$method])>
368              
369             =cut
370              
371             sub unmock {
372 0     0 1 0 my ($self, $class, $method) = @_;
373              
374 0 0       0 if (!$class) {
    0          
375 0 0       0 die('It is not legal to unmock a method in many or unspecified classes') if ($method);
376 0         0 $self->clearMocks;
377             } elsif (!$method) {
378 0         0 delete($self->{mock_module}->{$class});
379 0         0 delete($self->{mock_args}->{$class});
380             } else {
381 0 0       0 if ($self->{mock_module}->{$class}) {
382 0         0 $self->{mock_module}->{$class}->unmock($method);
383             }
384 0         0 delete($self->{mock_args}->{$class}->{$method});
385             }
386              
387 0         0 return $self;
388             }
389              
390             =item C<mockCalls($class, $method)>
391              
392             See L<Test::Module::Runnable/mockCalls($class, $method)>
393              
394             =cut
395              
396             sub mockCalls {
397 0     0 1 0 my ($self, $class, $method) = @_;
398 0         0 return $self->__mockCalls($class, $method);
399             }
400              
401             =item C<mockCallsWithObject($class, $method)>
402              
403             See L<Test::Module::Runnable/mockCallsWithObject($class, $method)>
404              
405             =cut
406              
407             sub mockCallsWithObject {
408 0     0 1 0 my ($self, $class, $method) = @_;
409 0         0 return $self->__mockCalls($class, $method, withObject => 1);
410             }
411              
412             =item C<clearMocks>
413              
414             See L<Test::Module::Runnable/clearMocks>
415              
416             =cut
417              
418             sub clearMocks {
419 0     0 1 0 my ($self) = @_;
420              
421 0         0 $self->{mock_module} = {};
422 0         0 $self->{mock_args} = {};
423 0         0 return;
424             }
425              
426             =back
427              
428             =head1 USER DEFINED METHODS
429              
430             =over
431              
432             =item C<setUpBeforeClass>
433              
434             See L<Test::Module::Runnable/setUpBeforeClass>
435              
436             =item C<tearDownAfterClass>
437              
438             See L<Test::Module::Runnable/tearDownAfterClass>
439              
440             =back
441              
442             =head1 PROTECTED METHODS
443              
444             =over
445              
446             =item C<_mockdump>
447              
448             See L<Test::Module::Runnable/_mockdump>
449              
450             =cut
451              
452             sub _mockdump {
453 0     0   0 my $arg = shift;
454 0         0 my $dumper = Data::Dumper->new([$arg], ['arg']);
455 0         0 $dumper->Indent(1);
456 0         0 $dumper->Maxdepth(1);
457 0         0 my $str = $dumper->Dump();
458 0         0 $str =~ s/\n\s*/ /g;
459 0         0 $str =~ s/^\$arg = \[\s*//;
460 0         0 $str =~ s/\s*\];\s*$//s;
461 0         0 return $str;
462             }
463              
464             =back
465              
466             =head1 PRIVATE METHODS
467              
468             =over
469              
470             =item C<__mockCalls>
471              
472             Helper method used by L</mockCalls($class, $method)> and L</mockCallsWithObject($class, $method)>.
473              
474             =cut
475              
476             sub __mockCalls {
477 0     0   0 my ($self, $class, $method, %args) = @_;
478              
479 0   0     0 my $calls = $self->{mock_args}->{$class}->{$method} || [];
480 0 0       0 unless ($args{withObject}) {
481             # This ugly code takes $calls, which is a an arrayref
482             # of arrayrefs, and maps it into a new arrayref, where
483             # each inner arrayref is a copy of the original, with the
484             # first element removed (i.e. the object reference).
485             #
486             # i.e. given $calls = [
487             # [ $obj, $arg1, $arg2 ],
488             # [ $obj, $arg3, $arg4 ],
489             # ]
490             # this will set $calls = [
491             # [ $arg1, $arg2 ],
492             # [ $arg3, $arg4 ],
493             # ]
494 0         0 $calls = [ map { [ @{$_}[1..$#$_] ] } @$calls ];
  0         0  
  0         0  
495             }
496              
497 0         0 return $calls;
498             }
499              
500             =item __generateMethodName
501              
502             This method returns the current mode of testing the C<sut> as defined
503             in a class derived from L<Test::Module::Runnable>, as a string including the
504             current test method, given to this function.
505              
506             If the subclass has not defined C<modeName> as a method or attribute,
507             or it is C<undef>, we return the C<methodName> passed, unmodified.
508              
509             =over
510              
511             =item C<methodName>
512              
513             The name of the method about to be executed. Must be a valid string.
514              
515             =back
516              
517             =cut
518              
519             sub __generateMethodName {
520 35     35   89 my ($self, $methodName) = @_;
521 35         91 my $modeName = $self->modeName;
522              
523 35 50 33     113 return $methodName unless (defined($modeName) && length($modeName)); # Simples
524 0         0 return sprintf('[%s] %s', $self->modeName, $methodName);
525             }
526              
527             =item C<__wrapFail>
528              
529             Called within L</run> in order to call L<Test::Builder/BAIL_OUT> with an appropriate message -
530             it essentially a way to wrap failures from user-defined methods.
531              
532             As soon as the user-defined method is called, call this method with the following arguments:
533              
534             =over
535              
536             =item C<$type>
537              
538             The name of the user-defined method, for example, 'setUp'
539              
540             =item C<$method>
541              
542             The name of the user test method, for example, 'testMyTestMethod'
543              
544             =item C<$fail>
545              
546             The exit code from the user-defined method. Not a boolean. If not C<EXIT_SUCCESS>,
547             C<BAIL_OUT> will be called.
548              
549             =back
550              
551             There is no return value.
552              
553             =cut
554              
555             sub __wrapFail {
556 129     129   341 my ($self, $type, $method, $returnValue) = @_;
557 129 50 33     652 return if (defined($returnValue) && $returnValue eq '0');
558 0 0         if (!defined($method)) { # Not method-specific
559 0 0 0       BAIL_OUT('Must specify type when evaluating result from method hooks')
560             if ('setUpBeforeClass' ne $type && 'tearDownAfterClass' ne $type);
561              
562 0           $method = 'N/A';
563             }
564 0           return BAIL_OUT($type . ' returned non-zero for ' . $method);
565             }
566              
567             =back
568              
569             =cut
570              
571             1;