#!/usr/bin/perl -w

#
# base.t
#
# Copyright (C) 2015-2017 the University of Southern California
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License.
#
#    This program 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
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# 


=head1 NAME

base.t - run basic antlink tests

=head1 SYNOPSIS

base.t [-v] [testname]

=head1 DESCRIPTION

This program does unit tests against antlink.

To run specific tests, list them on the command line by name
(value in the hash, see the code for names),
or 0-based index.

=head1 OPTIONS

=over

=item B[-d]
Enable debugging output.

=item B[-v]
Enable verbose output.

=item B<--help>
Show help.

=item B<--man>
Show full manual.

=back

=cut

use Test::More;
use Pod::Usage;
use Cwd;
use File::Temp;
use Getopt::Long;

Getopt::Long::Configure ("bundling");
pod2usage(2) if ($ARGV[0] eq '-?');
my($prog) = $0;
my $debug = undef;
my $verbose = undef;
my $keep_files = undef;
&GetOptions('d|debug+' => \$debug,   
 	'help' => sub { pod2usage(1); },
	'man' => sub { pod2usage(-verbose => 2); },
	'k|keep!' => \$keep_files,
         'v|verbose+' => \$verbose) or &pod2usage(2);

foreach (qw(README antlink antlink.spec.in)) {
    die "test_command.t: must be run from the antlink source root directory.\n\t(can't find $_)\n"
	if (! -f $_);
};

my $basedir = getcwd;
my $temp_template = "/tmp/antlink.XXXXXXXX";

sub check_is_gitrepo($) {
    my($p) = @_;
    return -d "$p" &&
	-f "$p/config";
}
sub check_is_gitrepo_co($) {
    my($p) = @_;
#    open E, ">>/tmp/error";
#    print E "check_is_gitrepo_co($p)\n";
#    print E "  -d $p: " . (-d "$p" ? "yes" : "NO") . "\n";
#    print E "  check_is_gitrepo $p/.git: " . (check_is_gitrepo("$p/.git") ? "yes" : "NO") . "\n";;
    return -d "$p" &&
	check_is_gitrepo("$p/.git");
}
sub check_initmeta($;$) {
    my($testdir) = @_;
    return check_is_gitrepo("$testdir/test.git") &&
	-d "$testdir/test_GIT";
};
sub check_clonemeta($;$) {
    my($testdir) = @_;
#    open E, ">>/tmp/error";
#    print E "CCM: check_initmeta($testdir): ". (check_initmeta($testdir) ? "yes" : "NO") . "\n";
#    print E "CCM: check_is_gitrepo_co($testdir/test): ". (check_is_gitrepo_co("$testdir/test") ? "yes" : "NO") . "\n";
    return check_initmeta($testdir) &&
	check_is_gitrepo_co("$testdir/test");
};

sub check_is_not_exists($) {
    my($target) = @_;
    return ! -e $target;
}


sub check_init($;$) {
    my($testdir) = @_;
#    open E, ">>/tmp/error";
#    print E "INIT: $testdir\n";
#    print E "check_clonemeta($testdir) : " . (check_clonemeta($testdir) ? "yes" : "NO") . "\n";
#    print E "check_is_gitrepo($testdir/test_GIT/subproject.git) " . (check_is_gitrepo("$testdir/test_GIT/subproject.git") ? "yes" : "NO") . "\n";
#    print E "check_is_gitrepo_co($testdir/test/subproject) " . (check_is_gitrepo_co("$testdir/test/subproject") ? "yes" : "NO") . "\n";
    return  check_clonemeta($testdir) &&
		check_is_gitrepo("$testdir/test_GIT/subproject.git") &&
		check_is_gitrepo_co("$testdir/test/subproject");
}


my($tests_aref) = [
    {
	name => 'initmeta',
	cmd => "$basedir/antlink initmeta !TESTDIR/test.git",
	check => \&check_initmeta,
    },
    {
	name => 'manual-clonemeta',
	cmd => "$basedir/antlink initmeta !TESTDIR/test.git &&
			{ cd !TESTDIR; git clone !TESTDIR/test.git; }",
	check => \&check_clonemeta,
    },
    {
	name => 'clonemeta',
	cmd => "$basedir/antlink initmeta !TESTDIR/test.git &&
	    	$basedir/antlink clonemeta !TESTDIR/test.git !TESTDIR/test",
	check => \&check_clonemeta,
    },
    {
	name => 'init',
	cmd => "$basedir/antlink initmeta !TESTDIR/test.git &&
	    	$basedir/antlink clonemeta !TESTDIR/test.git !TESTDIR/test &&
	    	$basedir/antlink init !TESTDIR/test/subproject",
	check => \&check_init,
    },
    {
	name => 'mv',
	cmd => "$basedir/antlink initmeta !TESTDIR/test.git &&
	    	$basedir/antlink clonemeta !TESTDIR/test.git !TESTDIR/test &&
	    	$basedir/antlink init !TESTDIR/test/pre_mv && 
	    	cd !TESTDIR/test && $basedir/antlink mv pre_mv post_mv",
	check => sub {
	    my($testdir) = @_;
	    return check_clonemeta($testdir) &&
		check_is_gitrepo("$testdir/test_GIT/post_mv.git") &&
		check_is_gitrepo_co("$testdir/test/post_mv") &&
		check_is_not_exists("$testdir/test_GIT/pre_mv.git") &&
		check_is_not_exists("$testdir/test/pre_mv");
	},
    },
];

sub run_one($) {
    my($test) = @_;

    my $testdir = File::Temp::tempdir(TEMPLATE => $temp_template, CLEANUP => !$keep_files);

    if ($test->{skip}) {
	skip 'skip requested', 1;
    };
	
    my $run_cmd = $test->{cmd};
    $run_cmd =~ s/!TESTDIR/$testdir/g;
    print "$run_cmd\n" if ($verbose);

    if (!system($run_cmd)) {
	# unsuccessful program.  is this a bad thing?
        if ($? == -1) {
	    # yes... system failure
	    diag "failed to execute command: $!\n";
	    return undef;
	} elsif ($? & 127) {
	    # yes... crash
	    diag "test " . $test->{name} . " received signal...very bad! $!\n\t$run_cmd";
	    return undef;
	} else {
	    # maybe... failure
	    my $exit_code = ($? >> 8);
	    if (defined($test->{expected_exit_code})) {
		my $expected_result = undef;
		if (!$expected_result && $test->{expected_exit_code} eq 'fail' && $exit_code != 0) {
		    print "expected failure and got it ($exit_code)\n" if ($debug);
		    $expected_result = 1;
		};
		if (!$expected_result && $test->{expected_exit_code} ne 'fail' && $exit_code == $test->{expected_exit_code}) {
		    print "expected some code (". $test->{expected_exit_code}. "), and got it\n" if ($debug);
		    $expected_result = 1;
		};
		if (!$expected_result) {
		    diag "test $test->{name} exited with unexpected exit code $exit_code (should be " . $test->{expected_exit_code} . ")\n\t$run_cmd\n";
		    return undef;
		    # fall through
		};
	    } else {
		# fall through... got EXPECTED non-zero exit code
	    };
	};
    } else {
	# check for sucessful programs that maybe should have failed
	if (defined($test->{expected_exit_code}) &&
		'0' ne $test->{expected_exit_code}) {
	    diag "test " . $test->{name} . " exited successfully when expected exit code was " . $test->{expected_exit_code} . "\n\t$run_cmd";
	    return undef;
	};
    };

    my $ok = &{$test->{check}}($testdir, $test);

    &{$test->{cleanup}}($testdir, $test) if (defined($test->{cleanup}));

    return $ok;
}

my(@tests_to_do);
if ($#ARGV >= 0) {
    # sigh O(n^2)
    foreach my $test_name (@ARGV) {
	my $found_test = undef;
	my $i = 0;
	foreach my $test (@$tests_aref) {
	    if ($test_name eq $test->{name} || $test_name eq $i) {
		$found_test = $test;
		last;
	    };
	    $i++;
	};
	die "unknown test $test_name\n" if (!defined($found_test));
	push @tests_to_do, $found_test;
    };
} else {
    push @tests_to_do, @$tests_aref;
};

plan tests => $#tests_to_do + 1;

foreach my $test (@tests_to_do) {
    SKIP: {
	  ok(run_one($test), $test->{name});
    }
};

exit 0;
