package ExceptionChecker;

use strict;
use warnings;

use Test::Unit::Error;
use Test::Unit::Failure;

use Error qw(:try);

sub check_failures {
    my $self = shift;
    $self->check_exceptions('Test::Unit::Failure', @_);
}

sub check_errors {
    my $self = shift;
    $self->check_exceptions('Test::Unit::Error', @_);
}

sub check_exceptions {
    my $self = shift;
    my ($exception_class, @tests) = @_;
    my ($asserter, $file, $line)
      = caller($Error::Depth + 1); # EVIL hack!  Assumes check_exceptions
                                   # always called via check_{failures,errors}.
                                   # My brain hurts too much right now to think
                                   # of a better way.
    while (@tests) {
        my $expected        = shift @tests;
        my $test_components = shift @tests;
        my ($test_code_line, $test) = @$test_components;
        my $exception;
        try {
            $self->$test();
        }
        catch $exception_class with {
            $exception = shift;
        }
        catch Error::Simple with {
            $exception = shift;
        }
        otherwise {
            $exception = 0;
        };

        try {
            $self->check_exception($exception_class, $expected, $exception);
            $self->check_file_and_line($exception,
                                       $file,
                                       $test_code_line);
        }
        catch Test::Unit::Failure with {
            my $failure = shift;
            $failure->throw_new(
                -package => $asserter,
                -file    => $file,
                -line    => $line,
                -object  => $self
            );
        }
    }
}

sub check_exception {
    my $self = shift;
    my ($exception_class, $expected, $exception) = @_;
    Test::Unit::Failure->throw(
        -text => "Didn't get $exception_class `$expected'",
        -object => $self,
    ) unless $exception;

    my $got = $exception->text();
    Test::Unit::Failure->throw(
        -text => "Expected $exception_class `$expected', got `$got'",
        -object => $self,
    ) unless UNIVERSAL::isa($expected, 'Regexp')
               ? $got =~ /$expected/ : $got eq $expected;
}

sub check_file_and_line {
    my $self = shift;
    my ($exception, $expected_file, $test_code_line) = @_;
    if ($exception->file() ne $expected_file) {
        throw Test::Unit::Failure(
            -text   => "failure's file() should have returned $expected_file"
                       . " (line $test_code_line), not " . $exception->file(),
            -object => $self,
        );
    }
    if ($exception->line() != $test_code_line) {
        throw Test::Unit::Failure(
            -text   => "failure's line() should have returned "
                       . "$test_code_line, not " . $exception->line(),
            -object => $self,
        );
    }
}

1;
