1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
"""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'
)
|