#!/bin/sh

# ^C during an install or a removal on the command line lets the rpm run in
# progress finish, skips what is left and ends poldek with an error when
# something was skipped, as the shell does, rather than killing poldek.
# rpm and the scriptlet it runs ignore the ^C; a second one asks rpm to stop.
# A ^C before the first rpm run stops an install, upgrade-dist and removals
# with an error, and rpm does not run. Started with SIGINT ignored, poldek
# keeps ignoring it. On a terminal rpm reads from the pty it writes to, not
# from poldek's terminal, where sudo would take the ^C before poldek.

. ./sh/lib/setup
. ./sh/lib/repo-setup
. ./sh/lib/rpm-setup

ORIGREPO=
DESTINATION_REPO=
DEPENDENCY_SOLVER=3

setUp() {
    is_rpm_testing_off && return

    rpm_up
    # provide /bin/sh auto requirement
    rpm_build_installed sh -p /bin/sh

    RUNS="$TMPDIR/rpm-runs"
    rm -f $RUNS

    # stands for a ^C typed while rpm runs: it logs its run, sends SIGINT
    # to poldek and finishes
    FAKE_RPM="$TMPDIR/fake-rpm"
    printf '#!/bin/sh\necho rpm "$@" >> %s\nkill -INT $PPID\n' "$RUNS" > $FAKE_RPM
    chmod 755 $FAKE_RPM

    SIGINT_AT_INIT="$PWD/.libs/sigint_at_init.so"
    TERMINAL_RPM="$TMPDIR/terminal-rpm"
}

# terminal_rpm N: writes the fake rpm for N ^C typed on the terminal while
# rpm runs. The terminal sends SIGINT to every process of poldek's group, so
# each ^C is SIGINT to poldek and to rpm. The fake rpm logs its run, a SIGTERM
# and its end.
terminal_rpm() {
    printf '#!/bin/sh\ntrap "echo TERM >> %s" TERM\n' "$RUNS"
    printf 'echo rpm "$@" >> %s\n' "$RUNS"
    for i in $(seq $1); do
        printf 'kill -INT $PPID $$\nsleep 1\n'
    done
    printf 'sleep 1\necho rpm done >> %s\n' "$RUNS"
}

# poldek_on_pipes|poldek_on_pty <args>: poldek with that fake rpm and SIGINT
# as on a terminal; rpm runs on pipes, or on a pty when poldek's output is one
poldek_on_pipes() {
    chmod 755 $TERMINAL_RPM
    with_sigint DEFAULT env LC_ALL=C $POLDEK_INSTALL -Ouse_sudo=n --pmcmd $TERMINAL_RPM "$@" 2>&1
}

poldek_on_pty() {
    chmod 755 $TERMINAL_RPM
    timeout 30 script -qec "perl -e '\$SIG{INT} = \"DEFAULT\"; exec @ARGV' \
        env LC_ALL=C $POLDEK_INSTALL -Ouse_sudo=n --pmcmd $TERMINAL_RPM $*" /dev/null </dev/null 2>&1
}

# with_sigint DEFAULT|IGNORE <command>: run it with SIGINT so set, whatever
# the tests themselves inherited (a background job ignores it)
with_sigint() {
    sig=$1; shift
    perl -e '$SIG{INT} = shift; exec @ARGV or die "$ARGV[0]: $!\n"' $sig "$@"
}

# the fake rpm needs no root, and sudo would not pass its SIGINT on to poldek
poldek_with_sigint() {
    sig=$1; shift
    with_sigint $sig $POLDEK_INSTALL -Ouse_sudo=n --pmcmd $FAKE_RPM "$@"
}

# poldek_interrupted_before_rpm <args>: poldek gets SIGINT right after it
# sets up its ^C handler, as from a ^C typed before the operation gets to rpm
poldek_interrupted_before_rpm() {
    with_sigint DEFAULT env LC_ALL=C LD_PRELOAD=$SIGINT_AT_INIT \
                $POLDEK_INSTALL -Ouse_sudo=n --pmcmd $FAKE_RPM "$@" 2>&1
}

# assertStoppedBeforeRpm <exit code> <output>
assertStoppedBeforeRpm() {
    assertEquals "exit code $1" "1" "$1"
    assertEquals "no SIGINT from $SIGINT_AT_INIT: $2" "1" \
                 "$(echo "$2" | grep -c '^sigint_at_init: SIGINT')"
    assertEquals "not reported: $2" "1" "$(echo "$2" | grep -c Interrupted)"
    assertEquals "rpm runs" "0" "$(cat $RUNS 2>/dev/null | wc -l)"
}

build() {
    build_package $REPO $@
}

testInterruptSkipsRemainingSets()
{
    skip_if_rpm_testing_off && return

    build a
    build b
    build c

    # three packages with no dependencies between them are three sets
    poldek_with_sigint DEFAULT -uv a b c
    rc=$?
    assertEquals "exit code $rc" "1" "$rc"
    assertEquals "rpm runs" "1" "$(wc -l < $RUNS)"
}

testInterruptLetsRemovalFinish()
{
    skip_if_rpm_testing_off && return

    rpm_build_installed a

    poldek_with_sigint DEFAULT uninstall a
    rc=$?
    assertEquals "exit code $rc" "0" "$rc"
    assertEquals "rpm runs" "1" "$(wc -l < $RUNS)"
}

testRpmReadsFromItsPty()
{
    skip_if_rpm_testing_off && return

    build a
    printf '#!/bin/sh\necho rpm stdin $(tty) stderr $(tty 0<&2) >> %s\n' "$RUNS" > $FAKE_RPM

    timeout 30 script -qec "$POLDEK_INSTALL -Ouse_sudo=n --pmcmd $FAKE_RPM -u a" \
        /dev/null </dev/null >/dev/null 2>&1
    assertEquals "rpm runs" "1" "$(grep -c '^rpm stdin /dev/' $RUNS)"
    set -- $(cat $RUNS)
    assertEquals "rpm stdin: $*" "$5" "$3"
}

testInterruptLetsRpmAndScriptletsRun()
{
    skip_if_rpm_testing_off && return

    build a
    terminal_rpm 1 > $TERMINAL_RPM

    out=$(poldek_on_pty -uv a)
    rc=$?
    assertEquals "exit code $rc: $out" "0" "$rc"
    assertEquals "rpm got the ^C: $out" "1" "$(grep -c 'rpm done' $RUNS)"
    assertEquals "rpm asked to stop" "0" "$(grep -c TERM $RUNS)"
    assertEquals "not told: $out" "1" \
                 "$(echo "$out" | grep -c 'Interrupted: rpm runs to the end')"
}

testSecondInterruptAsksRpmToStop()
{
    skip_if_rpm_testing_off && return

    build a
    terminal_rpm 2 > $TERMINAL_RPM

    out=$(poldek_on_pipes -uv a)
    assertEquals "rpm got the ^C: $out" "1" "$(grep -c 'rpm done' $RUNS)"
    assertEquals "rpm not asked to stop: $out" "1" "$(grep -c TERM $RUNS)"
    assertEquals "not told: $out" "1" \
                 "$(echo "$out" | grep -c '^notice: Stopping rpm: a transaction')"
}

testInterruptBeforeRpmStopsInstall()
{
    skip_if_rpm_testing_off && return

    build a

    out=$(poldek_interrupted_before_rpm -u a)
    assertStoppedBeforeRpm "$?" "$out"
}

testInterruptBeforeRpmStopsUpgradeDist()
{
    skip_if_rpm_testing_off && return

    rpm_build_installed a 1-1
    build a 2-1

    poldek_with_sigint DEFAULT --upgrade-dist
    assertEquals "a not upgraded without ^C" "1" "$(grep -c 'a-2-1' $RUNS)"
    rm -f $RUNS

    out=$(poldek_interrupted_before_rpm --upgrade-dist)
    assertStoppedBeforeRpm "$?" "$out"
}

testInterruptBeforeRpmStopsRemoval()
{
    skip_if_rpm_testing_off && return

    rpm_build_installed a
    rpm_build_installed b -r a

    # b requires a, so removing a takes b too
    poldek_with_sigint DEFAULT -e a
    assertEquals "b not removed with a without ^C" "1" "$(grep -c 'b-1-1' $RUNS)"
    rm -f $RUNS

    out=$(poldek_interrupted_before_rpm -e a)
    assertStoppedBeforeRpm "$?" "$out"
}

testIgnoredInterruptStaysIgnored()
{
    skip_if_rpm_testing_off && return

    build a
    build b
    build c

    poldek_with_sigint IGNORE -uv a b c
    rc=$?
    assertEquals "exit code $rc" "0" "$rc"
    assertEquals "rpm runs" "3" "$(wc -l < $RUNS)"
}

. ./sh/lib/shunit2
