Merge pull request #89 from chriskuehl/clean-up-tests

Clean up tests a bit
This commit is contained in:
Anthony Sottile 2016-06-17 15:58:46 -07:00 committed by GitHub
commit 843626f152
9 changed files with 118 additions and 131 deletions

View file

@ -1,5 +1,10 @@
import os
import re import re
import signal import signal
import sys
from contextlib import contextmanager
from subprocess import PIPE
from subprocess import Popen
from py._path.local import LocalPath from py._path.local import LocalPath
@ -18,6 +23,27 @@ NORMAL_SIGNALS = frozenset(
) )
@contextmanager
def print_signals(args=()):
"""Start print_signals and yield dumb-init process and print_signals PID."""
proc = Popen(
(
('dumb-init',) +
tuple(args) +
(sys.executable, '-m', 'testing.print_signals')
),
stdout=PIPE,
)
line = proc.stdout.readline()
m = re.match(b'^ready \(pid: ([0-9]+)\)\n$', line)
assert m, line
yield proc, m.group(1).decode('ascii')
for pid in pid_tree(proc.pid):
os.kill(pid, signal.SIGKILL)
def child_pids(pid): def child_pids(pid):
"""Return a list of direct child PIDs for the given PID.""" """Return a list of direct child PIDs for the given PID."""
pid = str(pid) pid = str(pid)

View file

@ -8,8 +8,8 @@ from subprocess import Popen
import pytest import pytest
from tests.lib.testing import is_alive from testing import is_alive
from tests.lib.testing import pid_tree from testing import pid_tree
def spawn_and_kill_pipeline(): def spawn_and_kill_pipeline():
@ -34,7 +34,8 @@ def living_pids(pids):
return set(pid for pid in pids if is_alive(pid)) return set(pid for pid in pids if is_alive(pid))
def test_setsid_signals_entire_group(both_debug_modes, setsid_enabled): @pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')
def test_setsid_signals_entire_group():
"""When dumb-init is running in setsid mode, it should only signal the """When dumb-init is running in setsid mode, it should only signal the
entire process group rooted at it. entire process group rooted at it.
""" """
@ -42,10 +43,8 @@ def test_setsid_signals_entire_group(both_debug_modes, setsid_enabled):
assert len(living_pids(pids)) == 0 assert len(living_pids(pids)) == 0
def test_no_setsid_doesnt_signal_entire_group( @pytest.mark.usefixtures('both_debug_modes', 'setsid_disabled')
both_debug_modes, def test_no_setsid_doesnt_signal_entire_group():
setsid_disabled,
):
"""When dumb-init is not running in setsid mode, it should only signal its """When dumb-init is not running in setsid mode, it should only signal its
immediate child. immediate child.
""" """
@ -73,7 +72,7 @@ def spawn_process_which_dies_with_children():
# we need to sleep before the shell exits, or dumb-init might send # we need to sleep before the shell exits, or dumb-init might send
# TERM to print_signals before it has had time to register custom # TERM to print_signals before it has had time to register custom
# signal handlers # signal handlers
'{python} -m tests.lib.print_signals & sleep 0.1'.format( '{python} -m testing.print_signals & sleep 0.1'.format(
python=sys.executable, python=sys.executable,
), ),
), ),
@ -96,10 +95,8 @@ def spawn_process_which_dies_with_children():
return child_pid, proc.stdout return child_pid, proc.stdout
def test_all_processes_receive_term_on_exit_if_setsid( @pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')
both_debug_modes, def test_all_processes_receive_term_on_exit_if_setsid():
setsid_enabled,
):
"""If the child exits for some reason, dumb-init should send TERM to all """If the child exits for some reason, dumb-init should send TERM to all
processes in its session if setsid mode is enabled.""" processes in its session if setsid mode is enabled."""
child_pid, child_stdout = spawn_process_which_dies_with_children() child_pid, child_stdout = spawn_process_which_dies_with_children()
@ -110,10 +107,8 @@ def test_all_processes_receive_term_on_exit_if_setsid(
os.kill(child_pid, signal.SIGKILL) os.kill(child_pid, signal.SIGKILL)
def test_processes_dont_receive_term_on_exit_if_no_setsid( @pytest.mark.usefixtures('both_debug_modes', 'setsid_disabled')
both_debug_modes, def test_processes_dont_receive_term_on_exit_if_no_setsid():
setsid_disabled,
):
"""If the child exits for some reason, dumb-init should not send TERM to """If the child exits for some reason, dumb-init should not send TERM to
any other processes if setsid mode is disabled.""" any other processes if setsid mode is disabled."""
child_pid, child_stdout = spawn_process_which_dies_with_children() child_pid, child_stdout = spawn_process_which_dies_with_children()
@ -133,7 +128,8 @@ def test_processes_dont_receive_term_on_exit_if_no_setsid(
('-c', '/doesnotexist'), ('-c', '/doesnotexist'),
('--single-child', '--', '/doesnotexist'), ('--single-child', '--', '/doesnotexist'),
]) ])
def test_fails_nonzero_with_bad_exec(args, both_debug_modes, both_setsid_modes): @pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_fails_nonzero_with_bad_exec(args):
"""If dumb-init can't exec as requested, it should exit nonzero.""" """If dumb-init can't exec as requested, it should exit nonzero."""
proc = Popen(('dumb-init',) + args, stderr=PIPE) proc = Popen(('dumb-init',) + args, stderr=PIPE)
proc.wait() proc.wait()

View file

@ -11,7 +11,8 @@ def current_version():
return open('VERSION').read().strip() return open('VERSION').read().strip()
def test_no_arguments_prints_usage(both_debug_modes, both_setsid_modes): @pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_no_arguments_prints_usage():
proc = Popen(('dumb-init'), stderr=PIPE) proc = Popen(('dumb-init'), stderr=PIPE)
_, stderr = proc.communicate() _, stderr = proc.communicate()
assert proc.returncode != 0 assert proc.returncode != 0
@ -33,7 +34,8 @@ def test_exits_invalid_with_invalid_args():
@pytest.mark.parametrize('flag', ['-h', '--help']) @pytest.mark.parametrize('flag', ['-h', '--help'])
def test_help_message(flag, both_debug_modes, both_setsid_modes, current_version): @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, """dumb-init should say something useful when called with the help flag,
and exit zero. and exit zero.
""" """
@ -63,7 +65,8 @@ def test_help_message(flag, both_debug_modes, both_setsid_modes, current_version
@pytest.mark.parametrize('flag', ['-V', '--version']) @pytest.mark.parametrize('flag', ['-V', '--version'])
def test_version_message(flag, both_debug_modes, both_setsid_modes, current_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.""" """dumb-init should print its version when asked to."""
proc = Popen(('dumb-init', flag), stderr=PIPE) proc = Popen(('dumb-init', flag), stderr=PIPE)

View file

@ -5,7 +5,8 @@ import pytest
@pytest.mark.parametrize('exit_status', [0, 1, 2, 32, 64, 127, 254, 255]) @pytest.mark.parametrize('exit_status', [0, 1, 2, 32, 64, 127, 254, 255])
def test_exit_status_regular_exit(exit_status, both_debug_modes, both_setsid_modes): @pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_exit_status_regular_exit(exit_status):
"""dumb-init should exit with the same exit status as the process that it """dumb-init should exit with the same exit status as the process that it
supervises when that process exits normally. supervises when that process exits normally.
""" """
@ -20,7 +21,8 @@ def test_exit_status_regular_exit(exit_status, both_debug_modes, both_setsid_mod
signal.SIGQUIT, signal.SIGQUIT,
signal.SIGKILL, signal.SIGKILL,
]) ])
def test_exit_status_terminated_by_signal(signal, both_debug_modes, both_setsid_modes): @pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_exit_status_terminated_by_signal(signal):
"""dumb-init should exit with status 128 + signal when the child process is """dumb-init should exit with status 128 + signal when the child process is
terminated by a signal. terminated by a signal.
""" """

View file

View file

@ -1,42 +1,18 @@
import os import os
import re
import signal import signal
import sys
from contextlib import contextmanager
from itertools import chain from itertools import chain
from subprocess import PIPE
from subprocess import Popen
import pytest import pytest
from tests.lib.testing import NORMAL_SIGNALS from testing import NORMAL_SIGNALS
from tests.lib.testing import pid_tree from testing import print_signals
from tests.lib.testing import process_state from testing import process_state
@contextmanager
def _print_signals(args=()):
"""Start print_signals and return dumb-init process."""
proc = Popen(
(
('dumb-init',) +
tuple(args) +
(sys.executable, '-m', 'tests.lib.print_signals')
),
stdout=PIPE,
)
assert re.match(b'^ready \(pid: (?:[0-9]+)\)\n$', proc.stdout.readline())
yield proc
for pid in pid_tree(proc.pid):
os.kill(pid, signal.SIGKILL)
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes') @pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_proxies_signals(): def test_proxies_signals():
"""Ensure dumb-init proxies regular signals to its child.""" """Ensure dumb-init proxies regular signals to its child."""
with _print_signals() as proc: with print_signals() as (proc, _):
for signum in NORMAL_SIGNALS: for signum in NORMAL_SIGNALS:
proc.send_signal(signum) proc.send_signal(signum)
assert proc.stdout.readline() == '{0}\n'.format(signum).encode('ascii') assert proc.stdout.readline() == '{0}\n'.format(signum).encode('ascii')
@ -81,7 +57,7 @@ def _rewrite_map_to_args(rewrite_map):
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes') @pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_proxies_signals_with_rewrite(rewrite_map, sequence, expected): def test_proxies_signals_with_rewrite(rewrite_map, sequence, expected):
"""Ensure dumb-init can rewrite signals.""" """Ensure dumb-init can rewrite signals."""
with _print_signals(_rewrite_map_to_args(rewrite_map)) as proc: with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _):
for send, expect_receive in zip(sequence, expected): for send, expect_receive in zip(sequence, expected):
proc.send_signal(send) proc.send_signal(send)
assert proc.stdout.readline() == '{0}\n'.format(expect_receive).encode('ascii') assert proc.stdout.readline() == '{0}\n'.format(expect_receive).encode('ascii')
@ -97,7 +73,7 @@ def test_default_rewrites_can_be_overriden_with_setsid_enabled():
signal.SIGTTOU: signal.SIGINT, signal.SIGTTOU: signal.SIGINT,
signal.SIGTSTP: signal.SIGHUP, signal.SIGTSTP: signal.SIGHUP,
} }
with _print_signals(_rewrite_map_to_args(rewrite_map)) as proc: with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _):
for send, expect_receive in rewrite_map.items(): for send, expect_receive in rewrite_map.items():
assert process_state(proc.pid) in ['running', 'sleeping'] assert process_state(proc.pid) in ['running', 'sleeping']
proc.send_signal(send) proc.send_signal(send)
@ -119,7 +95,7 @@ def test_ignored_signals_are_not_proxied():
signal.SIGINT: 0, signal.SIGINT: 0,
signal.SIGWINCH: 0, signal.SIGWINCH: 0,
} }
with _print_signals(_rewrite_map_to_args(rewrite_map)) as proc: with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _):
proc.send_signal(signal.SIGTERM) proc.send_signal(signal.SIGTERM)
proc.send_signal(signal.SIGINT) proc.send_signal(signal.SIGINT)
assert proc.stdout.readline() == '{0}\n'.format(signal.SIGQUIT).encode('ascii') assert proc.stdout.readline() == '{0}\n'.format(signal.SIGQUIT).encode('ascii')

View file

@ -1,28 +1,20 @@
import os import os
import re
import sys
import time import time
from signal import SIGCONT from signal import SIGCONT
from signal import SIGKILL
from subprocess import PIPE
from subprocess import Popen
from tests.lib.testing import pid_tree import pytest
from tests.lib.testing import process_state
from tests.lib.testing import SUSPEND_SIGNALS from testing import print_signals
from testing import process_state
from testing import SUSPEND_SIGNALS
def test_shell_background_support_setsid(both_debug_modes, setsid_enabled): @pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')
def test_shell_background_support_setsid():
"""In setsid mode, dumb-init should suspend itself and its children when it """In setsid mode, dumb-init should suspend itself and its children when it
receives SIGTSTP, SIGTTOU, or SIGTTIN. receives SIGTSTP, SIGTTOU, or SIGTTIN.
""" """
proc = Popen( with print_signals() as (proc, pid):
('dumb-init', sys.executable, '-m', 'tests.lib.print_signals'),
stdout=PIPE,
)
match = re.match(b'^ready \(pid: ([0-9]+)\)\n$', proc.stdout.readline())
pid = match.group(1).decode('ascii')
for signum in SUSPEND_SIGNALS: for signum in SUSPEND_SIGNALS:
# both dumb-init and print_signals should be running or sleeping # both dumb-init and print_signals should be running or sleeping
assert process_state(pid) in ['running', 'sleeping'] assert process_state(pid) in ['running', 'sleeping']
@ -51,21 +43,13 @@ def test_shell_background_support_setsid(both_debug_modes, setsid_enabled):
assert process_state(pid) in ['running', 'sleeping'] assert process_state(pid) in ['running', 'sleeping']
assert process_state(proc.pid) in ['running', 'sleeping'] assert process_state(proc.pid) in ['running', 'sleeping']
for pid in pid_tree(proc.pid):
os.kill(pid, SIGKILL)
@pytest.mark.usefixtures('both_debug_modes', 'setsid_disabled')
def test_shell_background_support_without_setsid(both_debug_modes, setsid_disabled): def test_shell_background_support_without_setsid():
"""In non-setsid mode, dumb-init should forward the signals SIGTSTP, """In non-setsid mode, dumb-init should forward the signals SIGTSTP,
SIGTTOU, and SIGTTIN, and then suspend itself. SIGTTOU, and SIGTTIN, and then suspend itself.
""" """
proc = Popen( with print_signals() as (proc, _):
('dumb-init', sys.executable, '-m', 'tests.lib.print_signals'),
stdout=PIPE,
)
assert re.match(b'^ready \(pid: (?:[0-9]+)\)\n$', proc.stdout.readline())
for signum in SUSPEND_SIGNALS: for signum in SUSPEND_SIGNALS:
assert process_state(proc.pid) in ['running', 'sleeping'] assert process_state(proc.pid) in ['running', 'sleeping']
proc.send_signal(signum) proc.send_signal(signum)
@ -78,6 +62,3 @@ def test_shell_background_support_without_setsid(both_debug_modes, setsid_disabl
proc.stdout.readline() == '{0}\n'.format(SIGCONT).encode('ascii') proc.stdout.readline() == '{0}\n'.format(SIGCONT).encode('ascii')
) )
assert process_state(proc.pid) in ['running', 'sleeping'] assert process_state(proc.pid) in ['running', 'sleeping']
for pid in pid_tree(proc.pid):
os.kill(pid, SIGKILL)

View file

@ -1,3 +1,10 @@
import os
import pty
import termios
import pytest
EOF = b'\x04' EOF = b'\x04'
@ -5,20 +12,18 @@ def ttyflags(fd):
"""normalize tty i/o for testing""" """normalize tty i/o for testing"""
# see: # see:
# http://www.gnu.org/software/libc/manual/html_mono/libc.html#Output-Modes # http://www.gnu.org/software/libc/manual/html_mono/libc.html#Output-Modes
import termios as T attrs = termios.tcgetattr(fd)
attrs = T.tcgetattr(fd) attrs[1] &= ~termios.OPOST # don't munge output
attrs[1] &= ~T.OPOST # don't munge output attrs[3] &= ~termios.ECHO # don't echo input
attrs[3] &= ~T.ECHO # don't echo input termios.tcsetattr(fd, termios.TCSANOW, attrs)
T.tcsetattr(fd, T.TCSANOW, attrs)
def readall(fd): def readall(fd):
"""read until EOF""" """read until EOF"""
from os import read
result = b'' result = b''
while True: while True:
try: try:
chunk = read(fd, 1 << 10) chunk = os.read(fd, 1 << 10)
except OSError as error: except OSError as error:
if error.errno == 5: # linux pty EOF if error.errno == 5: # linux pty EOF
return result return result
@ -33,23 +38,21 @@ def readall(fd):
def _test(fd): def _test(fd):
"""write to tac via the pty and verify its output""" """write to tac via the pty and verify its output"""
ttyflags(fd) ttyflags(fd)
from os import write assert os.write(fd, b'1\n2\n3\n') == 6
assert write(fd, b'1\n2\n3\n') == 6 assert os.write(fd, EOF * 2) == 2
assert write(fd, EOF * 2) == 2
output = readall(fd) output = readall(fd)
assert output == b'3\n2\n1\n', repr(output) assert output == b'3\n2\n1\n', repr(output)
print('PASS') print('PASS')
# disable debug output so it doesn't break our assertion # disable debug output so it doesn't break our assertion
def test_tty(debug_disabled): @pytest.mark.usefixtures('debug_disabled')
def test_tty():
""" """
Ensure processes wrapped by dumb-init can write successfully, given a tty Ensure processes wrapped by dumb-init can write successfully, given a tty
""" """
import pty
pid, fd = pty.fork() pid, fd = pty.fork()
if pid == 0: if pid == 0:
from os import execvp os.execvp('dumb-init', ('dumb-init', 'tac'))
execvp('dumb-init', ('dumb-init', 'tac'))
else: else:
_test(fd) _test(fd)