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()