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