#!/usr/bin/perl -w

use strict;
use Test::Repocop::Common;
use Test::Repocop::Options;
use Test::Repocop::SQL;
use Test::Repocop::RPMdb;
use File::Path;
use File::Basename;

use File::Temp qw/tempfile tempdir/;
use RPM::Header;
use DBI;

$verbose=1;
my ($repocop_use_fakeroot);
&Test::Repocop::Options::get_common_options(
	    "fakeroot!"  => \$repocop_use_fakeroot,
);

my $rundir=`pwd`;
chomp $rundir;

# global; we use it in exit_repocop
my $PKGTMPDIR;

$SIG{'INT'}  = \&sigexit_repocop;
$SIG{'QUIT'} = \&sigexit_repocop;
$SIG{'HUP'} = \&sigexit_repocop;

&repocop_note("Note: using fakeroot (slower).") if $repocop_use_fakeroot;
die "cachedir $repocop_cachedir does is not a directory!\n" if (-e $repocop_cachedir && ! -d $repocop_cachedir);

my $repocop_test_cachedir="$repocop_cachedir/testcache";
my $repocop_test_tmpdir="$repocop_cachedir/tmp";
mkpath([$repocop_cachedir,$repocop_test_outputdir,$repocop_test_cachedir,
	$repocop_metadatadir,$repocop_test_tmpdir,$repocop_test_dbdir]);

# list of last packages that passed through tests
# we empty it there and create again at each run
my $repocop_lastrun_id="$repocop_metadatadir/last-run";
my $repocop_lastrun_path="$repocop_metadatadir/last-run-path";
unlink $repocop_lastrun_id,$repocop_lastrun_path;

$ENV{'REPOCOP_CACHEDIR'}=$repocop_cachedir;
$ENV{'REPOCOP_TEST_OUTPUTDIR'}=$repocop_test_outputdir;

foreach my $test (&Test::Repocop::Common::glob_tests('',@Repocop::arg::tests_dir)) {
    my $testname=basename($test);
    mkpath (["$repocop_test_cachedir/$testname", "$repocop_test_tmpdir/$testname"]);
}

# sql constructor
foreach my $sqlconstructor (&Test::Repocop::Common::glob_tests('init.sql*', @Repocop::arg::tests_dir)) {
    my $testname=&Test::Repocop::Common::filepath2testname($sqlconstructor);
    my $repocop_test_db="$repocop_test_dbdir/$testname.db";
    &Test::Repocop::SQL::init_db($repocop_test_db,$sqlconstructor,$testname);
}

&Test::Repocop::RPMdb::connect_rpm_db("$repocop_test_dbdir/rpm.db");

foreach my $testconstructor (&Test::Repocop::Common::glob_tests('init', @Repocop::arg::tests_dir)) {
    my $testname=&Test::Repocop::Common::filepath2testname($testconstructor);
    &Test::Repocop::Common::set_test_environment($testname);
    if (-x $testconstructor) {
	system($testconstructor)==0 || die "error: $testname/init exited abnormally\n";
    }
}

my $tmpdir_template=$0.".XXXXXXXX";
$tmpdir_template=~s!^.*/!!;

foreach my $rpmarg (sort @Repocop::arg::pkglist) {
    &main_loop($rpmarg);
}

chdir $rundir;

my @testdestructors = &Test::Repocop::Common::glob_tests('done', @Repocop::arg::tests_dir);
my @destructornames;
foreach my $testdestructor (@testdestructors) {
    my $testname=&Test::Repocop::Common::filepath2testname($testdestructor);
    push @destructornames, $testname;
}

&repocop_note("preparing [postcollectors]");#@destructornames
&Test::Repocop::TestCache::remove_test(@destructornames);

foreach my $testdestructor (@testdestructors) {
    my $testname=&Test::Repocop::Common::filepath2testname($testdestructor);
    &Test::Repocop::Common::set_test_environment($testname);
    &repocop_note("trying [done] $testname");
    if (-x $testdestructor) {
	system($testdestructor)==0 || die "error: $testname/done exited abnormally\n";
    }
}

foreach my $test (&Test::Repocop::Common::glob_tests('',@Repocop::arg::tests_dir)) {
    my $testname=basename($test);
    # only empty directories are deleted
    rmdir "$repocop_test_cachedir/$testname";
}

&cleanup_on_repocop_exit();

sub cleanup_on_repocop_exit {
    &del_PKGTMPDIR();
    rmtree ([$repocop_test_tmpdir]) if $repocop_test_tmpdir;
    &Test::Repocop::RPMdb::done_rpm_db();
}


sub sigexit_repocop {
    print "exiting repocop (signal)...\n" if $verbose;
    &cleanup_on_repocop_exit();
    exit 10 ;
}

sub del_PKGTMPDIR {
    if (defined $PKGTMPDIR and -d $PKGTMPDIR) {
	system 'chmod', '-Rf', 'u+rwX', $PKGTMPDIR;
	rmtree ([$PKGTMPDIR]);
    }

}

sub append_file {
    my ($filename, $line)=@_;
    open (AFILE, '>>', $filename) || die "can't open $filename: $!";
    print AFILE $line, "\n";
    close AFILE;
}

sub main_loop {
    my ($rpmarg)=@_;
    &repocop_note("processing $rpmarg");
    $rpmarg=$rundir.'/'.$rpmarg unless $rpmarg=~m!^/!;
    my $pkgname=basename($rpmarg);
    my $repocop_pkg_key=$pkgname;
    $repocop_pkg_key=~s/\.rpm$//;
    # mark the rpm as processed
    &append_file($repocop_lastrun_id,$repocop_pkg_key);
    &append_file($repocop_lastrun_path,"$repocop_pkg_key\t$rpmarg");
    #
    $ENV{'REPOCOP_PKG'}=$rpmarg;
    
    $ENV{'REPOCOP_PKG_KEY'}=$repocop_pkg_key;

    $PKGTMPDIR = tempdir( $tmpdir_template, TMPDIR => 1, CLEANUP=>1);
    die "can't create temporary directory!" unless $PKGTMPDIR;
    $ENV{'RPM_BUILD_ROOT'}=$PKGTMPDIR;
    $ENV{'REPOCOP_PKG_ROOT'}=$PKGTMPDIR;

    my $rhref = new RPM::Header $rpmarg;
    my $is_source=$rhref->is_source();
    my $testsdir_ref=$is_source ? \@Repocop::arg::srcscripts_dir : \@Repocop::arg::pkgscripts_dir;
    my $specname;
    if ($is_source) {
	#SPEC files have flag 32
	for (my $i=0;$i<scalar(@{$rhref->{'FILEFLAGS'}});$i++) {
	    $specname = $rhref->{'BASENAMES'}[$i] if (@{$rhref->{'FILEFLAGS'}}[$i] == 32);
	}
    }

    mkpath(["$repocop_test_outputdir/$repocop_pkg_key"]);

    my $rpmdb_touchfile="$repocop_test_outputdir/$repocop_pkg_key/rpm";
    unless (-e $rpmdb_touchfile) {
	&Test::Repocop::RPMdb::add_rpm_metadata($rhref,$repocop_pkg_key);
	system('touch',$rpmdb_touchfile)==0 or die "touch: $!";
    }

    foreach my $test (&Test::Repocop::Common::glob_tests('test',@$testsdir_ref)) {
	my $testname=&Test::Repocop::Common::filepath2testname($test);
	&Test::Repocop::Common::set_test_environment($testname);
	my $repocop_test_result="$repocop_test_outputdir/$repocop_pkg_key/$testname";
	if (-e $repocop_test_result) {
	    if (-M $repocop_test_result < -M "$test") {
	        # tests are already performed
		&repocop_note("skipped $testname (cached)");
 		next;
	    } else {
		&repocop_note("cache for $testname is obsolete - run again");
		unlink <$repocop_test_outputdir/$repocop_pkg_key/${testname}*>;
	    }
	}
	if (! -x $test ) {
	    &repocop_note("skipped $testname: not executable");
	} else {
	    die "can't change directory to $PKGTMPDIR" unless chdir $PKGTMPDIR;
	    # unpack rpm only if required to speed up skipping
	    if (! -e "$PKGTMPDIR/.unpack") {
		`touch "$PKGTMPDIR/.unpack"`;
		if ($is_source) {
		    system("rpm2cpio '$rpmarg' | cpio -i --quiet --no-absolute-filenames $specname") && die "rpm2cpio failed on srpm $rpmarg";
		    $ENV{'REPOCOP_PKG_SPECFILE'}="$PKGTMPDIR/$specname";
		} else {
		    if ($repocop_use_fakeroot) {
			system("rpm2cpio '$rpmarg' | fakeroot -s .fakedata -- cpio -idmu --quiet --no-absolute-filenames") && die "rpm2cpio (wia fakeroot) failed on $rpmarg";
		    } else {
			system("rpm2cpio '$rpmarg' | cpio -idmu --quiet --no-absolute-filenames") && die "rpm2cpio failed on $rpmarg";
		    }
		}
		foreach my $tag ('NAME','VERSION','RELEASE','EPOCH','ARCH') {
		    my $tagval=$rhref->{$tag};
		    $tagval='' unless defined $tagval;
		    $ENV{'REPOCOP_PKG_'.$tag}=$tagval;
		}
		unless ($is_source) {
		    my $srcshortname=$rhref->{'SOURCERPM'};
		    $srcshortname=~s/\.src\.rpm$//;
		    $ENV{'REPOCOP_PKG_SOURCEPKG'}=$srcshortname;
 		}
	    }
	    &repocop_note("trying $testname");
	    if ($repocop_use_fakeroot and not $is_source) {
		system "fakeroot -i .fakedata -- $test";
	    } else {
		system $test;
	    }
	    system ('touch',$repocop_test_result);
        }
    }
    chdir $rundir;
    &del_PKGTMPDIR();
}

__END__

=head1	NAME

repocop - run unit tests against the repo or a given set of RPM packages

=head1	SYNOPSIS

B<repocop-run>
[B<-f|--fakeroot> ]
[B<--no-fakeroot> ]
[B<-h|--help>]
[B<-v|--verbose>]
[B<-q|--quiet>]
[B<-c|--cachedir> I<cachedir>]
[B<--et|--exclude-test> I<comma separated list of tests>]
[B<--it|--include-test> I<comma separated list of tests>]
[B<--ep|--exclude-packager> I<comma separated list of packager's nicks>]
[B<--ip|--include-packager> I<comma separated list of packager's nicks>]
[B<--pkgcollectors-dir> I<comma separated list of local collectors' dirs>]
[B<--srccollectors-dir> I<comma separated list of local collectors' dirs>]
[B<--pkgtests-dir> I<comma separated list of local tests' dirs>]
[B<--srctests-dir> I<comma separated list of local tests' dirs>]
[B<--ex|--except>] 
[B<-g|--given>] 
[B<-l|--last-run>] 
[B<--newer>] I<filename>
[B<-r|--report> <s[kip]|o[k]|w[arn]|f[ail]>]
[I<DIR>...] [I<FILE>...]

=head1	DESCRIPTION

B<repocop> executes a set of tests against each RPM package given on the
command line. Presize subset of tests can be selected using B<--include>
and B<--exclude> options.
Extra word splitting is performed on the I<comma separated list of tests>.
Each I<FILE> is treated as RPM package.  Each I<DIR> is listed with C<*.rpm>
pattern, and all RPM files found are processed.

=head1	OPTIONS

=over

=item	B<-f,--fakeroot>

Use fakeroot for unpacking rpms.
(note: there are stability issues with some versions of fakeroot). 
It is slower and required only for tests that check ownership and so on.

=item	B<--no-fakeroot>

Do not use fakeroot for unpacking rpms (default).

=item	B<-c,--cachedir> I<dir>

Provides alternative location for cachedir. 
Repocop cachedir is a place where test results and 
packages metadata information are stored.

=item	B<--except>, B<--given>

Control processing of rpm arguments. 
B<--given> (default) means processing only given rpm arguments.
B<--except>  means processing all data except given rpm arguments.

=item	B<--et, --exclude-test> I<comma separated list of tests>

Report all processed tests exept the given excluded set.

=item	B<--it, --include-test> I<comma separated list of tests>

Report the given set of tests.

=item	B<--ep, --exclude-packager> I<comma separated list of tests>

=item	B<--it, --include-packager> I<comma separated list of tests>

Exclude/include packages according to Packager: tag.

=item [B<--pkgcollectors-dir> I<comma separated list of local collectors' dirs>]

=item [B<--srccollectors-dir> I<comma separated list of local collectors' dirs>]

=item [B<--pkgtests-dir> I<comma separated list of local tests' dirs>]

=item [B<--srctests-dir> I<comma separated list of local tests' dirs>]

Append user's local tests and collectors to repocop.

=item	B<-h, --help>

Display this help and exit.

=item	B<-v, --verbose>, B<-q, --quiet>

Verbosity level. Multiple -v increase the verbosity level, -q sets it to 0.

=item	B<-l, --last-run>

Use the set of packages processed at last run as an argument.

=item	B<--newer> I<filename>

Process packages newer then I<filename> only.
Note: this filtering does not apply to B<--last-run> option.

=item	B<--acl-file> I<file>

the argument is /path/to/Sisyphus/files/list/list.src.classic
This option is ALTLinux-specific. The file content is ACL db,
which is used to sort result by ALTLinux ACL.


=back

=head1	AUTHOR

Written by Igor Vlasenko <viy@altlinux.org>.

=head1	ACKNOWLEGEMENTS

To Alexey Torbin <at@altlinux.org>, whose qa-robot package
had a strong influence on repocop. 

=head1	COPYING

Copyright (c) 2008 Igor Vlasenko, ALT Linux Team.

This 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, or (at your option) any later version.

=cut

