]> git.vpit.fr Git - perl/modules/Test-Leaner.git/blob - lib/Test/Leaner.pm
Fall back to Test::More when PERL_TEST_LEANER_USES_TEST_MORE is set
[perl/modules/Test-Leaner.git] / lib / Test / Leaner.pm
1 package Test::Leaner;
2
3 use 5.006;
4 use strict;
5 use warnings;
6
7 =head1 NAME
8
9 Test::Leaner - A slimmer Test::More for when you favor performance over completeness.
10
11 =head1 VERSION
12
13 Version 0.01
14
15 =cut
16
17 our $VERSION = '0.01';
18
19 =head1 SYNOPSIS
20
21     use Test::Leaner tests => 10_000;
22     for (1 .. 10_000) {
23      ...
24      is $one, 1, "checking situation $_";
25     }
26
27
28 =head1 DESCRIPTION
29
30 When profiling some L<Test::More>-based test script that contained about 10 000 unit tests, I realized that 60% of the time was spent in L<Test::Builder> itself, even though every single test actually involved a costly C<eval STRING>.
31
32 This module aims to be a partial replacement to L<Test::More> in those situations where you want to run a large number of simple tests.
33 Its functions behave the same as their L<Test::More> counterparts, except for the following differences :
34
35 =over 4
36
37 =item *
38
39 Stringification isn't forced on the test operands.
40 However, L</ok> honors C<'bool'> overloading, L</is> and L</is_deeply> honor C<'eq'> overloading (and just that one) and L</cmp_ok> honors whichever overloading category corresponds to the specified operator.
41
42 =item *
43
44 L</pass>, L</fail>, L</ok>, L</is>, L</isnt>, L</like>, L</unlike> and L</cmp_ok> are all guaranteed to return the truth value of the test.
45
46 =item *
47
48 L</like> and L</unlike> don't special case regular expressions that are passed as C<'/.../'> strings.
49 A string regexp argument is always treated as a the source of the regexp, making C<like $text, $rx> and C<like $text, qr[$rx]> equivalent to each other and to C<cmp_ok $text, '=~', $rx> (and likewise for C<unlike>).
50
51 =item *
52
53 L</cmp_ok> throws an exception if the given operator isn't a valid Perl binary operator (except C<'='> and variants).
54 It also tests in scalar context, so C<'..'> will be treated as the flip-flop operator and not the range operator.
55
56 =item *
57
58 L</is_deeply> doesn't guard for memory cycles.
59 If the two first arguments present parallel memory cycles, the test may result in an infinite loop.
60
61 =item *
62
63 The tests don't output any kind of default diagnostic in case of failure ; the rationale being that if you have a large number of tests and a lot of them are failing, then you don't want to be flooded by diagnostics.
64
65 =item *
66
67 C<use_ok>, C<require_ok>, C<can_ok>, C<isa_ok>, C<new_ok>, C<subtest>, C<explain>, C<TODO> blocks and C<todo_skip> are not implemented.
68
69 =item *
70
71 L<Test::Leaner> depends on L<Scalar::Util>, while L<Test::More> does not.
72
73 =back
74
75 =cut
76
77 use Exporter     ();
78 use Scalar::Util ();
79
80 BEGIN {
81  if ($] >= 5.008 and $INC{'threads.pm'}) {
82   my $use_ithreads = do {
83    require Config;
84    no warnings 'once';
85    $Config::Config{useithreads};
86   };
87   if ($use_ithreads) {
88    require threads::shared;
89    *THREADSAFE = sub () { 1 };
90   }
91  }
92  unless (defined &Test::Leaner::THREADSAFE) {
93   *THREADSAFE = sub () { 0 }
94  }
95 }
96
97 my ($TAP_STREAM, $DIAG_STREAM);
98
99 my ($plan, $test, $failed, $no_diag, $done_testing);
100
101 our @EXPORT = qw<
102  plan
103  skip
104  done_testing
105  pass
106  fail
107  ok
108  is
109  isnt
110  like
111  unlike
112  cmp_ok
113  is_deeply
114  diag
115  note
116  BAIL_OUT
117 >;
118
119 =head1 ENVIRONMENT
120
121 =head2 C<PERL_TEST_LEANER_USES_TEST_MORE>
122
123 If this environment variable is set, L<Test::Leaner> will replace its functions by those from L<Test::More>.
124 Moreover, the symbols that are imported you C<use Test::Leaner> will be those from L<Test::More>, but you can still only import the symbols originally defined in L<Test::Leaner> (hence the functions from L<Test::More> that are not implemented in L<Test::Leaner> will not be imported).
125 If your version of L<Test::More> is too old and doesn't have some symbols (like L</note> or L</done_testing>), they will be replaced in L<Test::Leaner> by croaking stubs.
126
127 This may be useful if your L<Test::Leaner>-based test script fails and you want extra diagnostics.
128
129 =cut
130
131 sub _handle_import_args {
132  my @imports;
133
134  my $i = 0;
135  while ($i <= $#_) {
136   my $item = $_[$i];
137   my $splice;
138   if (defined $item) {
139    if ($item eq 'import') {
140     push @imports, @{ $_[$i+1] };
141     $splice  = 2;
142    } elsif ($item eq 'no_diag') {
143     lock $plan if THREADSAFE;
144     $no_diag = 1;
145     $splice  = 1;
146    }
147   }
148   if ($splice) {
149    splice @_, $i, $splice;
150   } else {
151    ++$i;
152   }
153  }
154
155  return @imports;
156 }
157
158 if ($ENV{PERL_TEST_LEANER_USES_TEST_MORE}) {
159  require Test::More;
160
161  my $leaner_stash = \%Test::Leaner::;
162  my $more_stash   = \%Test::More::;
163
164  my %valid_imports;
165
166  for (@EXPORT) {
167   my $replacement = exists $more_stash->{$_} ? *{$more_stash->{$_}}{CODE}
168                                              : undef;
169   if (defined $replacement) {
170    $valid_imports{$_} = 1;
171   } else {
172    $replacement = sub {
173     @_ = ("$_ is not implemented in this version of Test::More");
174     goto &croak;
175    };
176   }
177   no warnings 'redefine';
178   $leaner_stash->{$_} = $replacement;
179  }
180
181  my $import = sub {
182   shift;
183   my @imports = &_handle_import_args;
184   @imports = @EXPORT unless @imports;
185   my @test_more_imports;
186   for (@imports) {
187    if ($valid_imports{$_}) {
188     push @test_more_imports, $_;
189    } else {
190     my $pkg = caller;
191     no strict 'refs';
192     *{$pkg."::$_"} = $leaner_stash->{$_};
193    }
194   }
195   my $test_more_import = 'Test::More'->can('import');
196   @_ = (
197    'Test::More',
198    @_,
199    import => \@test_more_imports,
200   );
201   {
202    lock $plan if THREADSAFE;
203    push @_, 'no_diag' if $no_diag;
204   }
205   goto $test_more_import;
206  };
207
208  no warnings 'redefine';
209  *import = $import;
210
211  return 1;
212 }
213
214 sub NO_PLAN  () { -1 }
215 sub SKIP_ALL () { -2 }
216
217 BEGIN {
218  if (THREADSAFE) {
219   threads::shared::share($_) for $plan, $test, $failed, $no_diag, $done_testing;
220  }
221
222  lock $plan if THREADSAFE;
223
224  $plan   = undef;
225  $test   = 0;
226  $failed = 0;
227 }
228
229 sub carp {
230  my $level = 1 + ($Test::Builder::Level || 0);
231  my @caller;
232  do {
233   @caller = caller $level--;
234  } while (!@caller and $level >= 0);
235  my ($file, $line) = @caller[1, 2];
236  warn @_, " at $file line $line.\n";
237 }
238
239 sub croak {
240  my $level = 1 + ($Test::Builder::Level || 0);
241  my @caller;
242  do {
243   @caller = caller $level--;
244  } while (!@caller and $level >= 0);
245  my ($file, $line) = @caller[1, 2];
246  die @_, " at $file line $line.\n";
247 }
248
249 sub _sanitize_comment {
250  $_[0] =~ s/\n+\z//;
251  $_[0] =~ s/#/\\#/g;
252  $_[0] =~ s/\n/\n# /g;
253 }
254
255 =head1 FUNCTIONS
256
257 The following functions from L<Test::More> are implemented and exported by default.
258
259 =head2 C<< plan [ tests => $count | 'no_plan' | skip_all => $reason ] >>
260
261 See L<Test::More/plan>.
262
263 =cut
264
265 sub plan {
266  my ($key, $value) = @_;
267
268  return unless $key;
269
270  lock $plan if THREADSAFE;
271
272  croak("You tried to plan twice") if defined $plan;
273
274  my $plan_str;
275
276  if ($key eq 'no_plan') {
277   croak("no_plan takes no arguments") if $value;
278   $plan       = NO_PLAN;
279  } elsif ($key eq 'tests') {
280   croak("Got an undefined number of tests") unless defined $value;
281   croak("You said to run 0 tests")          unless $value;
282   croak("Number of tests must be a positive integer.  You gave it '$value'")
283                                             unless $value =~ /^\+?[0-9]+$/;
284   $plan       = $value;
285   $plan_str   = "1..$value";
286  } elsif ($key eq 'skip_all') {
287   $plan       = SKIP_ALL;
288   $plan_str   = '1..0 # SKIP';
289   if (defined $value) {
290    _sanitize_comment($value);
291    $plan_str .= " $value" if length $value;
292   }
293  } else {
294   my @args = grep defined, $key, $value;
295   croak("plan() doesn't understand @args");
296  }
297
298  if (defined $plan_str) {
299   local $\;
300   print $TAP_STREAM "$plan_str\n";
301  }
302
303  exit 0 if $plan == SKIP_ALL;
304
305  return 1;
306 }
307
308 sub import {
309  my $class = shift;
310
311  my @imports = &_handle_import_args;
312
313  if (@_) {
314   local $Test::Builder::Level = ($Test::Builder::Level || 0) + 1;
315   &plan;
316  }
317
318  @_ = ($class, @imports);
319  goto &Exporter::import;
320 }
321
322 =head2 C<< skip $reason => $count >>
323
324 See L<Test::More/skip>.
325
326 =cut
327
328 sub skip {
329  my ($reason, $count) = @_;
330
331  lock $plan if THREADSAFE;
332
333  if (not defined $count) {
334   carp("skip() needs to know \$how_many tests are in the block")
335                                       unless defined $plan and $plan == NO_PLAN;
336   $count = 1;
337  } elsif ($count =~ /[^0-9]/) {
338   carp('skip() was passed a non-numeric number of tests.  Did you get the arguments backwards?');
339   $count = 1;
340  }
341
342  for (1 .. $count) {
343   ++$test;
344
345   my $skip_str = "ok $test # skip";
346   if (defined $reason) {
347    _sanitize_comment($reason);
348    $skip_str  .= " $reason" if length $reason;
349   }
350
351   local $\;
352   print $TAP_STREAM "$skip_str\n";
353  }
354
355  no warnings 'exiting';
356  last SKIP;
357 }
358
359 =head2 C<done_testing [ $count ]>
360
361 See L<Test::More/done_testing>.
362
363 =cut
364
365 sub done_testing {
366  my ($count) = @_;
367
368  lock $plan if THREADSAFE;
369
370  $count = $test unless defined $count;
371  croak("Number of tests must be a positive integer.  You gave it '$count'")
372                                                  unless $count =~ /^\+?[0-9]+$/;
373
374  if (not defined $plan or $plan == NO_PLAN) {
375   $plan         = $count; # $plan can't be NO_PLAN anymore
376   $done_testing = 1;
377   local $\;
378   print $TAP_STREAM "1..$plan\n";
379  } else {
380   if ($done_testing) {
381    @_ = ('done_testing() was already called');
382    goto &fail;
383   } elsif ($plan != $count) {
384    @_ = ("planned to run $plan tests but done_testing() expects $count");
385    goto &fail;
386   }
387  }
388
389  return 1;
390 }
391
392 =head2 C<ok $ok [, $desc ]>
393
394 See L<Test::More/ok>.
395
396 =cut
397
398 sub ok ($;$) {
399  my ($ok, $desc) = @_;
400
401  lock $plan if THREADSAFE;
402
403  ++$test;
404
405  my $test_str = "ok $test";
406  unless ($ok) {
407   $test_str   = "not $test_str";
408   ++$failed;
409  }
410  if (defined $desc) {
411   _sanitize_comment($desc);
412   $test_str .= " - $desc" if length $desc;
413  }
414
415  local $\;
416  print $TAP_STREAM "$test_str\n";
417
418  return $ok;
419 }
420
421 =head2 C<pass [ $desc ]>
422
423 See L<Test::More/pass>.
424
425 =cut
426
427 sub pass (;$) {
428  unshift @_, 1;
429  goto &ok;
430 }
431
432 =head2 C<fail [ $desc ]>
433
434 See L<Test::More/fail>.
435
436 =cut
437
438 sub fail (;$) {
439  unshift @_, 0;
440  goto &ok;
441 }
442
443 =head2 C<is $got, $expected [, $desc ]>
444
445 See L<Test::More/is>.
446
447 =cut
448
449 sub is ($$;$) {
450  my ($got, $expected, $desc) = @_;
451  no warnings 'uninitialized';
452  @_ = (
453   (not(defined $got xor defined $expected) and $got eq $expected),
454   $desc,
455  );
456  goto &ok;
457 }
458
459 =head2 C<isnt $got, $expected [, $desc ]>
460
461 See L<Test::More/isnt>.
462
463 =cut
464
465 sub isnt ($$;$) {
466  my ($got, $expected, $desc) = @_;
467  no warnings 'uninitialized';
468  @_ = (
469   ((defined $got xor defined $expected) or $got ne $expected),
470   $desc,
471  );
472  goto &ok;
473 }
474
475 my %binops = (
476  'or'  => 'or',
477  'xor' => 'xor',
478  'and' => 'and',
479
480  '||'  => 'hor',
481  ('//' => 'dor') x ($] >= 5.010),
482  '&&'  => 'hand',
483
484  '|'   => 'bor',
485  '^'   => 'bxor',
486  '&'   => 'band',
487
488  'lt'  => 'lt',
489  'le'  => 'le',
490  'gt'  => 'gt',
491  'ge'  => 'ge',
492  'eq'  => 'eq',
493  'ne'  => 'ne',
494  'cmp' => 'cmp',
495
496  '<'   => 'nlt',
497  '<='  => 'nle',
498  '>'   => 'ngt',
499  '>='  => 'nge',
500  '=='  => 'neq',
501  '!='  => 'nne',
502  '<=>' => 'ncmp',
503
504  '=~'  => 'like',
505  '!~'  => 'unlike',
506  ('~~' => 'smartmatch') x ($] >= 5.010),
507
508  '+'   => 'add',
509  '-'   => 'substract',
510  '*'   => 'multiply',
511  '/'   => 'divide',
512  '%'   => 'modulo',
513  '<<'  => 'lshift',
514  '>>'  => 'rshift',
515
516  '.'   => 'concat',
517  '..'  => 'flipflop',
518  '...' => 'altflipflop',
519  ','   => 'comma',
520  '=>'  => 'fatcomma',
521 );
522
523 my %binop_handlers;
524
525 sub _create_binop_handler {
526  my ($op) = @_;
527  my $name = $binops{$op};
528  croak("Operator $op not supported") unless defined $name;
529  {
530   local $@;
531   eval <<"IS_BINOP";
532 sub is_$name (\$\$;\$) {
533  my (\$got, \$expected, \$desc) = \@_;
534  \@_ = (scalar(\$got $op \$expected), \$desc);
535  goto &ok;
536 }
537 IS_BINOP
538   die $@ if $@;
539  }
540  $binop_handlers{$op} = do {
541   no strict 'refs';
542   \&{__PACKAGE__."::is_$name"};
543  }
544 }
545
546 =head2 C<like $got, $regexp_expected [, $desc ]>
547
548 See L<Test::More/like>.
549
550 =head2 C<unlike $got, $regexp_expected, [, $desc ]>
551
552 See L<Test::More/unlike>.
553
554 =cut
555
556 {
557  no warnings 'once';
558  *like   = _create_binop_handler('=~');
559  *unlike = _create_binop_handler('!~');
560 }
561
562 =head2 C<cmp_ok $got, $op, $expected [, $desc ]>
563
564 See L<Test::More/cmp_ok>.
565
566 =cut
567
568 sub cmp_ok ($$$;$) {
569  my ($got, $op, $expected, $desc) = @_;
570  my $handler = $binop_handlers{$op};
571  unless ($handler) {
572   local $Test::More::Level = ($Test::More::Level || 0) + 1;
573   $handler = _create_binop_handler($op);
574  }
575  @_ = ($got, $expected, $desc);
576  goto $handler;
577 }
578
579 =head2 C<is_deeply $got, $expected [, $desc ]>
580
581 See L<Test::More/is_deeply>.
582
583 =cut
584
585 sub _deep_ref_check {
586  my ($x, $y, $ry) = @_;
587
588  no warnings qw<numeric uninitialized>;
589
590  if ($ry eq 'ARRAY') {
591   return 0 unless $#$x == $#$y;
592
593   my ($ex, $ey);
594   for (0 .. $#$y) {
595    $ex = $x->[$_];
596    $ey = $y->[$_];
597
598    # Inline the beginning of _deep_check
599    return 0 if defined $ex xor defined $ey;
600
601    next if not(ref $ex xor ref $ey) and $ex eq $ey;
602
603    $ry = Scalar::Util::reftype($ey);
604    return 0 if Scalar::Util::reftype($ex) ne $ry;
605
606    return 0 unless $ry and _deep_ref_check($ex, $ey, $ry);
607   }
608
609   return 1;
610  } elsif ($ry eq 'HASH') {
611   return 0 unless keys(%$x) == keys(%$y);
612
613   my ($ex, $ey);
614   for (keys %$y) {
615    return 0 unless exists $x->{$_};
616    $ex = $x->{$_};
617    $ey = $y->{$_};
618
619    # Inline the beginning of _deep_check
620    return 0 if defined $ex xor defined $ey;
621
622    next if not(ref $ex xor ref $ey) and $ex eq $ey;
623
624    $ry = Scalar::Util::reftype($ey);
625    return 0 if Scalar::Util::reftype($ex) ne $ry;
626
627    return 0 unless $ry and _deep_ref_check($ex, $ey, $ry);
628   }
629
630   return 1;
631  } elsif ($ry eq 'SCALAR' or $ry eq 'REF') {
632   return _deep_check($$x, $$y);
633  }
634
635  return 0;
636 }
637
638 sub _deep_check {
639  my ($x, $y) = @_;
640
641  no warnings qw<numeric uninitialized>;
642
643  return 0 if defined $x xor defined $y;
644
645  # Try object identity/eq overloading first. It also covers the case where
646  # $x and $y are both undefined.
647  # If either $x or $y is overloaded but none has eq overloading, the test will
648  # break at that point.
649  return 1 if not(ref $x xor ref $y) and $x eq $y;
650
651  # Test::More::is_deeply happily breaks encapsulation if the objects aren't
652  # overloaded.
653  my $ry = Scalar::Util::reftype($y);
654  return 0 if Scalar::Util::reftype($x) ne $ry;
655
656  # Shortcut if $x and $y are both not references and failed the previous
657  # $x eq $y test.
658  return 0 unless $ry;
659
660  # We know that $x and $y are both references of type $ry, without overloading.
661  _deep_ref_check($x, $y, $ry);
662 }
663
664 sub is_deeply {
665  @_ = (
666   &_deep_check,
667   $_[2],
668  );
669  goto &ok;
670 }
671
672 sub _diag_fh {
673  my $fh = shift;
674
675  return unless @_;
676
677  lock $plan if THREADSAFE;
678  return if $no_diag;
679
680  my $msg = join '', map { defined($_) ? $_ : 'undef' } @_;
681  _sanitize_comment($msg);
682  return unless length $msg;
683
684  local $\;
685  print $fh "# $msg\n";
686
687  return 0;
688 };
689
690 =head2 C<diag @text>
691
692 See L<Test::More/diag>.
693
694 =cut
695
696 sub diag {
697  unshift @_, $DIAG_STREAM;
698  goto &_diag_fh;
699 }
700
701 =head2 C<note @text>
702
703 See L<Test::More/note>.
704
705 =cut
706
707 sub note {
708  unshift @_, $TAP_STREAM;
709  goto &_diag_fh;
710 }
711
712 =head2 C<BAIL_OUT [ $desc ]>
713
714 See L<Test::More/BAIL_OUT>.
715
716 =cut
717
718 sub BAIL_OUT {
719  my ($desc) = @_;
720
721  lock $plan if THREADSAFE;
722
723  my $bail_out_str = 'Bail out!';
724  if (defined $desc) {
725   _sanitize_comment($desc);
726   $bail_out_str  .= "  $desc" if length $desc; # Two spaces
727  }
728
729  local $\;
730  print $TAP_STREAM "$bail_out_str\n";
731
732  exit 255;
733 }
734
735 END {
736  unless ($?) {
737   lock $plan if THREADSAFE;
738
739   if (defined $plan) {
740    if ($failed) {
741     $? = $failed <= 254 ? $failed : 254;
742    } elsif ($plan >= 0) {
743     $? = $test == $plan ? 0 : 255;
744    }
745    if ($plan == NO_PLAN) {
746     local $\;
747     print $TAP_STREAM "1..$test\n";
748    }
749   }
750  }
751 }
752
753 =pod
754
755 L<Test::Leaner> also provides some functions of its own, which are never exported.
756
757 =head2 C<tap_stream [ $fh ]>
758
759 Read/write accessor for the filehandle to which the tests are outputted.
760 On write, it also turns autoflush on onto C<$fh>.
761
762 Note that it can only be used as a write accessor before you start any thread, as L<threads::shared> cannot reliably share filehandles.
763
764 Defaults to C<STDOUT>.
765
766 =cut
767
768 sub tap_stream (;*) {
769  if (@_) {
770   $TAP_STREAM = $_[0];
771
772   my $fh = select $TAP_STREAM;
773   $|++;
774   select $fh;
775  }
776
777  return $TAP_STREAM;
778 }
779
780 tap_stream *STDOUT;
781
782 =head2 C<diag_stream [ $fh ]>
783
784 Read/write accessor for the filehandle to which the diagnostics are printed.
785 On write, it also turns autoflush on onto C<$fh>.
786
787 Just like L</tap_stream>, it can only be used as a write accessor before you start any thread, as L<threads::shared> cannot reliably share filehandles.
788
789 Defaults to C<STDERR>.
790
791 =cut
792
793 sub diag_stream (;*) {
794  if (@_) {
795   $DIAG_STREAM = $_[0];
796
797   my $fh = select $DIAG_STREAM;
798   $|++;
799   select $fh;
800  }
801
802  return $DIAG_STREAM;
803 }
804
805 diag_stream *STDERR;
806
807 =head2 C<THREADSAFE>
808
809 This constant evaluates to true if and only if L<Test::Leaner> is thread-safe, i.e. when this version of C<perl> is at least 5.8, has been compiled with C<useithreads> defined, and L<threads> has been loaded B<before> L<Test::Leaner>.
810 In that case, it also needs a working L<threads::shared>.
811
812 =head1 DEPENDENCIES
813
814 L<perl> 5.6.
815
816 L<Exporter>, L<Scalar::Util>, L<Test::More>.
817
818 =head1 AUTHOR
819
820 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
821
822 You can contact me by mail or on C<irc.perl.org> (vincent).
823
824 =head1 BUGS
825
826 Please report any bugs or feature requests to C<bug-test-leaner at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-Leaner>.
827 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
828
829 =head1 SUPPORT
830
831 You can find documentation for this module with the perldoc command.
832
833     perldoc Test::Leaner
834
835 =head1 COPYRIGHT & LICENSE
836
837 Copyright 2010 Vincent Pit, all rights reserved.
838
839 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
840
841 =cut
842
843 1; # End of Test::Leaner