Rewrite tests in Python using pytest
This commit is contained in:
parent
88af52b504
commit
e3d9f84d6f
25 changed files with 226 additions and 199 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,3 +4,6 @@ dist/
|
||||||
*.egg-info
|
*.egg-info
|
||||||
.tox
|
.tox
|
||||||
build/
|
build/
|
||||||
|
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|
|
@ -2,24 +2,31 @@
|
||||||
sha: 003e43251aea1da33f2072f2365ec8b9ceaae070
|
sha: 003e43251aea1da33f2072f2365ec8b9ceaae070
|
||||||
hooks:
|
hooks:
|
||||||
- id: autopep8-wrapper
|
- id: autopep8-wrapper
|
||||||
|
language_version: python2.7
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- id: check-docstring-first
|
- id: check-docstring-first
|
||||||
|
language_version: python2.7
|
||||||
- id: check-json
|
- id: check-json
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-xml
|
- id: check-xml
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
|
language_version: python2.7
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
- id: double-quote-string-fixer
|
- id: double-quote-string-fixer
|
||||||
|
language_version: python2.7
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: flake8
|
- id: flake8
|
||||||
|
language_version: python2.7
|
||||||
- id: name-tests-test
|
- id: name-tests-test
|
||||||
|
exclude: ^tests/lib
|
||||||
- id: requirements-txt-fixer
|
- id: requirements-txt-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- repo: https://github.com/asottile/reorder_python_imports.git
|
- repo: https://github.com/asottile/reorder_python_imports.git
|
||||||
sha: 3d86483455ab5bd06cc1069fdd5ac57be5463f10
|
sha: 3d86483455ab5bd06cc1069fdd5ac57be5463f10
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
|
language_version: python2.7
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks.git
|
- repo: https://github.com/Lucas-C/pre-commit-hooks.git
|
||||||
sha: 181a63c511691da58116fa19a7241956018660bc
|
sha: 181a63c511691da58116fa19a7241956018660bc
|
||||||
hooks:
|
hooks:
|
||||||
|
|
14
Makefile
14
Makefile
|
@ -1,28 +1,30 @@
|
||||||
CFLAGS=-std=gnu99 -static -Wall -Werror
|
CFLAGS=-std=gnu99 -static -Wall -Werror
|
||||||
|
|
||||||
TEST_PACKAGE_DEPS := python procps psmisc libpcre3
|
TEST_PACKAGE_DEPS := python python-pip
|
||||||
|
|
||||||
DOCKER_RUN_TEST := docker run -v $(PWD):/mnt:ro
|
DOCKER_RUN_TEST := docker run -v $(PWD):/mnt:ro
|
||||||
DOCKER_DEB_TEST := sh -euxc ' \
|
DOCKER_DEB_TEST := sh -euxc ' \
|
||||||
apt-get update \
|
apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends $(TEST_PACKAGE_DEPS) \
|
&& apt-get install -y --no-install-recommends $(TEST_PACKAGE_DEPS) \
|
||||||
&& (which timeout || apt-get install -y --no-install-recommends timeout) \
|
|
||||||
&& dpkg -i /mnt/dist/*.deb \
|
&& dpkg -i /mnt/dist/*.deb \
|
||||||
&& cd /mnt \
|
&& tmp=$$(mktemp -d) \
|
||||||
&& ./test \
|
&& cp -r /mnt/* "$$tmp" \
|
||||||
|
&& cd "$$tmp" \
|
||||||
|
&& pip install pytest \
|
||||||
|
&& py.test tests/ \
|
||||||
&& exec dumb-init /mnt/tests/test-zombies \
|
&& exec dumb-init /mnt/tests/test-zombies \
|
||||||
'
|
'
|
||||||
DOCKER_PYTHON_TEST := sh -uexc ' \
|
DOCKER_PYTHON_TEST := sh -uexc ' \
|
||||||
apt-get update \
|
apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends python-pip build-essential $(TEST_PACKAGE_DEPS) \
|
&& apt-get install -y --no-install-recommends python-pip build-essential $(TEST_PACKAGE_DEPS) \
|
||||||
&& (which timeout || apt-get install -y --no-install-recommends timeout) \
|
|
||||||
&& tmp=$$(mktemp -d) \
|
&& tmp=$$(mktemp -d) \
|
||||||
&& cp -r /mnt/* "$$tmp" \
|
&& cp -r /mnt/* "$$tmp" \
|
||||||
&& cd "$$tmp" \
|
&& cd "$$tmp" \
|
||||||
&& python setup.py clean \
|
&& python setup.py clean \
|
||||||
&& python setup.py sdist \
|
&& python setup.py sdist \
|
||||||
&& pip install -vv dist/*.tar.gz \
|
&& pip install -vv dist/*.tar.gz \
|
||||||
&& ./test \
|
&& pip install pytest \
|
||||||
|
&& py.test tests/ \
|
||||||
&& exec dumb-init /mnt/tests/test-zombies \
|
&& exec dumb-init /mnt/tests/test-zombies \
|
||||||
'
|
'
|
||||||
|
|
||||||
|
|
3
debian/control
vendored
3
debian/control
vendored
|
@ -3,8 +3,7 @@ Section: utils
|
||||||
Priority: extra
|
Priority: extra
|
||||||
Maintainer: Chris Kuehl <ckuehl@yelp.com>
|
Maintainer: Chris Kuehl <ckuehl@yelp.com>
|
||||||
Uploaders: Kent Wills <rkwills@yelp.com>
|
Uploaders: Kent Wills <rkwills@yelp.com>
|
||||||
Build-Depends: debhelper (>= 7), gcc, fakeroot, procps, psmisc, libpcre3,
|
Build-Depends: debhelper (>= 7), gcc, fakeroot, python, python-pytest
|
||||||
python
|
|
||||||
Standards-Version: 3.9.6
|
Standards-Version: 3.9.6
|
||||||
|
|
||||||
Package: dumb-init
|
Package: dumb-init
|
||||||
|
|
4
debian/rules
vendored
4
debian/rules
vendored
|
@ -6,4 +6,6 @@ override_dh_builddeb:
|
||||||
dh_builddeb -- -Zgzip
|
dh_builddeb -- -Zgzip
|
||||||
|
|
||||||
override_dh_auto_test:
|
override_dh_auto_test:
|
||||||
./test ./dumb-init
|
find . -name '*.pyc' -delete
|
||||||
|
PATH=.:$$PATH py.test tests/
|
||||||
|
ps aux
|
||||||
|
|
2
requirements-dev.txt
Normal file
2
requirements-dev.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pre-commit>=0.5.0
|
||||||
|
pytest
|
34
test
34
test
|
@ -1,34 +0,0 @@
|
||||||
#!/bin/bash -eux
|
|
||||||
if [ "$#" -eq 1 ]; then
|
|
||||||
dumb_init_bin=$(readlink -f "$1")
|
|
||||||
else
|
|
||||||
dumb_init_bin=$(which dumb-init) || {
|
|
||||||
echo "Couldn't find dumb-init on your path, exiting."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Running with dumb-init at '$dumb_init_bin'"
|
|
||||||
|
|
||||||
run_tests() {
|
|
||||||
./test-tty "$dumb_init_bin"
|
|
||||||
|
|
||||||
export DUMB_INIT_SETSID
|
|
||||||
for DUMB_INIT_SETSID in 0 1; do
|
|
||||||
./test-proxies-signals "$dumb_init_bin"
|
|
||||||
./test-exit-status "$dumb_init_bin"
|
|
||||||
./test-help-message "$dumb_init_bin"
|
|
||||||
done
|
|
||||||
|
|
||||||
DUMB_INIT_SETSID=0 ./test-setsid "$dumb_init_bin" 4
|
|
||||||
DUMB_INIT_SETSID=1 ./test-setsid "$dumb_init_bin" 0
|
|
||||||
}
|
|
||||||
|
|
||||||
cd tests
|
|
||||||
|
|
||||||
echo "Running tests in normal mode."
|
|
||||||
run_tests
|
|
||||||
|
|
||||||
echo "Running tests in debug mode."
|
|
||||||
export DUMB_INIT_DEBUG=1
|
|
||||||
run_tests
|
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
51
tests/child_processes_test.py
Normal file
51
tests/child_processes_test.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
|
from subprocess import Popen
|
||||||
|
|
||||||
|
from tests.lib.testing import is_alive
|
||||||
|
from tests.lib.testing import pid_tree
|
||||||
|
|
||||||
|
|
||||||
|
def spawn_and_kill_pipeline():
|
||||||
|
proc = Popen((
|
||||||
|
'dumb-init',
|
||||||
|
'sh', '-c',
|
||||||
|
"yes 'oh, hi' | tail & yes error | tail >&2"
|
||||||
|
))
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
pids = pid_tree(os.getpid())
|
||||||
|
assert len(living_pids(pids)) == 6
|
||||||
|
|
||||||
|
proc.send_signal(signal.SIGTERM)
|
||||||
|
proc.wait()
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
||||||
|
return pids
|
||||||
|
|
||||||
|
|
||||||
|
def living_pids(pids):
|
||||||
|
return {pid for pid in pids if is_alive(pid)}
|
||||||
|
|
||||||
|
|
||||||
|
def test_setsid_signals_entire_group(both_debug_modes):
|
||||||
|
"""When dumb-init is running in setsid mode, it should only signal the
|
||||||
|
entire process group rooted at it.
|
||||||
|
"""
|
||||||
|
os.environ['DUMB_INIT_SETSID'] = '1'
|
||||||
|
pids = spawn_and_kill_pipeline()
|
||||||
|
assert len(living_pids(pids)) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_setsid_doesnt_signal_entire_group(both_debug_modes):
|
||||||
|
"""When dumb-init is not running in setsid mode, it should only signal its
|
||||||
|
immediate child.
|
||||||
|
"""
|
||||||
|
os.environ['DUMB_INIT_SETSID'] = '0'
|
||||||
|
pids = spawn_and_kill_pipeline()
|
||||||
|
|
||||||
|
living = living_pids(pids)
|
||||||
|
assert len(living) == 4
|
||||||
|
for pid in living:
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
13
tests/conftest.py
Normal file
13
tests/conftest.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(params=['1', '0'])
|
||||||
|
def both_debug_modes(request):
|
||||||
|
os.environ['DUMB_INIT_DEBUG'] = request.param
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(params=['1', '0'])
|
||||||
|
def both_setsid_modes(request):
|
||||||
|
os.environ['DUMB_INIT_SETSID'] = request.param
|
11
tests/exit_status_test.py
Normal file
11
tests/exit_status_test.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from subprocess import Popen
|
||||||
|
|
||||||
|
|
||||||
|
def test_exit_status(both_debug_modes, both_setsid_modes):
|
||||||
|
"""dumb-init should exit with the same exit status as the process that it
|
||||||
|
supervises.
|
||||||
|
"""
|
||||||
|
for status in [0, 1, 2, 32, 64, 127, 254, 255]:
|
||||||
|
proc = Popen(('dumb-init', 'sh', '-c', 'exit {}'.format(status)))
|
||||||
|
proc.wait()
|
||||||
|
assert proc.returncode == status
|
12
tests/help_message_test.py
Normal file
12
tests/help_message_test.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from subprocess import PIPE
|
||||||
|
from subprocess import Popen
|
||||||
|
|
||||||
|
|
||||||
|
def test_exit_status(both_debug_modes, both_setsid_modes):
|
||||||
|
"""dumb-init should say something useful when called with no arguments, and
|
||||||
|
exit nonzero.
|
||||||
|
"""
|
||||||
|
proc = Popen(('dumb-init'), stderr=PIPE)
|
||||||
|
_, stderr = proc.communicate()
|
||||||
|
assert proc.returncode != 0
|
||||||
|
assert len(stderr) >= 50
|
0
tests/lib/__init__.py
Normal file
0
tests/lib/__init__.py
Normal file
|
@ -1,19 +0,0 @@
|
||||||
#!/bin/sh -eux
|
|
||||||
# XXX: We use /bin/sh instead of /bin/bash since some old versions of bash
|
|
||||||
# exhibit an issue where they seem to receive the same signal twice.
|
|
||||||
# With /bin/sh, this does not seem to happen.
|
|
||||||
|
|
||||||
# Print received signals into a file, one per line
|
|
||||||
file="$1"
|
|
||||||
|
|
||||||
. ./lib/testlib.sh
|
|
||||||
|
|
||||||
for i in $(catchable_signals); do
|
|
||||||
trap "echo $i > \"$file\"" "$i"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo 'ready' > "$file"
|
|
||||||
|
|
||||||
echo 'loop forever...'
|
|
||||||
set +x
|
|
||||||
while :; do true; done
|
|
39
tests/lib/print_signals.py
Executable file
39
tests/lib/print_signals.py
Executable file
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""Print received signals to stdout.
|
||||||
|
|
||||||
|
Since all signals are printed and otherwise ignored, you'll need to send
|
||||||
|
SIGKILL (kill -9) to this process to actually end it.
|
||||||
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
from tests.lib.testing import CATCHABLE_SIGNALS
|
||||||
|
|
||||||
|
|
||||||
|
print_queue = []
|
||||||
|
|
||||||
|
|
||||||
|
def unbuffered_print(line):
|
||||||
|
sys.stdout.write('{}\n'.format(line))
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def print_signal(signum, _):
|
||||||
|
print_queue.append(signum)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
for signum in CATCHABLE_SIGNALS:
|
||||||
|
signal.signal(signum, print_signal)
|
||||||
|
|
||||||
|
unbuffered_print('ready')
|
||||||
|
|
||||||
|
# loop forever just printing signals
|
||||||
|
while True:
|
||||||
|
if print_queue:
|
||||||
|
unbuffered_print(print_queue.pop())
|
||||||
|
|
||||||
|
time.sleep(0.01)
|
34
tests/lib/testing.py
Normal file
34
tests/lib/testing.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import signal
|
||||||
|
|
||||||
|
from py._path.local import LocalPath
|
||||||
|
|
||||||
|
|
||||||
|
CATCHABLE_SIGNALS = frozenset(
|
||||||
|
set(range(1, 32)) - {signal.SIGKILL, signal.SIGSTOP, signal.SIGCHLD}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def child_pids(pid):
|
||||||
|
"""Return a list of direct child PIDs for the given PID."""
|
||||||
|
pid = str(pid)
|
||||||
|
tasks = LocalPath('/proc').join(pid, 'task').listdir()
|
||||||
|
return {
|
||||||
|
int(child_pid)
|
||||||
|
for task in tasks
|
||||||
|
for child_pid in task.join('children').read().split()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def pid_tree(pid):
|
||||||
|
"""Return a list of all descendant PIDs for the given PID."""
|
||||||
|
children = child_pids(pid)
|
||||||
|
return {
|
||||||
|
pid
|
||||||
|
for child in children
|
||||||
|
for pid in pid_tree(child)
|
||||||
|
} | children
|
||||||
|
|
||||||
|
|
||||||
|
def is_alive(pid):
|
||||||
|
"""Return whether a process is running with the given PID."""
|
||||||
|
return LocalPath('/proc').join(str(pid)).isdir()
|
|
@ -1,4 +0,0 @@
|
||||||
catchable_signals() {
|
|
||||||
# We can't handle the signals SIGKILL=9, SIGCHLD=17, SIGSTOP=19
|
|
||||||
seq 1 31 | grep -vE '^(9|17|19)$'
|
|
||||||
}
|
|
24
tests/proxies_signals_test.py
Normal file
24
tests/proxies_signals_test.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
from subprocess import PIPE
|
||||||
|
from subprocess import Popen
|
||||||
|
|
||||||
|
from tests.lib.testing import CATCHABLE_SIGNALS
|
||||||
|
from tests.lib.testing import pid_tree
|
||||||
|
|
||||||
|
|
||||||
|
def test_prints_signals(both_debug_modes, both_setsid_modes):
|
||||||
|
proc = Popen(
|
||||||
|
('dumb-init', sys.executable, '-m', 'tests.lib.print_signals'),
|
||||||
|
stdout=PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert proc.stdout.readline() == b'ready\n'
|
||||||
|
|
||||||
|
for signum in CATCHABLE_SIGNALS:
|
||||||
|
proc.send_signal(signum)
|
||||||
|
assert proc.stdout.readline() == '{}\n'.format(signum).encode('ascii')
|
||||||
|
|
||||||
|
for pid in pid_tree(proc.pid):
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
|
@ -1,12 +0,0 @@
|
||||||
#!/bin/bash -eux
|
|
||||||
dumb_init="$1"
|
|
||||||
|
|
||||||
# dumb-init should exit with the same exit status as the process it launches.
|
|
||||||
for i in 0 1 2 32 64 127 254 255; do
|
|
||||||
status=$($dumb_init sh -c "exit $i"; echo $?)
|
|
||||||
|
|
||||||
if [ "$status" -ne "$i" ]; then
|
|
||||||
echo "Error: Expected exit status $i, got $status."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
|
@ -1,20 +0,0 @@
|
||||||
#!/bin/bash -eux
|
|
||||||
# dumb-init should say something useful when called with no arguments, and exit
|
|
||||||
# nonzero.
|
|
||||||
|
|
||||||
dumb_init="$1"
|
|
||||||
|
|
||||||
status=$($dumb_init > /dev/null 2>&1; echo $?)
|
|
||||||
|
|
||||||
if [ "$status" -eq 0 ]; then
|
|
||||||
echo "Error: Expected dumb-init with no arguments to return nonzero, but it returned ${status}."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg=$($dumb_init 2>&1 || true)
|
|
||||||
msg_len=${#msg}
|
|
||||||
|
|
||||||
if [ "$msg_len" -le 50 ]; then
|
|
||||||
echo "Error: Expected dumb-init with no arguments to print a useful message, but it was only ${msg_len} chars long."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
|
@ -1,46 +0,0 @@
|
||||||
#!/bin/bash -euxm
|
|
||||||
# dumb-init should proxy all possible signals to the child process.
|
|
||||||
dumb_init="$1"
|
|
||||||
|
|
||||||
# Try sending all signals via dumb-init to our `print-signals` script, ensure
|
|
||||||
# they were all received.
|
|
||||||
. ./lib/testlib.sh
|
|
||||||
|
|
||||||
# The easiest way to communicate with the background process is with a FIFO.
|
|
||||||
# (piping spawns additional subshells and makes it hard to get the right PID)
|
|
||||||
fifo=$(mktemp -u)
|
|
||||||
mkfifo -m 600 "$fifo"
|
|
||||||
read_cmd="timeout 1 head -n1 $fifo"
|
|
||||||
|
|
||||||
$dumb_init ./lib/print-signals "$fifo" &
|
|
||||||
pid="$!"
|
|
||||||
|
|
||||||
# Wait for `print-signals` to indicate it's ready.
|
|
||||||
$read_cmd > /dev/null
|
|
||||||
|
|
||||||
for expected in $(catchable_signals); do
|
|
||||||
kill -s "$expected" "$pid"
|
|
||||||
echo -n "Sent signal ${expected}... "
|
|
||||||
received=$($read_cmd) || {
|
|
||||||
echo
|
|
||||||
echo "Error: Didn't receive signal within 1 second."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "received signal ${received}."
|
|
||||||
|
|
||||||
if [ "$expected" -ne "$received" ]; then
|
|
||||||
echo "Error: Received signal $received, but expected signal $expected."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Turn off job monitoring so we don't get a spurious "[1] + Killed" printed
|
|
||||||
set +m
|
|
||||||
|
|
||||||
# $pid is the PID of the dumb-init process. Since print-signals ignores all
|
|
||||||
# signals, we need to `kill -9` it. If we just `kill -9` the dumb-init process,
|
|
||||||
# `print-signals` will still be running. So we instead kill children of the
|
|
||||||
# dumb-init process.
|
|
||||||
pkill -9 -P "$pid"
|
|
||||||
rm "$fifo"
|
|
|
@ -1,33 +0,0 @@
|
||||||
#!/bin/bash -eux
|
|
||||||
# dumb-init should proxy signals to a session rooted at its child when
|
|
||||||
# requested.
|
|
||||||
dumb_init="$1"
|
|
||||||
after_count="$2"
|
|
||||||
|
|
||||||
$dumb_init sh -c "yes 'oh, hi' | tail & yes error | tail >&2" &
|
|
||||||
pid="$!"
|
|
||||||
|
|
||||||
sleep 1
|
|
||||||
pstree -p "$pid"
|
|
||||||
pids=$(pstree -p "$pid" | grep -Po '(\d+)' | grep -Po '\d+')
|
|
||||||
|
|
||||||
# ensure processes are running
|
|
||||||
child_count=$(ps -o pid= $pids | wc -l) || true
|
|
||||||
|
|
||||||
if [ "$child_count" -ne 6 ]; then
|
|
||||||
echo "Error: Expected 6 children, instead we had ${child_count}."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ensure processes are dead after signal
|
|
||||||
kill -TERM "$pid"
|
|
||||||
sleep 1
|
|
||||||
child_count=$(ps -o pid= $pids | wc -l) || true
|
|
||||||
|
|
||||||
if [ "$child_count" -ne "$after_count" ]; then
|
|
||||||
echo "Error: Expected $after_count children, instead we had ${child_count}."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo 'Killing any leftover processes.'
|
|
||||||
xargs kill -9 <<< "$pids" || true
|
|
|
@ -4,7 +4,8 @@
|
||||||
# dumb-init as PID1.
|
# dumb-init as PID1.
|
||||||
#
|
#
|
||||||
# We run it as the last step of the integration tests inside our Docker
|
# We run it as the last step of the integration tests inside our Docker
|
||||||
# containers.
|
# containers. Since this test must run as PID 1, we don't use pytest and
|
||||||
|
# instead write it in bash.
|
||||||
|
|
||||||
bash -euxc "bash -euxc 'echo i am a zombie' &" &
|
bash -euxc "bash -euxc 'echo i am a zombie' &" &
|
||||||
|
|
||||||
|
|
31
tests/test-tty → tests/tty_test.py
Executable file → Normal file
31
tests/test-tty → tests/tty_test.py
Executable file → Normal file
|
@ -1,10 +1,13 @@
|
||||||
#!/usr/bin/env python
|
import os
|
||||||
|
|
||||||
|
|
||||||
EOF = b'\x04'
|
EOF = b'\x04'
|
||||||
|
|
||||||
|
|
||||||
def ttyflags(fd):
|
def ttyflags(fd):
|
||||||
"""normalize tty i/o for testing"""
|
"""normalize tty i/o for testing"""
|
||||||
# see: http://www.gnu.org/software/libc/manual/html_mono/libc.html#Output-Modes
|
# see:
|
||||||
|
# http://www.gnu.org/software/libc/manual/html_mono/libc.html#Output-Modes
|
||||||
import termios as T
|
import termios as T
|
||||||
attrs = T.tcgetattr(fd)
|
attrs = T.tcgetattr(fd)
|
||||||
attrs[1] &= ~T.OPOST # don't munge output
|
attrs[1] &= ~T.OPOST # don't munge output
|
||||||
|
@ -12,13 +15,13 @@ def ttyflags(fd):
|
||||||
T.tcsetattr(fd, T.TCSANOW, attrs)
|
T.tcsetattr(fd, T.TCSANOW, attrs)
|
||||||
|
|
||||||
|
|
||||||
def tac(dumb_init):
|
def tac():
|
||||||
"""
|
"""
|
||||||
run tac. if it fails to complete in 1 second send SIGKILL and exit with an
|
run tac. if it fails to complete in 1 second send SIGKILL and exit with an
|
||||||
error.
|
error.
|
||||||
"""
|
"""
|
||||||
from os import execvp
|
from os import execvp
|
||||||
execvp('timeout', ('timeout', '1', dumb_init, 'tac'))
|
execvp('timeout', ('timeout', '1', 'dumb-init', 'tac'))
|
||||||
|
|
||||||
|
|
||||||
def readall(fd):
|
def readall(fd):
|
||||||
|
@ -39,7 +42,7 @@ def readall(fd):
|
||||||
result += chunk
|
result += chunk
|
||||||
|
|
||||||
|
|
||||||
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
|
from os import write
|
||||||
|
@ -50,23 +53,15 @@ def test(fd):
|
||||||
print('PASS')
|
print('PASS')
|
||||||
|
|
||||||
|
|
||||||
def test_tty(dumb_init):
|
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
|
||||||
"""
|
"""
|
||||||
|
# disable debug output so it doesn't break our assertion
|
||||||
|
os.environ['DUMB_INIT_DEBUG'] = '0'
|
||||||
import pty
|
import pty
|
||||||
pid, fd = pty.fork()
|
pid, fd = pty.fork()
|
||||||
if pid == 0:
|
if pid == 0:
|
||||||
tac(dumb_init)
|
tac()
|
||||||
else:
|
else:
|
||||||
test(fd)
|
_test(fd)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
from os import environ
|
|
||||||
environ['DUMB_INIT_DEBUG'] = '0' # disable debug output so it doesn't break our assertion
|
|
||||||
from sys import argv
|
|
||||||
test_tty(argv[1])
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
6
tox.ini
6
tox.ini
|
@ -1,10 +1,10 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py27,py33,py34
|
envlist = py27,py34
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps = pre-commit>=0.5.0
|
deps = -rrequirements-dev.txt
|
||||||
commands =
|
commands =
|
||||||
./test
|
python -m pytest
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
|
|
||||||
[testenv:pre-commit]
|
[testenv:pre-commit]
|
||||||
|
|
Loading…
Reference in a new issue