| Fennec documentation | Contained in the Fennec distribution. |
Fennec::Runner - The runner class that loads test files/classes and runs them.
Loads test classes and files, processes them, then runs the tests. This class is a singleton instantiated by import() or new(), whichever comes first.
If you directly run a file that has use Fennec it will re-execute perl and
call the test file from within the runner. In most cases you will not need to
use the runner directly. However you may want to create a runner script or
module that loads multiple test files at once before running the test groups.
This section tells you how to do that.
The simplest way to load modules and files is to simply use Fennec::Runner with filenames and/or module names as arguments.
#!/usr/bin/env perl
use strict;
use warnings;
use Fennec::Runner qw{
Some::Test::Module
a_test_file.t
/path/to/file.pl
Other::Module
};
run();
This will attempt to guess weather each item is a module or a file, then
attempt to load it. Once all the files are loaded, run() will be
exported into your namespace for you to call.
You can also provide coderefs to generate lists of modules and files:
#!/usr/bin/env perl
use strict;
use warnings;
use Fennec::Runner sub {
my $runner = shift;
...
return ( 'Some::Module', 'a_file.pl' );
};
run();
If you want to have more control over what is loaded, and do not want run()
to be run until you run it yourself you can do this:
#!/usr/bin/env perl
use strict;
use warnings;
use Fennec::Runner;
our $runner = Fennec::Runner->new(); # Get the singleton
$runner->load_file( 'some_file.t' );
$runner->load_module( 'Some::Module' );
...
$runner->run();
For regular Fennec tests this works perfectly fine. However if any of the test files use Test::Class you will have to wrap the load method calls in a BEGIN block.
If you use a test framework that is not based on Test::Builder it may be useful to subclass the runner and override the listener_class() and init() methods.
For more information see Fennec::Recipe::CustomRunner.
Fennec versions below 1.000 were considered experimental, and the API was subject to change. As of version 1.0 the API is considered stabalized. New versions may add functionality, but not remove or significantly alter existing functionality.
Chad Granum exodist7@gmail.com
Copyright (C) 2011 Chad Granum
Fennec is free software; Standard perl licence.
Fennec is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
| Fennec documentation | Contained in the Fennec distribution. |
package Fennec::Runner; use strict; use warnings; BEGIN { my @ltime = localtime; $ltime[5] += 1900; for ( 3, 4 ) { $ltime[4] = "0$ltime[$_]" unless $ltime[$_] > 9; } my $seed = $ENV{FENNEC_SEED} || join( '', @ltime[5,4,3] ); print "\n*** Seeding random with date ($seed) ***\n"; srand( $seed ); } use Carp qw/carp croak/; use Scalar::Util qw/blessed/; use Fennec::Util qw/accessors/; use Fennec::Listener; accessors qw/pid listener test_classes/; my $SINGLETON; my $listener_class; sub listener_class { unless ( $listener_class ) { if ( $^O eq 'MSWin32' ) { require Fennec::Listener::TBWin32; $listener_class = 'Fennec::Listener::TBWin32'; } elsif (eval { require Test::Builder2; 1 }) { require Fennec::Listener::TB2; $listener_class = 'Fennec::Listener::TB2'; } else { require Fennec::Listener::TB; $listener_class = 'Fennec::Listener::TB'; } } return $listener_class; } sub init {} sub import { my $self = shift->new(); return unless @_; $self->_load_guess( $_ ) for @_; $self->inject_run( scalar caller ) } sub inject_run { my $self = shift; my ($caller) = @_; require Fennec::Util; Fennec::Util::inject_sub( $caller, 'run', sub { $self->run } ); } sub new { my $class = shift; return $SINGLETON if $SINGLETON; $SINGLETON = bless({ test_classes => [], pid => $$, listener => $class->listener_class->new() || croak "Could not init listener!", }, $class); $SINGLETON->init( @_ ); return $SINGLETON; }; sub _load_guess { my $self = shift; my ( $item ) = @_; if ( ref $item && ref $item eq 'CODE' ) { $self->_load_guess( $_ ) for ($self->$item); return; } return $self->load_file( $item ) if $item =~ m/\.(pm|t|pl)$/i || $item =~ m{/}; return $self->load_module( $item ) if $item =~ m/::/ || $item =~ m/^\w[\w\d_]+$/; die "Not sure how to load '$item'\n"; } sub load_file { my $self = shift; my ( $file ) = @_; print "Loading: $file\n"; eval { require $file; 1 } || $self->exception( $file, $@ ); $self->check_pid(); } sub check_pid { my $self = shift; return unless $self->pid != $$; die "PID has changed! Did you forget to exit a child process?\n"; } sub load_module { my $self = shift; my $module = shift; print "Loading: $module\n"; eval "require $module" || $self->exception( $module, $@ ); $self->check_pid(); } sub run { my $self = shift; Test::Class->runtests if $INC{'Test/Class.pm'} && !$ENV{'FENNEC_TEST'}; for my $class ( @{ $self->test_classes }) { next unless $class && $class->can('TEST_WORKFLOW'); print "Running: $class\n"; my $instance = $class->can('new') ? $class->new : bless( {}, $class ); my $meta = $instance->TEST_WORKFLOW; my $prunner; if ( my $max = $class->FENNEC->parallel ) { if ( $^O eq 'MSWin32' ) { print "Parallization unavailable on windows.\n"; } else { require Parallel::Runner; $prunner = Parallel::Runner->new( $max ); $meta->test_run( sub { my $sub = shift; $prunner->run( sub { $instance->TEST_WORKFLOW->test_run(undef); $sub->(); }); }); } } Test::Workflow::run_tests( $instance ); $prunner->finish if $prunner; $meta->test_run( undef ); $self->check_pid(); } $self->listener->terminate(); } sub exception { my $self = shift; my ( $name, $exception ) = @_; if ( $exception =~ m/^FENNEC_SKIP: (.*)\n/ ) { $self->listener->ok( 1, "SKIPPING $name: $1") } else { $self->listener->ok( 0, $name ); $self->listener->diag( $exception ); } } 1; __END__