File Coverage

blib/lib/Test/Module/Runnable/Base.pm
Criterion Covered Total %
statement 187 196 95.4
branch 74 88 84.0
condition 32 41 78.0
subroutine 29 29 100.0
pod 14 14 100.0
total 336 368 91.3


line stmt bran cond sub pod time code
1             package Test::Module::Runnable::Base;
2             # Module test framework
3             # Copyright (c) 2015-2024, Duncan Ross Palmer (2E0EOL) and others,
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # * Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # * Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # * Neither the name of the Daybo Logic nor the names of its contributors
17             # may be used to endorse or promote products derived from this software
18             # without specific prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31              
32             =head1 NAME
33              
34             Test::Module::Runnable::Base - See L<Test::Module::Runnable>
35              
36             =head1 DESCRIPTION
37              
38             This is the base class for L<Test::Module::Runnable>, and all user-documentation
39             must be sought there.
40              
41             A few internal-only methods are documented here for project maintainers.
42              
43             =cut
44              
45 13     13   9935 use Data::Dumper;
  13         135465  
  13         1286  
46 13     13   110 use Moose;
  13         29  
  13         136  
47              
48             BEGIN {
49 13     13   107104 our $VERSION = '0.6.2';
50             }
51              
52 13     13   109 use POSIX qw/EXIT_SUCCESS/;
  13         29  
  13         137  
53 13     13   7885 use Readonly;
  13         53381  
  13         944  
54 13     13   6723 use Test::MockModule;
  13         83267  
  13         125  
55 13     13   7572 use Test::More 0.96;
  13         671718  
  13         129  
56              
57             Readonly my @UNIQUE_STR_CHARS => ('a'..'z', 'A'..'Z', '0'..'9');
58             Readonly my @UNIQUE_STR_CI_CHARS => ('a'..'z', '0'..'9');
59             Readonly my @UNIQUE_LETTERS_CHARS => ('a'..'z');
60              
61             Readonly my $DOMAIN_DEFAULT => 'db3eb5cf-a597-4038-aea8-fd06faea6eed';
62              
63             # This hash tracks the numbers returned from C<unique>.
64             my %__unique;
65              
66             # This counter used by the uniqueDomain() function
67             my $__domainCounter;
68              
69             # nb. don't add any more static globals here; Construct the object where needed, on the fly, even if you
70             # have not subclassed it, in legacy tests
71              
72             =head1 ATTRIBUTES
73              
74             =over
75              
76             =item C<sut>
77              
78             See L<Test::Module::Runnable/sut>
79              
80             =cut
81              
82             has 'sut' => (is => 'rw', required => 0);
83              
84             =item C<pattern>
85              
86             See L<Test::Module::Runnable/pattern>
87              
88             =cut
89              
90             has 'pattern' => (is => 'ro', isa => 'Regexp', default => sub { qr/^test/ });
91              
92             =item C<logger>
93              
94             See L<Test::Module::Runnable/logger>
95              
96             =cut
97              
98             has 'logger' => (is => 'rw', required => 0);
99              
100             =item C<mocker>
101              
102             See L<Test::Module::Runnable/mocker>
103              
104             =cut
105              
106             has 'mocker' => (
107             is => 'rw',
108             isa => 'Maybe[Test::MockModule]',
109             required => 0,
110             default => undef,
111             );
112              
113             =back
114              
115             =head1 PRIVATE ATTRIBUTES
116              
117             =over
118              
119             =item C<__unique>
120              
121             Tracks the counter returned by C<unique>.
122             Always contains the previous value returned, or zero before any calls.
123             A hash is used to support multiple domains.
124              
125             =cut
126              
127             has '__unique' => (
128             is => 'ro',
129             isa => 'HashRef[Int]',
130             default => sub {
131             { }
132             },
133             );
134              
135             =item C<__random>
136              
137             Hash of random numbers already given out.
138              
139             =cut
140              
141             has '__random' => (
142             is => 'ro',
143             isa => 'HashRef[Int]',
144             default => sub {
145             { }
146             },
147             );
148              
149             =back
150              
151             =head1 METHODS
152              
153             =over
154              
155             =item C<unique>
156              
157             See L<Test::Module::Runnable/unique>
158              
159             =cut
160              
161             sub unique {
162 1309     1309 1 2916 my (@args) = @_;
163 1309 50 66     5690 confess('Suspected incorrect call: Test::Module::Runnable->unique(...)') if ($args[0] && $args[0] eq __PACKAGE__);
164              
165 1309 100       3202 shift(@args) if ref($args[0]);
166 1309         2866 my $domain = $args[0];
167              
168 1309 100 100     5682 if (!defined($domain) || length($domain) == 0) {
    100          
169 806         2636 $domain = $DOMAIN_DEFAULT;
170             } elsif ($domain eq 'rand') {
171 500         3388 return $__unique{$domain} = int(rand(999_999_999));
172             }
173              
174 809 100 100     5629 if (!defined($__unique{$domain}) && $ENV{TEST_UNIQUE}) {
175 6         26 $__unique{$domain} = int($ENV{TEST_UNIQUE}) - 1; # we add 1 to first return value below
176             }
177              
178 809         2075 return ++$__unique{$domain};
179             }
180              
181             =item C<uniqueStr([$length])>
182              
183             See L<Test::Module::Runnable/uniqueStr($length)>
184              
185             =cut
186              
187             sub uniqueStr {
188 67     67 1 76 my (@args) = @_;
189 67         95 return __uniqueStrHelper(\@UNIQUE_STR_CHARS, @args);
190             }
191              
192             =item C<uniqueStrCI($length)>
193              
194             See L<Test::Module::Runnable/uniqueStrCI($length)>
195              
196             =cut
197              
198             sub uniqueStrCI {
199 380     380 1 874 my (@args) = @_;
200 380         927 return __uniqueStrHelper(\@UNIQUE_STR_CI_CHARS, @args);
201             }
202              
203             =item C<uniqueDomain([$options])>
204              
205             See L<Test::Module::Runnable/uniqueDomain([$options])>
206              
207             =cut
208              
209             sub uniqueDomain {
210 208     208 1 427 my (@args) = @_;
211              
212 208 100       622 shift @args if ref($args[0]); # Remove $self
213 208         461 my ($options) = (@args);
214              
215 208         399 my $counter = ++$__domainCounter;
216 208         400 my $extraParts = ($counter % 4);
217              
218 208         498 my $firstPart = __domainPart($counter, -1, $options);
219 208         817 return lc join('.', $firstPart, (map { __domainPart($counter, $_, $options) } 0..$extraParts), 'test');
  521         1112  
220             }
221              
222             =item C<uniqueLetters($length)>
223              
224             See L<Test::Module::Runnable/uniqueLetters($length)>
225              
226             =cut
227              
228             sub uniqueLetters {
229 352     352 1 811 my (@args) = @_;
230 352         895 return __uniqueStrHelper(\@UNIQUE_LETTERS_CHARS, @args);
231             }
232              
233             =item C<methodNames>
234              
235             See L<Test::Module::Runnable/methodNames>
236              
237             =cut
238              
239             sub methodNames {
240 15     15 1 8760 my ($self) = @_;
241 15         37 my @ret = ( );
242 15         104 my @methodList = $self->meta->get_all_methods();
243              
244 15         230354 foreach my $method (@methodList) {
245 1190         3831 $method = $method->name;
246 1190 50       5872 next unless ($self->can($method)); # Skip stuff we cannot do
247 1190 100       48103 next if ($method !~ $self->pattern); # Skip our own helpers
248 36         120 push(@ret, $method);
249             }
250              
251 15         206 return @ret;
252             }
253              
254             =item C<methodCount>
255              
256             See L<Test::Module::Runnable/methodCount>
257              
258             =cut
259              
260             sub methodCount {
261 2     2 1 577 my ($self) = @_;
262 2         6 return scalar($self->methodNames());
263             }
264              
265             =item C<run>
266              
267             See L<Test::Module::Runnable/run>
268              
269             =cut
270              
271             sub run {
272 12     12 1 17811 my ($self, %params) = @_;
273 12         38 my ($fail, @tests) = (0);
274              
275 12 100       93 $params{n} = 1 unless ($params{n});
276              
277 12 50       70 if (ref($params{tests}) eq 'ARRAY') { # User specified
278 0         0 @tests = @{ $params{tests} };
  0         0  
279             } else {
280 12         107 @tests = $self->methodNames();
281 12 100       70 if (@ARGV) {
282 1         2 my @userRunTests = ( );
283 1         2 foreach my $testName (@tests) {
284 1         2 foreach my $arg (@ARGV) {
285 2 50       12 next if ($arg ne $testName);
286 0         0 push(@userRunTests, $testName);
287             }
288             }
289              
290 1 50       3 if (scalar(@userRunTests) > 0) {
291 0         0 @tests = @userRunTests;
292             }
293             }
294             }
295              
296 12         106 plan tests => scalar(@tests) * $params{n};
297              
298 12         15607 $fail = $self->setUpBeforeClass(); # Call any registered pre-suite routine
299 12         333 $self->__wrapFail('setUpBeforeClass', undef, $fail);
300 12         76 for (my $i = 0; $i < $params{n}; $i++) {
301 29         104 foreach my $method (@tests) {
302 62         126 my $printableMethodName;
303              
304             # Run correct test (or all)
305 62         237 $printableMethodName = $self->__generateMethodName($method);
306              
307 62         219 $fail = 0;
308              
309             # Check if user specified just one test, and this isn't it
310 62 50       861 confess(sprintf('Test \'%s\' does not exist', $method))
311             unless $self->can($method);
312              
313 62         351 $fail = $self->setUp(method => $method); # Call any registered pre-test routine
314 62         1309 $self->__wrapFail('setUp', $method, $fail);
315              
316             subtest $printableMethodName => sub {
317 62     62   96054 $fail = $self->$method(
318             method => $method,
319             printableMethodName => $printableMethodName,
320             );
321 62         599 };
322              
323 62         1617207 $self->__wrapFail('method', $method, $fail);
324 62 50       4603 $self->mocker->unmock_all() if ($self->mocker);
325 62         143 $fail = 0;
326 62         8516 $fail = $self->tearDown(method => $method); # Call any registered post-test routine
327 62         1798 $self->__wrapFail('tearDown', $method, $fail);
328             }
329 29         180 $fail = $self->modeSwitch($i);
330 29         1467 $self->__wrapFail('modeSwitch', $self->sut, $fail);
331             }
332 12         63 $fail = $self->tearDownAfterClass(); # Call any registered post-suite routine
333 12         70 $self->__wrapFail('tearDownAfterClass', undef, $fail);
334              
335 12         351 return EXIT_SUCCESS;
336             }
337              
338             =item C<debug>
339              
340             See L<Test::Module::Runnable/debug>
341              
342             =cut
343              
344             sub debug {
345 40     40 1 226 my (undef, $format, @params) = @_;
346 40 100       203 return unless ($ENV{'TEST_VERBOSE'});
347 4         26 diag(sprintf($format, @params));
348 4         96 return;
349             }
350              
351             =item C<mock($class, $method, $return)>
352              
353             See L<mock($class, $method, $return)>
354              
355             =cut
356              
357             sub mock {
358 22     22 1 1040 my ($self, $class, $method, $return) = @_;
359              
360 22 50 66     249 unless ($class->can($method) || $class->can('AUTOLOAD')) {
361 0         0 BAIL_OUT("Cannot mock $class->$method because it doesn't exist and $class has no AUTOLOAD")
362             }
363              
364 22 100 100     149 die('$return must be CODE or ARRAY ref') if defined($return) && ref($return) ne 'CODE' && ref($return) ne 'ARRAY';
      100        
365              
366 21 100       80 unless ($self->{mock_module}->{$class}) {
367 16         106 $self->{mock_module}->{$class} = Test::MockModule->new($class);
368             }
369              
370             $self->{mock_module}->{$class}->mock($method, sub {
371 38     38   3709 my @ret;
372 38         113 my @args = @_;
373              
374 38         58 push @{$self->{mock_args}->{$class}->{$method}}, [@args];
  38         214  
375              
376 38 100       125 if ($return) {
377 31         57 my ($val, $empty);
378 31 100       153 if (ref($return) eq 'ARRAY') {
379             # $return is an array ref, so shift the next value
380 20 100       44 if (@$return) {
381 16         38 $val = shift @$return;
382             } else {
383 4         9 $empty = 1;
384             }
385             } else {
386             # here $return must be a CODE ref, so just set $val
387             # and carry on.
388 11         19 $val = $return;
389             }
390              
391 31 100       105 if (ref($val) eq 'CODE') {
392 13 100       33 if (wantarray) {
393 6         21 @ret = $val->(@_);
394             } else {
395 7         22 $ret[0] = scalar $val->(@_);
396             }
397             } else {
398             # just return this value, unless we're in the case
399             # where we exhausted the array, in which case we
400             # don't set this - it would make us return (undef)
401             # rather than empty list in list context.
402 18 100       54 $ret[0] = $val unless $empty;
403             }
404             }
405              
406             # TODO: When running the CODE ref above, we should catch any fatal error,
407             # log them here, and then re-throw the error.
408 37         139 shift @args;
409 37         120 $self->debug(sprintf('%s::%s(%s) returning (%s)',
410             $class, $method, _mockdump(\@args), _mockdump(\@ret)));
411 37 100       304 return (wantarray ? @ret : $ret[0]);
412 21         1026 });
413              
414 21         3359 return;
415             }
416              
417             =item unmock([class], [$method])
418              
419             See L<Test::Module::Runnable/unmock([class], [$method])>
420              
421             =cut
422              
423             sub unmock {
424 6     6 1 84 my ($self, $class, $method) = @_;
425              
426 6 100       20 if (!$class) {
    100          
427 2 100       14 die('It is not legal to unmock a method in many or unspecified classes') if ($method);
428 1         2 $self->clearMocks;
429             } elsif (!$method) {
430 1         4 delete($self->{mock_module}->{$class});
431 1         10 delete($self->{mock_args}->{$class});
432             } else {
433 3 100       11 if ($self->{mock_module}->{$class}) {
434 2         10 $self->{mock_module}->{$class}->unmock($method);
435             }
436 3         385 delete($self->{mock_args}->{$class}->{$method});
437             }
438              
439 5         327 return $self;
440             }
441              
442             =item C<mockCalls($class, $method)>
443              
444             See L<Test::Module::Runnable/mockCalls($class, $method)>
445              
446             =cut
447              
448             sub mockCalls {
449 5     5 1 17 my ($self, $class, $method) = @_;
450 5         18 return $self->__mockCalls($class, $method);
451             }
452              
453             =item C<mockCallsWithObject($class, $method)>
454              
455             See L<Test::Module::Runnable/mockCallsWithObject($class, $method)>
456              
457             =cut
458              
459             sub mockCallsWithObject {
460 1     1 1 3 my ($self, $class, $method) = @_;
461 1         4 return $self->__mockCalls($class, $method, withObject => 1);
462             }
463              
464             =item C<clearMocks>
465              
466             See L<Test::Module::Runnable/clearMocks>
467              
468             =cut
469              
470             sub clearMocks {
471 16     16 1 71 my ($self) = @_;
472              
473 16         92 $self->{mock_module} = {};
474 16         1218 $self->{mock_args} = {};
475 16         1124 return;
476             }
477              
478             =back
479              
480             =head1 USER DEFINED METHODS
481              
482             =over
483              
484             =item C<setUpBeforeClass>
485              
486             See L<Test::Module::Runnable/setUpBeforeClass>
487              
488             =item C<tearDownAfterClass>
489              
490             See L<Test::Module::Runnable/tearDownAfterClass>
491              
492             =back
493              
494             =head1 PROTECTED METHODS
495              
496             =over
497              
498             =item C<_mockdump>
499              
500             See L<Test::Module::Runnable/_mockdump>
501              
502             =cut
503              
504             sub _mockdump {
505 74     74   154 my ($arg) = @_;
506 74         384 my $dumper = Data::Dumper->new([$arg], ['arg']);
507 74         2314 $dumper->Indent(1);
508 74         1029 $dumper->Maxdepth(1);
509 74         486 my $str = $dumper->Dump();
510 74         1950 $str =~ s/\n\s*/ /g;
511 74         248 $str =~ s/^\$arg = \[\s*//;
512 74         349 $str =~ s/\s*\];\s*$//s;
513 74         812 return $str;
514             }
515              
516             =back
517              
518             =head1 PRIVATE METHODS
519              
520             =over
521              
522             =item C<__mockCalls>
523              
524             Helper method used by L</mockCalls($class, $method)> and L</mockCallsWithObject($class, $method)>.
525              
526             =cut
527              
528             sub __mockCalls {
529 6     6   18 my ($self, $class, $method, %args) = @_;
530              
531 6   100     31 my $calls = $self->{mock_args}->{$class}->{$method} || [];
532 6 100       21 unless ($args{withObject}) {
533             # This ugly code takes $calls, which is a an arrayref
534             # of arrayrefs, and maps it into a new arrayref, where
535             # each inner arrayref is a copy of the original, with the
536             # first element removed (i.e. the object reference).
537             #
538             # i.e. given $calls = [
539             # [ $obj, $arg1, $arg2 ],
540             # [ $obj, $arg3, $arg4 ],
541             # ]
542             # this will set $calls = [
543             # [ $arg1, $arg2 ],
544             # [ $arg3, $arg4 ],
545             # ]
546 5         15 $calls = [ map { [ @{$_}[1..$#$_] ] } @$calls ];
  7         19  
  7         28  
547             }
548              
549 6         47 return $calls;
550             }
551              
552             =item __generateMethodName
553              
554             This method returns the current mode of testing the C<sut> as defined
555             in a class derived from L<Test::Module::Runnable>, as a string including the
556             current test method, given to this function.
557              
558             If the subclass has not defined C<modeName> as a method or attribute,
559             or it is C<undef>, we return the C<methodName> passed, unmodified.
560              
561             =over
562              
563             =item C<methodName>
564              
565             The name of the method about to be executed. Must be a valid string.
566              
567             =back
568              
569             =cut
570              
571             sub __generateMethodName {
572 62     62   205 my ($self, $methodName) = @_;
573 62         284 my $modeName = $self->modeName;
574              
575 62 100 66     424 return $methodName unless (defined($modeName) && length($modeName)); # Simples
576 3         9 return sprintf('[%s] %s', $self->modeName, $methodName);
577             }
578              
579             =item C<__wrapFail>
580              
581             Called within L</run> in order to call L<Test::Builder/BAIL_OUT> with an appropriate message -
582             it essentially a way to wrap failures from user-defined methods.
583              
584             As soon as the user-defined method is called, call this method with the following arguments:
585              
586             =over
587              
588             =item C<$type>
589              
590             The name of the user-defined method, for example, 'setUp'
591              
592             =item C<$method>
593              
594             The name of the user test method, for example, 'testMyTestMethod'
595              
596             =item C<$fail>
597              
598             The exit code from the user-defined method. Not a boolean. If not C<EXIT_SUCCESS>,
599             C<BAIL_OUT> will be called.
600              
601             =back
602              
603             There is no return value.
604              
605             =cut
606              
607             sub __wrapFail {
608 239     239   718 my ($self, $type, $method, $returnValue) = @_;
609 239 50 33     1604 return if (defined($returnValue) && $returnValue eq '0');
610 0 0       0 if (!defined($method)) { # Not method-specific
611 0 0 0     0 BAIL_OUT('Must specify type when evaluating result from method hooks')
612             if ('setUpBeforeClass' ne $type && 'tearDownAfterClass' ne $type);
613              
614 0         0 $method = 'N/A';
615             }
616 0         0 return BAIL_OUT($type . ' returned non-zero for ' . $method);
617             }
618              
619             =item C<__uniqueStrHelper(@args)>
620              
621             Helper method for L</uniqueStr([$length])>.
622              
623             =cut
624              
625             sub __uniqueStrHelper {
626 801     801   1664 my (@args) = @_;
627 801         2062 my $chars = shift(@args);
628 801         2867 my $len = scalar(@$chars);
629              
630 801         7471 my $func = (caller(1))[3];
631 801         5620 $func =~ s/.*:://;
632 801 50 66     3936 confess("Suspected incorrect call: Test::Module::Runnable->$func") if ($args[0] && $args[0] eq __PACKAGE__);
633              
634 801 100       1771 shift(@args) if ref($args[0]);
635 801         1543 my $length = $args[0];
636 801         1528 my $str = '';
637              
638             # default length 1
639 801 100       1890 $length = 1 unless(defined($length));
640              
641 801         1758 my $num = unique();
642 801         1565 my $oddOrEven = ($num % 2);
643 801   100     2103 while ($num > 0 || length($str) < $length) {
644             # This character will be the current number modulo the character set length
645 2409         4094 my $modulo = $num % $len;
646              
647             # use any remainder next time through
648 2409         4742 $num = int($num / $len);
649              
650 2409         6814 my $char = $chars->[$modulo];
651 2409 100 100     25525 if ($chars == \@UNIQUE_STR_CI_CHARS && length($str) % 2 == $oddOrEven) {
652 604         1281 $char = uc($char);
653             }
654              
655 2409         13718 $str = $char . $str;
656             }
657              
658 801 100       1850 $str = __uniqueStrHelper($chars, $length) if ($str eq '0'); # unacceptable
659              
660 801         2348 return $str;
661             }
662              
663             =item C<__domainPart>
664              
665             Helper method used by L</uniqueDomain([$options])> to construct a single part of a domain name.
666              
667             Returns a string.
668              
669             =cut
670              
671             sub __domainPart {
672 729     729   1701 my ($counter, $pos, $options) = @_;
673              
674 729         1224 my $length;
675 729 100 100     2332 if ($pos < 0 && $options->{length}) {
676             # Use specified length, if there is one, for the first part only.
677 1         3 $length = $options->{length};
678             }
679              
680             # arbitrary calculation to come up with varying but
681             # predictable lengths for a given counter (n-th domain created)
682             # and position $pos within the domain
683 729 100       2276 $length = 1 + (($counter * 7 ^ $pos) % 5) unless $length;
684              
685 729 100       2184 my $str = $options->{lettersOnly} ? uniqueLetters($length) : uniqueStrCI($length);
686 729 100       3000 $str = uniqueLetters(1).$str unless $str =~ /^[a-z]/i;
687              
688 729         3319 return $str;
689             }
690              
691             =back
692              
693             =cut
694              
695             1;