]> git.vpit.fr Git - perl/modules/Test-Leaner.git/blob - lib/Test/Leaner.pm
Also share $done_testing
[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 our $VERSION = '0.01';
8
9 use Exporter ();
10
11 BEGIN {
12  if ($] >= 5.008 and $INC{'threads.pm'}) {
13   my $use_ithreads = do {
14    require Config;
15    no warnings 'once';
16    $Config::Config{useithreads};
17   };
18   if ($use_ithreads) {
19    require threads::shared;
20    *THREADSAFE = sub () { 1 };
21   }
22  }
23  unless (defined &Test::Leaner::THREADSAFE) {
24   *THREADSAFE = sub () { 0 }
25  }
26 }
27
28 my $TAP_STREAM  = *STDOUT;
29 my $DIAG_STREAM = *STDERR;
30
31 my ($plan, $test, $failed, $no_diag, $done_testing);
32
33 sub NO_PLAN  () { -1 }
34 sub SKIP_ALL () { -2 }
35
36 BEGIN {
37  if (THREADSAFE) {
38   threads::shared::share($_) for $plan, $test, $failed, $no_diag, $done_testing;
39  }
40
41  lock $plan if THREADSAFE;
42
43  $plan   = undef;
44  $test   = 0;
45  $failed = 0;
46 }
47
48 sub carp {
49  my $level = 1 + ($Test::Builder::Level || 0);
50  my ($file, $line) = (caller $level)[1, 2];
51  warn @_, " at $file line $line.\n";
52 }
53
54 sub croak {
55  my $level = 1 + ($Test::Builder::Level || 0);
56  my ($file, $line) = (caller $level)[1, 2];
57  die @_, " at $file line $line.\n";
58 }
59
60 sub sanitize_comment {
61  $_[0] =~ s/\n+\z//;
62  $_[0] =~ s/#/\\#/g;
63  $_[0] =~ s/\n/\n# /g;
64 }
65
66 sub plan {
67  my ($key, $value) = @_;
68
69  return unless $key;
70
71  lock $plan if THREADSAFE;
72
73  croak("You tried to plan twice") if defined $plan;
74
75  my $plan_str;
76
77  if ($key eq 'no_plan') {
78   croak("no_plan takes no arguments") if $value;
79   $plan       = NO_PLAN;
80  } elsif ($key eq 'tests') {
81   croak("Got an undefined number of tests") unless defined $value;
82   croak("You said to run 0 tests")          unless $value;
83   croak("Number of tests must be a positive integer.  You gave it '$value'")
84                                             unless $value =~ /^\+?[0-9]+$/;
85   $plan       = $value;
86   $plan_str   = "1..$value";
87  } elsif ($key eq 'skip_all') {
88   $plan       = SKIP_ALL;
89   $plan_str   = '1..0 # SKIP';
90   if (defined $value) {
91    sanitize_comment($value);
92    $plan_str .= " $value" if length $value;
93   }
94  } else {
95   my @args = grep defined, $key, $value;
96   croak("plan() doesn't understand @args");
97  }
98
99  {
100   my $fh = select $TAP_STREAM;
101   $|++;
102   select $fh;
103  }
104
105  if (defined $plan_str) {
106   local $\;
107   print $TAP_STREAM "$plan_str\n";
108  }
109
110  exit 0 if $plan == SKIP_ALL;
111
112  return 1;
113 }
114
115 our @EXPORT = qw<
116  plan
117  skip_all
118  skip
119  done_testing
120  pass
121  fail
122  ok
123  is
124  isnt
125  cmp_ok
126  like
127  unlike
128  diag
129  note
130  BAIL_OUT
131 >;
132
133 sub import {
134  my $class = shift;
135
136  my @imports;
137  my $i = 0;
138  while ($i <= $#_) {
139   my $item = $_[$i];
140   my $splice;
141   if (defined $item) {
142    if ($item eq 'import') {
143     push @imports, @{ $_[$i+1] };
144     $splice  = 2;
145    } elsif ($item eq 'no_diag') {
146     $no_diag = 1;
147     $splice  = 1;
148    }
149   }
150   if ($splice) {
151    splice @_, $i, $splice;
152   } else {
153    ++$i;
154   }
155  }
156
157  if (@_) {
158   local $Test::Builder::Level = ($Test::Builder::Level || 0) + 1;
159   &plan;
160  }
161
162  @_ = ($class, @imports);
163  goto &Exporter::import;
164 }
165
166 sub skip_all {
167  @_ = (skip_all => $_[0]);
168  goto &plan;
169 }
170
171 sub skip {
172  my ($reason, $count) = @_;
173
174  lock $plan if THREADSAFE;
175
176  if (not defined $count) {
177   carp("skip() needs to know \$how_many tests are in the block")
178                                       unless defined $plan and $plan == NO_PLAN;
179   $count = 1;
180  } elsif ($count =~ /[^0-9]/) {
181   carp('skip() was passed a non-numeric number of tests.  Did you get the arguments backwards?');
182   $count = 1;
183  }
184
185  for (1 .. $count) {
186   ++$test;
187
188   my $skip_str = "ok $test # skip";
189   if (defined $reason) {
190    sanitize_comment($reason);
191    $skip_str  .= " $reason" if length $reason;
192   }
193
194   local $\;
195   print $TAP_STREAM "$skip_str\n";
196  }
197
198  no warnings 'exiting';
199  last SKIP;
200 }
201
202 sub done_testing {
203  my ($count) = @_;
204
205  lock $plan if THREADSAFE;
206
207  $count = $test unless defined $count;
208  croak("Number of tests must be a positive integer.  You gave it '$count'")
209                                                  unless $count =~ /^\+?[0-9]+$/;
210
211  if (not defined $plan or $plan == NO_PLAN) {
212   $plan         = $count; # $plan can't be NO_PLAN anymore
213   $done_testing = 1;
214   local $\;
215   print $TAP_STREAM "1..$plan\n";
216  } else {
217   if ($done_testing) {
218    @_ = ('done_testing() was already called');
219    goto &fail;
220   } elsif ($plan != $count) {
221    @_ = ("planned to run $plan tests but done_testing() expects $count");
222    goto &fail;
223   }
224  }
225
226  return 1;
227 }
228
229 sub ok ($;$) {
230  my ($ok, $desc) = @_;
231
232  lock $plan if THREADSAFE;
233
234  ++$test;
235
236  my $test_str = "ok $test";
237  unless ($ok) {
238   $test_str   = "not $test_str";
239   ++$failed;
240  }
241  if (defined $desc) {
242   sanitize_comment($desc);
243   $test_str .= " - $desc" if length $desc;
244  }
245
246  local $\;
247  print $TAP_STREAM "$test_str\n";
248
249  return $ok;
250 }
251
252 sub pass (;$) {
253  unshift @_, 1;
254  goto &ok;
255 }
256
257 sub fail (;$) {
258  unshift @_, 0;
259  goto &ok;
260 }
261
262 sub is ($$;$) {
263  my ($got, $expected, $desc) = @_;
264  no warnings 'uninitialized';
265  @_ = (
266   (not(defined $got xor defined $expected) and $got eq $expected),
267   $desc,
268  );
269  goto &ok;
270 }
271
272 sub isnt ($$;$) {
273  my ($got, $expected, $desc) = @_;
274  no warnings 'uninitialized';
275  @_ = (
276   ((defined $got xor defined $expected) or $got ne $expected),
277   $desc,
278  );
279  goto &ok;
280 }
281
282 my %binops = (
283  'or'  => 'or',
284  'and' => 'and',
285  'xor' => 'xor',
286
287  '||'  => 'hor',
288  '&&'  => 'hand',
289
290  'lt'  => 'lt',
291  'le'  => 'le',
292  'gt'  => 'gt',
293  'ge'  => 'ge',
294  'eq'  => 'eq',
295  'ne'  => 'ne',
296  'cmp' => 'cmp',
297
298  '<'   => 'nlt',
299  '<='  => 'nle',
300  '>'   => 'ngt',
301  '>='  => 'nge',
302  '=='  => 'neq',
303  '!='  => 'nne',
304  '<=>' => 'ncmp',
305
306  '=~'  => 'like',
307  '!~'  => 'unlike',
308  '~~'  => 'smartmatch',
309 );
310
311 my %binop_handlers;
312
313 sub _create_binop_handler {
314  my ($op) = @_;
315  my $name = $binops{$op};
316  croak("Operator $op not supported") unless defined $name;
317  {
318   local $@;
319   eval <<"IS_BINOP";
320 sub is_$name (\$\$;\$) {
321  my (\$got, \$expected, \$desc) = \@_;
322  \@_ = ((\$got $op \$expected), \$desc);
323  goto &ok;
324 }
325 IS_BINOP
326   die $@ if $@;
327  }
328  $binop_handlers{$op} = do {
329   no strict 'refs';
330   \&{__PACKAGE__."::is_$name"};
331  }
332 }
333
334 {
335  no warnings 'once';
336  *like   = _create_binop_handler('=~');
337  *unlike = _create_binop_handler('!~');
338 }
339
340 sub cmp_ok ($$$;$) {
341  my ($got, $op, $expected, $desc) = @_;
342  my $handler = $binop_handlers{$op};
343  unless ($handler) {
344   local $Test::More::Level = ($Test::More::Level || 0) + 1;
345   $handler = _create_binop_handler($op);
346  }
347  @_ = ($got, $expected, $desc);
348  goto $handler;
349 }
350
351 sub _diag_fh {
352  my $fh = shift;
353
354  return unless @_;
355
356  lock $plan if THREADSAFE;
357  return if $no_diag;
358
359  my $msg = join '', map { defined($_) ? $_ : 'undef' } @_;
360  sanitize_comment($msg);
361  return unless length $msg;
362
363  local $\;
364  print $fh "# $msg\n";
365
366  return 0;
367 };
368
369 sub diag {
370  unshift @_, $DIAG_STREAM;
371  goto &_diag_fh;
372 }
373
374 sub note {
375  unshift @_, $TAP_STREAM;
376  goto &_diag_fh;
377 }
378
379 sub BAIL_OUT {
380  my ($desc) = @_;
381
382  lock $plan if THREADSAFE;
383
384  my $bail_out_str = 'Bail out!';
385  if (defined $desc) {
386   sanitize_comment($desc);
387   $bail_out_str  .= "  $desc" if length $desc; # Two spaces
388  }
389
390  local $\;
391  print $TAP_STREAM "$bail_out_str\n";
392
393  exit 255;
394 }
395
396 END {
397  unless ($?) {
398   lock $plan if THREADSAFE;
399
400   if (defined $plan) {
401    if ($failed) {
402     $? = $failed <= 254 ? $failed : 254;
403    } elsif ($plan >= 0) {
404     $? = $test == $plan ? 0 : 255;
405    } elsif ($plan == NO_PLAN) {
406     local $\;
407     print $TAP_STREAM "1..$test\n";
408    }
409   }
410  }
411 }
412
413 1; # End of Test::Leaner