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