File Coverage

blib/lib/Test/Subs.pm
Criterion Covered Total %
statement 146 182 80.2
branch 39 80 48.7
condition 7 14 50.0
subroutine 35 36 97.2
pod 10 18 55.5
total 237 330 71.8


line stmt bran cond sub pod time code
1             package Test::Subs;
2             our $VERSION = '0.08';
3 4     4   2936 use strict;
  4         44  
  4         128  
4 4     4   307 use warnings;
  4         9  
  4         105  
5 4     4   22 use feature 'switch';
  4         9  
  4         484  
6 4     4   10024 use parent 'Exporter';
  4         1577  
  4         21  
7 4     4   3952 use Filter::Simple;
  4         153538  
  4         29  
8 4     4   215 use Carp;
  4         9  
  4         248  
9 4     4   4976 use Pod::Checker;
  4         54414  
  4         600  
10 4     4   42 use File::Basename;
  4         9  
  4         402  
11 4     4   4029 use File::Spec::Functions;
  4         3591  
  4         366  
12 4     4   3958 use List::MoreUtils 'any';
  4         5127  
  4         12277  
13              
14              
15             our @EXPORT = ('test', 'todo', 'not_ok', 'match', 'fail', 'failwith', 'comment',
16             'debug', 'test_pod', 'skip'
17             );
18              
19             my (@tests, @todo, @comments,@pods);
20             my ($has_run, $is_running);
21              
22             my $debug_mode = 0;
23             my $pod_warn_level = 1;
24             my $path_to_lib = './lib';
25              
26             sub debug_mode (;$) {
27 10     10 0 16 my $r = $debug_mode;
28 10 50       42 $debug_mode = ($_[0] ? 1 : 0) if @_;
    100          
29 10         109 return $r;
30             }
31              
32             sub path_to_lib (;$) {
33 3     3 0 8 my $r = $path_to_lib;
34 3 100       33 return $r if not @_;
35 1 50       6 if (defined $_[0]) {
36 1 50       7 if (file_name_is_absolute($_[0])) {
37 0         0 $path_to_lib = $_[0];
38             } else {
39 1         109 $path_to_lib = catdir(dirname($0), $_[0]);
40             }
41 1 50       34 if (not -d $path_to_lib) {
42 0         0 my $v = $path_to_lib;
43 0         0 $path_to_lib = $r;
44 0         0 croak "Cannot find directory '${v}'" ;
45             }
46             } else {
47 0         0 undef $path_to_lib;
48             }
49 1         6 return $r;
50             }
51              
52             sub pod_warn_level (;$) {
53 4     4 0 10 my $r = $pod_warn_level;
54 4 100       27 return $r if not @_;
55 2 50 33     28 if (defined $_[0] && $_[0] =~ m/^\s*(\d+)\s*$/) {
56 2         8 $pod_warn_level = $1;
57             } else {
58 0 0       0 $pod_warn_level = $_[0] ? 1 : 0;
59             }
60 2         10 return $r;
61             }
62              
63             sub check_text {
64 12     12 0 26 my ($t) = @_;
65              
66 12 100       33 if ($t) {
    50          
67 5         21 $t =~ m/^(?: - )?([^\n]*)/;
68 5         113 return " - $1";
69             } elsif (not defined $t) {
70 7         44 my ($package, $filename, $line) = caller(1);
71 7         47 return " - $filename at line $line";
72             } else {
73 0         0 return '';
74             }
75             }
76              
77             sub check_run {
78 19     19 0 188 my @c = caller(0);
79              
80 19 100       157 if ($is_running) {
    50          
81 1         246 croak "You cannot call '$c[3]' inside of an other test"
82 32     32   131 } elsif (any { m/__BAD_MARKER__/ } @_) {
83 0         0 croak "Improper syntax, you may have forgotten a ';'"
84             }
85             }
86              
87             sub debug (&;$) {
88 6     6 1 14 my ($v, $t) = @_;
89 6         10 &check_run;
90              
91             push @tests, {
92             code => sub {
93 6     6   9 my $r = eval { $v->() };
  6         18  
94 6 50       103 print STDERR $@ if $@;
95 6         14 $r
96             },
97 6         31 text => check_text($t)
98             };
99              
100 6         15 return '__BAD_MARKER__';
101             }
102              
103             sub test (&;$) {
104 6     6 1 49 my ($v, $t) = @_;
105 6         12 &check_run;
106 5 50       17 goto &debug if debug_mode;
107              
108             push @tests, {
109 0     0   0 code => sub { eval { $v->() } },
  0         0  
110 0         0 text => check_text($t)
111             };
112              
113 0         0 return '__BAD_MARKER__';
114             }
115              
116             sub match (&$;$) {
117 2     2 1 23 my ($v, $re, $t) = @_;
118              
119 2         8 &check_run;
120              
121 2 50       47 $re = qr/$re/ if not ref $re;
122             push @tests, {
123             code => sub {
124 2     2   5 my $r = eval { $v->() };
  2         9  
125 2 50       114328 if ($@) {
    50          
    50          
126 0 0       0 print STDERR $@ if debug_mode;
127 0         0 return;
128             } elsif (not defined $r) {
129 0 0       0 print STDERR "test sub returned 'undef'\n" if debug_mode;
130 0         0 return;
131             } elsif ($r =~ m/$re/) {
132 2         20 return 1;
133             } else {
134 0 0       0 print STDERR "'$r' does not match '$re'\n" if debug_mode;
135 0         0 return;
136             }
137             },
138 2         19 text => check_text($t)
139             };
140              
141 2         7 return '__BAD_MARKER__';
142             }
143              
144             sub todo (&;$) {
145 1     1 1 9 &check_run;
146 1         4 push @todo, (scalar(@tests) + 1);
147 1         3 goto &test;
148              
149 0         0 return '__BAD_MARKER__';
150             }
151              
152             sub not_ok (&;$) {
153 1     1 1 8 my $v = $_[0];
154              
155 1         3 &check_run;
156              
157             push @tests, {
158             code => sub {
159 1     1   2 my $r = eval { $v->() };
  1         4  
160 1 50       10 if ($@) {
    50          
161 0 0       0 print STDERR $@ if debug_mode;
162 0         0 return;
163             } elsif ($r) {
164 0 0       0 print STDERR "Test sub returned '$r', expected a false value\n" if debug_mode;
165 0         0 return;
166             } else {
167 1         3 return 1;
168             }
169             },
170 1         9 text => check_text($_[1])
171             };
172              
173 1         4 return '__BAD_MARKER__';
174             }
175              
176             sub failwith (&$;$) {
177 2     2 1 17 my ($v, $re, $t) = @_;
178              
179 2         4 &check_run;
180              
181 2 100       20 $re = qr/$re/ if not ref $re;
182             push @tests, {
183             code => sub {
184 2     2   3 eval { $v->() };
  2         7  
185 2 50 33     72 if ($@ && $@ =~ m/$re/) {
    0          
186 2         5 return 1;
187             } elsif ($@) {
188 0 0       0 print STDERR "'$@' does not match '$re'\n" if debug_mode;
189 0         0 return;
190             } else {
191 0 0       0 print STDERR "Test sub did not return any exception\n" if debug_mode;
192 0         0 return;
193             }
194             },
195 2         11 text => check_text($t)
196             };
197              
198 2         5 return '__BAD_MARKER__';
199             }
200              
201             sub fail (&;$) {
202 1     1 1 8 my ($v, $t) = @_;
203 1         6 &failwith($v, qr//, check_text($t), @_); # @_ est là juste pour le test du marqueur
204              
205 1         3 return '__BAD_MARKER__';
206             }
207              
208             sub test_pod (@) {
209 2     2 1 37 push @pods, @_;
210              
211 2         12 return '__BAD_MARKER__';
212             }
213              
214             sub comment (&) {
215 1     1 1 8 my ($c) = @_;
216 1 50       3 if ($is_running) { # undocumented feature
217 0         0 my $r = eval { $c->() };
  0         0  
218 0         0 chomp($r);
219 0         0 print STDERR $r."\n";
220             } else {
221 1         6 push @comments, {
222             comment => $c,
223             after => scalar(@tests)
224             };
225             }
226              
227 1         2 return '__BAD_MARKER__';
228             }
229              
230             my $count = 0;
231              
232             sub print_comment {
233 14   100 14 0 111 while (@comments and $comments[0]->{after} == $count) {
234 1         3 my $c = shift @comments;
235 1         2 my $r = eval { $c->{comment}->() };
  1         5  
236 1         7 chomp($r);
237 1         78 print STDERR $r."\n";
238             }
239             }
240              
241             sub print_res {
242 13     13 0 50 my ($ok, $m) = @_;
243 13 100       3764 printf STDOUT "%sok %d%s\n", ($ok ? '' : 'not '), ++$count, $m;
244             }
245              
246             sub skip {
247 1     1 1 8 my ($reason) = @_;
248 1         5 &check_run;
249            
250 1 50       5 if ($reason) {
251 1         481 print STDOUT "1..0 # skip $reason\n";
252             } else {
253 0         0 print STDOUT "1..0\n";
254             }
255            
256 1         3 $has_run = 1;
257              
258 1         106 exit 0;
259             }
260              
261             sub run_test {
262 3     3 0 20 $is_running = 1;
263              
264 3 100       297 printf STDERR "Running tests in DEBUG mode\n" if $debug_mode;
265              
266 3         13 my $nb_test = @tests + @pods;
267 3 100       20 my $todo_str = @todo ? ' todo '.join(' ', @todo).';' : '';
268            
269 3         1529 printf STDOUT "1..%d%s\n", $nb_test, $todo_str;
270            
271 3         47 print_comment();
272 3         10 for my $t (@tests) {
273 11         42 my $r = $t->{code}->();
274 11   50     57 chomp(my $cr = $r // ''); # //
275 11         47 my $m = sprintf $t->{text}, $cr;
276 11         45 print_res($r, $m);
277 11         62 print_comment();
278             }
279              
280 3         15 for my $m (@pods) {
281 2         13 my $checker = Pod::Checker->new(-warnings => pod_warn_level(), -quiet => (not debug_mode()));
282 2         166 my $f = $m;
283 2         25 $f =~ s{::}{/}g;
284 2         10 $f = catfile(path_to_lib(), "${f}.pm");
285 2 50 33     119 if (-e $f and -r _) {
286 2         5 eval { $checker->parse_from_file($f, \*STDERR) };
  2         3991  
287 2 50       53897 if ($@) {
288 0 0       0 print STDERR $@ if debug_mode;
289 0         0 print_res(0, " - error while checking POD for $m");
290             } else {
291 2         13 print_res(!$checker->num_errors(), " - POD check for $m");
292             }
293             } else {
294 0         0 print_res(0, " - Cannot read $f");
295             }
296             }
297              
298 3         11 $has_run = 1;
299              
300 3         498 return 1; # pour le mécanisme de 'do' utilisé dans CLI-Args/t/magic.t
301             }
302              
303             BEGIN {
304 4     4   21 $| = 1;
305 4         16 select(STDERR);
306 4         3251 $| = 1;
307             }
308              
309             END {
310 4 50   4   0 if (not $has_run) {
311 0         0 printf STDOUT "1..1\nnot ok 1 - compilation of file '$0' failed.\n";
312             }
313             }
314              
315             FILTER {
316             $_ .= ';Test::Subs::run_test()'
317             };
318              
319              
320             sub import {
321             my ($class, @args) = @_;
322              
323             while (my $o = shift @args) {
324             given ($o) {
325             when('debug') {
326             croak "Missing argument to the '$o' option" unless @args;
327             debug_mode(shift @args);
328             }
329             when('lib') {
330             croak "Missing argument to the '$o' option" unless @args;
331             path_to_lib(shift @args);
332             }
333             when('pod_warn') {
334             croak "Missing argument to the '$o' option" unless @args;
335             pod_warn_level(shift @args);
336             }
337             default {
338             croak "Unknown argument '$o'";
339             }
340             }
341             }
342            
343             #@_ = ($class);
344             #goto &Exporter::import;
345             __PACKAGE__->export_to_level(1, $class, @EXPORT);
346             }
347              
348             1;
349              
350             =encoding utf-8
351              
352             =head1 NAME
353              
354             Test::Subs - Test your modules with a lightweight syntax based on anonymous block
355              
356             =head1 SYNOPSIS
357              
358             use SomeModule;
359             use Test::Subs;
360            
361             test { 1 == 2 };
362              
363             =head1 DESCRIPTION
364              
365             This module provide a very lightweight syntax to run C or
366             C compliant test on your code.
367              
368             As opposed to other similar packages, the two main functionnalities of C
369             are that the tests are anonymous code block (rather than list of values), which
370             are (subjectively) cleaner and easier to read, and that you do not need to
371             pre-declare the number of tests that are going to be run (so all modifications in
372             a test file are local).
373              
374             Using this module is just a matter of C followed by the
375             declaration of your tests with the functions described below. All tests are then
376             run at the end of the execution of your test file.
377              
378             As a protection against some error, if the compilation phase fail, the output of
379             the test file will be one failed pseudo-test.
380              
381             =head1 FUNCTIONS
382              
383             This is a list of the public function of this library. Functions not listed here
384             are for internal use only by this module and should not be used in any external
385             code unless .
386              
387             All the functions described below are automatically exported into your package
388             except if you explicitely request to opposite with C.
389              
390             Finally, these function must all be called from the top-level and not inside of
391             the code of another test function. That is because the library must know the
392             number of test before their execution.
393              
394             =head2 test
395              
396             test { CODE };
397             test { CODE } DESCR;
398              
399             This function register a code-block containing a test. During the execution of
400             the test, the code will be run and the test will be deemed successful if the
401             returned value is C.
402              
403             The optionnal C is a string (or an expression returning a string) which
404             will be added as a comment to the result of this test. If this string contains
405             a C I (e.g. C<%s> or C<%d>) it will be replaced by the result
406             of the code block. If the description is omitted, it will be replaced by the
407             filename and line number of the test. You can use an empty string C<''> to
408             deactivate completely the output of a comment to the test.
409              
410             =head2 todo
411              
412             todo { CODE };
413             todo { CODE } DESCR;
414              
415             This function is the same as the function C, except that the test will be
416             registered as I. So a failure of this test will be ignored when your test
417             is run inside a test plan by C or C.
418              
419             =head2 match
420              
421             match { CODE } REGEXP;
422             match { CODE } REGEXP, DESCR;
423              
424             This function declares a test which will succeed if the result of the code block
425             match the given regular expression.
426              
427             The regexp may be given as a scalar string or as a C encoded regexp.
428              
429             =head2 not_ok
430              
431             not_ok { CODE };
432             not_ok { CODE } DESCR;
433              
434             This function is exactly the opposite of the C one. The test that it declares
435             will succeed if the code block return a C value.
436              
437             =head2 fail
438              
439             fail { CODE };
440             fail { CODE } DESCR;
441              
442             This function declares a test that will succeed if its code block C (raise
443             any exception).
444              
445             =head2 failwith
446              
447             failwith { CODE } REGEXP;
448             failwith { CODE } REGEXP, DESCR;
449              
450             As for the C function, this function declares a test which expects that its
451             code block C. Except that the test will succeed only if the raised exception
452             (the content of the C<$@> variable) match the given regular expression.
453              
454             The regexp may be given as a scalar string or as a C encoded regexp.
455              
456             =head2 comment
457              
458             comment { CODE };
459              
460             This function evaluate its code and display the resulting value on the standard
461             error handle. The buffering on C and C is deactivated when
462             C is used and the output of this function should appear in between
463             the result of the test when the test file is run stand-alone.
464              
465             This function must be used outside of the code of the other functions described
466             above. To output comment to C inside a test, just use the C or
467             C function. The default output has been C
468             the result of the test will not be altered.
469              
470             =head2 skip (new in 0.07)
471              
472             skip 'reason' unless eval 'use Foo::Bar';
473              
474             This function allows to skip a test file. It must be used outside of test subs
475             of the other functions. You will typically use it to disable a test file if the
476             current version of Perl is missing some required functionnalities for the tests.
477              
478             The argument for the function is a string explaining the reason why the tests
479             have been skipped. This reasion will be reported in the output of a C
480             run.
481              
482             =head2 test_pod (new in 0.04)
483              
484             test_pod(LIST);
485              
486             This function takes a list of module name and registers one test for each given
487             module. The test will run the module file through C> and fail if
488             there is errors in the POD of the file. Moreover, in debug mode, all errors and
489             warnings are printed to C.
490              
491             =head2 debug
492              
493             debug { CODE } DESCR;
494              
495             This function register and executes a dummy test: the CODE is executed and
496             error messages (if any) are written on C. The test will succeed under the
497             same condition as with the C function.
498              
499             Usefull when a test fail to quickly see what is going on.
500              
501             =head1 OPTIONS
502              
503             =head2 Debug mode (new in 0.03)
504              
505             You can pass a C argument to the package when you are C it:
506              
507             use Test::Subs debug => 1;
508              
509             If the value supplied to this option is I then all call to the C
510             functions will behave like calls to the C function. Also, most of the
511             function of this library will give more output (on C) if their test
512             fails.
513              
514             =head2 Path to the library files (new in 0.05)
515              
516             By default, if you specify a C<'My::Module'> module as a target of the C
517             function, the file for this module will be searched in C
518             B. This should work for standard
519             distribution. Yau can modify this behaviour with the C option as argument
520             to the package when you are C it:
521              
522             use Test::Subs lib => '../lib';
523              
524             The supplied path will serve as the base directory to look for the module file
525             (e.g. C), B (and not
526             to the current working directory as in the default case).
527              
528             =head2 Warning level for POD Checking (new in 0.05)
529              
530             You can tune the number of warning generated by the C function using
531             a C argument to the package when you are C it:
532              
533             use Test::Subs pod_warn => 0;
534              
535             This option expects an integer value. A value of C<'0'> will deactivates all
536             warnings, a value of C<'1'> will activates most warnings and a value of C<'2'>
537             will activates some additionnals warnings. More details on the available warnings
538             can be found in the L documentation|Pod::Checker/"Warnings">.
539              
540             Note that, in any case, the warnings will only be printed in C mode.
541              
542             =head1 EXAMPLE
543              
544             Here is an example of a small test file using this module.
545              
546             use strict;
547             use warnings;
548             use Test::Subs debug => 1, lib => '../lib';
549             use My::Module;
550            
551             test { My::Module::init() } 'This is the first test';
552            
553             todo { My::Module::make_coffee() };
554            
555             not_ok { 0 };
556            
557             fail { die "fail" };
558            
559             test_pod('My::Module', 'My::Module::Internal');
560              
561             Run through C this file will pass, with only the second test failing
562             (but marked I so that's OK).
563              
564             =head1 CAVEATS
565              
566             This package does not use the C facility and as such is not compatible
567             with other testing modules are using C. This may be changed in a
568             future release.
569              
570             The standard set by C is that all output to C is
571             interpreted by the test parser. So a test file should write additional output
572             only to C. This is what will be done by the C fonction. To help
573             with this, during the execution of your test file, the C file-handle will
574             be C
575             C.
576              
577             This package use source filtering (with C>). The filter
578             applied is very simple, but there is a slight possibility that it is incompatible
579             with other source filters. If so, do not hesitate to report this as a bug.
580              
581             =head1 BUGS
582              
583             Please report any bugs or feature requests to C, or
584             through the web interface at L.
585              
586             =head1 SEE ALSO
587              
588             L, L, L, L
589              
590             =head1 AUTHOR
591              
592             Mathias Kende (mathias@cpan.org)
593              
594             =head1 VERSION
595              
596             Version 0.08 (March 2013)
597              
598             =head1 COPYRIGHT & LICENSE
599              
600             Copyright 2013 © Mathias Kende. All rights reserved.
601              
602             This program is free software; you can redistribute it and/or
603             modify it under the same terms as Perl itself.
604              
605             =cut
606              
607