#!/usr/bin/perl
use warnings;
use strict;

use Carp qw(croak);
use Getopt::Long qw(:config no_ignore_case pass_through);
use File::Copy::Recursive qw(dircopy);
use File::Find;
use File::Path qw(remove_tree);
use File::Temp;
use Module::Load;
use Test::BrewBuild;

our $VERSION = '1.04';

my $log = Test::BrewBuild->new()->log()->child('brewbuild');
my (%args, $setup, $help);

GetOptions(
    "on=s@"         => \$args{on},
    "n|new=i"       => \$args{new},
    "r|remove"      => \$args{remove},
    "R|revdep"      => \$args{revdep},
    "plugin=s"      => \$args{plugin},
    "args=s@"       => \$args{args},
    "debug=i"       => \$args{debug},
    "install=s@"    => \$args{install},
    "N|notest"      => \$args{notest},
    "L|legacy"      => \$args{legacy},
    "setup"         => \$setup,
    "help"          => \$help,
);

my @valid_args = qw(
    on o new n remove r revdep R plugin p args a debug d install i help h
    n notest setup s legacy L
);

if ($^O =~ /MSWin/ && $args{on}){
    warn "\nwe can't use --on with Windows... running on all\n\n";
    delete $args{on};
}

if (@ARGV){
    my @args = grep /^-/, @ARGV;
    for my $arg (@args){
        $arg =~ s/-//g;
        if (! grep { $arg eq $_ } @valid_args){
            $help = 1;
        }
    }
}

if ($help){
    print <<EOF;

Usage: perl build/brewbuild.pl [options]

Options:

--on      | -o:  perl version number to run against (can be supplied multiple times). Can not be used on Windows
--revdep  | -R   run tests, install, then run tests on all CPAN reverse dependency modules
--debug   | -d:  0-7, sets logging verbosity
--new     | -n:  how many random versions of perl to install
--remove  | -r:  remove all installed perls (less the current one)
--install | -i:  number portion of an available perl version according to "*brew available". Multiple versions can besent in at once
--notest  | -N:  do not run tests. Allows you to --remove and install without testing
--legacy  | -L:  operate on perls < 5.8.x. The default plugins won't work with this flag set if a lower version is installed

--plugin  | -p:  module name of the exec command plugin to use
--args    | -a:  List of args to pass into the plugin (one arg per loop)

--setup   | -s:  Display test platform setup information
--help    | -h:  print this help message
EOF
exit;
}

if ($setup){
    print "\n";
    my @setup = <DATA>;
    print $_ for @setup;
    exit;
}
if (! -d 't/' && ! $args{notest}){
    croak "\nthere's no 't/' directory, nothing to test. Exiting...\n";
}

# configure output for if *brew isn't installed

my $is_win = $^O =~ /MSWin/ ? 1 : 0;
my $brew_prog = $is_win ? 'berrybrew.exe' : 'perlbrew';
my $brew_link = $is_win 
    ? 'https://github.com/dnmfarrell/berrybrew'
    : 'http://perlbrew.pl';
my $sep = $is_win ? ';' : ':';
my @modules;

# check in PATH to see if berry/perlbrew exists

if (! grep { -x "$_/$brew_prog"} split /$sep/, $ENV{PATH}){
    warn "\nYou need to install '$brew_prog' to use this program...\n\n" .
         "See $brew_link\n\n";
    exit;
}

# legacy perls in use?

$args{legacy} = defined $args{legacy} ? 1 : 0;

# temp directory for error logs

remove_tree 'bblog';
my $dir = File::Temp->newdir;
my $dir_name = $dir->dirname;

$args{tempdir} = $dir_name;

if ($args{revdep}){

    $log->_6('running --revdep');

    delete $args{args};
    load 'CPAN::ReverseDependencies';

    find({ wanted => \&_module_find, no_chdir => 1}, 'lib/');
    my $mod = $modules[0];

    $log->_7("using '$mod' as the project we're working on");

    $mod =~ s|lib/||;
    $mod =~ s|/|-|g;
    $mod =~ s|\.pm||;

    $log->_7("working module translated to $mod");

    my $rvdep = CPAN::ReverseDependencies->new;
    my @revdeps = $rvdep->get_reverse_dependencies($mod);

    $log->_0("working on reverse dependencies: " . join ', ', @revdeps);

    $args{plugin} = 'Test::BrewBuild::Plugin::TestAgainst';

    for (@revdeps){
        s/-/::/g;
        $args{plugin_arg} = $_;
        _exec_brew(%args);
    }
}
elsif (defined $args{args}){
    my %opts = %args;

    $log->_7("running a plugin with args");

    delete $args{args};

    for (@{ $opts{args} }) {
        $args{plugin_arg} = $_;
        _exec_brew( %args );
    }

    delete $args{new};
    delete $args{remove};
}
else {
    $log->_7("default run");
    _exec_brew(%args);
}

# clean up the logs

mkdir 'bblog';
dircopy $dir_name, 'bblog';

sub _exec_brew {
    my $brew = Test::BrewBuild->new(%args);

    $log = $log->child('_exec_brew');

    my @opts;
    for (keys %args){
        push @opts, "$_ => $args{$_}" if defined $args{$_};
    }

    $log->_6("executing Test::BrewBuild run() with args: " . join ', ', @opts);

    $brew->run;
}
sub _module_find {
    $log = $log->child('_module_find');
    $log->_7("finding modules");
    push @modules, $_ if -f $_;
}

=pod

=head1 NAME

brewbuild - Automate module and reverse-dependency testing, on Windows and Unix.

=head1 SYNOPSIS

You must be in the root directory of the distribution you want to test.

Run all unit tests against all installed instances with no other action. Upon a
FAIL, a log file will be generated in the current working directory named
"bblog/version.bblog"

    brewbuild

    # output

    5.8.9 :: PASS
    5.20.3 :: FAIL
    5.22.1 :: PASS

Run tests on the local working copy of the current module, then run all tests
of all reverse dependencies of this module (as reported by CPAN), to ensure
the down river modules will work with your new build. In this case, FAILs are
stored in "bblog/revdep-module-name.version.bblog"

    brewbuild --revdep # or -R

    # example output, in my Mock::Sub repo directory

    - working on reverse dependencies: Test-BrewBuild, File-Edit-Portable,
      Devel-Examine-Subs, Devel-Trace-Subs

    Test::BrewBuild
    5.18.4 :: FAIL
    5.22.1 :: PASS

    File::Edit::Portable
    5.18.4 :: PASS
    5.22.1 :: PASS

    Devel::Examine::Subs
    5.18.4 :: PASS
    5.22.1 :: PASS

    Devel::Trace::Subs
    5.18.4 :: PASS
    5.22.1 :: PASS

Print usage information

    brewbuild -h

Run on specific versions only (Unix)

    brewbuild --on 5.20.3 -o 5.8.9

Install three new instances of perl, randomly

    brewbuild --new 3

Remove all perl instances (less the currently used one), install two new random
versions, and run tests against all installed perls

    brewbuild --remove --new 2

Install all available perl versions, and run tests against all of them

    brewbuild --new -1

Install a specific version and run tests on all instances (include just the
number portion of the version per "perlbrew available" or "berrybrew available"

    brewbuild --install 5.20.3

...multiple versions can be passed in at once

    brewbuild -i 5.20.3 -i 5.14.4 -i 5.23.5

Display test platform setup instructions for Unix and Windows

    brewbuild --setup

=head1 DESCRIPTION

This C<brewbuild> script installed by the L<Test::Brewbuild> module allows you
to perform your unit tests across all of your Perlbrew (Unix) or Berrybrew
(Windows) Perl instances, as well as test all of your down-river CPAN modules
that rely on your module against the locally updated version.

For Windows, you'll need to install Berrybrew (see L<SEE ALSO> for details).
For Unix, you'll need Perlbrew.

It allows you to remove and reinstall on each test run, install random versions
of perl, or install specific versions.

All unit tests are run against all installed instances, unless otherwise
specified.

The actual module is just a helper for the installed script, and isn't designed
for end-user use.

=head1 TEST PLATFORM CONFIGURATION

Test::BrewBuild test platform configuration guide

=head2 UNIX

Install perlbrew and related requirements:

    cpanm App::perlbrew
    perlbrew install-patchperl
    perlbrew install-cpanm

Install and switch to your base perl instance, and install C<Test::BrewBuild>:

    perlbrew install 5.22.1
    perlbrew switch 5.22.1
    cpanm Test::BrewBuild

=head2 WINDOWS

Note that the key here is that your %PATH% must be free and clear of anything
Perl. That means that if you're using an existing box with Strawberry or
ActiveState installed, you *must* remove all traces of them in the PATH
environment variable for ``brewbuild'' to work correctly.

Easiest way to guarantee a working environment is using a clean-slate Windows
server with nothing on it. For a Windows test platform, I mainly used an
Amazon AWS t2.small server.

Download/install git for Windows:

    https://git-scm.com/download/win

Create a repository directory, and enter it:

    mkdir c:\repos
    cd c:\repos

Clone and configure berrybrew

    git clone https://github.com/dnmfarrell/berrybrew
    cd berrybrew
    bin\berrybrew.exe config (type 'y' when asked to install in PATH)

Close the current CMD window and open a new one to update env vars

Check available perls, and install one that'll become your core base install

    berrybrew available
    berrybrew install 5.22.1_64
    berrybrew switch 5.22.1_64
    close CMD window, and open new one

Make sure it took

    perl -v

Install Test::BrewBuild

    cpanm Test::BrewBuild

=head1 AUTHOR

Steve Bertrand, C<< <steveb at cpan.org> >>

=head2 CONTRIBUTING

Any and all feedback and help is appreciated. A Pull Request is the preferred
method of receiving changes (L<https://github.com/stevieb9/p5-test-brewbuild>),
but regular patches through the bug tracker, or even just email discussions are
welcomed.

=head1 BUGS

L<https://github.com/stevieb9/p5-test-brewbuild/issues>

=head1 SUPPORT

You can find documentation for this script and module with the perldoc command.

    perldoc brewbuild
    perldoc Test::BrewBuild

=head1 SEE ALSO

Berrybrew for Windows:

L<https://github.com/dnmfarrell/berrybrew>

Perlbrew for Unixes:

L<http://perlbrew.pl>

=head1 LICENSE AND COPYRIGHT

Copyright 2016 Steve Bertrand.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See L<http://dev.perl.org/licenses/> for more information.

=cut

__DATA__

Test::BrewBuild test platform configuration guide

*** Unix ***

Install perlbrew and related requirements:
    cpanm App::perlbrew
    perlbrew install-patchperl
    perlbrew install-cpanm

Install and switch to your base perl instance, and install C<Test::BrewBuild>:
    perlbrew install 5.22.1
    perlbrew switch 5.22.1
    cpanm Test::BrewBuild

*** Windows ***

Note that the key here is that your %PATH% must be free and clear of anything
Perl. That means that if you're using an existing box with Strawberry or
ActiveState installed, you *must* remove all traces of them in the PATH
environment variable for ``brewbuild'' to work correctly.

Easiest way to guarantee a working environment is using a clean-slate Windows
server with nothing on it. For a Windows test platform, I mainly used an
Amazon AWS t2.small server.

Download/install git for Windows:
    https://git-scm.com/download/win)

Create a repository directory, and enter it:
    mkdir c:\repos
    cd c:\repos

Clone and configure berrybrew
    git clone https://github.com/dnmfarrell/berrybrew
    cd berrybrew
    bin\berrybrew.exe config (type 'y' when asked to install in PATH)

Close the current CMD window and open a new one to update env vars

Check available perls, and install one that'll become your core base install
    berrybrew available
    berrybrew install 5.22.1_64
    berrybrew switch 5.22.1_64
    close CMD window, and open new one

Make sure it took
    perl -v

Install Test::BrewBuild
    cpanm Test::BrewBuild

