Perl comes with a fantastic tool: prove. Prove searches for all the test files under a directory, runs them and reports the results. Anyone used to developing code and tests in parallel ends up running prove all the time, or at least after every code change. It is also an essential component of a continuous integration setup for perl projects.
Yet, through my years of addicted prove usage, I have encountered two limitations with it:
* by default, prove does not support running more than one type of test file. It can run perl test files ending with .t, or it can run python tests with the appropriate Test::Harness source handler, or pgTAP test with yet another source handler, and so on as long as the test script outputs TAP. But you can't have one project containing both perl, python and pgTAP tests and run prove to execute all of them at once.
* a need often arises for doing some generic pre- and post-execution setup around the executed tests (such as initializing a database or kicking up a daemon). It would be very nice to make prove handle that in some way instead of having to explicitly handle it in every test file.
As of version 3.22 of Test::Harness (the latest at the time of writing), those limitations are gone. It is now possible to write a universal test runner based on prove and Test::Harness. The code below does just that:
package TAP::Parser::SourceHandler::Universal;
use strict;
use warnings;
use Data::Dumper;
use Carp qw(confess);
use TAP::Parser::IteratorFactory ();
use TAP::Parser::Iterator::Process ();
use TAP::Parser::SourceHandler::pgTAP;
use base qw( TAP::Parser::SourceHandler );
TAP::Parser::IteratorFactory
->register_handler(__PACKAGE__);
# We handle everything :-)
sub can_handle { return 1 }
# Dispatch to the right kind of iterator,
# depending on what file it is
sub make_iterator {
my ( $class, $source ) = @_;
confess "Source is not a file"
unless $source->meta->{is_file};
my $ext = $source->meta->{file}{lc_ext};
my $file = ref $source->raw ? ${ $source->raw } : $source->raw;
# Do some test setup here. May be parse
# some optional metadata in the test file
# header, and apply the corresponding setup?
# Look at the file's extension
if ($ext eq '.pg') {
# This is a pgTAP test file.
# Return a source handler for pgTAP
# that connects to our local test
# database
$source->{config}->{pgTAP} = {
username => 'foo',
host => '127.0.0.1',
port => 5432,
dbname => 'test',
};
return TAP::Parser::SourceHandler::pgTAP::make_iterator( $class, $source );
} elsif ($ext eq '.t') {
# This is a standard perl test file...
return TAP::Parser::Iterator::Process->new({
command => [ 'perl', $file ],
merge => $source->merge
});
} else {
confess "Do not know how to handle ".
"test file $file of type $ext";
}
}
END {
# Cleanup after the tests
}
42;
The code above is basically a custom TAP source handler that says it supports any kind of test file (though it does not really) and just returns a native TAP source handler when passed a perl or pgTAP test file.
To run prove with this Universal source handler on all files ending with .t or .pg, just do:
$ prove --source Universal --ext .t --ext .pg
Now, this is just a skeleton: you will have to flesh it up according to your specific needs and to add support for more file formats. But it does what I needed: it let me define a TAP source handler for every type of test file I want to support and it gives me a place where to do some setup/cleaning before and after running every test file. From there on, the sky is the limit :-)