mirror of
https://github.com/isc-projects/bind9.git
synced 2026-04-21 06:09:13 -04:00
Some distributions (notably, debian bookworm) have deprecated the `python` interpreter in favor of `python3`. Since our scripts are python3 anyway, use the proper numbered version in shebang to make scripts easily executable.
148 lines
4.6 KiB
Python
Executable file
148 lines
4.6 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
#
|
|
# 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()
|