"""Sanity checks for command-line options."""
import re
import signal
from subprocess import PIPE
from subprocess import Popen

import pytest


@pytest.fixture
def current_version():
    return open('VERSION').read().strip()


@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_no_arguments_prints_usage():
    proc = Popen(('dumb-init'), stderr=PIPE)
    _, stderr = proc.communicate()
    assert proc.returncode != 0
    assert stderr == (
        b'Usage: dumb-init [option] program [args]\n'
        b'Try dumb-init --help for full usage.\n'
    )


@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_exits_invalid_with_invalid_args():
    proc = Popen(('dumb-init', '--yolo', '/bin/true'), stderr=PIPE)
    _, stderr = proc.communicate()
    assert proc.returncode == 1
    assert stderr in (
        b"dumb-init: unrecognized option '--yolo'\n",  # glibc
        b'dumb-init: unrecognized option: yolo\n',  # musl
    )


@pytest.mark.parametrize('flag', ['-h', '--help'])
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_help_message(flag, current_version):
    """dumb-init should say something useful when called with the help flag,
    and exit zero.
    """
    proc = Popen(('dumb-init', flag), stderr=PIPE)
    _, stderr = proc.communicate()
    assert proc.returncode == 0
    assert stderr == (
        b'dumb-init v' + current_version.encode('ascii') + b'\n'
        b'Usage: dumb-init [option] command [[arg] ...]\n'
        b'\n'
        b'dumb-init is a simple process supervisor that forwards signals to children.\n'
        b'It is designed to run as PID1 in minimal container environments.\n'
        b'\n'
        b'Optional arguments:\n'
        b'   -c, --single-child   Run in single-child mode.\n'
        b'                        In this mode, signals are only proxied to the\n'
        b'                        direct child and not any of its descendants.\n'
        b'   -r, --rewrite s:r    Rewrite received signal s to new signal r before proxying.\n'
        b'                        To ignore (not proxy) a signal, rewrite it to 0.\n'
        b'                        This option can be specified multiple times.\n'
        b'   -v, --verbose        Print debugging information to stderr.\n'
        b'   -h, --help           Print this help message and exit.\n'
        b'   -V, --version        Print the current version and exit.\n'
        b'\n'
        b'Full help is available online at https://github.com/Yelp/dumb-init\n'
    )


@pytest.mark.parametrize('flag', ['-V', '--version'])
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_version_message(flag, current_version):
    """dumb-init should print its version when asked to."""

    proc = Popen(('dumb-init', flag), stderr=PIPE)
    _, stderr = proc.communicate()
    assert proc.returncode == 0
    assert stderr == b'dumb-init v' + current_version.encode('ascii') + b'\n'


@pytest.mark.parametrize('flag', ['-v', '--verbose'])
def test_verbose(flag):
    """dumb-init should print debug output when asked to."""
    proc = Popen(('dumb-init', flag, 'echo', 'oh,', 'hi'), stdout=PIPE, stderr=PIPE)
    stdout, stderr = proc.communicate()
    assert proc.returncode == 0
    assert stdout == b'oh, hi\n'

    # child/parent race to print output after the fork(), can't guarantee exact order
    assert re.search(b'(^|\n)\\[dumb-init\\] setsid complete\\.\n', stderr), stderr  # child
    assert re.search(  # parent
        (
            '(^|\n)\\[dumb-init\\] Child spawned with PID [0-9]+\\.\n'
            '.*'  # child might print here
            '\\[dumb-init\\] Received signal {signal.SIGCHLD}\\.\n'
            '\\[dumb-init\\] A child with PID [0-9]+ exited with exit status 0.\n'
            '\\[dumb-init\\] Forwarded signal 15 to children\\.\n'
            '\\[dumb-init\\] Child exited with status 0\\. Goodbye\\.\n$'
        ).format(signal=signal).encode('utf8'),
        stderr,
        re.DOTALL,
    ), stderr


@pytest.mark.parametrize('flag1', ['-v', '--verbose'])
@pytest.mark.parametrize('flag2', ['-c', '--single-child'])
def test_verbose_and_single_child(flag1, flag2):
    """dumb-init should print debug output when asked to."""
    proc = Popen(('dumb-init', flag1, flag2, 'echo', 'oh,', 'hi'), stdout=PIPE, stderr=PIPE)
    stdout, stderr = proc.communicate()
    assert proc.returncode == 0
    assert stdout == b'oh, hi\n'
    assert re.match(
        (
            '^\\[dumb-init\\] Child spawned with PID [0-9]+\\.\n'
            '\\[dumb-init\\] Received signal {signal.SIGCHLD}\\.\n'
            '\\[dumb-init\\] A child with PID [0-9]+ exited with exit status 0.\n'
            '\\[dumb-init\\] Forwarded signal 15 to children\\.\n'
            '\\[dumb-init\\] Child exited with status 0\\. Goodbye\\.\n$'
        ).format(signal=signal).encode('utf8'),
        stderr,
    )


@pytest.mark.parametrize(
    'extra_args', [
        ('-r',),
        ('-r', ''),
        ('-r', 'herp'),
        ('-r', 'herp:derp'),
        ('-r', '15'),
        ('-r', '15::12'),
        ('-r', '15:derp'),
        ('-r', '15:12', '-r'),
        ('-r', '15:12', '-r', '0'),
        ('-r', '15:12', '-r', '1:32'),
    ],
)
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_rewrite_errors(extra_args):
    proc = Popen(
        ('dumb-init',) + extra_args + ('echo', 'oh,', 'hi'),
        stdout=PIPE, stderr=PIPE,
    )
    stdout, stderr = proc.communicate()
    assert proc.returncode == 1
    assert stderr == (
        b'Usage: -r option takes <signum>:<signum>, where <signum> '
        b'is between 1 and 31.\n'
        b'This option can be specified multiple times.\n'
        b'Use --help for full usage.\n'
    )
