#!/usr/bin/perl -c

package Test::Assert;

=head1 NAME

Test::Assert - Assertion methods for those who like JUnit.

=head1 SYNOPSIS

  # Use as imported methods
  #
  package My::Test;

  use Test::Assert ':all';

  assert_true(1, "pass");
  assert_true(0, "fail");

  use Test::More;
  assert_test(sub { require_ok($module) });

  # Use for debugging purposes
  # Assertions are compiled only if Test::Assert was used
  # from the main package.
  #
  package My::Package;

  use Test::Assert ':assert';

  my $state = do_something();
  assert_true($state >= 1 && $state <=2) if ASSERT;
  if ($state == 1) {
      # 1st state
      do_foo();
  } elsif ($state == 2) {
      # 2nd and last state
      do_bar();
  }

  my $a = get_a();
  my $b = get_b();
  assert_num_not_equals(0, $b) if ASSERT;
  my $c = $a / $b;

  # Clean the namespace
  no Test::Assert;

  # From command line
  $ perl -MTest::Assert script.pl  # sets Test::Assert::ASSERT to 1

=head1 DESCRIPTION

This class provides a set of assertion methods useful for writing tests.  The
API is based on JUnit4 and L<Test::Unit::Lite> and the methods die on failure.

These assertion methods might be not useful for common L<Test::Builder>-based
(L<Test::Simple>, L<Test::More>, etc.) test units.

The assertion methods can be used in class which is derived from
C<Test::Assert> or used as standard Perl functions after importing them into
user's namespace.

C<Test::Assert> can also wrap standard L<Test::Simple>, L<Test::More> or other
L<Test::Builder>-based tests.

The assertions can be also used for run-time checking.

=for readme stop

=cut

use 5.006;

use strict;
use warnings;

our $VERSION = '0.0503';


use Exception::Base (
    'ignore_class' => [ __PACKAGE__, 'Test::Builder' ],
    'Exception::Assertion',
);


# TRUE and FALSE
use constant::boolean;


# Debug mode is disabled by default
## no critic (ProhibitConstantPragma)
use constant ASSERT => FALSE;


# Export ASSERT flag, all assert_* methods and fail method
use Symbol::Util qw( export_package unexport_package stash );


# Variable required for assert_deep_equal
my $DNE = bless [], 'Test::Assert::Does::Not::Exist';


# Enable debug mode
sub import {
    my ($package, @names) = @_;
    my $caller = caller();

    # Enable only if called from main
    if ($caller eq 'main') {
        undef *ASSERT;
        *ASSERT = sub () { TRUE; };
    };

    my @export_ok = ( 'ASSERT', grep { /^(assert_|fail)/ } keys %{ stash(__PACKAGE__) } );
    my %export_tags = (
        all => [ @export_ok ],
        assert => [ grep { /^assert_/i } @export_ok ],
    );

    return export_package($caller, $package, {
        OK   => \@export_ok,
        TAGS => \%export_tags,
    }, @names);
};


# Disable debug mode
sub unimport {
    my ($package, @names) = @_;
    my $caller = caller();

    # Disable only if called from main
    if ($caller eq 'main') {
        undef *ASSERT;
        *ASSERT = sub () { FALSE; };
    };

    return unexport_package($caller, $package);
};


## no critic (ProhibitNegativeExpressionsInUnlessAndUntilConditions)
## no critic (ProhibitSubroutinePrototypes)
## no critic (RequireArgUnpacking)
## no critic (RequireCheckingReturnValueOfEval)

# Fails a test with the given name.
sub fail (;$$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;
    my ($message, $reason) = @_;

    Exception::Assertion->throw(
        message   => $message,
        reason    => $reason,
    );

    assert_false("Should never occured") if ASSERT;
    return FALSE;
};


# Asserts that a condition is true.
sub assert_true ($;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;
    my ($boolean, $message) = @_;

    $self->fail($message, "Expected true value, got undef") unless defined $boolean;
    $self->fail($message, "Expected true value, got '$boolean'") unless $boolean;
    return TRUE;
};


# Asserts that a condition is false.
sub assert_false ($;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;
    my ($boolean, $message) = @_;

    $self->fail($message, "Expected false value, got '$boolean'") unless not $boolean;
    return TRUE;
};


# Asserts that a value is null.
sub assert_null ($;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;
    my ($value, $message) = @_;

    $self->fail($message, "'$value' is defined") unless not defined $value;
    return TRUE;
};


# Asserts that a value is not null.
sub assert_not_null ($;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;
    my ($value, $message) = @_;

    $self->fail($message, 'undef unexpected') unless defined $value;
    return TRUE;
};


# Assert that two values are equal
sub assert_equals ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;
    my ($value1, $value2, $message) = @_;

    return TRUE if (not defined $value1 and not defined $value2);
    $self->fail(
        $message, 'Expected value was undef; should be using assert_null?'
    ) unless defined $value1;
    $self->fail($message, "Expected '$value1', got undef") unless defined $value2;
    if ($value1 =~ /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/ and
        $value2 =~ /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/)
    {
        no warnings 'numeric';
        $self->fail($message, 'Expected ' . (0+$value1) . ', got ' . (0+$value2)) unless $value1 == $value2;
    }
    else {
        $self->fail($message, "Expected '$value1', got '$value2'") unless $value1 eq $value2;
    };
    return TRUE;
};


# Assert that two values are not equal
sub assert_not_equals ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;
    my ($value1, $value2, $message) = @_;

    if (not defined $value1 and not defined $value2) {
        $self->fail($message, 'Both values were undefined');
    };
    return TRUE if (not defined $value1 xor not defined $value2);
    if ($value1 =~ /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/ and
           $value2 =~ /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/)
    {
        no warnings 'numeric';
        $self->fail($message, (0+$value1) . ' and ' . (0+$value2) . ' should differ') unless $value1 != $value2;
    }
    else {
        $self->fail($message, "'$value1' and '$value2' should differ") unless $value1 ne $value2;
    };
    return TRUE;
};


# Assert that two values are numerically equal
sub assert_num_equals ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($value1, $value2, $message) = @_;
    return TRUE if (not defined $value1 and not defined $value2);
    no warnings 'numeric';
    $self->fail($message, 'Expected undef, got ' . (0+$value2)) if not defined $value1;
    $self->fail($message, 'Expected ' . (0+$value1) . ', got undef') if not defined $value2;
    $self->fail($message, 'Expected ' . (0+$value1) . ', got ' . (0+$value2)) unless $value1 == $value2;
    return TRUE;
};


# Assert that two values are numerically not equal
sub assert_num_not_equals ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($value1, $value2, $message) = @_;
    if (not defined $value1 and not defined $value2) {
        $self->fail($message, 'Both values were undefined');
    };
    return TRUE if (not defined $value1 xor not defined $value2);
    no warnings 'numeric';
    $self->fail($message, (0+$value1) . ' and ' . (0+$value2) . ' should differ') unless $value1 != $value2;
    return TRUE;
};


# Assert that two strings are equal
sub assert_str_equals ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($value1, $value2, $message) = @_;
    return TRUE if (not defined $value1 and not defined $value2);
    $self->fail(
        $message, 'Expected value was undef; should be using assert_null?'
    ) unless defined $value1;
    $self->fail($message, "Expected '$value1', got undef") unless defined $value2;
    $self->fail($message, "Expected '$value1', got '$value2'") unless "$value1" eq "$value2";
    return TRUE;
};


# Assert that two strings are not equal
sub assert_str_not_equals ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($value1, $value2, $message) = @_;
    if (not defined $value1 and not defined $value2) {
        $self->fail($message, 'Both values were undefined');
    };
    return TRUE if (not defined $value1 xor not defined $value2);
    $self->fail($message, "'$value1' and '$value2' should differ") unless "$value1" ne "$value2";
    return TRUE;
};


# Assert that string matches regexp
sub assert_matches ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($regexp, $value, $message) = @_;
    $self->fail(
        $message, 'Expected value was undef; should be using assert_null?'
    ) unless defined $regexp;
    $self->fail(
        $message, 'Argument 1 to assert_matches() must be a regexp'
    ) unless ref $regexp eq 'Regexp';
    $self->fail($message, "Expected /$regexp/, got undef") unless defined $value;
    $self->fail($message, "'$value' didn't match /$regexp/") unless $value =~ $regexp;
    return TRUE;
};


# Assert that string matches regexp
sub assert_not_matches ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($regexp, $value, $message) = @_;
    $self->fail(
        $message, 'Expected value was undef; should be using assert_null?'
    ) unless defined $regexp;
    return TRUE if not defined $value;
    $self->fail(
        $message, 'Argument 1 to assert_not_matches() must be a regexp'
    ) unless ref $regexp eq 'Regexp';
    $self->fail($message, "'$value' matched /$regexp/") unless $value !~ $regexp;
    return TRUE;
};


# Assert that data structures are deeply equal
sub assert_deep_equals ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($value1, $value2, $message) = @_;
    $self->fail($message, 'Both arguments were not references') unless ref $value1 or ref $value2;
    $self->fail($message, 'Argument 1 to assert_deep_equals() must be a reference') unless ref $value1;
    $self->fail($message, 'Argument 2 to assert_deep_equals() must be a reference') unless ref $value2;

    my $data_stack = [];
    my $seen_refs = {};

    $self->fail(
        $message, $self->_format_stack($data_stack)
    ) unless $self->_deep_check($value1, $value2, $data_stack, $seen_refs);

    return TRUE;
};


# Assert that data structures are deeply equal
sub assert_deep_not_equals ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($value1, $value2, $message) = @_;
    $self->fail($message, 'Both arguments were not references') unless ref $value1 or ref $value2;
    $self->fail($message, 'Argument 1 to assert_deep_equals() must be a reference') unless ref $value1;
    $self->fail($message, 'Argument 2 to assert_deep_equals() must be a reference') unless ref $value2;

    my $data_stack = [];
    my $seen_refs = {};

    $self->fail(
        $message, 'Both structures should differ'
    ) unless not $self->_deep_check($value1, $value2, $data_stack, $seen_refs);

    return TRUE;
};


# Assert that object is a class
sub assert_isa ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($class, $value, $message) = @_;

    $self->fail(
        $message, 'Class name was undef; should be using assert_null?'
    ) unless defined $class;
    $self->fail($message, "Expected '$class' object or class, got undef") unless defined $value;
    if (not __isa($value, $class)) {
        $self->fail($message, "Expected '$class' object or class, got '" . ref($value) . "' reference") if ref $value;
        $self->fail($message, "Expected '$class' object or class, got '$value' value");
    };
    return TRUE;
};


# Assert that object is not a class
sub assert_not_isa ($$;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($class, $value, $message) = @_;

    $self->fail(
        $message, 'Class name was undef; should be using assert_null?'
    ) unless defined $class;
    if (__isa($value, $class)) {
        $self->fail($message, "'$value' is a '$class' object or class");
    };
    return TRUE;
};


# Assert that code throws an exception
sub assert_raises ($&;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($expected, $code, $message) = @_;

    eval {
        $code->();
    };
    if ($@) {
        my $e = $@;
        if (ref $e and __isa($e, 'Exception::Base')) {
            return TRUE if $e->matches($expected);
        }
        else {
            if (ref $expected eq 'Regexp') {
                return TRUE if "$e" =~ $expected;
            }
            elsif (ref $expected eq 'ARRAY') {
                return TRUE if grep { __isa($e, $_) } @{ $expected };
            }
            elsif (not ref $expected) {
                my $caught_message = "$e";
                while ($caught_message =~ s/\t\.\.\.propagated at (?!.*\bat\b.*).* line \d+( thread \d+)?\.\n$//s) { }
                $caught_message =~ s/( at (?!.*\bat\b.*).* line \d+( thread \d+)?\.)?\n$//s;
                return TRUE if $caught_message eq $expected;
            };
        };
        # Rethrow an exception
        ## no critic (RequireCarping)
        die $e;
    }
    else {
        $self->fail(
            $message, 'Expected exception was not raised'
        );
    };
    return TRUE;
};


# Assert that Test::Builder method is ok
sub assert_test (&;$) {
    # check if called as function
    my $self = __isa($_[0], __PACKAGE__) ? shift : __PACKAGE__;

    my ($code, $message) = @_;

    my $diag_message = '';
    my $ok_message = '';
    my $ok_return = TRUE;

    no warnings 'once', 'redefine';
    local *Test::Builder::diag = sub {
        $diag_message .= $_[1] if defined $_[1];
    };
    local *Test::Builder::ok = sub {
        $ok_message .= $_[2] if defined $_[2];
        return $ok_return = $_[1];
    };

    $code->();
    if (not $ok_return) {
        my $new_message = (defined $message ? $message : '')
                        . (defined $message && $message ne '' && $ok_message ne '' ? ': ' : '')
                        . ($ok_message =~ /\n/s ? "\n" : '')
                        . $ok_message
                        . ($ok_message ne '' && $diag_message ne '' ? ': ' : '')
                        . ($diag_message =~ /\n/s ? "\n" : '')
                        . $diag_message;
        $self->fail(
            $new_message, 'assert_test failed'
        ) unless $ok_return;
    };
    return TRUE;
};


# Checks if deep structures are equal
sub _deep_check {
    my ($self, $e1, $e2, $data_stack, $seen_refs) = @_;

    if ( ! defined $e1 || ! defined $e2 ) {
        return TRUE if !defined $e1 && !defined $e2;
        push @$data_stack, { vals => [$e1, $e2] };
        return FALSE;
    };

    return TRUE if $e1 eq $e2;

    if ( ref $e1 && ref $e2 ) {
        my $e2_ref = "$e2";
        return TRUE if defined $seen_refs->{$e1} && $seen_refs->{$e1} eq $e2_ref;
        $seen_refs->{$e1} = $e2_ref;
    };

    if (ref $e1 eq 'ARRAY' and ref $e2 eq 'ARRAY') {
        return $self->_eq_array($e1, $e2, $data_stack, $seen_refs);
    }
    elsif (ref $e1 eq 'HASH' and ref $e2 eq 'HASH') {
        return $self->_eq_hash($e1, $e2, $data_stack, $seen_refs);
    }
    elsif (ref $e1 eq 'REF' and ref $e2 eq 'REF') {
        push @$data_stack, { type => 'REF', vals => [$e1, $e2] };
        my $ok = $self->_deep_check($$e1, $$e2, $data_stack, $seen_refs);
        pop @$data_stack if $ok;
        return $ok;
    }
    elsif (ref $e1 eq 'SCALAR' and ref $e2 eq 'SCALAR') {
        push @$data_stack, { type => 'REF', vals => [$e1, $e2] };
        return $self->_deep_check($$e1, $$e2, $data_stack, $seen_refs);
    }
    else {
        push @$data_stack, { vals => [$e1, $e2] };
    };

    return FALSE;
};


# Checks if arrays are equal
sub _eq_array  {
    my ($self, $a1, $a2, $data_stack, $seen_refs) = @_;

    return TRUE if $a1 eq $a2;

    my $ok = TRUE;
    my $max = $#$a1 > $#$a2 ? $#$a1 : $#$a2;

    foreach (0..$max) {
        my $e1 = $_ > $#$a1 ? $DNE : $a1->[$_];
        my $e2 = $_ > $#$a2 ? $DNE : $a2->[$_];

        push @$data_stack, { type => 'ARRAY', idx => $_, vals => [$e1, $e2] };
        $ok = $self->_deep_check($e1, $e2, $data_stack, $seen_refs);
        pop @$data_stack if $ok;

        last unless $ok;
    };

    return $ok;
};


# Checks if hashes are equal
sub _eq_hash {
    my ($self, $a1, $a2, $data_stack, $seen_refs) = @_;

    return TRUE if $a1 eq $a2;

    my $ok = TRUE;
    my $bigger = keys %$a1 > keys %$a2 ? $a1 : $a2;
    foreach my $k (keys %$bigger) {
        my $e1 = exists $a1->{$k} ? $a1->{$k} : $DNE;
        my $e2 = exists $a2->{$k} ? $a2->{$k} : $DNE;

        push @$data_stack, { type => 'HASH', idx => $k, vals => [$e1, $e2] };
        $ok = $self->_deep_check($e1, $e2, $data_stack, $seen_refs);
        pop @$data_stack if $ok;

        last unless $ok;
    };

    return $ok;
};


# Dumps the differences for deep structures
sub _format_stack {
    my ($self, $data_stack) = @_;

    my $var = '$FOO';
    my $did_arrow = 0;
    foreach my $entry (@$data_stack) {
        my $type = $entry->{type} || '';
        my $idx  = $entry->{'idx'};
        if ($type eq 'HASH') {
            $var .= "->" unless $did_arrow++;
            $var .= "{$idx}";
        }
        elsif ($type eq 'ARRAY') {
            $var .= "->" unless $did_arrow++;
            $var .= "[$idx]";
        }
        elsif ($type eq 'REF') {
            $var = "\${$var}";
        };
    };

    my @vals = @{$data_stack->[-1]{vals}}[0,1];

    my @vars = ();
    ($vars[0] = $var) =~ s/\$FOO/  \$a/;
    ($vars[1] = $var) =~ s/\$FOO/  \$b/;

    my $out = "Structures begin differing at:\n";
    foreach my $idx (0..$#vals) {
        my $val = $vals[$idx];
        $vals[$idx] = !defined $val ? 'undef' :
                      $val eq $DNE  ? 'Does not exist'
                                    : "'$val'";
    };

    $out .= "$vars[0] = $vals[0]\n";
    $out .= "$vars[1] = $vals[1]";

    return $out;
};


# Better, safe "isa" function
sub __isa {
    my ($object, $class) = @_;
    local $@ = '';
    local $SIG{__DIE__} = '';
    return eval { $object->isa($class) };
};


no constant::boolean;
no Symbol::Util;


1;


=begin umlwiki

= Component Diagram =

[ Test::Assert |
  [Test::Assert {=}]
  [Exception::Assertion {=}] ]

= Class Diagram =

[                                 <<utility>>
                                 Test::Assert
 ----------------------------------------------------------------------------------
 ----------------------------------------------------------------------------------
 fail( message : Str = undef, reason : Str = undef )
 assert_true( boolean : Bool, message : Str = undef )
 assert_false( boolean : Bool, message : Str = undef )
 assert_null( value : Any, message : Str = undef )
 assert_not_null( value : Any, message : Str = undef )
 assert_equals( value1 : Defined, value2 : Defined, message : Str = undef )
 assert_not_equals( value1 : Defined, value2 : Defined, message : Str = undef )
 assert_num_equals( value1 : Defined, value2 : Defined, message : Str = undef )
 assert_num_not_equals( value1 : Defined, value2 : Defined, message : Str = undef )
 assert_str_equals( value1 : Defined, value2 : Defined, message : Str = undef )
 assert_str_not_equals( value1 : Defined, value2 : Defined, message : Str = undef )
 assert_matches( regexp : RegexpRef, value : Str, message : Str = undef )
 assert_not_matches( regexp : RegexpRef, value : Str, message : Str = undef )
 assert_deep_equals( value1 : Ref, value2 : Ref, message : Str = undef )
 assert_deep_not_equals( value1 : Ref, value2 : Ref, message : Str = undef )
 assert_isa( class : Str, object : Defined, message : Str = undef )
 assert_not_isa( class : Str, object : Defined, message : Str = undef )
 assert_raises( expected : Any, code : CodeRef, message : Str = undef )
 assert_test( code : CodeRef, message : Str = undef )
 <<constant>> ASSERT() : Bool                                                        ]

[Test::Assert] ---> <<exception>> [Exception::Assertion]

[Exception::Assertion] ---|> [Exception::Base]

=end umlwiki

=head1 EXCEPTIONS

=over

=item Exception::Assertion

Thrown whether an assertion failed.

=back

=head1 IMPORTS

By default, the class does not export its symbols.

=over

=item use Test::Assert;

Enables debug mode if it is used in C<main> package.

  package main;
  use Test::Assert;    # Test::Assert::ASSERT is set to TRUE

  $ perl -MTest::Assert script.pl    # ditto

=item use Test::Assert 'assert_true', 'fail', ...;

Imports some methods.

=item use Test::Assert ':all';

Imports all C<assert_*> methods, C<fail> method and C<ASSERT> constant.

=item use Test::Assert ':assert';

Imports all C<assert_*> methods and C<ASSERT> constant.

=item no Test::Assert;

Disables debug mode if it is used in C<main> package.

=back

=head1 CONSTANTS

=over

=item ASSERT

This constant is set to true value if C<Test::Assert> module is used from
C<main> package.  It allows to enable debug mode globally from command line.
The debug mode is disabled by default.

  package My::Test;
  use Test::Assert ':assert';
  assert_true( 0 ) if ASSERT;  # fails only if debug mode is enabled

  $ perl -MTest::Assert script.pl  # enable debug mode

=back

=head1 METHODS

=over

=item fail( I<message> : Str = undef, I<reason> : Str = undef )

Immediate fail the test.  The L<Exception::Assertion> object will have set
I<message> and I<reason> attribute based on arguments.

=item assert_true( I<boolean> : Bool, I<message> : Str = undef )

Checks if I<boolean> expression returns true value.

=item assert_false( I<boolean> : Bool, I<message> : Str = undef )

Checks if I<boolean> expression returns false value.

=item assert_null( I<value> : Any, I<message> : Str = undef )

=item assert_not_null( I<value> : Any, I<message> : Str = undef )

Checks if I<value> is defined or not defined.

=item assert_equals( I<value1> : Defined, I<value2> : Defined, I<message> : Str = undef )

=item assert_not_equals( I<value1> : Defined, I<value2> : Defined, I<message> : Str = undef )

Checks if I<value1> and I<value2> are equals or not equals.  If I<value1> and
I<value2> look like numbers then they are compared with '==' operator,
otherwise the string 'eq' operator is used.

=item assert_num_equals( I<value1> : Defined, I<value2> : Defined, I<message> : Str = undef )

=item assert_num_not_equals( I<value1> : Defined, I<value2> : Defined, I<message> : Str = undef )

Force numeric comparation.

=item assert_str_equals( I<value1> : Defined, I<value2> : Defined, I<message> : Str = undef )

=item assert_str_not_equals( I<value1> : Defined, I<value2> : Defined, I<message> : Str = undef )

Force string comparation.

=item assert_matches( I<regexp> : RegexpRef, I<value> : Str, I<message> : Str = undef )

=item assert_not_matches( I<regexp> : RegexpRef, I<value> : Str, I<message> : Str = undef )

Checks if I<value> matches I<pattern> regexp.

=item assert_deep_equals( I<value1> : Ref, I<value2> : Ref, I<message> : Str = undef )

=item assert_deep_not_equals( I<value1> : Ref, I<value2> : Ref, I<message> : Str = undef )

Checks if reference I<value1> is a deep copy of reference I<value2> or not.
The references can be deep structure.  If they are different, the message will
display the place where they start differing.

=item assert_isa( I<class> : Str, I<object> : Defined, I<message> : Str = undef )

=item assert_not_isa( I<class> : Str, I<object> : Defined, I<message> : Str = undef )

Checks if I<value> is a I<class> or not.

  assert_isa( 'My::Class', $obj );

=item assert_raises( I<expected> : Any, I<code> : CodeRef, I<message> : Str = undef )

Runs the I<code> and checks if it raises the I<expected> exception.

If raised exception is an L<Exception::Base> object, the assertion passes if
the exception C<matches> I<expected> argument (via
C<L<Exception::Base>-E<gt>matches> method).

If raised exception is not an L<Exception::Base> object, several conditions
are checked.  If I<expected> argument is a string or array reference, the
assertion passes if the raised exception is a given class.  If the argument is
a regexp, the string representation of exception is matched against regexp.

  use Test::Assert 'assert_raises';

  assert_raises( 'foo', sub { die 'foo' } );
  assert_raises( ['Exception::Base'], sub { Exception::Base->throw } );

=item assert_test( I<code> : CodeRef, I<message> : Str = undef )

Wraps L<Test::Builder> based test function and throws L<Exception::Assertion>
if the test is failed.  The plan test have to be disabled manually.  The
L<Test::More> module imports the C<fail> method by default which conflicts
with C<Test::Assert> C<fail> method.

  use Test::Assert ':all';
  use Test::More ignore => [ '!fail' ];

  Test::Builder->new->no_plan;
  Test::Builder->new->no_ending(1);

  assert_test( sub { cmp_ok($got, '==', $expected, $test_name) } );

=back

=head1 SEE ALSO

L<Exception::Assertion>, L<Test::Unit::Lite>.

=head1 BUGS

If you find the bug or want to implement new features, please report it at
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Test-Assert>

=for readme continue

=head1 AUTHOR

Piotr Roszatycki <dexter@cpan.org>

=head1 COPYRIGHT

Copyright (C) 2008, 2009 by Piotr Roszatycki <dexter@cpan.org>.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

See L<http://www.perl.com/perl/misc/Artistic.html>
