dumb-init/tests/child_processes_test.py

148 lines
4.4 KiB
Python
Raw Normal View History

2015-09-12 01:57:22 +03:00
import os
import re
2015-09-12 01:57:22 +03:00
import signal
import sys
from subprocess import PIPE
2015-09-12 01:57:22 +03:00
from subprocess import Popen
import pytest
2016-06-17 22:47:34 +03:00
from testing import is_alive
2016-08-01 18:40:55 +03:00
from testing import kill_if_alive
2016-06-17 22:47:34 +03:00
from testing import pid_tree
from testing import sleep_until
2015-09-12 01:57:22 +03:00
def spawn_and_kill_pipeline():
proc = Popen((
'dumb-init',
'sh', '-c',
"yes 'oh, hi' | tail & yes error | tail >&2"
))
def assert_living_pids():
assert len(living_pids(pid_tree(os.getpid()))) == 6
sleep_until(assert_living_pids)
2015-09-12 01:57:22 +03:00
pids = pid_tree(os.getpid())
2015-09-12 01:57:22 +03:00
proc.send_signal(signal.SIGTERM)
proc.wait()
return pids
def living_pids(pids):
2015-09-12 03:24:18 +03:00
return set(pid for pid in pids if is_alive(pid))
2015-09-12 01:57:22 +03:00
2016-06-17 22:47:34 +03:00
@pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')
def test_setsid_signals_entire_group():
"""When dumb-init is running in setsid mode, it should signal the entire
process group rooted at it.
2015-09-12 01:57:22 +03:00
"""
pids = spawn_and_kill_pipeline()
def assert_no_living_pids():
assert len(living_pids(pids)) == 0
sleep_until(assert_no_living_pids)
2015-09-12 01:57:22 +03:00
2016-06-17 22:47:34 +03:00
@pytest.mark.usefixtures('both_debug_modes', 'setsid_disabled')
def test_no_setsid_doesnt_signal_entire_group():
2015-09-12 01:57:22 +03:00
"""When dumb-init is not running in setsid mode, it should only signal its
immediate child.
"""
pids = spawn_and_kill_pipeline()
def assert_four_living_pids():
assert len(living_pids(pids)) == 4
sleep_until(assert_four_living_pids)
for pid in living_pids(pids):
2016-08-01 18:40:55 +03:00
kill_if_alive(pid)
def spawn_process_which_dies_with_children():
"""Spawn a process which spawns some children and then dies without
signaling them, wrapped in dumb-init.
Returns a tuple (child pid, child stdout pipe), where the child is
print_signals. This is useful because you can signal the PID and see if
anything gets printed onto the stdout pipe.
"""
proc = Popen(
(
'dumb-init',
'sh', '-c',
# 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
2016-06-17 22:47:34 +03:00
'{python} -m testing.print_signals & sleep 0.1'.format(
python=sys.executable,
),
),
stdout=PIPE,
)
proc.wait()
assert proc.returncode == 0
# read a line from print_signals, figure out its pid
line = proc.stdout.readline()
match = re.match(b'ready \(pid: ([0-9]+)\)\n', line)
2016-07-25 19:54:39 +03:00
assert match, line
child_pid = int(match.group(1))
# at this point, the shell and dumb-init have both exited, but
# print_signals may or may not still be running (depending on whether
# setsid mode is enabled)
return child_pid, proc.stdout
2016-06-17 22:47:34 +03:00
@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()
# print_signals should have received TERM
assert child_stdout.readline() == b'15\n'
os.kill(child_pid, signal.SIGKILL)
2016-06-17 22:47:34 +03:00
@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()
# print_signals should not have received TERM; to test this, we send it
# some other signals and ensure they were received (and TERM wasn't)
for signum in [1, 2, 3]:
os.kill(child_pid, signum)
assert child_stdout.readline() == str(signum).encode('ascii') + b'\n'
os.kill(child_pid, signal.SIGKILL)
2015-09-29 20:33:26 +03:00
@pytest.mark.parametrize('args', [
('/doesnotexist',),
('--', '/doesnotexist'),
('-c', '/doesnotexist'),
('--single-child', '--', '/doesnotexist'),
])
2016-06-17 22:47:34 +03:00
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
def test_fails_nonzero_with_bad_exec(args):
2015-09-29 20:33:26 +03:00
"""If dumb-init can't exec as requested, it should exit nonzero."""
proc = Popen(('dumb-init',) + args, stderr=PIPE)
2016-07-25 19:54:39 +03:00
_, stderr = proc.communicate()
2015-09-29 20:33:26 +03:00
assert proc.returncode != 0
assert (
2015-10-02 20:48:23 +03:00
b'[dumb-init] /doesnotexist: No such file or directory\n'
2016-07-25 19:54:39 +03:00
in stderr
2015-09-29 20:33:26 +03:00
)