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