OK, I'm tired of submitting bug reports.  Instead, I'll include my new 
'Test.pm' as an attachment rather than a patch.  You can try it locally and see 
if I've just screwed up royally.

Aside from exporting a 'die_on_fail' sub (which is a godsend for debugging), it 
also started adding very primitive have/want diagnostics:

  not ok 28 - 3rd element is trimmed again with no effect # TODO trim on lists
  # have: undef
  # want: "baz"
  not ok 29 - 0 is not true # TODO expected failures
  # Expected a true value.
  # have: 0
  not ok 30 - expected failure # TODO expected failures
  # Expected a false value.
  # have: ["a", "b", "c"]
  not ok 31 -
  # have: ["a", "b", "c"]
  # want: ["a", "b", "z"]

 
It's not a patch because I got tired of adding exceptions for bugs, but then I 
realized that the bugs might be in my brain :)  Specifically, I started adding 
stuff like this because of lack of a .perl method:

      my $have = $passed.WHAT eq any(<Match Exception Iterator>)
        ?? $passed
        !! $passed.perl;

Features:

* die_on_fail (no longer hunt back through your terminal for the failure)
* diagnostics on most tests
* eliminate most multi-subs in favor of default "$description=''" arguments.
* Better diagnostics for boolean ok/nok tests.

It's still an awful, awful hack, but it's a start.

Cheers,
Ovid
--
Buy the book         - http://www.oreilly.com/catalog/perlhks/
Tech blog            - http://use.perl.org/~Ovid/journal/
Twitter              - http://twitter.com/OvidPerl
Official Perl 6 Wiki - http://www.perlfoundation.org/perl6
# Copyright (C) 2007, The Perl Foundation.
# $Id: Test.pm 34904 2009-01-03 23:24:38Z masak $

## This is a temporary Test.pm to get us started until we get pugs's Test.pm
## working. It's shamelessly stolen & adapted from MiniPerl6 in the pugs repo.

# globals to keep track of our tests
our $num_of_tests_run     = 0;
our $num_of_tests_failed  = 0;
our $todo_upto_test_num   = 0;
our $todo_reason          = '';
our $die_on_fail          = 0;
our $num_of_tests_planned;

our $*WARNINGS = 0;

# for running the test suite multiple times in the same process
our $testing_started;


## test functions

# Compare numeric values with approximation
sub approx ($x, $y) {
    my $epsilon = 0.00001;
    my $diff = abs($x - $y);
    ($diff < $epsilon);
}

sub plan($number_of_tests) is export() {
    $testing_started      = 1;
    $num_of_tests_planned = $number_of_tests;

    say '1..' ~ $number_of_tests;
}

sub die_on_fail() is export() {
    $die_on_fail = 1;
}

sub pass($desc) is export() {
    proclaim(1, $desc);
}

sub fail($desc) is export() {
    proclaim(0, $desc);
}

sub ok(Object $passed, $desc='') is export() {
    my $diagnostics = diag_bool_true($passed);
    proclaim($passed, $desc, $diagnostics);
}

sub nok(Object $passed, $desc='') is export() {
    my $diagnostics = diag_bool_false($passed);
    proclaim(!$passed, $desc, $diagnostics);
}

sub is(Object $have, Object $want, $desc='') is export() {
    my $passed = $have eq $want;
    proclaim($passed, $desc, diag_eq($passed, $have, $want));
}

sub isnt(Object $have, Object $want, $desc='') is export() {
    my $passed = !($have eq $want);
    proclaim($passed, $desc, diag_neq($passed, $have, $want));
}

sub is_approx(Object $have, Object $want, $desc='') is export() {
    my $passed = abs($have - $want) <= 0.00001;
    proclaim($passed, $desc, diag_approx($passed, $have, $want));
}

sub todo($reason, $count=1) is export() {
    $todo_upto_test_num = $num_of_tests_run + $count;
    $todo_reason = '# TODO ' ~ $reason;
}

multi sub skip()                is export() { proclaim(1, "# SKIP"); }
multi sub skip($reason)         is export() { proclaim(1, "# SKIP " ~ $reason); 
}
multi sub skip($count, $reason) is export() {
    for 1..$count {
        proclaim(1, "# SKIP " ~ $reason);
    }
}

multi sub skip_rest() is export() {
    skip($num_of_tests_planned - $num_of_tests_run, "");
}

multi sub skip_rest($reason) is export() {
    skip($num_of_tests_planned - $num_of_tests_run, $reason);
}

sub diag($message) is export() { say '# '~$message; }


multi sub flunk($reason) is export() { proclaim(0, "flunk $reason")}


multi sub isa_ok($var,$type) is export() {
    ok($var.isa($type), "The object is-a '$type'");
}
multi sub isa_ok($var,$type, $msg) is export() { ok($var.isa($type), $msg); }

multi sub dies_ok($closure, $reason) is export() {
    try {
        $closure();
    }
    proclaim((defined $!), $reason);
}
multi sub dies_ok($closure) is export() {
    dies_ok($closure, '');
}

multi sub lives_ok($closure, $reason) is export() {
    try {
        $closure();
    }
    proclaim((not defined $!), $reason);
}
multi sub lives_ok($closure) is export() {
    lives_ok($closure, '');
}

multi sub eval_dies_ok($code, $reason) is export() {
    proclaim((defined eval_exception($code)), $reason);
}
multi sub eval_dies_ok($code) is export() {
    eval_dies_ok($code, '');
}

multi sub eval_lives_ok($code, $reason) is export() {
    proclaim((not defined eval_exception($code)), $reason);
}
multi sub eval_lives_ok($code) is export() {
    eval_lives_ok($code, '');
}


sub is_deeply($have, $want, $reason='') {
    my $passed = _is_deeply( $have, $want );
    my $diagnostics = diag_eq($passed, $have, $want);
    proclaim($passed, $reason, $diagnostics);
}

sub _is_deeply( $this, $that) {

    if $this ~~ List && $that ~~ List {
        return if +$this.values != +$that.values;
        for $this Z $that -> $a, $b {
            return if ! _is_deeply( $a, $b );
        }
        return True;
    }
    elsif $this ~~ Hash && $that ~~ Hash {
        return if +$this.keys != +$that.keys;
        for $this.keys.sort Z $that.keys.sort -> $a, $b {
            return if $a ne $b;
            return if ! _is_deeply( $this{$a}, $that{$b} );
        }
        return True;
    }
    elsif $this ~~ Str | Num | Int && $that ~~ Str | Num | Int {
        return $this eq $that;
    }
    elsif $this ~~ Pair && $that ~~ Pair {
        return $this.key eq $that.key
               && _is_deeply( $this.value, $this.value );
    }
    elsif $this ~~ undef && $that ~~ undef && $this.WHAT eq $that.WHAT {
        return True;
    }

    return;
}


## 'private' subs

sub diag_bool_true($passed) {
    # Workaround for: Method 'perl' not found for invocant of class 'Match'
    # and issues with the Exception&Iterator class might work, but I don't grok 
it.
    my $have = $passed.WHAT eq any(<Match Exception Iterator>)
        ?? $passed
        !! $passed.perl;
    return $passed 
        ?? '' 
        !! "# Expected a true value.\n# have: {$have}";
}

sub diag_bool_false($passed) {
    return $passed 
        ?? "# Expected a false value.\n# have: {$passed.perl}"
        !! '';
}

sub diag_eq($passed, $have, $want) {
    # Workaround for: Method 'perl' not found for invocant of class 'Match'
    # and issues with the Exception&Iterator class might work, but I don't grok 
it.
    my $x_have = $passed.WHAT eq any(<Match Exception Iterator>)
        ?? $passed
        !! $passed.perl;
    return $passed ?? '' !! "# have: {$x_have}\n# want: {$want.perl}";
}

sub diag_neq($passed, $have, $want) {
    return $passed ?? '' !! "# Expected different values\n# have: 
{$have.perl}\n# want: {$want.perl}";
}

sub diag_approx($passed, $have, $want) {
    return $passed ?? '' !! "# Expected approximately the same values\n# have: 
{$have.perl}\n# want: {$want.perl}";
}

sub eval_exception($code) {
    my $eval_exception;
    try { eval ($code); $eval_exception = $! }
    $eval_exception // $!;
}

sub proclaim($passed, $desc, $diagnostics='') {
    $testing_started  = 1;
    $num_of_tests_run = $num_of_tests_run + 1;

    unless $passed {
        print "not ";
        $num_of_tests_failed = $num_of_tests_failed + 1
            unless  $num_of_tests_run <= $todo_upto_test_num;
    }
    print "ok ", $num_of_tests_run, " - ", $desc;
    if $todo_reason and $num_of_tests_run <= $todo_upto_test_num {
        print $todo_reason;
    }
    print "\n";
    say $diagnostics if $diagnostics;
    if !$passed && $die_on_fail && !$todo_reason {
        die "Test failed.  Stopping test.";
    }
    $todo_reason = '';   # must reset between tests
    return $passed;
}

END {
    # until END blocks can access compile-time symbol tables of outer scopes,
    #  we need these declarations
    our $testing_started;
    our $num_of_tests_planned;
    our $num_of_tests_run;
    our $num_of_tests_failed;

    if ($testing_started and $num_of_tests_planned != $num_of_tests_run) {  
##Wrong quantity of tests
        diag("Looks like you planned $num_of_tests_planned tests, but ran 
$num_of_tests_run");
    }
    if ($testing_started and $num_of_tests_failed) {
        diag("Looks like you failed $num_of_tests_failed tests of 
$num_of_tests_run");
    }
}

Reply via email to