Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up tests a bit #89

Merged
merged 1 commit into from
Jun 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions tests/lib/testing.py → testing/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import os
import re
import signal
import sys
from contextlib import contextmanager
from subprocess import PIPE
from subprocess import Popen

from py._path.local import LocalPath

Expand All @@ -18,6 +23,27 @@
)


@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):
"""Return a list of direct child PIDs for the given PID."""
pid = str(pid)
Expand Down
File renamed without changes.
30 changes: 13 additions & 17 deletions tests/child_processes_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import pytest

from tests.lib.testing import is_alive
from tests.lib.testing import pid_tree
from testing import is_alive
from testing import pid_tree


def spawn_and_kill_pipeline():
Expand All @@ -34,18 +34,17 @@ def living_pids(pids):
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
entire process group rooted at it.
"""
pids = spawn_and_kill_pipeline()
assert len(living_pids(pids)) == 0


def test_no_setsid_doesnt_signal_entire_group(
both_debug_modes,
setsid_disabled,
):
@pytest.mark.usefixtures('both_debug_modes', 'setsid_disabled')
def test_no_setsid_doesnt_signal_entire_group():
"""When dumb-init is not running in setsid mode, it should only signal its
immediate child.
"""
Expand Down Expand Up @@ -73,7 +72,7 @@ def spawn_process_which_dies_with_children():
# 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
# signal handlers
'{python} -m tests.lib.print_signals & sleep 0.1'.format(
'{python} -m testing.print_signals & sleep 0.1'.format(
python=sys.executable,
),
),
Expand All @@ -96,10 +95,8 @@ def spawn_process_which_dies_with_children():
return child_pid, proc.stdout


def test_all_processes_receive_term_on_exit_if_setsid(
both_debug_modes,
setsid_enabled,
):
@pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')
def test_all_processes_receive_term_on_exit_if_setsid():
"""If the child exits for some reason, dumb-init should send TERM to all
processes in its session if setsid mode is enabled."""
child_pid, child_stdout = spawn_process_which_dies_with_children()
Expand All @@ -110,10 +107,8 @@ def test_all_processes_receive_term_on_exit_if_setsid(
os.kill(child_pid, signal.SIGKILL)


def test_processes_dont_receive_term_on_exit_if_no_setsid(
both_debug_modes,
setsid_disabled,
):
@pytest.mark.usefixtures('both_debug_modes', 'setsid_disabled')
def test_processes_dont_receive_term_on_exit_if_no_setsid():
"""If the child exits for some reason, dumb-init should not send TERM to
any other processes if setsid mode is disabled."""
child_pid, child_stdout = spawn_process_which_dies_with_children()
Expand All @@ -133,7 +128,8 @@ def test_processes_dont_receive_term_on_exit_if_no_setsid(
('-c', '/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."""
proc = Popen(('dumb-init',) + args, stderr=PIPE)
proc.wait()
Expand Down
9 changes: 6 additions & 3 deletions tests/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ def current_version():
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)
_, stderr = proc.communicate()
assert proc.returncode != 0
Expand All @@ -33,7 +34,8 @@ def test_exits_invalid_with_invalid_args():


@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,
and exit zero.
"""
Expand Down Expand Up @@ -63,7 +65,8 @@ def test_help_message(flag, both_debug_modes, both_setsid_modes, current_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."""

proc = Popen(('dumb-init', flag), stderr=PIPE)
Expand Down
6 changes: 4 additions & 2 deletions tests/exit_status_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@


@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
supervises when that process exits normally.
"""
Expand All @@ -20,7 +21,8 @@ def test_exit_status_regular_exit(exit_status, both_debug_modes, both_setsid_mod
signal.SIGQUIT,
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
terminated by a signal.
"""
Expand Down
Empty file removed tests/lib/__init__.py
Empty file.
38 changes: 7 additions & 31 deletions tests/proxies_signals_test.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,18 @@
import os
import re
import signal
import sys
from contextlib import contextmanager
from itertools import chain
from subprocess import PIPE
from subprocess import Popen

import pytest

from tests.lib.testing import NORMAL_SIGNALS
from tests.lib.testing import pid_tree
from tests.lib.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)
from testing import NORMAL_SIGNALS
from testing import print_signals
from testing import process_state


@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_proxies_signals():
"""Ensure dumb-init proxies regular signals to its child."""
with _print_signals() as proc:
with print_signals() as (proc, _):
for signum in NORMAL_SIGNALS:
proc.send_signal(signum)
assert proc.stdout.readline() == '{0}\n'.format(signum).encode('ascii')
Expand Down Expand Up @@ -81,7 +57,7 @@ def _rewrite_map_to_args(rewrite_map):
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_proxies_signals_with_rewrite(rewrite_map, sequence, expected):
"""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):
proc.send_signal(send)
assert proc.stdout.readline() == '{0}\n'.format(expect_receive).encode('ascii')
Expand All @@ -97,7 +73,7 @@ def test_default_rewrites_can_be_overriden_with_setsid_enabled():
signal.SIGTTOU: signal.SIGINT,
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():
assert process_state(proc.pid) in ['running', 'sleeping']
proc.send_signal(send)
Expand All @@ -119,7 +95,7 @@ def test_ignored_signals_are_not_proxied():
signal.SIGINT: 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.SIGINT)
assert proc.stdout.readline() == '{0}\n'.format(signal.SIGQUIT).encode('ascii')
Expand Down
109 changes: 45 additions & 64 deletions tests/shell_background_test.py
Original file line number Diff line number Diff line change
@@ -1,83 +1,64 @@
import os
import re
import sys
import time
from signal import SIGCONT
from signal import SIGKILL
from subprocess import PIPE
from subprocess import Popen

from tests.lib.testing import pid_tree
from tests.lib.testing import process_state
from tests.lib.testing import SUSPEND_SIGNALS
import pytest

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
receives SIGTSTP, SIGTTOU, or SIGTTIN.
"""
proc = Popen(
('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:
# both dumb-init and print_signals should be running or sleeping
assert process_state(pid) in ['running', 'sleeping']
assert process_state(proc.pid) in ['running', 'sleeping']
with print_signals() as (proc, pid):
for signum in SUSPEND_SIGNALS:
# both dumb-init and print_signals should be running or sleeping
assert process_state(pid) in ['running', 'sleeping']
assert process_state(proc.pid) in ['running', 'sleeping']

# both should now suspend
proc.send_signal(signum)
# both should now suspend
proc.send_signal(signum)

for _ in range(1000):
time.sleep(0.001)
try:
assert process_state(proc.pid) == 'stopped'
assert process_state(pid) == 'stopped'
except AssertionError:
pass
for _ in range(1000):
time.sleep(0.001)
try:
assert process_state(proc.pid) == 'stopped'
assert process_state(pid) == 'stopped'
except AssertionError:
pass
else:
break
else:
break
else:
raise RuntimeError('Timed out waiting for processes to stop.')

# and then both wake up again
proc.send_signal(SIGCONT)
assert (
proc.stdout.readline() == '{0}\n'.format(SIGCONT).encode('ascii')
)
assert process_state(pid) in ['running', 'sleeping']
assert process_state(proc.pid) in ['running', 'sleeping']
raise RuntimeError('Timed out waiting for processes to stop.')

for pid in pid_tree(proc.pid):
os.kill(pid, SIGKILL)
# and then both wake up again
proc.send_signal(SIGCONT)
assert (
proc.stdout.readline() == '{0}\n'.format(SIGCONT).encode('ascii')
)
assert process_state(pid) in ['running', 'sleeping']
assert process_state(proc.pid) in ['running', 'sleeping']


def test_shell_background_support_without_setsid(both_debug_modes, setsid_disabled):
@pytest.mark.usefixtures('both_debug_modes', 'setsid_disabled')
def test_shell_background_support_without_setsid():
"""In non-setsid mode, dumb-init should forward the signals SIGTSTP,
SIGTTOU, and SIGTTIN, and then suspend itself.
"""
proc = Popen(
('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:
assert process_state(proc.pid) in ['running', 'sleeping']
proc.send_signal(signum)
assert proc.stdout.readline() == '{0}\n'.format(signum).encode('ascii')
os.waitpid(proc.pid, os.WUNTRACED)
assert process_state(proc.pid) == 'stopped'

proc.send_signal(SIGCONT)
assert (
proc.stdout.readline() == '{0}\n'.format(SIGCONT).encode('ascii')
)
assert process_state(proc.pid) in ['running', 'sleeping']
with print_signals() as (proc, _):
for signum in SUSPEND_SIGNALS:
assert process_state(proc.pid) in ['running', 'sleeping']
proc.send_signal(signum)
assert proc.stdout.readline() == '{0}\n'.format(signum).encode('ascii')
os.waitpid(proc.pid, os.WUNTRACED)
assert process_state(proc.pid) == 'stopped'

for pid in pid_tree(proc.pid):
os.kill(pid, SIGKILL)
proc.send_signal(SIGCONT)
assert (
proc.stdout.readline() == '{0}\n'.format(SIGCONT).encode('ascii')
)
assert process_state(proc.pid) in ['running', 'sleeping']
Loading