capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

mega-test.py (3926B)


      1 #! /usr/bin/env python
      2 
      3 # MEGA TEST
      4 #
      5 # usage:  mega-test.py <config>
      6 #
      7 # This runs several tests in parallel and shows progress bars for each, based on a config file.
      8 #
      9 # <config> is a file containing a list of commands to run along with the expected number of lines
     10 # they will output (to stdout and stderr combined), which is how the progress bar is calculated.
     11 # The format of the file is simply one test per line, with the line containing the test name,
     12 # the number of output lines expected, and the test command.  Example:
     13 #
     14 #     mytest 1523 ./my-test --foo bar
     15 #     another 862 ./another-test --baz
     16 #
     17 # Each command is interpreted by `sh -euc`, therefore it is acceptable to use environment
     18 # variables and other shell syntax.
     19 #
     20 # After all tests complete, the config file will be rewritten to update the line counts to the
     21 # actual number of lines seen for all passing tests (failing tests are not updated).
     22 
     23 import sys
     24 import re
     25 import os
     26 from errno import EAGAIN
     27 from fcntl import fcntl, F_GETFL, F_SETFL
     28 from select import poll, POLLIN, POLLHUP
     29 from subprocess import Popen, PIPE, STDOUT
     30 
     31 CONFIG_LINE = re.compile("^([^ ]+) +([0-9]+) +(.*)$")
     32 
     33 if len(sys.argv) != 2:
     34   sys.stderr.write("Wrong number of arguments.\n");
     35   sys.exit(1)
     36 
     37 if not os.access("/tmp/test-output", os.F_OK):
     38   os.mkdir("/tmp/test-output")
     39 
     40 config = open(sys.argv[1], 'r')
     41 
     42 tests = []
     43 
     44 class Test:
     45   def __init__(self, name, command, lines):
     46     self.name = name
     47     self.command = command
     48     self.lines = lines
     49     self.count = 0
     50     self.done = False
     51 
     52   def start(self, poller):
     53     self.proc = Popen(["sh", "-euc", test.command], stdin=dev_null, stdout=PIPE, stderr=STDOUT)
     54     fd = self.proc.stdout.fileno()
     55     flags = fcntl(fd, F_GETFL)
     56     fcntl(fd, F_SETFL, flags | os.O_NONBLOCK)
     57     poller.register(self.proc.stdout, POLLIN)
     58     self.log = open("/tmp/test-output/" + self.name + ".log", "w")
     59 
     60   def update(self):
     61     try:
     62       while True:
     63         text = self.proc.stdout.read()
     64         if text == "":
     65           self.proc.wait()
     66           self.done = True
     67           self.log.close()
     68           return True
     69         self.count += text.count("\n")
     70         self.log.write(text)
     71     except IOError as e:
     72       if e.errno == EAGAIN:
     73         return False
     74       raise
     75 
     76   def print_bar(self):
     77     percent = self.count * 100 / self.lines
     78     status = "(%3d%%)" % percent
     79 
     80     color_on = ""
     81     color_off = ""
     82 
     83     if self.done:
     84       if self.proc.returncode == 0:
     85         color_on = "\033[0;32m"
     86         status = "PASS"
     87       else:
     88         color_on = "\033[0;31m"
     89         status = "FAIL: /tmp/test-output/%s.log" % self.name
     90       color_off = "\033[0m"
     91 
     92     print "%s%-16s |%-25s| %6d/%6d %s%s    " % (
     93         color_on, self.name, '=' * min(percent / 4, 25), self.count, self.lines, status, color_off)
     94 
     95   def passed(self):
     96     return self.proc.returncode == 0
     97 
     98 for line in config:
     99   if len(line) > 0 and not line.startswith("#"):
    100     match = CONFIG_LINE.match(line)
    101     if not match:
    102       sys.stderr.write("Invalid config syntax: %s\n" % line);
    103       sys.exit(1)
    104     test = Test(match.group(1), match.group(3), int(match.group(2)))
    105     tests.append(test)
    106 
    107 config.close()
    108 
    109 dev_null = open("/dev/null", "rw")
    110 poller = poll()
    111 fd_map = {}
    112 
    113 for test in tests:
    114   test.start(poller)
    115   fd_map[test.proc.stdout.fileno()] = test
    116 
    117 active_count = len(tests)
    118 
    119 def print_bars():
    120   for test in tests:
    121     test.print_bar()
    122 
    123 print_bars()
    124 
    125 while active_count > 0:
    126   for (fd, event) in poller.poll():
    127     if fd_map[fd].update():
    128       active_count -= 1
    129       poller.unregister(fd)
    130   sys.stdout.write("\033[%dA\r" % len(tests))
    131   print_bars()
    132 
    133 new_config = open(sys.argv[1], "w")
    134 for test in tests:
    135   if test.passed():
    136     new_config.write("%-16s %6d %s\n" % (test.name, test.count, test.command))
    137   else:
    138     new_config.write("%-16s %6d %s\n" % (test.name, test.lines, test.command))
    139 
    140 for test in tests:
    141   if not test.passed():
    142     sys.exit(1)
    143 
    144 sys.exit(0)