summaryrefslogtreecommitdiffstats
path: root/bin/tests/convert-trs-to-junit.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xbin/tests/convert-trs-to-junit.py148
1 files changed, 148 insertions, 0 deletions
diff --git a/bin/tests/convert-trs-to-junit.py b/bin/tests/convert-trs-to-junit.py
new file mode 100755
index 0000000..85b37dd
--- /dev/null
+++ b/bin/tests/convert-trs-to-junit.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# Convert automake .trs files into JUnit format suitable for Gitlab
+
+import argparse
+import os
+import sys
+from xml.etree import ElementTree
+from xml.etree.ElementTree import Element
+from xml.etree.ElementTree import SubElement
+
+
+# getting explicit encoding specification right for Python 2/3 would be messy,
+# so let's hope for the best
+def read_whole_text(filename):
+ with open(filename) as inf: # pylint: disable-msg=unspecified-encoding
+ return inf.read().strip()
+
+
+def read_trs_result(filename):
+ result = None
+ with open(filename, "r") as trs: # pylint: disable-msg=unspecified-encoding
+ for line in trs:
+ items = line.split()
+ if len(items) < 2:
+ raise ValueError("unsupported line in trs file", filename, line)
+ if items[0] != (":test-result:"):
+ continue
+ if result is not None:
+ raise NotImplementedError("double :test-result:", filename)
+ result = items[1].upper()
+
+ if result is None:
+ raise ValueError(":test-result: not found", filename)
+
+ return result
+
+
+def find_test_relative_path(source_dir, in_path):
+ """Return {in_path}.c if it exists, with fallback to {in_path}"""
+ candidates_relative = [in_path + ".c", in_path]
+ for relative in candidates_relative:
+ absolute = os.path.join(source_dir, relative)
+ if os.path.exists(absolute):
+ return relative
+ raise KeyError
+
+
+def err_out(exception):
+ raise exception
+
+
+def walk_trss(source_dir):
+ for cur_dir, _dirs, files in os.walk(source_dir, onerror=err_out):
+ for filename in files:
+ if not filename.endswith(".trs"):
+ continue
+
+ filename_prefix = filename[: -len(".trs")]
+ log_name = filename_prefix + ".log"
+ full_trs_path = os.path.join(cur_dir, filename)
+ full_log_path = os.path.join(cur_dir, log_name)
+ sub_dir = os.path.relpath(cur_dir, source_dir)
+ test_name = os.path.join(sub_dir, filename_prefix)
+
+ t = {
+ "name": test_name,
+ "full_log_path": full_log_path,
+ "rel_log_path": os.path.relpath(full_log_path, source_dir),
+ }
+ t["result"] = read_trs_result(full_trs_path)
+
+ # try to find dir/file path for a clickable link
+ try:
+ t["rel_file_path"] = find_test_relative_path(source_dir, test_name)
+ except KeyError:
+ pass # no existing path found
+
+ yield t
+
+
+def append_testcase(testsuite, t):
+ # attributes taken from
+ # https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/lib/gitlab/ci/parsers/test/junit.rb
+ attrs = {"name": t["name"]}
+ if "rel_file_path" in t:
+ attrs["file"] = t["rel_file_path"]
+
+ testcase = SubElement(testsuite, "testcase", attrs)
+
+ # Gitlab accepts only [[ATTACHMENT| links for system-out, not raw text
+ s = SubElement(testcase, "system-out")
+ s.text = "[[ATTACHMENT|" + t["rel_log_path"] + "]]"
+ if t["result"].lower() == "pass":
+ return
+
+ # Gitlab shows output only for failed or skipped tests
+ if t["result"].lower() == "skip":
+ err = SubElement(testcase, "skipped")
+ else:
+ err = SubElement(testcase, "failure")
+ err.text = read_whole_text(t["full_log_path"])
+
+
+def gen_junit(results):
+ testsuites = Element("testsuites")
+ testsuite = SubElement(testsuites, "testsuite")
+ for test in results:
+ append_testcase(testsuite, test)
+ return testsuites
+
+
+def check_directory(path):
+ try:
+ os.listdir(path)
+ return path
+ except OSError as ex:
+ msg = "Path {} cannot be listed as a directory: {}".format(path, ex)
+ raise argparse.ArgumentTypeError(msg)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Recursively search for .trs + .log files and compile "
+ "them into JUnit XML suitable for Gitlab. Paths in the "
+ "XML are relative to the specified top directory."
+ )
+ parser.add_argument(
+ "top_directory",
+ type=check_directory,
+ help="root directory where to start scanning for .trs files",
+ )
+ args = parser.parse_args()
+ junit = gen_junit(walk_trss(args.top_directory))
+
+ # encode results into file format, on Python 3 it produces bytes
+ xml = ElementTree.tostring(junit, "utf-8")
+ # use stdout as a binary file object, Python2/3 compatibility
+ output = getattr(sys.stdout, "buffer", sys.stdout)
+ output.write(xml)
+
+
+if __name__ == "__main__":
+ main()