| Badger documentation | Contained in the Badger distribution. |
Badger::Test::Manager - test manager module
use Badger::Test::Manager;
# object methods
my $manager = Badger::Test::Manager->new( plan => 7 );
$manager->ok($bool, 'This is a test');
$manager->pass('This is ok');
$manager->fail('This is not ok');
$manager->is($this, $this, 'This and that are equal');
$manager->isnt($this, $this, 'This and that are not equal');
$manager->like($this, qr/that/, 'This is matched by that');
$manager->unlike($this, qr/that/, 'This is not matched by that');
# class methods
Badger::Test::Manager->plan(7);
Badger::Test::Manager->ok($bool, 'This is a test');
Badger::Test::Manager->pass('This is ok');
Badger::Test::Manager->fail('This is not ok');
# ... plus is(), isnt(), like() unlike() methods, as above
This module implements a simple test manager for Badger::Test.
All methods can be called as class methods or object methods. In the case of class methods, they are called against a prototype object returned by the prototype() method inherited from Badger::Prototype.
How many tests you plan to run. An error will be thrown if you try to call this method twice.
Low-level method to generate a test result.
Report on the success or failure of a test:
$manager->ok(1, 'This is good');
$manager->ok(0, 'This is bad');
Test if the first two arguments are equal.
$manager->is($this, $that, "This and that are equal");
Test if the first two arguments are not equal.
$manager->isnt($this, $that, "This and that are equal");
Test if the first argument is matched by the regex passed as the second argument.
$manager->like($this, qr/like that/i, "This and that are alike");
Test if the first argument is not matched by the regex passed as the second argument.
$manager->unlike($this, qr/like that/i, "This and that are unalike");
Pass a test.
$manager->pass('Module Loaded');
Fail a test.
$manager->fail('Stonehenge crushed by a dwarf');
Skip a single test.
$manager->skip("We don't have that piece of scenery any more");
Skip a number of tests.
$manager->skip_some(11, "We don't have that piece of scenery any more");
Skip any remaining tests.
$manager->skip_rest("We don't have that piece of scenery any more");
Skip all tests. This should be called instead of plan()
$manager->skip_all("We don't have that piece of scenery any more");
Method to enable or disable colour mode.
An alias for colour().
This method is called automatically when the Badger::Test::Manager object
is destroyed. It flushes any pending tests, performs any final sanity checks
and prints a summary if requested.
This methods flushes any cached test results. You don't need to worry about it.
This method generates a final summary of the tests
Used to generate the test messages displayed via the Badger::Base
messages() method. The message formats are
defined in the $MESSAGES package variable.
Use to generate a name for a test if one isn't explicitly provided.
This method is call when a test find a result that doesn't match the
expected value. If Algorithm::Diff is installed on your machine, it
will generate a message showing how the output and expected values differ.
Otherwise it will generate a regular message reporting the mismatch.
Andy Wardley http://wardley.org/
Copyright (C) 1996-2009 Andy Wardley. All Rights Reserved.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Badger documentation | Contained in the Badger distribution. |
package Badger::Test::Manager; use Badger::Class version => 0.01, debug => 1, base => 'Badger::Prototype', import => 'class'; use Badger::Rainbow 'ANSI_colours ANSI_escape'; eval "use Algorithm::Diff qw( diff )"; our $CAN_DIFF = $@ ? 0 : 1; our $ESCAPES = qr/\e\[(.*?)m/; # remove ANSI escapes our $REASON = 'No reason given'; our $MESSAGES = { no_plan => "You haven't called plan() yet!\n", dup_plan => "You called plan() twice!\n", plan => "1..%s\n", skip_all => "1..0 # skipped: %s\n", skip_one => "ok %s # skipped: %s\n", name => "test %s at %s line %s", ok => "ok %s - %s\n", not_ok => "not ok %s - %s\n%s", not_eq => "# expect: [%s]\n# result: [%s]\n", not_ne => "# unexpected match: [%s]\n", not_like => "# expect: /%s/\n# result: [%s]\n", not_unlike => "# expect: ! /%s/\n# result: [%s]\n", too_few => "# Looks like you planned %s tests but only ran %s.\n", too_many => "# Looks like you planned only %s tests but ran %s.\n", pass => "# PASS: All %d tests passed\n", fail => "# FAIL: %d tests failed\n", mess => "# FAIL: Inconsistent test results\n", summary => "# %d/%d tests run, %d passed, %d failed, %d skipped\n", hunk => "# -- diffs %s of %s --\n", delta => "# %s %3d %s\n", }; our $SCHEME = { green => 'ok pass', red => 'not_ok too_few too_many fail mess', cyan => 'skip_one skip_all hunk delta', yellow => 'plan not_eq not_ne not_like not_unlike summary', }; # Sorry, English and American/Spanish only, no couleur, colori, farbe, etc. *color = \&colour; # for nice cleanup in END block our $INSTANCES = { }; #----------------------------------------------------------------------- # constructor method #----------------------------------------------------------------------- sub init { my ($self, $config) = @_; $self->{ plan } = $config->{ plan } || 0; $self->{ count } = $config->{ count } || 1; $self->{ results } = $config->{ results } || [ ]; $self->{ summary } = $config->{ summary } || 0; $self->{ reason } = $config->{ reason } || $REASON; $self->{ colour } = $config->{ colour } || $config->{ color } || 0; $INSTANCES->{ $self } = $self; return $self; } #------------------------------------------------------------------------ # plan($n) # # Declare how many (more) tests are expected to come. If ok() is called # before plan() then the results are cached instead of being printed up # front. When plan() is called, the total number of tests (including any # cached) is known and the "1..$n" line can be printed along with any # cached results. After that, calls to ok() generated output immediately. #------------------------------------------------------------------------ sub plan ($$;$) { my $self = shift->prototype; my ($tests, $reason) = @_; # calling plan() twice would be ambiguous return $self->error_msg('dup_plan') if $self->{ plan }; # if $tests == 0 then skip all return $self->skip_all($reason) unless $tests; # update the plan to account for any tests that have already been run my $results = $self->{ results }; $tests += @$results; $self->test_msg( plan => $tests ); $self->{ plan } = $tests; $self->{ tested } = 0; $self->{ passed } = 0; $self->{ failed } = 0; $self->{ skipped } = 0; # now flush any cached test results while (@$results) { my $test = shift @$results; $self->result(@$test); } } sub ok ($$;$$) { my $self = shift->prototype; my ($ok, $name, $detail) = @_; $detail ||= ''; $name ||= $self->test_name; if ($self->{ plan }) { $self->result($ok, $self->{ count }, $name, $detail); } else { # cache results if plan() not yet called push(@{ $self->{ results } }, [ $ok, $self->{ count }, $name, $detail ]); } $self->{ count }++; $self->{ tested }++; return $ok; } sub pass ($;$) { shift->ok(1, @_); } sub fail ($;$) { shift->ok(0, @_); } sub is ($$$;$) { my $self = shift->prototype; my ($result, $expect, $msg) = @_; $msg ||= $self->test_name(); # force stringification of $result to avoid 'no eq method' overload errors $result = "$result" if ref $result; # if we have coloured output enabled then the result might not match # the expected because of embedded ANSI escapes, so we strip them out my ($r, $e) = map { s/$ESCAPES//g if $self->{ colour }; $_ } ($result, $expect); if ($r eq $e) { return $self->pass($msg); } else { return $self->fail($msg, $self->different($expect, $result)); } } sub isnt ($$$;$) { my $self = shift->prototype; my ($result, $expect, $msg) = @_; $msg ||= $self->test_name(); # force stringification of $result to avoid 'no eq method' overload errors $result = "$result" if ref $result; # if we have coloured output enabled then the result might not match # the expected because of embedded ANSI escapes, so we strip them out my ($r, $e) = map { s/$ESCAPES//g if $self->{ colour }; $_ } ($result, $expect); if ($r ne $e) { return $self->pass($msg); } else { for ($expect, $result) { s/\n/\n |/g; } return $self->fail($msg, $self->message( not_eq => $expect, $result )); } } sub like ($$$;$) { my $self = shift->prototype; my ($result, $expect, $name) = @_; $name ||= $self->test_name(); # strip ANSI escapes if necessary my $r = $result; $r =~ s/$ESCAPES//g if $self->{ colour }; if ($r =~ $expect) { $self->pass($name); } else { return $self->fail($name, $self->message( not_like => $expect, $result )); } } sub unlike ($$$;$) { my $self = shift->prototype; my ($result, $expect, $name) = @_; $name ||= $self->test_name(); # strip ANSI escapes if necessary my $r = $result; $r =~ s/$ESCAPES//g if $self->{ colour }; if ($r !~ $expect) { $self->pass($name); } else { return $self->fail($name, $self->message( not_unlike => $expect, $result )); } } sub skip ($;$) { my $self = shift->prototype; my $msg = shift || $self->test_name; return $self->error_msg('no_plan') unless $self->{ plan }; $self->{ tested }++; $self->{ skipped }++; return $self->test_msg( skip_one => $self->{ count }++, $msg ); } sub skip_some { my ($self, $n, $msg) = @_; $n = int $n; return unless $n > 0; while ($n--) { $self->skip($msg); } } sub skip_rest { my $self = shift->prototype; my $msg = shift; my $plan = $self->{ plan }; while ($self->{ tested } < $plan) { $self->skip($msg); } exit; } sub skip_all ($;$) { my $self = shift->prototype; $self->test_msg( skip_all => shift || $self->{ reason } ); exit; } sub result { my $self = shift->prototype; my $ok = shift; return $self->error_msg('no_plan') unless $self->{ plan }; if ($ok) { $self->{ passed }++; return $self->test_msg( ok => @_ ); } else { $self->{ failed }++; return $self->test_msg( not_ok => @_ ); } } sub test_msg { my $self = shift; print $self->message(@_); } sub test_name ($) { my $self = shift->prototype; my ($pkg, $file, $line) = caller(2); $self->message( name => $self->{ count }, $file, $line ); } sub different { my ($self, $expect, $result) = @_; my ($pad_exp, $pad_res) = ($expect, $result); for ($pad_exp, $pad_res) { s/\n/\n# |/g; } my $msg = $self->message( not_eq => $pad_exp, $pad_res ); return $msg unless $CAN_DIFF; my $diffs = diff( map { [ split(/\n/) ] } $expect, $result ); my $n = 0; my $m = scalar @$diffs; foreach my $hunk (@$diffs) { $msg .= $self->message( hunk => ++$n, $m ); foreach my $delta (@$hunk) { $msg .= $self->message( delta => @$delta ); } # $msg .= "\n"; } return $msg; } sub colour { my $self = shift->prototype; my $ansi = ANSI_colours; # enable colour mode by inserting ANSI escapes into $MESSAGES if (@_ && ($self->{ colour } = shift)) { foreach my $col (keys %$SCHEME) { my $code = $ansi->{ $col } || $self->error("Invalid colour name in \$SCHEME: $col\n"); $MESSAGES->{ $_ } = ANSI_escape($code, $MESSAGES->{ $_ }) for split(/\s+/, $SCHEME->{ $col }); } Badger::Debug->enable_colour; } return $self->{ colour }; } sub flush { my $self = shift->prototype; my $results = shift || $self->{ results }; return unless @$results; $self->{ plan } ||= @$results; while (@$results) { my $test = shift @$results; $self->result(@$test); } } sub summary { my $self = shift->prototype; return @_ ? ($self->{ summary } = shift) : $self->{ summary }; } sub finish { my $self = shift->prototype; $self->flush; # output any cached results my ($plan, $ran, $pass, $fail, $skip) = @$self{ qw( plan tested passed failed skipped ) }; return unless $plan; # mandatory warnings about too many/too few if ($ran < $plan) { $self->test_msg( too_few => $plan, $ran ); } elsif ($ran > $plan) { $self->test_msg( too_many => $plan, $ran ); } # optional summary follows for those who want it return unless $self->{ summary }; my $good = $pass + $skip; if ($fail) { $self->test_msg( fail => $fail ); } elsif ($good == $plan) { $self->test_msg( pass => $plan ); } else { $self->test_msg('mess'); } $self->test_msg( summary => $ran, $plan, $pass, $fail, $skip ); # remove ourselves from the index delete $INSTANCES->{ $self }; } END { # Cleanup test managers so they can report errors using test_msg(). If # we leave it until global destruction (e.g. by using a DESTROY method to # call finish() then there's a chance that the Badger::Class object that # perform the $MESSAGE lookup will have already been cleaned up. $_->finish for values %$INSTANCES; } 1; __END__
# Local Variables: # mode: perl # perl-indent-level: 4 # indent-tabs-mode: nil # End: # # vim: expandtab shiftwidth=4: