# unit_test.pm -- Simple unit testing for Perl.
#
# Author: Mark Triggs <mark@dishevelled.net>
#
# To define a test:
#
#   define_test {
#       statement 1;
#       statement 2;
#       # ...
#       return some_value;          # Return `true' for pass or false otherwise
#   } "name of test", "test group";
#
# To run tests:
#
#   run_tests (verbose => bool, group => "test group");
#
# If a value for `group' is not given, all tests are run.  run_tests returns 0
# if all tests pass, or 1 if some fail.
#
# If you want to put your tests in the same file as your code, a good idiom is:
#
# BEGIN {
#     my $enable_unit_tests = 1;    # or 0
#
#     if ($enable_unit_tests) {
#         eval 'use unit_test';
#     } else {
#         no warnings 'redefine';
#         eval 'sub define_test (&$$) {}';
#         eval 'sub run_tests { 0 }';
#     }
# }
#
# This allows the tests to be easily switched off and removes any dependency on
# this module.
#

package unit_test;

use strict;

BEGIN {
    use Exporter   ();
    use vars       qw(@ISA @EXPORT);

    @ISA         = qw(Exporter);
    @EXPORT      = qw(define_test run_tests);
}


my %tests;                      # Tests in groups

sub define_test (&$$)
{
    my ($code, $name, $group) = @_;
    my ($package, $filename, $line, $subr, $has_args, $wantarray) = caller(0);

    if (!defined ($tests{$group})) {
        $tests{$group} = [];
    }

    push (@{$tests{$group}}, {name => "$group:$name", code => $code,
                              file => $filename, line => $line});
}

sub run_tests
{
    my %keywords = @_;
    my @tests = ();

    if (defined ($keywords{group})) {
        @tests = @{$tests{$keywords{group}}};
    } else {
        foreach my $t (values (%tests)) {
            push (@tests, @{$t});
        }
    }

    my $fail = 0;
    foreach my $t (@tests) {
        if (defined ($keywords{verbose})) {
            print STDERR "Test: " . $t->{name} . "... ";
        }

        my $ret = $t->{code}->();
        if (!$ret) {
            $fail = 1;
            print STDERR "$t->{file}: $t->{line}: $t->{name} failed!\n";
        } elsif (defined ($keywords{verbose})) {
            print STDERR "Passed\n";
        }
    }

    return $fail;
}

return 1;
