]> git.vpit.fr Git - perl/modules/Test-Leaner.git/blob - lib/Test/Leaner.pm
Document two more differences with Test::More
[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 sub NO_PLAN  () { -1 }
102 sub SKIP_ALL () { -2 }
103
104 BEGIN {
105  if (THREADSAFE) {
106   threads::shared::share($_) for $plan, $test, $failed, $no_diag, $done_testing;
107  }
108
109  lock $plan if THREADSAFE;
110
111  $plan   = undef;
112  $test   = 0;
113  $failed = 0;
114 }
115
116 sub carp {
117  my $level = 1 + ($Test::Builder::Level || 0);
118  my ($file, $line) = (caller $level)[1, 2];
119  warn @_, " at $file line $line.\n";
120 }
121
122 sub croak {
123  my $level = 1 + ($Test::Builder::Level || 0);
124  my ($file, $line) = (caller $level)[1, 2];
125  die @_, " at $file line $line.\n";
126 }
127
128 sub _sanitize_comment {
129  $_[0] =~ s/\n+\z//;
130  $_[0] =~ s/#/\\#/g;
131  $_[0] =~ s/\n/\n# /g;
132 }
133
134 =head1 FUNCTIONS
135
136 The following functions from L<Test::More> are implemented and exported by default.
137
138 =head2 C<< plan [ tests => $count | 'no_plan' | skip_all => $reason ] >>
139
140 =cut
141
142 sub plan {
143  my ($key, $value) = @_;
144
145  return unless $key;
146
147  lock $plan if THREADSAFE;
148
149  croak("You tried to plan twice") if defined $plan;
150
151  my $plan_str;
152
153  if ($key eq 'no_plan') {
154   croak("no_plan takes no arguments") if $value;
155   $plan       = NO_PLAN;
156  } elsif ($key eq 'tests') {
157   croak("Got an undefined number of tests") unless defined $value;
158   croak("You said to run 0 tests")          unless $value;
159   croak("Number of tests must be a positive integer.  You gave it '$value'")
160                                             unless $value =~ /^\+?[0-9]+$/;
161   $plan       = $value;
162   $plan_str   = "1..$value";
163  } elsif ($key eq 'skip_all') {
164   $plan       = SKIP_ALL;
165   $plan_str   = '1..0 # SKIP';
166   if (defined $value) {
167    _sanitize_comment($value);
168    $plan_str .= " $value" if length $value;
169   }
170  } else {
171   my @args = grep defined, $key, $value;
172   croak("plan() doesn't understand @args");
173  }
174
175  if (defined $plan_str) {
176   local $\;
177   print $TAP_STREAM "$plan_str\n";
178  }
179
180  exit 0 if $plan == SKIP_ALL;
181
182  return 1;
183 }
184
185 our @EXPORT = qw<
186  plan
187  skip
188  done_testing
189  pass
190  fail
191  ok
192  is
193  isnt
194  like
195  unlike
196  cmp_ok
197  is_deeply
198  diag
199  note
200  BAIL_OUT
201 >;
202
203 sub import {
204  my $class = shift;
205
206  my @imports;
207  my $i = 0;
208  while ($i <= $#_) {
209   my $item = $_[$i];
210   my $splice;
211   if (defined $item) {
212    if ($item eq 'import') {
213     push @imports, @{ $_[$i+1] };
214     $splice  = 2;
215    } elsif ($item eq 'no_diag') {
216     lock $plan if THREADSAFE;
217     $no_diag = 1;
218     $splice  = 1;
219    }
220   }
221   if ($splice) {
222    splice @_, $i, $splice;
223   } else {
224    ++$i;
225   }
226  }
227
228  if (@_) {
229   local $Test::Builder::Level = ($Test::Builder::Level || 0) + 1;
230   &plan;
231  }
232
233  @_ = ($class, @imports);
234  goto &Exporter::import;
235 }
236
237 =head2 C<< skip $reason => $count >>
238
239 =cut
240
241 sub skip {
242  my ($reason, $count) = @_;
243
244  lock $plan if THREADSAFE;
245
246  if (not defined $count) {
247   carp("skip() needs to know \$how_many tests are in the block")
248                                       unless defined $plan and $plan == NO_PLAN;
249   $count = 1;
250  } elsif ($count =~ /[^0-9]/) {
251   carp('skip() was passed a non-numeric number of tests.  Did you get the arguments backwards?');
252   $count = 1;
253  }
254
255  for (1 .. $count) {
256   ++$test;
257
258   my $skip_str = "ok $test # skip";
259   if (defined $reason) {
260    _sanitize_comment($reason);
261    $skip_str  .= " $reason" if length $reason;
262   }
263
264   local $\;
265   print $TAP_STREAM "$skip_str\n";
266  }
267
268  no warnings 'exiting';
269  last SKIP;
270 }
271
272 =head2 C<done_testing [ $count ]>
273
274 =cut
275
276 sub done_testing {
277  my ($count) = @_;
278
279  lock $plan if THREADSAFE;
280
281  $count = $test unless defined $count;
282  croak("Number of tests must be a positive integer.  You gave it '$count'")
283                                                  unless $count =~ /^\+?[0-9]+$/;
284
285  if (not defined $plan or $plan == NO_PLAN) {
286   $plan         = $count; # $plan can't be NO_PLAN anymore
287   $done_testing = 1;
288   local $\;
289   print $TAP_STREAM "1..$plan\n";
290  } else {
291   if ($done_testing) {
292    @_ = ('done_testing() was already called');
293    goto &fail;
294   } elsif ($plan != $count) {
295    @_ = ("planned to run $plan tests but done_testing() expects $count");
296    goto &fail;
297   }
298  }
299
300  return 1;
301 }
302
303 =head2 C<ok $ok [, $desc ]>
304
305 =cut
306
307 sub ok ($;$) {
308  my ($ok, $desc) = @_;
309
310  lock $plan if THREADSAFE;
311
312  ++$test;
313
314  my $test_str = "ok $test";
315  unless ($ok) {
316   $test_str   = "not $test_str";
317   ++$failed;
318  }
319  if (defined $desc) {
320   _sanitize_comment($desc);
321   $test_str .= " - $desc" if length $desc;
322  }
323
324  local $\;
325  print $TAP_STREAM "$test_str\n";
326
327  return $ok;
328 }
329
330 =head2 C<pass [ $desc ]>
331
332 =cut
333
334 sub pass (;$) {
335  unshift @_, 1;
336  goto &ok;
337 }
338
339 =head2 C<fail [ $desc ]>
340
341 =cut
342
343 sub fail (;$) {
344  unshift @_, 0;
345  goto &ok;
346 }
347
348 =head2 C<is $got, $expected [, $desc ]>
349
350 =cut
351
352 sub is ($$;$) {
353  my ($got, $expected, $desc) = @_;
354  no warnings 'uninitialized';
355  @_ = (
356   (not(defined $got xor defined $expected) and $got eq $expected),
357   $desc,
358  );
359  goto &ok;
360 }
361
362 =head2 C<isnt $got, $expected [, $desc ]>
363
364 =cut
365
366 sub isnt ($$;$) {
367  my ($got, $expected, $desc) = @_;
368  no warnings 'uninitialized';
369  @_ = (
370   ((defined $got xor defined $expected) or $got ne $expected),
371   $desc,
372  );
373  goto &ok;
374 }
375
376 my %binops = (
377  'or'  => 'or',
378  'xor' => 'xor',
379  'and' => 'and',
380
381  '||'  => 'hor',
382  ('//' => 'dor') x ($] >= 5.010),
383  '&&'  => 'hand',
384
385  '|'   => 'bor',
386  '^'   => 'bxor',
387  '&'   => 'band',
388
389  'lt'  => 'lt',
390  'le'  => 'le',
391  'gt'  => 'gt',
392  'ge'  => 'ge',
393  'eq'  => 'eq',
394  'ne'  => 'ne',
395  'cmp' => 'cmp',
396
397  '<'   => 'nlt',
398  '<='  => 'nle',
399  '>'   => 'ngt',
400  '>='  => 'nge',
401  '=='  => 'neq',
402  '!='  => 'nne',
403  '<=>' => 'ncmp',
404
405  '=~'  => 'like',
406  '!~'  => 'unlike',
407  ('~~' => 'smartmatch') x ($] >= 5.010),
408
409  '+'   => 'add',
410  '-'   => 'substract',
411  '*'   => 'multiply',
412  '/'   => 'divide',
413  '%'   => 'modulo',
414  '<<'  => 'lshift',
415  '>>'  => 'rshift',
416
417  '.'   => 'concat',
418  '..'  => 'flipflop',
419  '...' => 'altflipflop',
420  ','   => 'comma',
421  '=>'  => 'fatcomma',
422 );
423
424 my %binop_handlers;
425
426 sub _create_binop_handler {
427  my ($op) = @_;
428  my $name = $binops{$op};
429  croak("Operator $op not supported") unless defined $name;
430  {
431   local $@;
432   eval <<"IS_BINOP";
433 sub is_$name (\$\$;\$) {
434  my (\$got, \$expected, \$desc) = \@_;
435  \@_ = (scalar(\$got $op \$expected), \$desc);
436  goto &ok;
437 }
438 IS_BINOP
439   die $@ if $@;
440  }
441  $binop_handlers{$op} = do {
442   no strict 'refs';
443   \&{__PACKAGE__."::is_$name"};
444  }
445 }
446
447 =head2 C<like $got, $regexp_expected [, $desc ]>
448
449 =cut
450
451 =head2 C<unlike $got, $regexp_expected, [, $desc ]>
452
453 =cut
454
455 {
456  no warnings 'once';
457  *like   = _create_binop_handler('=~');
458  *unlike = _create_binop_handler('!~');
459 }
460
461 =head2 C<cmp_ok $got, $op, $expected [, $desc ]>
462
463 =cut
464
465 sub cmp_ok ($$$;$) {
466  my ($got, $op, $expected, $desc) = @_;
467  my $handler = $binop_handlers{$op};
468  unless ($handler) {
469   local $Test::More::Level = ($Test::More::Level || 0) + 1;
470   $handler = _create_binop_handler($op);
471  }
472  @_ = ($got, $expected, $desc);
473  goto $handler;
474 }
475
476 =head2 C<is_deeply $got, $expected [, $desc ]>
477
478 =cut
479
480 sub _deep_check {
481  my ($x, $y) = @_;
482
483  no warnings qw<numeric uninitialized>;
484
485  return 0 if defined($x) xor defined($y);
486
487  # Try object identity/eq overloading first. It also covers the case where
488  # $x and $y are both undefined.
489  # If either $x or $y is overloaded but none has eq overloading, the test will
490  # break at that point.
491  return 1 if not(ref($x) xor ref($y)) and $x eq $y;
492
493  # Test::More::is_deeply happily breaks encapsulation if the objects aren't
494  # overloaded.
495  my $ry = Scalar::Util::reftype($y);
496  return 0 if Scalar::Util::reftype($x) ne $ry;
497
498  # Shortcut if $x and $y are both not references and failed the previous
499  # $x eq $y test.
500  return 0 unless $ry;
501
502  if ($ry eq 'ARRAY') {
503   if ($#$x == $#$y) {
504    # Prevent vivification of deleted elements by fetching the array values.
505    my ($ex, $ey);
506    _deep_check($ex = $x->[$_], $ey = $y->[$_]) or return 0 for 0 .. $#$x;
507    return 1;
508   }
509  } elsif ($ry eq 'HASH') {
510   if (keys(%$x) == keys(%$y)) {
511    (exists $x->{$_} and _deep_check($x->{$_}, $y->{$_}))
512                                                        or return 0 for keys %$y;
513    return 1;
514   }
515  } elsif ($ry eq 'SCALAR' or $ry eq 'REF') {
516   return _deep_check($$x, $$y);
517  }
518
519  return 0;
520 };
521
522 sub is_deeply {
523  @_ = (
524   &_deep_check,
525   $_[2],
526  );
527  goto &ok;
528 }
529
530 sub _diag_fh {
531  my $fh = shift;
532
533  return unless @_;
534
535  lock $plan if THREADSAFE;
536  return if $no_diag;
537
538  my $msg = join '', map { defined($_) ? $_ : 'undef' } @_;
539  _sanitize_comment($msg);
540  return unless length $msg;
541
542  local $\;
543  print $fh "# $msg\n";
544
545  return 0;
546 };
547
548 =head2 C<diag @text>
549
550 =cut
551
552 sub diag {
553  unshift @_, $DIAG_STREAM;
554  goto &_diag_fh;
555 }
556
557 =head2 C<note @text>
558
559 =cut
560
561 sub note {
562  unshift @_, $TAP_STREAM;
563  goto &_diag_fh;
564 }
565
566 =head2 C<BAIL_OUT [ $desc ]>
567
568 =cut
569
570 sub BAIL_OUT {
571  my ($desc) = @_;
572
573  lock $plan if THREADSAFE;
574
575  my $bail_out_str = 'Bail out!';
576  if (defined $desc) {
577   _sanitize_comment($desc);
578   $bail_out_str  .= "  $desc" if length $desc; # Two spaces
579  }
580
581  local $\;
582  print $TAP_STREAM "$bail_out_str\n";
583
584  exit 255;
585 }
586
587 END {
588  unless ($?) {
589   lock $plan if THREADSAFE;
590
591   if (defined $plan) {
592    if ($failed) {
593     $? = $failed <= 254 ? $failed : 254;
594    } elsif ($plan >= 0) {
595     $? = $test == $plan ? 0 : 255;
596    } elsif ($plan == NO_PLAN) {
597     local $\;
598     print $TAP_STREAM "1..$test\n";
599    }
600   }
601  }
602 }
603
604 =pod
605
606 L<Test::Leaner> also provides some functions of its own, which are never exported.
607
608 =head2 C<tap_stream [ $fh ]>
609
610 Read/write accessor for the filehandle to which the tests are outputted.
611 On write, it also turns autoflush on onto C<$fh>.
612
613 Note that it can only be used as a write accessor before you start any thread, as L<threads::shared> cannot reliably share filehandles.
614
615 Defaults to C<STDOUT>.
616
617 =cut
618
619 sub tap_stream (;*) {
620  if (@_) {
621   $TAP_STREAM = $_[0];
622
623   my $fh = select $TAP_STREAM;
624   $|++;
625   select $fh;
626  }
627
628  return $TAP_STREAM;
629 }
630
631 tap_stream *STDOUT;
632
633 =head2 C<diag_stream [ $fh ]>
634
635 Read/write accessor for the filehandle to which the diagnostics are printed.
636 On write, it also turns autoflush on onto C<$fh>.
637
638 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.
639
640 Defaults to C<STDERR>.
641
642 =cut
643
644 sub diag_stream (;*) {
645  if (@_) {
646   $DIAG_STREAM = $_[0];
647
648   my $fh = select $DIAG_STREAM;
649   $|++;
650   select $fh;
651  }
652
653  return $DIAG_STREAM;
654 }
655
656 diag_stream *STDERR;
657
658 =head2 C<THREADSAFE>
659
660 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>.
661 In that case, it also needs a working L<threads::shared>.
662
663 =head1 DEPENDENCIES
664
665 L<perl> 5.6.
666
667 L<Exporter>, L<Scalar::Util>, L<Test::More>.
668
669 =head1 AUTHOR
670
671 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
672
673 You can contact me by mail or on C<irc.perl.org> (vincent).
674
675 =head1 BUGS
676
677 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>.
678 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
679
680 =head1 SUPPORT
681
682 You can find documentation for this module with the perldoc command.
683
684     perldoc Test::Leaner
685
686 =head1 COPYRIGHT & LICENSE
687
688 Copyright 2010 Vincent Pit, all rights reserved.
689
690 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
691
692 =cut
693
694 1; # End of Test::Leaner