Fennec::Runner - The runner class that loads test files/classes and runs them.


Fennec documentation Contained in the Fennec distribution.

Index


Code Index:

NAME

Top

Fennec::Runner - The runner class that loads test files/classes and runs them.

DESCRIPTION

Top

Loads test classes and files, processes them, then runs the tests. This class is a singleton instantiated by import() or new(), whichever comes first.

USING THE RUNNER

Top

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.

CUSTOM RUNNER CLASS

Top

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.

API STABILITY

Top

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.

AUTHORS

Top

Chad Granum exodist7@gmail.com

COPYRIGHT

Top


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__