]> git.vpit.fr Git - perl/modules/Test-Leaner.git/blob - lib/Test/Leaner.pm
Document that tests functions return the value of the test
[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
34 =cut
35
36 use Exporter ();
37
38 BEGIN {
39  if ($] >= 5.008 and $INC{'threads.pm'}) {
40   my $use_ithreads = do {
41    require Config;
42    no warnings 'once';
43    $Config::Config{useithreads};
44   };
45   if ($use_ithreads) {
46    require threads::shared;
47    *THREADSAFE = sub () { 1 };
48   }
49  }
50  unless (defined &Test::Leaner::THREADSAFE) {
51   *THREADSAFE = sub () { 0 }
52  }
53 }
54
55 my $TAP_STREAM  = *STDOUT;
56 my $DIAG_STREAM = *STDERR;
57
58 for ($TAP_STREAM, $DIAG_STREAM) {
59  my $fh = select $_;
60  $|++;
61  select $fh;
62 }
63
64 my ($plan, $test, $failed, $no_diag, $done_testing);
65
66 sub NO_PLAN  () { -1 }
67 sub SKIP_ALL () { -2 }
68
69 BEGIN {
70  if (THREADSAFE) {
71   threads::shared::share($_) for $plan, $test, $failed, $no_diag, $done_testing;
72  }
73
74  lock $plan if THREADSAFE;
75
76  $plan   = undef;
77  $test   = 0;
78  $failed = 0;
79 }
80
81 sub carp {
82  my $level = 1 + ($Test::Builder::Level || 0);
83  my ($file, $line) = (caller $level)[1, 2];
84  warn @_, " at $file line $line.\n";
85 }
86
87 sub croak {
88  my $level = 1 + ($Test::Builder::Level || 0);
89  my ($file, $line) = (caller $level)[1, 2];
90  die @_, " at $file line $line.\n";
91 }
92
93 sub sanitize_comment {
94  $_[0] =~ s/\n+\z//;
95  $_[0] =~ s/#/\\#/g;
96  $_[0] =~ s/\n/\n# /g;
97 }
98
99 =head1 FUNCTIONS
100
101 The following functions from L<Test::More> are implemented and exported by default.
102
103 =head2 C<< plan [ tests => $count | 'no_plan' | skip_all => $reason ] >>
104
105 =cut
106
107 sub plan {
108  my ($key, $value) = @_;
109
110  return unless $key;
111
112  lock $plan if THREADSAFE;
113
114  croak("You tried to plan twice") if defined $plan;
115
116  my $plan_str;
117
118  if ($key eq 'no_plan') {
119   croak("no_plan takes no arguments") if $value;
120   $plan       = NO_PLAN;
121  } elsif ($key eq 'tests') {
122   croak("Got an undefined number of tests") unless defined $value;
123   croak("You said to run 0 tests")          unless $value;
124   croak("Number of tests must be a positive integer.  You gave it '$value'")
125                                             unless $value =~ /^\+?[0-9]+$/;
126   $plan       = $value;
127   $plan_str   = "1..$value";
128  } elsif ($key eq 'skip_all') {
129   $plan       = SKIP_ALL;
130   $plan_str   = '1..0 # SKIP';
131   if (defined $value) {
132    sanitize_comment($value);
133    $plan_str .= " $value" if length $value;
134   }
135  } else {
136   my @args = grep defined, $key, $value;
137   croak("plan() doesn't understand @args");
138  }
139
140  if (defined $plan_str) {
141   local $\;
142   print $TAP_STREAM "$plan_str\n";
143  }
144
145  exit 0 if $plan == SKIP_ALL;
146
147  return 1;
148 }
149
150 our @EXPORT = qw<
151  plan
152  skip_all
153  skip
154  done_testing
155  pass
156  fail
157  ok
158  is
159  isnt
160  cmp_ok
161  like
162  unlike
163  diag
164  note
165  BAIL_OUT
166 >;
167
168 sub import {
169  my $class = shift;
170
171  my @imports;
172  my $i = 0;
173  while ($i <= $#_) {
174   my $item = $_[$i];
175   my $splice;
176   if (defined $item) {
177    if ($item eq 'import') {
178     push @imports, @{ $_[$i+1] };
179     $splice  = 2;
180    } elsif ($item eq 'no_diag') {
181     lock $plan if THREADSAFE;
182     $no_diag = 1;
183     $splice  = 1;
184    }
185   }
186   if ($splice) {
187    splice @_, $i, $splice;
188   } else {
189    ++$i;
190   }
191  }
192
193  if (@_) {
194   local $Test::Builder::Level = ($Test::Builder::Level || 0) + 1;
195   &plan;
196  }
197
198  @_ = ($class, @imports);
199  goto &Exporter::import;
200 }
201
202 =head2 C<skip_all $reason>
203
204 =cut
205
206 sub skip_all {
207  @_ = (skip_all => $_[0]);
208  goto &plan;
209 }
210
211 =head2 C<< skip $reason => $count >>
212
213 =cut
214
215 sub skip {
216  my ($reason, $count) = @_;
217
218  lock $plan if THREADSAFE;
219
220  if (not defined $count) {
221   carp("skip() needs to know \$how_many tests are in the block")
222                                       unless defined $plan and $plan == NO_PLAN;
223   $count = 1;
224  } elsif ($count =~ /[^0-9]/) {
225   carp('skip() was passed a non-numeric number of tests.  Did you get the arguments backwards?');
226   $count = 1;
227  }
228
229  for (1 .. $count) {
230   ++$test;
231
232   my $skip_str = "ok $test # skip";
233   if (defined $reason) {
234    sanitize_comment($reason);
235    $skip_str  .= " $reason" if length $reason;
236   }
237
238   local $\;
239   print $TAP_STREAM "$skip_str\n";
240  }
241
242  no warnings 'exiting';
243  last SKIP;
244 }
245
246 =head2 C<done_testing [ $count ]>
247
248 =cut
249
250 sub done_testing {
251  my ($count) = @_;
252
253  lock $plan if THREADSAFE;
254
255  $count = $test unless defined $count;
256  croak("Number of tests must be a positive integer.  You gave it '$count'")
257                                                  unless $count =~ /^\+?[0-9]+$/;
258
259  if (not defined $plan or $plan == NO_PLAN) {
260   $plan         = $count; # $plan can't be NO_PLAN anymore
261   $done_testing = 1;
262   local $\;
263   print $TAP_STREAM "1..$plan\n";
264  } else {
265   if ($done_testing) {
266    @_ = ('done_testing() was already called');
267    goto &fail;
268   } elsif ($plan != $count) {
269    @_ = ("planned to run $plan tests but done_testing() expects $count");
270    goto &fail;
271   }
272  }
273
274  return 1;
275 }
276
277 =head2 C<ok $ok [, $desc ]>
278
279 =cut
280
281 sub ok ($;$) {
282  my ($ok, $desc) = @_;
283
284  lock $plan if THREADSAFE;
285
286  ++$test;
287
288  my $test_str = "ok $test";
289  unless ($ok) {
290   $test_str   = "not $test_str";
291   ++$failed;
292  }
293  if (defined $desc) {
294   sanitize_comment($desc);
295   $test_str .= " - $desc" if length $desc;
296  }
297
298  local $\;
299  print $TAP_STREAM "$test_str\n";
300
301  return $ok;
302 }
303
304 =head2 C<pass [ $desc ]>
305
306 =cut
307
308 sub pass (;$) {
309  unshift @_, 1;
310  goto &ok;
311 }
312
313 =head2 C<fail [ $desc ]>
314
315 =cut
316
317 sub fail (;$) {
318  unshift @_, 0;
319  goto &ok;
320 }
321
322 =head2 C<is $got, $expected [, $desc ]>
323
324 =cut
325
326 sub is ($$;$) {
327  my ($got, $expected, $desc) = @_;
328  no warnings 'uninitialized';
329  @_ = (
330   (not(defined $got xor defined $expected) and $got eq $expected),
331   $desc,
332  );
333  goto &ok;
334 }
335
336 =head2 C<isnt $got, $expected [, $desc ]>
337
338 =cut
339
340 sub isnt ($$;$) {
341  my ($got, $expected, $desc) = @_;
342  no warnings 'uninitialized';
343  @_ = (
344   ((defined $got xor defined $expected) or $got ne $expected),
345   $desc,
346  );
347  goto &ok;
348 }
349
350 my %binops = (
351  'or'  => 'or',
352  'and' => 'and',
353  'xor' => 'xor',
354
355  '||'  => 'hor',
356  '&&'  => 'hand',
357
358  'lt'  => 'lt',
359  'le'  => 'le',
360  'gt'  => 'gt',
361  'ge'  => 'ge',
362  'eq'  => 'eq',
363  'ne'  => 'ne',
364  'cmp' => 'cmp',
365
366  '<'   => 'nlt',
367  '<='  => 'nle',
368  '>'   => 'ngt',
369  '>='  => 'nge',
370  '=='  => 'neq',
371  '!='  => 'nne',
372  '<=>' => 'ncmp',
373
374  '=~'  => 'like',
375  '!~'  => 'unlike',
376  '~~'  => 'smartmatch',
377 );
378
379 my %binop_handlers;
380
381 sub _create_binop_handler {
382  my ($op) = @_;
383  my $name = $binops{$op};
384  croak("Operator $op not supported") unless defined $name;
385  {
386   local $@;
387   eval <<"IS_BINOP";
388 sub is_$name (\$\$;\$) {
389  my (\$got, \$expected, \$desc) = \@_;
390  \@_ = ((\$got $op \$expected), \$desc);
391  goto &ok;
392 }
393 IS_BINOP
394   die $@ if $@;
395  }
396  $binop_handlers{$op} = do {
397   no strict 'refs';
398   \&{__PACKAGE__."::is_$name"};
399  }
400 }
401
402 =head2 C<like $got, $regexp_expected [, $desc ]>
403
404 =head2 C<unlike $got, $regexp_expected, [, $desc ]>
405
406 =cut
407
408 {
409  no warnings 'once';
410  *like   = _create_binop_handler('=~');
411  *unlike = _create_binop_handler('!~');
412 }
413
414 =head2 C<cmp_ok $got, $op, $expected [, $desc ]>
415
416 =cut
417
418 sub cmp_ok ($$$;$) {
419  my ($got, $op, $expected, $desc) = @_;
420  my $handler = $binop_handlers{$op};
421  unless ($handler) {
422   local $Test::More::Level = ($Test::More::Level || 0) + 1;
423   $handler = _create_binop_handler($op);
424  }
425  @_ = ($got, $expected, $desc);
426  goto $handler;
427 }
428
429 sub _diag_fh {
430  my $fh = shift;
431
432  return unless @_;
433
434  lock $plan if THREADSAFE;
435  return if $no_diag;
436
437  my $msg = join '', map { defined($_) ? $_ : 'undef' } @_;
438  sanitize_comment($msg);
439  return unless length $msg;
440
441  local $\;
442  print $fh "# $msg\n";
443
444  return 0;
445 };
446
447 =head2 C<diag @text>
448
449 =cut
450
451 sub diag {
452  unshift @_, $DIAG_STREAM;
453  goto &_diag_fh;
454 }
455
456 =head2 C<note @text>
457
458 =cut
459
460 sub note {
461  unshift @_, $TAP_STREAM;
462  goto &_diag_fh;
463 }
464
465 =head2 C<BAIL_OUT [ $desc ]>
466
467 =cut
468
469 sub BAIL_OUT {
470  my ($desc) = @_;
471
472  lock $plan if THREADSAFE;
473
474  my $bail_out_str = 'Bail out!';
475  if (defined $desc) {
476   sanitize_comment($desc);
477   $bail_out_str  .= "  $desc" if length $desc; # Two spaces
478  }
479
480  local $\;
481  print $TAP_STREAM "$bail_out_str\n";
482
483  exit 255;
484 }
485
486 END {
487  unless ($?) {
488   lock $plan if THREADSAFE;
489
490   if (defined $plan) {
491    if ($failed) {
492     $? = $failed <= 254 ? $failed : 254;
493    } elsif ($plan >= 0) {
494     $? = $test == $plan ? 0 : 255;
495    } elsif ($plan == NO_PLAN) {
496     local $\;
497     print $TAP_STREAM "1..$test\n";
498    }
499   }
500  }
501 }
502
503 =pod
504
505 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.
506 Their L<Test::More> counterparts behave the same, but it is not documented anywhere.
507
508 =head1 DEPENDENCIES
509
510 L<perl> 5.6.
511
512 L<Exporter>, L<Test::More>
513
514 =head1 AUTHOR
515
516 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
517
518 You can contact me by mail or on C<irc.perl.org> (vincent).
519
520 =head1 BUGS
521
522 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>.
523 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
524
525 =head1 SUPPORT
526
527 You can find documentation for this module with the perldoc command.
528
529     perldoc Test::Leaner
530
531 =head1 COPYRIGHT & LICENSE
532
533 Copyright 2010 Vincent Pit, all rights reserved.
534
535 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
536
537 =cut
538
539 1; # End of Test::Leaner