=item *
Stringification isn't forced on the test operands.
-However, L</ok> honors C<'bool'> overloading, L</is> honors C<'eq'> overloading and L</cmp_ok> honors whichever overloading category corresponds to the specified operator.
+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.
=item *
=item *
+L</like> and L</unlike> don't special case regular expressions that are passed as C<'/.../'> strings.
+A string regexp argument is always treated as a the source of the regexp, making C<like $text, $rx> and C<like $text, qr[$rx]> equivalent to each other and to C<cmp_ok $text, '=~', $rx> (and likewise for C<unlike>).
+
+=item *
+
+L</cmp_ok> throws an exception if the given operator isn't a valid Perl binary operator (except C<'='> and variants).
+It also tests in scalar context, so C<'..'> will be treated as the flip-flop operator and not the range operator.
+
+=item *
+
+L</is_deeply> doesn't guard for memory cycles.
+If the two first arguments present parallel memory cycles, the test may result in an infinite loop.
+
+=item *
+
+The tests don't output any kind of default diagnostic in case of failure ; the rationale being that if you have a large number of tests and a lot of them are failing, then you don't want to be flooded by diagnostics.
+
+=item *
+
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.
+=item *
+
+L<Test::Leaner> depends on L<Scalar::Util>, while L<Test::More> does not.
+
=back
=cut
-use Exporter ();
+use Exporter ();
+use Scalar::Util ();
BEGIN {
if ($] >= 5.008 and $INC{'threads.pm'}) {
die @_, " at $file line $line.\n";
}
-sub sanitize_comment {
+sub _sanitize_comment {
$_[0] =~ s/\n+\z//;
$_[0] =~ s/#/\\#/g;
$_[0] =~ s/\n/\n# /g;
=head2 C<< plan [ tests => $count | 'no_plan' | skip_all => $reason ] >>
+See L<Test::More/plan>.
+
=cut
sub plan {
$plan = SKIP_ALL;
$plan_str = '1..0 # SKIP';
if (defined $value) {
- sanitize_comment($value);
+ _sanitize_comment($value);
$plan_str .= " $value" if length $value;
}
} else {
our @EXPORT = qw<
plan
- skip_all
skip
done_testing
pass
ok
is
isnt
- cmp_ok
like
unlike
+ cmp_ok
+ is_deeply
diag
note
BAIL_OUT
goto &Exporter::import;
}
-=head2 C<skip_all $reason>
-
-=cut
-
-sub skip_all {
- @_ = (skip_all => $_[0]);
- goto &plan;
-}
-
=head2 C<< skip $reason => $count >>
+See L<Test::More/skip>.
+
=cut
sub skip {
my $skip_str = "ok $test # skip";
if (defined $reason) {
- sanitize_comment($reason);
+ _sanitize_comment($reason);
$skip_str .= " $reason" if length $reason;
}
=head2 C<done_testing [ $count ]>
+See L<Test::More/done_testing>.
+
=cut
sub done_testing {
=head2 C<ok $ok [, $desc ]>
+See L<Test::More/ok>.
+
=cut
sub ok ($;$) {
++$failed;
}
if (defined $desc) {
- sanitize_comment($desc);
+ _sanitize_comment($desc);
$test_str .= " - $desc" if length $desc;
}
=head2 C<pass [ $desc ]>
+See L<Test::More/pass>.
+
=cut
sub pass (;$) {
=head2 C<fail [ $desc ]>
+See L<Test::More/fail>.
+
=cut
sub fail (;$) {
=head2 C<is $got, $expected [, $desc ]>
+See L<Test::More/is>.
+
=cut
sub is ($$;$) {
=head2 C<isnt $got, $expected [, $desc ]>
+See L<Test::More/isnt>.
+
=cut
sub isnt ($$;$) {
my %binops = (
'or' => 'or',
- 'and' => 'and',
'xor' => 'xor',
+ 'and' => 'and',
'||' => 'hor',
+ ('//' => 'dor') x ($] >= 5.010),
'&&' => 'hand',
+ '|' => 'bor',
+ '^' => 'bxor',
+ '&' => 'band',
+
'lt' => 'lt',
'le' => 'le',
'gt' => 'gt',
'=~' => 'like',
'!~' => 'unlike',
- '~~' => 'smartmatch',
+ ('~~' => 'smartmatch') x ($] >= 5.010),
+
+ '+' => 'add',
+ '-' => 'substract',
+ '*' => 'multiply',
+ '/' => 'divide',
+ '%' => 'modulo',
+ '<<' => 'lshift',
+ '>>' => 'rshift',
+
+ '.' => 'concat',
+ '..' => 'flipflop',
+ '...' => 'altflipflop',
+ ',' => 'comma',
+ '=>' => 'fatcomma',
);
my %binop_handlers;
eval <<"IS_BINOP";
sub is_$name (\$\$;\$) {
my (\$got, \$expected, \$desc) = \@_;
- \@_ = ((\$got $op \$expected), \$desc);
+ \@_ = (scalar(\$got $op \$expected), \$desc);
goto &ok;
}
IS_BINOP
=head2 C<like $got, $regexp_expected [, $desc ]>
+See L<Test::More/like>.
+
=head2 C<unlike $got, $regexp_expected, [, $desc ]>
+See L<Test::More/unlike>.
+
=cut
{
=head2 C<cmp_ok $got, $op, $expected [, $desc ]>
+See L<Test::More/cmp_ok>.
+
=cut
sub cmp_ok ($$$;$) {
goto $handler;
}
+=head2 C<is_deeply $got, $expected [, $desc ]>
+
+See L<Test::More/is_deeply>.
+
+=cut
+
+sub _deep_check {
+ my ($x, $y) = @_;
+
+ no warnings qw<numeric uninitialized>;
+
+ return 0 if defined($x) xor defined($y);
+
+ # Try object identity/eq overloading first. It also covers the case where
+ # $x and $y are both undefined.
+ # If either $x or $y is overloaded but none has eq overloading, the test will
+ # break at that point.
+ return 1 if not(ref($x) xor ref($y)) and $x eq $y;
+
+ # Test::More::is_deeply happily breaks encapsulation if the objects aren't
+ # overloaded.
+ my $ry = Scalar::Util::reftype($y);
+ return 0 if Scalar::Util::reftype($x) ne $ry;
+
+ # Shortcut if $x and $y are both not references and failed the previous
+ # $x eq $y test.
+ return 0 unless $ry;
+
+ if ($ry eq 'ARRAY') {
+ if ($#$x == $#$y) {
+ # Prevent vivification of deleted elements by fetching the array values.
+ my ($ex, $ey);
+ _deep_check($ex = $x->[$_], $ey = $y->[$_]) or return 0 for 0 .. $#$y;
+ return 1;
+ }
+ } elsif ($ry eq 'HASH') {
+ if (keys(%$x) == keys(%$y)) {
+ (exists $x->{$_} and _deep_check($x->{$_}, $y->{$_}))
+ or return 0 for keys %$y;
+ return 1;
+ }
+ } elsif ($ry eq 'SCALAR' or $ry eq 'REF') {
+ return _deep_check($$x, $$y);
+ }
+
+ return 0;
+};
+
+sub is_deeply {
+ @_ = (
+ &_deep_check,
+ $_[2],
+ );
+ goto &ok;
+}
+
sub _diag_fh {
my $fh = shift;
return if $no_diag;
my $msg = join '', map { defined($_) ? $_ : 'undef' } @_;
- sanitize_comment($msg);
+ _sanitize_comment($msg);
return unless length $msg;
local $\;
=head2 C<diag @text>
+See L<Test::More/diag>.
+
=cut
sub diag {
=head2 C<note @text>
+See L<Test::More/note>.
+
=cut
sub note {
=head2 C<BAIL_OUT [ $desc ]>
+See L<Test::More/BAIL_OUT>.
+
=cut
sub BAIL_OUT {
my $bail_out_str = 'Bail out!';
if (defined $desc) {
- sanitize_comment($desc);
+ _sanitize_comment($desc);
$bail_out_str .= " $desc" if length $desc; # Two spaces
}
diag_stream *STDERR;
+=head2 C<THREADSAFE>
+
+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>.
+In that case, it also needs a working L<threads::shared>.
+
=head1 DEPENDENCIES
L<perl> 5.6.
-L<Exporter>, L<Test::More>
+L<Exporter>, L<Scalar::Util>, L<Test::More>.
=head1 AUTHOR