Update tests to Python 3

Time to do this — Python 2 is EOL. This exposed a bug in one of the
tests that was stripping the entire environment from its subprocesses
(though they did sort of work on Python 2).
build-updates
Thomas Orozco 5 years ago
parent 53e3d32b0e
commit 12197f95f6
  1. 1
      .gitignore
  2. 2
      Dockerfile
  3. 5
      ci/install_deps.sh
  4. 4
      ci/run_build.sh
  5. 3
      test/reaping/stage_1.py
  6. 174
      test/run_inner_tests.py
  7. 4
      test/subreaper-proxy.py

1
.gitignore vendored

@ -1,3 +1,4 @@
dist dist
sign.key sign.key
.env .env
.mypy_cache

@ -6,7 +6,7 @@ COPY ci/install_deps.sh /install_deps.sh
RUN /install_deps.sh RUN /install_deps.sh
# Pre-install those here for faster local builds. # Pre-install those here for faster local builds.
RUN CFLAGS="-DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" pip install psutil python-prctl bitmap RUN CFLAGS="-DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" python3 -m pip install psutil python-prctl bitmap
ARG ARCH_NATIVE ARG ARCH_NATIVE
ARG CC ARG CC

@ -5,7 +5,7 @@ set -o xtrace
DEPS=( DEPS=(
build-essential git gdb valgrind cmake rpm file build-essential git gdb valgrind cmake rpm file
libcap-dev python-dev python-pip python-setuptools libcap-dev python3-dev python3-pip python3-setuptools
hardening-includes gnupg hardening-includes gnupg
) )
@ -26,4 +26,5 @@ apt-get update
apt-get install --no-install-recommends --yes "${DEPS[@]}" apt-get install --no-install-recommends --yes "${DEPS[@]}"
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
pip install virtualenv python3 -m pip install --upgrade pip
python3 -m pip install virtualenv

@ -206,10 +206,10 @@ if [[ -n "${ARCH_NATIVE-}" ]]; then
export CFLAGS # We need them to build our test suite, regardless of FORCE_SUBREAPER export CFLAGS # We need them to build our test suite, regardless of FORCE_SUBREAPER
# Install test dependencies # Install test dependencies
CC=gcc pip install psutil python-prctl bitmap CC=gcc python3 -m pip install psutil python-prctl bitmap
# Run tests # Run tests
python "${SOURCE_DIR}/test/run_inner_tests.py" python3 "${SOURCE_DIR}/test/run_inner_tests.py"
else else
if [[ ! -n "${ARCH_SUFFIX-}" ]]; then if [[ ! -n "${ARCH_SUFFIX-}" ]]; then
echo "Built cross package, but $ARCH_SUFFIX is empty!" echo "Built cross package, but $ARCH_SUFFIX is empty!"

@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
from __future__ import print_function
import os import os
import sys import sys
import subprocess import subprocess

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# coding:utf-8 # coding:utf-8
import os import os
import sys import sys
@ -11,16 +11,18 @@ import re
import itertools import itertools
import tempfile import tempfile
DEVNULL = open(os.devnull, 'wb') DEVNULL = open(os.devnull, "wb")
SIGNUM_TO_SIGNAME = dict((v, k) for k,v in signal.__dict__.items() if re.match("^SIG[A-Z]+$", k)) SIGNUM_TO_SIGNAME = dict(
(v, k) for k, v in signal.__dict__.items() if re.match("^SIG[A-Z]+$", k)
)
def busy_wait(condition_callable, timeout): def busy_wait(condition_callable, timeout):
checks = 100 checks = 100
increment = float(timeout) / checks increment = float(timeout) / checks
for _ in xrange(checks): for _ in range(checks):
if condition_callable(): if condition_callable():
return return
time.sleep(increment) time.sleep(increment)
@ -42,27 +44,37 @@ def main():
# Run the exit code test. We use POSIXLY_CORRECT here to not need -- # Run the exit code test. We use POSIXLY_CORRECT here to not need --
# until that's the default in Tini anyways. # until that's the default in Tini anyways.
if not args_disabled: if not args_disabled:
print "Running exit code test for {0}".format(tini) print("Running exit code test for {0}".format(tini))
for code in range(0, 256): for code in range(0, 256):
p = subprocess.Popen( p = subprocess.Popen(
[tini, '-e', str(code), '--', 'sh', '-c', "exit {0}".format(code)], [tini, "-e", str(code), "--", "sh", "-c", "exit {0}".format(code)],
stdout=DEVNULL, stderr=DEVNULL stdout=DEVNULL,
stderr=DEVNULL,
universal_newlines=True,
) )
ret = p.wait() ret = p.wait()
assert ret == 0, "Inclusive exit code test failed for %s, exit: %s" % (code, ret) assert ret == 0, "Inclusive exit code test failed for %s, exit: %s" % (
code,
ret,
)
other_codes = [x for x in range(0, 256) if x != code] other_codes = [x for x in range(0, 256) if x != code]
args = list(itertools.chain(*[['-e', str(x)] for x in other_codes])) args = list(itertools.chain(*[["-e", str(x)] for x in other_codes]))
p = subprocess.Popen( p = subprocess.Popen(
[tini] + args + ['sh', '-c', "exit {0}".format(code)], [tini] + args + ["sh", "-c", "exit {0}".format(code)],
env=dict(os.environ, POSIXLY_CORRECT="1"), env=dict(os.environ, POSIXLY_CORRECT="1"),
stdout=DEVNULL, stderr=DEVNULL stdout=DEVNULL,
stderr=DEVNULL,
universal_newlines=True,
) )
ret = p.wait() ret = p.wait()
assert ret == code, "Exclusive exit code test failed for %s, exit: %s" % (code, ret) assert ret == code, "Exclusive exit code test failed for %s, exit: %s" % (
code,
ret,
)
tests = [([proxy, tini], {}),] tests = [([proxy, tini], {})]
if subreaper_support: if subreaper_support:
if not args_disabled: if not args_disabled:
@ -71,10 +83,14 @@ def main():
for target, env in tests: for target, env in tests:
# Run the reaping test # Run the reaping test
print "Running reaping test ({0} with env {1})".format(" ".join(target), env) print("Running reaping test ({0} with env {1})".format(" ".join(target), env))
p = subprocess.Popen(target + [os.path.join(src, "test", "reaping", "stage_1.py")], p = subprocess.Popen(
target + [os.path.join(src, "test", "reaping", "stage_1.py")],
env=dict(os.environ, **env), env=dict(os.environ, **env),
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
)
out, err = p.communicate() out, err = p.communicate()
@ -83,48 +99,81 @@ def main():
# and will output the error message here. # and will output the error message here.
assert "zombie reaping won't work" not in err, "Warning message was output!" assert "zombie reaping won't work" not in err, "Warning message was output!"
ret = p.wait() ret = p.wait()
assert "Reaped zombie process with pid=" not in err, "Warning message was output!" assert (
"Reaped zombie process with pid=" not in err
), "Warning message was output!"
assert ret == 0, "Reaping test failed!\nOUT: %s\nERR: %s" % (out, err) assert ret == 0, "Reaping test failed!\nOUT: %s\nERR: %s" % (out, err)
if not args_disabled: if not args_disabled:
print "Running reaping display test ({0} with env {1})".format(" ".join(target), env) print(
p = subprocess.Popen(target + ["-w", os.path.join(src, "test", "reaping", "stage_1.py")], "Running reaping display test ({0} with env {1})".format(
" ".join(target), env
)
)
p = subprocess.Popen(
target + ["-w", os.path.join(src, "test", "reaping", "stage_1.py")],
env=dict(os.environ, **env), env=dict(os.environ, **env),
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
)
out, err = p.communicate() out, err = p.communicate()
ret = p.wait() ret = p.wait()
assert "Reaped zombie process with pid=" in err, "Warning message was output!" assert (
assert ret == 0, "Reaping display test failed!\nOUT: %s\nERR: %s" % (out, err) "Reaped zombie process with pid=" in err
), "Warning message was output!"
assert ret == 0, "Reaping display test failed!\nOUT: %s\nERR: %s" % (
out,
err,
)
# Run the signals test # Run the signals test
for signum in [signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2]: for signum in [signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2]:
print "running signal test for: {0} ({1} with env {2})".format(signum, " ".join(target), env) print(
p = subprocess.Popen(target + [os.path.join(src, "test", "signals", "test.py")], env=dict(os.environ, **env)) "running signal test for: {0} ({1} with env {2})".format(
busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) > 1, 10) signum, " ".join(target), env
)
)
p = subprocess.Popen(
target + [os.path.join(src, "test", "signals", "test.py")],
env=dict(os.environ, **env),
universal_newlines=True,
)
busy_wait(
lambda: len(psutil.Process(p.pid).children(recursive=True)) > 1, 10
)
p.send_signal(signum) p.send_signal(signum)
ret = p.wait() ret = p.wait()
assert ret == 128 + signum, "Signals test failed (ret was {0}, expected {1})".format(ret, 128 + signum) assert (
ret == 128 + signum
), "Signals test failed (ret was {0}, expected {1})".format(
ret, 128 + signum
)
# Run the process group test # Run the process group test
# This test has Tini spawn a process that ignores SIGUSR1 and spawns a child that doesn't (and waits on the child) # This test has Tini spawn a process that ignores SIGUSR1 and spawns a child that doesn't (and waits on the child)
# We send SIGUSR1 to Tini, and expect the grand-child to terminate, then the child, and then Tini. # We send SIGUSR1 to Tini, and expect the grand-child to terminate, then the child, and then Tini.
if not args_disabled: if not args_disabled:
print "Running process group test (arguments)" print("Running process group test (arguments)")
p = subprocess.Popen([tini, '-g', os.path.join(src, "test", "pgroup", "stage_1.py")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) p = subprocess.Popen(
[tini, "-g", os.path.join(src, "test", "pgroup", "stage_1.py")],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
)
busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 2, 10) busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 2, 10)
p.send_signal(signal.SIGUSR1) p.send_signal(signal.SIGUSR1)
busy_wait(lambda: p.poll() is not None, 10) busy_wait(lambda: p.poll() is not None, 10)
print "Running process group test (environment variable)" print("Running process group test (environment variable)")
p = subprocess.Popen( p = subprocess.Popen(
[tini, os.path.join(src, "test", "pgroup", "stage_1.py")], [tini, os.path.join(src, "test", "pgroup", "stage_1.py")],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
env=dict(os.environ, TINI_KILL_PROCESS_GROUP="1") stderr=subprocess.PIPE,
env=dict(os.environ, TINI_KILL_PROCESS_GROUP="1"),
universal_newlines=True,
) )
busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 2, 10) busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 2, 10)
@ -133,11 +182,13 @@ def main():
# Run failing test. Force verbosity to 1 so we see the subreaper warning # Run failing test. Force verbosity to 1 so we see the subreaper warning
# regardless of whether MINIMAL is set. # regardless of whether MINIMAL is set.
print "Running zombie reaping failure test (Tini should warn)" print("Running zombie reaping failure test (Tini should warn)")
p = subprocess.Popen( p = subprocess.Popen(
[tini, os.path.join(src, "test", "reaping", "stage_1.py")], [tini, os.path.join(src, "test", "reaping", "stage_1.py")],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
env={'TINI_VERBOSITY': '1'} stderr=subprocess.PIPE,
env=dict(os.environ, TINI_VERBOSITY="1"),
universal_newlines=True,
) )
out, err = p.communicate() out, err = p.communicate()
assert "zombie reaping won't work" in err, "No warning message was output!" assert "zombie reaping won't work" in err, "No warning message was output!"
@ -145,37 +196,62 @@ def main():
assert ret == 1, "Reaping test succeeded (it should have failed)!" assert ret == 1, "Reaping test succeeded (it should have failed)!"
# Test that the signals are properly in place here. # Test that the signals are properly in place here.
print "Running signal configuration test" print("Running signal configuration test")
p = subprocess.Popen([os.path.join(build, "sigconf-test"), tini, "cat", "/proc/self/status"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) p = subprocess.Popen(
[os.path.join(build, "sigconf-test"), tini, "cat", "/proc/self/status"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
)
out, err = p.communicate() out, err = p.communicate()
# Extract the signal properties, and add a zero at the end. # Extract the signal properties, and add a zero at the end.
props = [line.split(":") for line in out.splitlines()] props = [line.split(":") for line in out.splitlines()]
props = [(k.strip(), v.strip()) for (k, v) in props] props = [(k.strip(), v.strip()) for (k, v) in props]
props = [(k, bitmap.BitMap.fromstring(bin(int(v, 16))[2:].zfill(32))) for (k, v) in props if k in ["SigBlk", "SigIgn", "SigCgt"]] props = [
(k, bitmap.BitMap.fromstring(bin(int(v, 16))[2:].zfill(32)))
for (k, v) in props
if k in ["SigBlk", "SigIgn", "SigCgt"]
]
props = dict(props) props = dict(props)
# Print actual handling configuration # Print actual handling configuration
for k, bmp in props.items(): for k, bmp in props.items():
print "{0}: {1}".format(k, ", ".join(["{0} ({1})".format(SIGNUM_TO_SIGNAME[n+1], n+1) for n in bmp.nonzero()])) print(
"{0}: {1}".format(
k,
", ".join(
[
"{0} ({1})".format(SIGNUM_TO_SIGNAME[n + 1], n + 1)
for n in bmp.nonzero()
]
),
)
)
for signal_set_name, signals_to_test_for in [ for signal_set_name, signals_to_test_for in [
("SigIgn", [signal.SIGTTOU, signal.SIGSEGV, signal.SIGINT,]), ("SigIgn", [signal.SIGTTOU, signal.SIGSEGV, signal.SIGINT]),
("SigBlk", [signal.SIGTTIN, signal.SIGILL, signal.SIGTERM,]), ("SigBlk", [signal.SIGTTIN, signal.SIGILL, signal.SIGTERM]),
]: ]:
for signum in signals_to_test_for: for signum in signals_to_test_for:
# Use signum - 1 because the bitmap is 0-indexed but represents signals strting at 1 # Use signum - 1 because the bitmap is 0-indexed but represents signals strting at 1
assert (signum - 1) in props[signal_set_name].nonzero(), "{0} ({1}) is missing in {2}!".format(SIGNUM_TO_SIGNAME[signum], signum, signal_set_name) assert (signum - 1) in props[
signal_set_name
].nonzero(), "{0} ({1}) is missing in {2}!".format(
SIGNUM_TO_SIGNAME[signum], signum, signal_set_name
)
# Test parent death signal handling. # Test parent death signal handling.
if not args_disabled: if not args_disabled:
print "Running parent death signal test" print("Running parent death signal test")
f = tempfile.NamedTemporaryFile() f = tempfile.NamedTemporaryFile()
try: try:
p = subprocess.Popen( p = subprocess.Popen(
[os.path.join(src, "test", "pdeathsignal", "stage_1.py"), tini, f.name], [os.path.join(src, "test", "pdeathsignal", "stage_1.py"), tini, f.name],
stdout=DEVNULL, stderr=DEVNULL stdout=DEVNULL,
stderr=DEVNULL,
universal_newlines=True,
) )
p.wait() p.wait()
@ -183,9 +259,9 @@ def main():
finally: finally:
f.close() f.close()
print "---------------------------" print("---------------------------")
print "All done, tests as expected" print("All done, tests as expected")
print "---------------------------" print("---------------------------")
if __name__ == "__main__": if __name__ == "__main__":

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
#coding:utf-8 #coding:utf-8
import os import os
import sys import sys
@ -9,7 +9,7 @@ import prctl
def main(): def main():
args = sys.argv[1:] args = sys.argv[1:]
print "subreaper-proxy: running '%s'" % (" ".join(args)) print("subreaper-proxy: running '%s'" % (" ".join(args)))
prctl.set_child_subreaper(1) prctl.set_child_subreaper(1)
os.execv(args[0], args) os.execv(args[0], args)

Loading…
Cancel
Save