]> git.vpit.fr Git - perl/modules/Test-Leaner.git/blob - lib/Test/Leaner.pm
c4c577d2dde61f0eeece12b0f6ccdf9c6ff1a721
[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;
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  like
126  unlike
127  diag
128  note
129  BAIL_OUT
130 >;
131
132 sub import {
133  my $class = shift;
134
135  my @imports;
136  my $i = 0;
137  while ($i <= $#_) {
138   my $item = $_[$i];
139   my $splice;
140   if (defined $item) {
141    if ($item eq 'import') {
142     push @imports, @{ $_[$i+1] };
143     $splice  = 2;
144    } elsif ($item eq 'no_diag') {
145     $no_diag = 1;
146     $splice  = 1;
147    }
148   }
149   if ($splice) {
150    splice @_, $i, $splice;
151   } else {
152    ++$i;
153   }
154  }
155
156  if (@_) {
157   local $Test::Builder::Level = ($Test::Builder::Level || 0) + 1;
158   &plan;
159  }
160
161  @_ = ($class, @imports);
162  goto &Exporter::import;
163 }
164
165 sub skip_all {
166  @_ = (skip_all => $_[0]);
167  goto &plan;
168 }
169
170 sub skip {
171  my ($reason, $count) = @_;
172
173  lock $plan if THREADSAFE;
174
175  if (not defined $count) {
176   carp("skip() needs to know \$how_many tests are in the block")
177                                       unless defined $plan and $plan == NO_PLAN;
178   $count = 1;
179  } elsif ($count =~ /[^0-9]/) {
180   carp('skip() was passed a non-numeric number of tests.  Did you get the arguments backwards?');
181   $count = 1;
182  }
183
184  for (1 .. $count) {
185   my $skip_str = "ok $test # skip";
186   if (defined $reason) {
187    sanitize_comment($reason);
188    $skip_str  .= " $reason" if length $reason;
189   }
190
191   local $\;
192   print $TAP_STREAM "$skip_str\n";
193
194   $test++;
195  }
196
197  no warnings 'exiting';
198  last SKIP;
199 }
200
201 sub done_testing {
202  my ($count) = @_;
203
204  lock $plan if THREADSAFE;
205
206  $count = $test unless defined $count;
207  croak("Number of tests must be a positive integer.  You gave it '$count'")
208                                                  unless $count =~ /^\+?[0-9]+$/;
209
210  if (not defined $plan or $plan == NO_PLAN) {
211   $plan         = $count; # $plan can't be NO_PLAN anymore
212   $done_testing = 1;
213   local $\;
214   print $TAP_STREAM "1..$plan\n";
215  } else {
216   if ($done_testing) {
217    @_ = ('done_testing() was already called');
218    goto &fail;
219   } elsif ($plan != $count) {
220    @_ = ("planned to run $plan tests but done_testing() expects $count");
221    goto &fail;
222   }
223  }
224
225  return 1;
226 }
227
228 sub ok ($;$) {
229  my ($ok, $desc) = @_;
230
231  lock $plan if THREADSAFE;
232
233  ++$test;
234
235  my $test_str = "ok $test";
236  unless ($ok) {
237   $test_str   = "not $test_str";
238   ++$failed;
239  }
240  if (defined $desc) {
241   sanitize_comment($desc);
242   $test_str .= " - $desc" if length $desc;
243  }
244
245  local $\;
246  print $TAP_STREAM "$test_str\n";
247
248  return $ok;
249 }
250
251 sub pass (;$) {
252  unshift @_, 1;
253  goto &ok;
254 }
255
256 sub fail (;$) {
257  unshift @_, 0;
258  goto &ok;
259 }
260
261 my %binops;
262 BEGIN {
263  %binops = (
264   'or'  => 'or',
265   'and' => 'and',
266   'xor' => 'xor',
267
268   '||'  => 'hor',
269   '&&'  => 'hand',
270
271   'lt'  => 'lt',
272   'le'  => 'le',
273   'gt'  => 'gt',
274   'ge'  => 'ge',
275   'eq'  => 'eq',
276   'ne'  => 'ne',
277   'cmp' => 'cmp',
278
279   '<'   => 'nlt',
280   '<='  => 'nle',
281   '>'   => 'ngt',
282   '>='  => 'nge',
283   '=='  => 'neq',
284   '!='  => 'nne',
285   '<=>' => 'ncmp',
286
287   '=~'  => 'like',
288   '!~'  => 'unlike',
289   '~~'  => 'smartmatch',
290  );
291
292  for my $op (sort keys %binops) {
293   my $name = $binops{$op};
294   local $@;
295   eval <<"IS_BINOP";
296 sub is_$name (\$\$;\$) {
297  my (\$x, \$y, \$desc) = \@_;
298  no warnings 'uninitialized';
299  \@_ = (
300   (not(defined \$x xor defined \$y) and \$x $op \$y),
301   \$desc,
302  );
303  goto &ok;
304 }
305 IS_BINOP
306   die $@ if $@;
307  }
308 }
309
310 {
311  no warnings 'once';
312  *is     = \&is_eq;
313  *like   = \&is_like;
314  *unlike = \&is_unlike;
315 }
316
317 sub isnt ($$;$) {
318  my ($x, $y, $desc) = @_;
319  no warnings 'uninitialized';
320  @_ = (
321   ((defined $x xor defined $y) or $x ne $y),
322   $desc,
323  );
324  goto &ok;
325 }
326
327 sub cmp_ok ($$$;$) {
328  my ($x, $op, $y, $desc) = @_;
329  my $name = $binops{$op};
330  croak("Operator $op not supported") unless defined $name;
331  @_ = ($x, $y, $desc);
332  no strict 'refs';
333  goto &{__PACKAGE__."is_$name"};
334 }
335
336 sub _diag_fh {
337  my $fh = shift;
338
339  return unless @_;
340
341  lock $plan if THREADSAFE;
342  return if $no_diag;
343
344  my $msg = join '', map { defined($_) ? $_ : 'undef' } @_;
345  sanitize_comment($msg);
346  return unless length $msg;
347
348  local $\;
349  print $fh "# $msg\n";
350
351  return 0;
352 };
353
354 sub diag {
355  unshift @_, $DIAG_STREAM;
356  goto &_diag_fh;
357 }
358
359 sub note {
360  unshift @_, $TAP_STREAM;
361  goto &_diag_fh;
362 }
363
364 sub BAIL_OUT {
365  my ($desc) = @_;
366
367  lock $plan if THREADSAFE;
368
369  my $bail_out_str = 'Bail out!';
370  if (defined $desc) {
371   sanitize_comment($desc);
372   $bail_out_str  .= "  $desc" if length $desc; # Two spaces
373  }
374
375  local $\;
376  print $TAP_STREAM "$bail_out_str\n";
377
378  exit 255;
379 }
380
381 END {
382  unless ($?) {
383   lock $plan if THREADSAFE;
384
385   if (defined $plan) {
386    if ($failed) {
387     $? = $failed <= 254 ? $failed : 254;
388    } elsif ($plan >= 0) {
389     $? = $test == $plan ? 0 : 255;
390    } elsif ($plan == NO_PLAN) {
391     local $\;
392     print $TAP_STREAM "1..$test\n";
393    }
394   }
395  }
396 }
397
398 1; # End of Test::Leaner