|
|
|
#coding:utf-8
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import pipes
|
|
|
|
import subprocess
|
|
|
|
import threading
|
|
|
|
import pexpect
|
|
|
|
import signal
|
|
|
|
|
|
|
|
|
|
|
|
class ReturnContainer():
|
|
|
|
def __init__(self):
|
|
|
|
self.value = None
|
|
|
|
|
|
|
|
|
|
|
|
class Command(object):
|
|
|
|
def __init__(self, cmd, fail_cmd, post_cmd=None, post_delay=0):
|
|
|
|
self.cmd = cmd
|
|
|
|
self.fail_cmd = fail_cmd
|
|
|
|
self.post_cmd = post_cmd
|
|
|
|
self.post_delay = post_delay
|
|
|
|
self.proc = None
|
|
|
|
|
|
|
|
def run(self, timeout=None, retcode=0):
|
|
|
|
print "Testing '{0}'...".format(" ".join(pipes.quote(s) for s in self.cmd)),
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
err = None
|
|
|
|
pipe_kwargs = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "stdin": subprocess.PIPE}
|
|
|
|
|
|
|
|
def target():
|
|
|
|
self.proc = subprocess.Popen(self.cmd, **pipe_kwargs)
|
|
|
|
self.stdout, self.stderr = self.proc.communicate()
|
|
|
|
|
|
|
|
thread = threading.Thread(target=target)
|
|
|
|
thread.daemon = True
|
|
|
|
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
if self.post_cmd is not None:
|
|
|
|
time.sleep(self.post_delay)
|
|
|
|
subprocess.check_call(self.post_cmd, **pipe_kwargs)
|
|
|
|
|
|
|
|
thread.join(timeout - self.post_delay if timeout is not None else timeout)
|
|
|
|
|
|
|
|
# Checks
|
|
|
|
if thread.is_alive():
|
|
|
|
subprocess.check_call(self.fail_cmd, **pipe_kwargs)
|
|
|
|
err = Exception("Test failed with timeout!")
|
|
|
|
|
|
|
|
elif self.proc.returncode != retcode:
|
|
|
|
err = Exception("Test failed with unexpected returncode (expected {0}, got {1})".format(retcode, self.proc.returncode))
|
|
|
|
|
|
|
|
if err is not None:
|
|
|
|
print "FAIL"
|
|
|
|
print "--- STDOUT ---"
|
|
|
|
print getattr(self, "stdout", "no stdout")
|
|
|
|
print "--- STDERR ---"
|
|
|
|
print getattr(self, "stderr", "no stderr")
|
|
|
|
print "--- ... ---"
|
|
|
|
raise err
|
|
|
|
else:
|
|
|
|
print "OK"
|
|
|
|
|
|
|
|
|
|
|
|
def attach_and_type_exit_0(name):
|
|
|
|
print "Attaching to {0} to exit 0".format(name)
|
|
|
|
p = pexpect.spawn("docker attach {0}".format(name))
|
|
|
|
p.sendline('')
|
|
|
|
p.sendline('exit 0')
|
|
|
|
p.close()
|
|
|
|
|
|
|
|
|
|
|
|
def attach_and_issue_ctrl_c(name):
|
|
|
|
print "Attaching to {0} to CTRL+C".format(name)
|
|
|
|
p = pexpect.spawn("docker attach {0}".format(name))
|
|
|
|
p.expect_exact('#')
|
|
|
|
p.sendintr()
|
|
|
|
p.close()
|
|
|
|
|
|
|
|
|
|
|
|
def test_tty_handling(img, name, base_cmd, fail_cmd, container_command, exit_function, expect_exit_code):
|
|
|
|
print "Testing TTY handling (using container command '{0}' and exit function '{1}')".format(container_command, exit_function.__name__)
|
|
|
|
rc = ReturnContainer()
|
|
|
|
|
|
|
|
shell_ready_event = threading.Event()
|
|
|
|
|
|
|
|
def spawn():
|
|
|
|
cmd = base_cmd + ["--tty", "--interactive", img, "/tini/dist/tini"]
|
|
|
|
if os.environ.get("MINIMAL") is None:
|
|
|
|
cmd.append("--")
|
|
|
|
cmd.append(container_command)
|
|
|
|
p = pexpect.spawn(" ".join(cmd))
|
|
|
|
p.expect_exact("#")
|
|
|
|
shell_ready_event.set()
|
|
|
|
rc.value = p.wait()
|
|
|
|
|
|
|
|
thread = threading.Thread(target=spawn)
|
|
|
|
thread.daemon = True
|
|
|
|
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
if not shell_ready_event.wait(2):
|
|
|
|
raise Exception("Timeout waiting for shell to spawn")
|
|
|
|
|
|
|
|
exit_function(name)
|
|
|
|
|
|
|
|
thread.join(timeout=2)
|
|
|
|
|
|
|
|
if thread.is_alive():
|
|
|
|
subprocess.check_call(fail_cmd)
|
|
|
|
raise Exception("Timeout waiting for container to exit!")
|
|
|
|
|
|
|
|
if rc.value != expect_exit_code:
|
|
|
|
raise Exception("Return code is: {0} (expected {1})".format(rc.value, expect_exit_code))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
img = sys.argv[1]
|
|
|
|
name = "{0}-test".format(img)
|
|
|
|
args_disabled = os.environ.get("MINIMAL")
|
|
|
|
|
|
|
|
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
|
|
|
|
|
|
base_cmd = [
|
|
|
|
"docker",
|
|
|
|
"run",
|
|
|
|
"--rm",
|
|
|
|
"--volume={0}:/tini".format(root),
|
|
|
|
"--name={0}".format(name),
|
|
|
|
]
|
|
|
|
|
|
|
|
fail_cmd = ["docker", "kill", "-s", "KILL", name]
|
|
|
|
|
|
|
|
# Funtional tests
|
|
|
|
for entrypoint in ["/tini/dist/tini", "/tini/dist/tini-static"]:
|
|
|
|
functional_base_cmd = base_cmd + [
|
|
|
|
"--entrypoint={0}".format(entrypoint),
|
|
|
|
"-e", "TINI_VERBOSITY=3",
|
|
|
|
img,
|
|
|
|
]
|
|
|
|
|
|
|
|
# Reaping test
|
|
|
|
Command(functional_base_cmd + ["/tini/test/reaping/stage_1.py"], fail_cmd).run(timeout=10)
|
|
|
|
|
|
|
|
# Signals test
|
|
|
|
for sig, retcode in [("TERM", 143), ("USR1", 138), ("USR2", 140)]:
|
|
|
|
Command(
|
|
|
|
functional_base_cmd + ["/tini/test/signals/test.py"],
|
|
|
|
fail_cmd,
|
|
|
|
["docker", "kill", "-s", sig, name],
|
|
|
|
2
|
|
|
|
).run(timeout=10, retcode=retcode)
|
|
|
|
|
|
|
|
# Exit code test
|
|
|
|
Command(functional_base_cmd + ["-z"], fail_cmd).run(retcode=127 if args_disabled else 1)
|
|
|
|
Command(functional_base_cmd + ["-h"], fail_cmd).run(retcode=127 if args_disabled else 0)
|
|
|
|
Command(functional_base_cmd + ["zzzz"], fail_cmd).run(retcode=127)
|
|
|
|
Command(functional_base_cmd + ["-w"], fail_cmd).run(retcode=127 if args_disabled else 0)
|
|
|
|
|
|
|
|
# Valgrind test (we only run this on the dynamic version, because otherwise Valgrind may bring up plenty of errors that are
|
|
|
|
# actually from libc)
|
|
|
|
Command(base_cmd + [img, "valgrind", "--leak-check=full", "--error-exitcode=1", "/tini/dist/tini", "ls"], fail_cmd).run()
|
|
|
|
|
|
|
|
# Test tty handling
|
|
|
|
test_tty_handling(img, name, base_cmd, fail_cmd, "dash", attach_and_type_exit_0, 0)
|
|
|
|
test_tty_handling(img, name, base_cmd, fail_cmd, "dash -c 'while true; do echo \#; sleep 0.1; done'", attach_and_issue_ctrl_c, 128 + signal.SIGINT)
|
|
|
|
|
|
|
|
# Installation tests (sh -c is used for globbing and &&)
|
|
|
|
for image, pkg_manager, extension in [
|
|
|
|
["ubuntu:precise", "dpkg", "deb"],
|
|
|
|
["ubuntu:trusty", "dpkg", "deb"],
|
|
|
|
["centos:6", "rpm", "rpm"],
|
|
|
|
["centos:7", "rpm", "rpm"],
|
|
|
|
]:
|
|
|
|
Command(base_cmd + [image, "sh", "-c", "{0} -i /tini/dist/*.{1} && /usr/bin/tini true".format(pkg_manager, extension)], fail_cmd).run()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|