1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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()
|