#!/usr/bin/perl -T

# Simple test for running debsums on a fake chroot

use strict;
use warnings;

use Test::More;
use Test::Command::Simple;

my $chroot;
my @command;

# Untaint stuff
delete $ENV{PATH};

# First check if we are testing a source package or an installed package.
my $command = './debsums';
if (exists $ENV{AUTOPKGTEST_TMP} or exists $ENV{DEBIAN_AS_INSTALLED_TESTING}) {
    $command = '/usr/bin/debsums';
}

ok(-e $command, "Command debsums found at $command");
ok(-x $command, "$command executable");

foreach my $state (qw(clean dirty)) {
    my $exit_code = $state eq 'clean' ? 0 : 2;
    $chroot = "t/fakechroots/$state";
    @command = ($command, "--root=$chroot");

    my $expected = <<"EOT";
$chroot/usr/share/doc/fakepkg/copyright                           OK
$chroot/usr/share/doc/fakepkg/changelog.Debian.gz                 OK
$chroot/usr/share/doc/fakepkg/README                          FAILED
EOT
    $expected = remove_failed($expected)
        if $state eq 'clean';

    my $expected_s = <<"EOT";
debsums: changed file $chroot/usr/share/doc/fakepkg/README (from fakepkg package)
EOT
    $expected_s = remove_failed($expected_s)
        if $state eq 'clean';

    my $expected_x = <<"EOT";
debsums: changed file $chroot/usr/share/doc/fakepkg/README (observed:1dd5525a854efa9dac37d84d0ef9bc9e expected:deadbeefcafebabedeadbeefcafebabe) (from fakepkg package)
EOT
    $expected_x = remove_failed($expected_x)
        if $state eq 'clean';

    my $expected_conffiles = <<"EOT";
$chroot/etc/fakepkg.cfg                                           OK
$chroot/etc/fakepkg-broken.cfg                                FAILED
EOT
    $expected_conffiles = remove_failed($expected_conffiles)
        if $state eq 'clean';

    # Test running with an unknown package
    run(@command, 'foobar');
    is(stderr,
       "debsums: package foobar is not installed\n",
       'Warning on STDERR');
    is(stdout, '', 'STDOUT is empty');
    expected_rc(1);

    # Test running with an unknown and a potentially dirty package
    run(@command, 'foobar', 'fakepkg');
    expected_rc($exit_code | 1);

    foreach my $pkg ('', 'fakepkg') {
        @command = (@command, $pkg) if $pkg;

        # Test running without any further parameters
        run(@command);
        is(stderr, '', 'STDERR is empty');
        is(stdout, $expected, 'STDOUT as expected');
        expected_rc($exit_code);

        # Test option -c
        run(@command, '-c');
        is(stderr, '', 'STDERR is empty');
        is(stdout,
           $state eq 'clean' ? '' : failed_only($expected),
           'STDOUT as expected');
        expected_rc($exit_code);

        # Test option -s
        run(@command, '-s');
        is(stderr,
           $state eq 'clean' ? '' : $expected_s,
           'STDERR as expected');
        is(stdout, '', 'STDOUT is empty');
        expected_rc($exit_code);

        # Test option -x
        run(@command, '-x');
        is(stderr,
           $state eq 'clean' ? '' : $expected_x,
           'STDERR as expected');
        is(stdout, '', 'STDOUT is empty');
        expected_rc($exit_code);

        # Test option -e
        run(@command, '-e');
        is(stderr, '', 'STDERR is empty');
        is(sort_lines(stdout),
           sort_lines($expected_conffiles),
           'STDOUT as expected');
        expected_rc($exit_code);

        # Test option -a
        run(@command, '-a');
        is(stderr, '', 'STDERR is empty');
        is(sort_lines(stdout),
           sort_lines($expected.$expected_conffiles),
           'STDOUT as expected');
        expected_rc($exit_code);

        # Test option -ce and variants
        foreach my $option (qw(-ce -ec)) {
            run(@command, $option);
            is(stderr, '', 'STDERR is empty');
            is(stdout,
               $state eq 'clean' ? '' : failed_only($expected_conffiles),
               'STDOUT as expected');
            expected_rc($exit_code);
        }
    }
}

################################################
# Checks for handling of missing md5sums files #
################################################

# Expect a warning
@command = ($command, "--root=t/fakechroots/lossy");
run(@command);
is(stderr, "debsums: no md5sums for fakepkg\n", 'Warning on STDERR');
is(stdout, '', 'STDOUT is empty');
expected_rc(0);

# Explicitly list packages
run(@command, '-l');
is(stderr, '', 'STDERR is empty');
is(stdout, "fakepkg\n", 'Affected package listed');
expected_rc(0);

##############################################
# Checks for handling of empty md5sums files #
##############################################

# Expect a warning
$chroot = "t/fakechroots/empty";
@command = ($command, "--root=$chroot");
run(@command);
is(stderr,
   "$chroot/var/lib/dpkg/info/fakepkg.md5sums is empty but shouldn't!\n",
   'Warning on STDERR');
is(stdout, '', 'STDOUT is empty');
expected_rc(2);

########################################
# Checks for handling of missing files #
########################################

# Expect a warning
$chroot = "t/fakechroots/missing";
@command = ($command, "--root=$chroot");
foreach my $option (qw(-c -s)) {
    run(@command, $option);
    is(stderr,
       "debsums: missing file $chroot/usr/share/doc/fakepkg/copyright (from fakepkg package)\n",
       'Warning on STDERR');
    is(stdout, '', 'STDOUT is empty');
    expected_rc(2);
}

########################################
# Checks for handling of symlinks      #
########################################

$chroot = "t/fakechroots/clean-with-link";
@command = ($command, "--root=$chroot");
foreach my $option (qw(-c -s)) {
    run(@command, $option);
    expected_rc(0);
}

########################################
# Checks symlinks with loops           #
########################################

# Expect a death
$chroot = "t/fakechroots/link-with-loop";
@command = ($command, "--root=$chroot");
foreach my $option (qw(-c -s)) {
    run(@command, $option);
    is(stderr,
       "debsums: Error: symlink loop detected in path 'usr/share/doc/fakepkg/loop.1'. Please file a bug against fakepkg.\n",
       'Warning on STDERR');
    expected_rc(255);
}

#######################
### All checks done ###
#######################

done_testing();

#############################
### Some helper functions ###
#############################

sub sort_lines {
    my $string = shift;
    return join("\n",
                sort
                split("\n", $string))
        . trailing_line_break_if_present($string);
}

sub remove_failed {
    my $string = shift;
    return join("\n",
                grep { !/FAILED|^debsums: changed file/ }
                split("\n", $string))
        . trailing_line_break_if_present($string);
}

sub failed_only {
    my $string = shift;
    return join("\n",
                grep { /FAILED/ && s/\s*FAILED// }
                split("\n", $string))
        . trailing_line_break_if_present($string);
}

sub expected_rc {
    my $exit_code = shift;
    is(rc >> 8, $exit_code, "Exit code is $exit_code");
}

sub trailing_line_break_if_present {
    my $string = shift;
    if ($string =~ m/\n\z/) {
        return "\n";
    } else {
        return '';
    }
}
