App-Prove-Plugin-Distributed

 view release on metacpan or  search on metacpan

lib/App/Prove/Plugin/Distributed.pm  view on Meta::CPAN

=head1 SYNOPSIS

  # All of the examples below is loading tests into the worker perl processes
  # If you want to run the tests in a separate perl process, you can specify
  # the '--detach' option to accomplish that.
  
  # Default workers with L<IPC::Open3> as worker processes.
  prove -PDistributed -j2 t/

  # Distributed jobs with LSF workers.
  prove -PDistributed --distributed-type=LSF -j2 t/

  # Distributed jobs with SSH workers.
  prove -PDistributed --distributed-type=SSH -j2 --hosts=host1,host2 t/

  # If you are using home network that does not have name server setup,
  # you can specify the option --use-local-public-ip
  prove -PDistributed --distributed-type=SSH --use-local-public-ip -j2 --hosts=host1,host2 t/
  
  # Distributed jobs with PBS workers using L<PBS::Client>. Note: This is not tested yet.
  prove -PDistributed --distributed-type=PBS -j2 t/

  # Distributed jobs with PBS workers using L<PBS::Client>. Note: This is not tested yet.
  # With PBS option
  prove -PDistributed --distributed-type=PBS --mem=200 -j2 t/

=head1 DESCRIPTION

A plugin for App::Prove to distribute job.  The core implementation of the plugin is to
provide a easy interface and functionality to extend the use of any distribution method.

The initiate release of this module was using the idea from L<FCGI::Daemon> that load perl
code file using "do" perl function to the worker perl process to execute tests.

Currently, the distribution comes with a few implementation of distribution methods to
initiate external "worker" processes.
Shown below is the list.
   
   L<IPC::Open3>
   LSF 
   SSH
   L<PBS::Client>  * Note: PBS implemetation is not tested yet.

=head1 FUNCTIONS

Basic functions.

=head3 C<load>

Load the plugin configuration.
It will setup all of the tests to be distributed through the 
L<TAP::Parser::SourceHandler::Worker> source handler class.

=cut

sub load {
    my ( $class, $p ) = @_;
    my @args = @{ $p->{args} };
    my $app  = $p->{app_prove};
    {
        local @ARGV = @args;

        push @ARGV, grep { /^--/ } @{ $app->{argv} };
        $app->{argv} = [ grep { !/^--/ } @{ $app->{argv} } ];
        Getopt::Long::Configure(qw(no_ignore_case bundling pass_through));

        # Don't add coderefs to GetOptions
        GetOptions(
            'manager=s'          => \$app->{manager},
            'distributed-type=s' => \$app->{distributed_type},
            'start-up=s'         => \$app->{start_up},
            'tear-down=s'        => \$app->{tear_down},
            'error-log=s'        => \$app->{error_log},
            'detach'             => \$app->{detach},
            'sync-type=s'        => \$app->{sync_type},
            'source-dir=s'       => \$app->{source_dir},
            'destination-dir=s'  => \$app->{destination_dir},
        ) or croak('Unable to continue');

#LSF: We pass the option to the source handler if the source handler want the options.
        unless ( $app->{manager} ) {
            my $source_handler_class =
                'TAP::Parser::SourceHandler::' 
              . 'Worker'
              . (
                $app->{distributed_type}
                ? '::' . $app->{distributed_type}
                : ''
              );
            eval "use $source_handler_class";
            unless ($@) {
                unless ( $source_handler_class->load_options( $app, \@ARGV ) ) {
                    croak('Unable to continue without needed worker options.');
                }
            }
        }

    }
    my $type = $app->{distributed_type};
    my $option_name = '--worker' . ( $type ? '-' . lc($type) : '' ) . '-option';
    if (   $app->{argv}->[0]
        && $app->{argv}->[0] =~ /$option_name=number_of_workers=(\d+)/ )
    {
        if ( $app->{jobs} ) {
            die
              "-j and $option_name=number_of_workers are mutually exclusive.\n";
        }
        else {
            $app->{jobs} = $1;
        }
    }
    else {
        $app->{jobs} ||= 1;
        unshift @{ $app->{argv} },
          "$option_name=number_of_workers=" . $app->{jobs};
    }

    for (
        qw(start_up tear_down error_log detach sync_type source_dir destination_dir)
      )
    {

lib/App/Prove/Plugin/Distributed.pm  view on Meta::CPAN

                : $socket->sockhost
              )
              . ':'
              . $socket->sockport;
            print $socket (
                '# ',
                ( join "\n# ", ( "Worker: <$spec>", ( split /\n/, $error ) ) ),
                "\n"
            );
            if ($error_log) {
                use IO::File;
                my $fh = IO::File->new( "$error_log", 'a+' );
                unless ( flock( $fh, LOCK_EX | LOCK_NB ) ) {
                    warn "can't immediately write-lock ",
                      "the file ($!), blocking ...";
                    unless ( flock( $fh, LOCK_EX ) ) {
                        die "can't get write-lock on numfile: $!";
                    }
                }
                print $fh (
                    join "\n",
                    (
                        "<< START $job_info >>",
                        "SERVER: $server_spec",
                        "PID: $$",
                        "ERROR: $error",
                        "<< END $job_info >>"
                    )
                );
                close $fh;
            }
        }

        #LSF: How to exit with END block trigger.
        &trigger_end_blocks_before_child_process_exit();

        #LSF: Might not need this.
        $socket->flush;
        exit(0);
    }
    else {
        die "should not get here.\n";
    }
    $socket->close();
    return 1;
}

sub _do {
    my $proto    = shift;
    my $job_info = shift;
    my $args     = shift;
    my $cwd      = File::Spec->rel2abs('.');

    #LSF: The code from here to exit is from  L<FCGI::Daemon> module.
    local *CORE::GLOBAL::exit = sub { die 'notr3a11yeXit' };
    local $0 = $job_info;    #fixes FindBin (in English $0 means $PROGRAM_NAME)
    no strict;               # default for Perl5
    {

        package main;
        local @ARGV = $args ? @$args : ();
        do $0;               # do $0; could be enough for strict scripts
        chdir($cwd);

        if ($EVAL_ERROR) {
            $EVAL_ERROR =~ s{\n+\z}{};
            unless ( $EVAL_ERROR =~ m{^notr3a11yeXit} ) {
                $error = $EVAL_ERROR;
                return;
            }
        }
        elsif ($@) {
            $error = $@;
            return;
        }
    }
    return 1;
}

=head3 C<trigger_end_blocks_before_child_process_exit>

Trigger END blocks before the child process exit.
The main reason is to have the Test::Builder to have
change to finish up.

=cut

sub trigger_end_blocks_before_child_process_exit {
    my $original_pid;
    if ( $Test::Builder::Test && $Test::Builder::Test->{Original_Pid} != $$ ) {
        $original_pid = $Test::Builder::Test->{Original_Pid};
        $Test::Builder::Test->{Original_Pid} = $$;
    }
    use B;
    my @ENDS = B::end_av->ARRAY;
    for my $END (@ENDS) {
        $END->object_2svref->();
    }
    if ( $Test::Builder::Test && $original_pid ) {
        $Test::Builder::Test->{Original_Pid} = $original_pid;
    }
}

=head3 C<rsync_test_env>

Rsync test enviroment to the worker host.

Parameters $app object
Returns boolean

=cut

sub rsync_test_env {
    my $proto   = shift;
    my $app     = shift;
    my $manager = $app->{manager};

    my ( $host, $port ) = split /:/, $manager, 2;
    my $dest = $app->{destination_dir};
    unless ($dest) {
        require File::Temp;



( run in 0.228 second using v1.01-cache-2.11-cpan-fd5d4e115d8 )