github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/tini/test/run_outer_tests.py (about) 1 #coding:utf-8 2 import os 3 import sys 4 import time 5 import pipes 6 import subprocess 7 import threading 8 import pexpect 9 import signal 10 11 12 class ReturnContainer(): 13 def __init__(self): 14 self.value = None 15 16 17 class Command(object): 18 def __init__(self, cmd, fail_cmd, post_cmd=None, post_delay=0): 19 self.cmd = cmd 20 self.fail_cmd = fail_cmd 21 self.post_cmd = post_cmd 22 self.post_delay = post_delay 23 self.proc = None 24 25 def run(self, timeout=None, retcode=0): 26 print "Testing '{0}'...".format(" ".join(pipes.quote(s) for s in self.cmd)), 27 sys.stdout.flush() 28 29 err = None 30 pipe_kwargs = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "stdin": subprocess.PIPE} 31 32 def target(): 33 self.proc = subprocess.Popen(self.cmd, **pipe_kwargs) 34 self.stdout, self.stderr = self.proc.communicate() 35 36 thread = threading.Thread(target=target) 37 thread.daemon = True 38 39 thread.start() 40 41 if self.post_cmd is not None: 42 time.sleep(self.post_delay) 43 subprocess.check_call(self.post_cmd, **pipe_kwargs) 44 45 thread.join(timeout - self.post_delay if timeout is not None else timeout) 46 47 # Checks 48 if thread.is_alive(): 49 subprocess.check_call(self.fail_cmd, **pipe_kwargs) 50 err = Exception("Test failed with timeout!") 51 52 elif self.proc.returncode != retcode: 53 err = Exception("Test failed with unexpected returncode (expected {0}, got {1})".format(retcode, self.proc.returncode)) 54 55 if err is not None: 56 print "FAIL" 57 print "--- STDOUT ---" 58 print getattr(self, "stdout", "no stdout") 59 print "--- STDERR ---" 60 print getattr(self, "stderr", "no stderr") 61 print "--- ... ---" 62 raise err 63 else: 64 print "OK" 65 66 67 def attach_and_type_exit_0(name): 68 print "Attaching to {0} to exit 0".format(name) 69 p = pexpect.spawn("docker attach {0}".format(name)) 70 p.sendline('') 71 p.sendline('exit 0') 72 p.close() 73 74 75 def attach_and_issue_ctrl_c(name): 76 print "Attaching to {0} to CTRL+C".format(name) 77 p = pexpect.spawn("docker attach {0}".format(name)) 78 p.expect_exact('#') 79 p.sendintr() 80 p.close() 81 82 83 def test_tty_handling(img, name, base_cmd, fail_cmd, container_command, exit_function, expect_exit_code): 84 print "Testing TTY handling (using container command '{0}' and exit function '{1}')".format(container_command, exit_function.__name__) 85 rc = ReturnContainer() 86 87 shell_ready_event = threading.Event() 88 89 def spawn(): 90 cmd = base_cmd + ["--tty", "--interactive", img, "/tini/dist/tini"] 91 if os.environ.get("MINIMAL") is None: 92 cmd.append("--") 93 cmd.append(container_command) 94 p = pexpect.spawn(" ".join(cmd)) 95 p.expect_exact("#") 96 shell_ready_event.set() 97 rc.value = p.wait() 98 99 thread = threading.Thread(target=spawn) 100 thread.daemon = True 101 102 thread.start() 103 104 if not shell_ready_event.wait(2): 105 raise Exception("Timeout waiting for shell to spawn") 106 107 exit_function(name) 108 109 thread.join(timeout=2) 110 111 if thread.is_alive(): 112 subprocess.check_call(fail_cmd) 113 raise Exception("Timeout waiting for container to exit!") 114 115 if rc.value != expect_exit_code: 116 raise Exception("Return code is: {0} (expected {1})".format(rc.value, expect_exit_code)) 117 118 119 120 def main(): 121 img = sys.argv[1] 122 name = "{0}-test".format(img) 123 args_disabled = os.environ.get("MINIMAL") 124 125 root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) 126 127 base_cmd = [ 128 "docker", 129 "run", 130 "--rm", 131 "--volume={0}:/tini".format(root), 132 "--name={0}".format(name), 133 ] 134 135 fail_cmd = ["docker", "kill", "-s", "KILL", name] 136 137 # Funtional tests 138 for entrypoint in ["/tini/dist/tini", "/tini/dist/tini-static"]: 139 functional_base_cmd = base_cmd + [ 140 "--entrypoint={0}".format(entrypoint), 141 "-e", "TINI_VERBOSITY=3", 142 img, 143 ] 144 145 # Reaping test 146 Command(functional_base_cmd + ["/tini/test/reaping/stage_1.py"], fail_cmd).run(timeout=10) 147 148 # Signals test 149 for sig, retcode in [("TERM", 143), ("USR1", 138), ("USR2", 140)]: 150 Command( 151 functional_base_cmd + ["/tini/test/signals/test.py"], 152 fail_cmd, 153 ["docker", "kill", "-s", sig, name], 154 2 155 ).run(timeout=10, retcode=retcode) 156 157 # Exit code test 158 Command(functional_base_cmd + ["-z"], fail_cmd).run(retcode=127 if args_disabled else 1) 159 Command(functional_base_cmd + ["-h"], fail_cmd).run(retcode=127 if args_disabled else 0) 160 Command(functional_base_cmd + ["zzzz"], fail_cmd).run(retcode=127) 161 Command(functional_base_cmd + ["-w"], fail_cmd).run(retcode=127 if args_disabled else 0) 162 163 # Valgrind test (we only run this on the dynamic version, because otherwise Valgrind may bring up plenty of errors that are 164 # actually from libc) 165 Command(base_cmd + [img, "valgrind", "--leak-check=full", "--error-exitcode=1", "/tini/dist/tini", "ls"], fail_cmd).run() 166 167 # Test tty handling 168 test_tty_handling(img, name, base_cmd, fail_cmd, "dash", attach_and_type_exit_0, 0) 169 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) 170 171 # Installation tests (sh -c is used for globbing and &&) 172 for image, pkg_manager, extension in [ 173 ["ubuntu:precise", "dpkg", "deb"], 174 ["ubuntu:trusty", "dpkg", "deb"], 175 ["centos:6", "rpm", "rpm"], 176 ["centos:7", "rpm", "rpm"], 177 ]: 178 Command(base_cmd + [image, "sh", "-c", "{0} -i /tini/dist/*.{1} && /usr/bin/tini true".format(pkg_manager, extension)], fail_cmd).run() 179 180 181 if __name__ == "__main__": 182 main()