]> git.vpit.fr Git - perl/modules/Test-Leaner.git/blob - lib/Test/Leaner.pm
Fix, export and test cmp_ok()
[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  no warnings 'uninitialized';
300  \@_ = (
301   (not(defined \$x xor defined \$y) and \$x $op \$y),
302   \$desc,
303  );
304  goto &ok;
305 }
306 IS_BINOP
307   die $@ if $@;
308  }
309 }
310
311 {
312  no warnings 'once';
313  *is     = \&is_eq;
314  *like   = \&is_like;
315  *unlike = \&is_unlike;
316 }
317
318 sub isnt ($$;$) {
319  my ($x, $y, $desc) = @_;
320  no warnings 'uninitialized';
321  @_ = (
322   ((defined $x xor defined $y) or $x ne $y),
323   $desc,
324  );
325  goto &ok;
326 }
327
328 sub cmp_ok ($$$;$) {
329  my ($x, $op, $y, $desc) = @_;
330  my $name = $binops{$op};
331  croak("Operator $op not supported") unless defined $name;
332  @_ = ($x, $y, $desc);
333  no strict 'refs';
334  goto &{__PACKAGE__."::is_$name"};
335 }
336
337 sub _diag_fh {
338  my $fh = shift;
339
340  return unless @_;
341
342  lock $plan if THREADSAFE;
343  return if $no_diag;
344
345  my $msg = join '', map { defined($_) ? $_ : 'undef' } @_;
346  sanitize_comment($msg);
347  return unless length $msg;
348
349  local $\;
350  print $fh "# $msg\n";
351
352  return 0;
353 };
354
355 sub diag {
356  unshift @_, $DIAG_STREAM;
357  goto &_diag_fh;
358 }
359
360 sub note {
361  unshift @_, $TAP_STREAM;
362  goto &_diag_fh;
363 }
364
365 sub BAIL_OUT {
366  my ($desc) = @_;
367
368  lock $plan if THREADSAFE;
369
370  my $bail_out_str = 'Bail out!';
371  if (defined $desc) {
372   sanitize_comment($desc);
373   $bail_out_str  .= "  $desc" if length $desc; # Two spaces
374  }
375
376  local $\;
377  print $TAP_STREAM "$bail_out_str\n";
378
379  exit 255;
380 }
381
382 END {
383  unless ($?) {
384   lock $plan if THREADSAFE;
385
386   if (defined $plan) {
387    if ($failed) {
388     $? = $failed <= 254 ? $failed : 254;
389    } elsif ($plan >= 0) {
390     $? = $test == $plan ? 0 : 255;
391    } elsif ($plan == NO_PLAN) {
392     local $\;
393     print $TAP_STREAM "1..$test\n";
394    }
395   }
396  }
397 }
398
399 1; # End of Test::Leaner