]> git.vpit.fr Git - perl/modules/Test-Leaner.git/blob - lib/Test/Leaner.pm
a022c92bedc56565fc3b671e60705b995b3878ab
[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  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   my $skip_str = "ok $test # skip";
187   if (defined $reason) {
188    sanitize_comment($reason);
189    $skip_str  .= " $reason" if length $reason;
190   }
191
192   local $\;
193   print $TAP_STREAM "$skip_str\n";
194
195   $test++;
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 my %binops;
263 BEGIN {
264  %binops = (
265   'or'  => 'or',
266   'and' => 'and',
267   'xor' => 'xor',
268
269   '||'  => 'hor',
270   '&&'  => 'hand',
271
272   'lt'  => 'lt',
273   'le'  => 'le',
274   'gt'  => 'gt',
275   'ge'  => 'ge',
276   'eq'  => 'eq',
277   'ne'  => 'ne',
278   'cmp' => 'cmp',
279
280   '<'   => 'nlt',
281   '<='  => 'nle',
282   '>'   => 'ngt',
283   '>='  => 'nge',
284   '=='  => 'neq',
285   '!='  => 'nne',
286   '<=>' => 'ncmp',
287
288   '=~'  => 'like',
289   '!~'  => 'unlike',
290   '~~'  => 'smartmatch',
291  );
292
293  for my $op (sort keys %binops) {
294   my $name = $binops{$op};
295   local $@;
296   eval <<"IS_BINOP";
297 sub is_$name (\$\$;\$) {
298  my (\$x, \$y, \$desc) = \@_;
299  \@_ = ((\$x $op \$y), \$desc);
300  goto &ok;
301 }
302 IS_BINOP
303   die $@ if $@;
304  }
305 }
306
307 sub is ($$;$) {
308  my ($got, $expected, $desc) = @_;
309  no warnings 'uninitialized';
310  @_ = (
311   (not(defined $got xor defined $expected) and $got eq $expected),
312   $desc,
313  );
314  goto &ok;
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 {
328  no warnings 'once';
329  *like   = \&is_like;
330  *unlike = \&is_unlike;
331 }
332
333 sub cmp_ok ($$$;$) {
334  my ($x, $op, $y, $desc) = @_;
335  my $name = $binops{$op};
336  croak("Operator $op not supported") unless defined $name;
337  @_ = ($x, $y, $desc);
338  no strict 'refs';
339  goto &{__PACKAGE__."::is_$name"};
340 }
341
342 sub _diag_fh {
343  my $fh = shift;
344
345  return unless @_;
346
347  lock $plan if THREADSAFE;
348  return if $no_diag;
349
350  my $msg = join '', map { defined($_) ? $_ : 'undef' } @_;
351  sanitize_comment($msg);
352  return unless length $msg;
353
354  local $\;
355  print $fh "# $msg\n";
356
357  return 0;
358 };
359
360 sub diag {
361  unshift @_, $DIAG_STREAM;
362  goto &_diag_fh;
363 }
364
365 sub note {
366  unshift @_, $TAP_STREAM;
367  goto &_diag_fh;
368 }
369
370 sub BAIL_OUT {
371  my ($desc) = @_;
372
373  lock $plan if THREADSAFE;
374
375  my $bail_out_str = 'Bail out!';
376  if (defined $desc) {
377   sanitize_comment($desc);
378   $bail_out_str  .= "  $desc" if length $desc; # Two spaces
379  }
380
381  local $\;
382  print $TAP_STREAM "$bail_out_str\n";
383
384  exit 255;
385 }
386
387 END {
388  unless ($?) {
389   lock $plan if THREADSAFE;
390
391   if (defined $plan) {
392    if ($failed) {
393     $? = $failed <= 254 ? $failed : 254;
394    } elsif ($plan >= 0) {
395     $? = $test == $plan ? 0 : 255;
396    } elsif ($plan == NO_PLAN) {
397     local $\;
398     print $TAP_STREAM "1..$test\n";
399    }
400   }
401  }
402 }
403
404 1; # End of Test::Leaner