#!/usr/bin/python # Copyright (C) 2014 Red Hat, Inc. # # Cockpit is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # # Cockpit is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with Cockpit; If not, see . # # This is a test output compiler which produces TAP from GTest output # if GTest output is detected. # # Versions of glib later than 2.38.x output TAP natively when tests are # run with the --tap option. However we can't depend on such a recent # version of glib for our purposes. # # This implements the Test Anything Protocol (ie: TAP) # https://metacpan.org/pod/release/PETDANCE/Test-Harness-2.64/lib/Test/Harness/TAP.pod # import argparse import os import select import subprocess import sys class NullCompiler: def __init__(self, command): self.command = command def input(self, line): sys.stdout.write(line) def process(self, proc): while True: line = proc.stdout.readline() if not line: break self.input(line) proc.wait() return proc.returncode def run(self, proc, line=None): if line: self.input(line) return self.process(proc) class GTestCompiler(NullCompiler): def __init__(self, filename): NullCompiler.__init__(self, filename) self.test_num = 0 self.test_name = None self.test_remaining = [] def input(self, line): line = line.strip() if line.startswith("GTest: "): (cmd, unused, data) = line[7:].partition(": ") cmd = cmd.strip() data = data.strip() if cmd == "run": self.test_name = data assert self.test_name in self.test_remaining, "%s %s" % (self.test_name, repr(self.test_remaining)) self.test_remaining.remove(self.test_name) self.test_num += 1 elif cmd == "result": if data == "OK": print "ok %d %s" % (self.test_num, self.test_name) if data == "FAIL": print "not ok %d %s", (self.test_num, self.test_name) self.test_name = None elif cmd == "skipping": if "/subprocess" not in data: print "ok %d # skip -- %s" % (self.test_num, data) self.test_name = None elif data: print "# %s: %s" % (cmd, data) else: print "# %s" % cmd elif line.startswith("(MSG: "): print "# %s" % line[6:-1] elif line: print "# %s" % line sys.stdout.flush() def run(self, proc, output=""): # Complete retrieval of the list of tests output += proc.stdout.read() proc.wait() if proc.returncode: sys.stderr.write("tap-gtester: listing GTest tests failed: %d\n" % proc.returncode) return proc.returncode self.test_remaining = [] for line in output.split("\n"): if line.startswith("/"): self.test_remaining.append(line.strip()) if not self.test_remaining: print "Bail out! No tests found in GTest: %s" % self.command[0] return 0 print "1..%d" % len(self.test_remaining) # First try to run all the tests in a batch proc = subprocess.Popen(self.command + ["--verbose" ], close_fds=True, stdout=subprocess.PIPE) result = self.process(proc) if result == 0: return 0 # Now pick up any stragglers due to failures while True: # Assume that the last test failed if self.test_name: print "not ok %d %s" % (self.test_num, self.test_name) self.test_name = None # Run any tests which didn't get run if not self.test_remaining: break proc = subprocess.Popen(self.command + ["--verbose", "-p", self.test_remaining[0]], close_fds=True, stdout=subprocess.PIPE) result = self.process(proc) # The various exit codes and signals we continue for if result not in [ 0, 1, -4, -5, -6, -7, -8, -11, 33 ]: break return result def main(argv): parser = argparse.ArgumentParser(description='Automake TAP compiler') parser.add_argument('--format', metavar='FORMAT', choices=[ "auto", "gtest", "tap" ], default="auto", help='The input format to compile') parser.add_argument('--verbose', action='store_true', default=True, help='Verbose mode (ignored)') parser.add_argument('command', nargs='+', help="A test command to run") args = parser.parse_args(argv[1:]) output = None format = args.format cmd = args.command proc = None os.environ['HARNESS_ACTIVE'] = '1' if format in ["auto", "gtest"]: list_cmd = cmd + ["-l", "--verbose"] proc = subprocess.Popen(list_cmd, close_fds=True, stdout=subprocess.PIPE) output = proc.stdout.readline() # Smell whether we're dealing with GTest list output from first line if "random seed" in output or "GTest" in output or output.startswith("/"): format = "gtest" else: format = "tap" else: proc = subprocess.Popen(cmd, close_fds=True, stdout=subprocess.PIPE) if format == "gtest": compiler = GTestCompiler(cmd) elif format == "tap": compiler = NullCompiler(cmd) else: assert False, "not reached" return compiler.run(proc, output) if __name__ == "__main__": sys.exit(main(sys.argv))