diff --git a/.gitignore b/.gitignore index fe84e5d..604964f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist sign.key .env +.mypy_cache diff --git a/Dockerfile b/Dockerfile index 8d77935..dc5e7dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ COPY ci/install_deps.sh /install_deps.sh RUN /install_deps.sh # 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 CC diff --git a/ci/install_deps.sh b/ci/install_deps.sh index aad98cd..273264a 100755 --- a/ci/install_deps.sh +++ b/ci/install_deps.sh @@ -5,7 +5,7 @@ set -o xtrace DEPS=( 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 ) @@ -26,4 +26,5 @@ apt-get update apt-get install --no-install-recommends --yes "${DEPS[@]}" rm -rf /var/lib/apt/lists/* -pip install virtualenv +python3 -m pip install --upgrade pip +python3 -m pip install virtualenv diff --git a/ci/run_build.sh b/ci/run_build.sh index d71c232..d51177d 100755 --- a/ci/run_build.sh +++ b/ci/run_build.sh @@ -206,10 +206,10 @@ if [[ -n "${ARCH_NATIVE-}" ]]; then export CFLAGS # We need them to build our test suite, regardless of FORCE_SUBREAPER # Install test dependencies - CC=gcc pip install psutil python-prctl bitmap + CC=gcc python3 -m pip install psutil python-prctl bitmap # Run tests - python "${SOURCE_DIR}/test/run_inner_tests.py" + python3 "${SOURCE_DIR}/test/run_inner_tests.py" else if [[ ! -n "${ARCH_SUFFIX-}" ]]; then echo "Built cross package, but $ARCH_SUFFIX is empty!" diff --git a/test/reaping/stage_1.py b/test/reaping/stage_1.py index 82d62d3..51ce73d 100755 --- a/test/reaping/stage_1.py +++ b/test/reaping/stage_1.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -from __future__ import print_function +#!/usr/bin/env python3 import os import sys import subprocess diff --git a/test/run_inner_tests.py b/test/run_inner_tests.py index e060e18..4bd522a 100755 --- a/test/run_inner_tests.py +++ b/test/run_inner_tests.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python -#coding:utf-8 +#!/usr/bin/env python3 +# coding:utf-8 import os import sys import signal @@ -11,16 +11,18 @@ import re import itertools 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): checks = 100 increment = float(timeout) / checks - for _ in xrange(checks): + for _ in range(checks): if condition_callable(): return time.sleep(increment) @@ -42,27 +44,37 @@ def main(): # Run the exit code test. We use POSIXLY_CORRECT here to not need -- # until that's the default in Tini anyways. 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): p = subprocess.Popen( - [tini, '-e', str(code), '--', 'sh', '-c', "exit {0}".format(code)], - stdout=DEVNULL, stderr=DEVNULL + [tini, "-e", str(code), "--", "sh", "-c", "exit {0}".format(code)], + stdout=DEVNULL, + stderr=DEVNULL, + universal_newlines=True, ) 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] - 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( - [tini] + args + ['sh', '-c', "exit {0}".format(code)], + [tini] + args + ["sh", "-c", "exit {0}".format(code)], env=dict(os.environ, POSIXLY_CORRECT="1"), - stdout=DEVNULL, stderr=DEVNULL + stdout=DEVNULL, + stderr=DEVNULL, + universal_newlines=True, ) 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 not args_disabled: @@ -71,10 +83,14 @@ def main(): for target, env in tests: # Run the reaping test - 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")], - env=dict(os.environ, **env), - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + 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")], + env=dict(os.environ, **env), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ) out, err = p.communicate() @@ -83,48 +99,81 @@ def main(): # and will output the error message here. assert "zombie reaping won't work" not in err, "Warning message was output!" 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) - if not args_disabled: - print "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), - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + print( + "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), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ) out, err = p.communicate() ret = p.wait() - assert "Reaped zombie process with pid=" in err, "Warning message was output!" - assert ret == 0, "Reaping display test failed!\nOUT: %s\nERR: %s" % (out, err) - + assert ( + "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 for signum in [signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2]: - print "running signal test for: {0} ({1} with env {2})".format(signum, " ".join(target), env) - p = subprocess.Popen(target + [os.path.join(src, "test", "signals", "test.py")], env=dict(os.environ, **env)) - busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) > 1, 10) + print( + "running signal test for: {0} ({1} with env {2})".format( + 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) 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 # 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. if not args_disabled: - 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) + 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, + universal_newlines=True, + ) busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 2, 10) p.send_signal(signal.SIGUSR1) 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( [tini, os.path.join(src, "test", "pgroup", "stage_1.py")], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env=dict(os.environ, TINI_KILL_PROCESS_GROUP="1") + stdout=subprocess.PIPE, + 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) @@ -133,11 +182,13 @@ def main(): # Run failing test. Force verbosity to 1 so we see the subreaper warning # 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( [tini, os.path.join(src, "test", "reaping", "stage_1.py")], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env={'TINI_VERBOSITY': '1'} + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=dict(os.environ, TINI_VERBOSITY="1"), + universal_newlines=True, ) out, err = p.communicate() 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)!" # 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() # Extract the signal properties, and add a zero at the end. props = [line.split(":") for line in out.splitlines()] 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) # Print actual handling configuration 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 [ - ("SigIgn", [signal.SIGTTOU, signal.SIGSEGV, signal.SIGINT,]), - ("SigBlk", [signal.SIGTTIN, signal.SIGILL, signal.SIGTERM,]), + ("SigIgn", [signal.SIGTTOU, signal.SIGSEGV, signal.SIGINT]), + ("SigBlk", [signal.SIGTTIN, signal.SIGILL, signal.SIGTERM]), ]: for signum in signals_to_test_for: # 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. if not args_disabled: - print "Running parent death signal test" + print("Running parent death signal test") f = tempfile.NamedTemporaryFile() try: p = subprocess.Popen( [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() @@ -183,9 +259,9 @@ def main(): finally: f.close() - print "---------------------------" - print "All done, tests as expected" - print "---------------------------" + print("---------------------------") + print("All done, tests as expected") + print("---------------------------") if __name__ == "__main__": diff --git a/test/subreaper-proxy.py b/test/subreaper-proxy.py index a125a44..32b16c5 100755 --- a/test/subreaper-proxy.py +++ b/test/subreaper-proxy.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 #coding:utf-8 import os import sys @@ -9,7 +9,7 @@ import prctl def main(): args = sys.argv[1:] - print "subreaper-proxy: running '%s'" % (" ".join(args)) + print("subreaper-proxy: running '%s'" % (" ".join(args))) prctl.set_child_subreaper(1) os.execv(args[0], args)