]>
Commit | Line | Data |
---|---|---|
49549fe2 CH |
1 | #!/usr/bin/env python3 |
2 | # -*- coding: utf-8 eval: (blacken-mode 1) -*- | |
acddc0ed | 3 | # SPDX-License-Identifier: GPL-2.0-or-later |
49549fe2 CH |
4 | # |
5 | # July 9 2021, Christian Hopps <chopps@labn.net> | |
6 | # | |
7 | # Copyright (c) 2021, LabN Consulting, L.L.C. | |
8 | # | |
49549fe2 CH |
9 | import argparse |
10 | import glob | |
49549fe2 CH |
11 | import logging |
12 | import os | |
49549fe2 CH |
13 | import re |
14 | import subprocess | |
15 | import sys | |
16 | from collections import OrderedDict | |
17 | ||
18 | import xmltodict | |
19 | ||
20 | ||
21 | def get_summary(results): | |
22 | ntest = int(results["@tests"]) | |
23 | nfail = int(results["@failures"]) | |
24 | nerror = int(results["@errors"]) | |
25 | nskip = int(results["@skipped"]) | |
26 | npass = ntest - nfail - nskip - nerror | |
27 | return ntest, npass, nfail, nerror, nskip | |
28 | ||
29 | ||
30 | def print_summary(results, args): | |
31 | ntest, npass, nfail, nerror, nskip = (0, 0, 0, 0, 0) | |
32 | for group in results: | |
33 | _ntest, _npass, _nfail, _nerror, _nskip = get_summary(results[group]) | |
34 | if args.verbose: | |
a53c08bc CH |
35 | print( |
36 | f"Group: {group} Total: {_ntest} PASSED: {_npass}" | |
37 | " FAIL: {_nfail} ERROR: {_nerror} SKIP: {_nskip}" | |
38 | ) | |
49549fe2 CH |
39 | ntest += _ntest |
40 | npass += _npass | |
41 | nfail += _nfail | |
42 | nerror += _nerror | |
43 | nskip += _nskip | |
44 | print(f"Total: {ntest} PASSED: {npass} FAIL: {nfail} ERROR: {nerror} SKIP: {nskip}") | |
45 | ||
46 | ||
47 | def get_global_testcase(results): | |
48 | for group in results: | |
49 | for testcase in results[group]["testcase"]: | |
50 | if "@file" not in testcase: | |
51 | return testcase | |
52 | return None | |
53 | ||
54 | ||
55 | def get_filtered(tfilters, results, args): | |
56 | if isinstance(tfilters, str) or tfilters is None: | |
57 | tfilters = [tfilters] | |
58 | found_files = OrderedDict() | |
59 | for group in results: | |
60 | if isinstance(results[group]["testcase"], list): | |
61 | tlist = results[group]["testcase"] | |
62 | else: | |
63 | tlist = [results[group]["testcase"]] | |
64 | for testcase in tlist: | |
65 | for tfilter in tfilters: | |
66 | if tfilter is None: | |
67 | if ( | |
68 | "failure" not in testcase | |
69 | and "error" not in testcase | |
70 | and "skipped" not in testcase | |
71 | ): | |
72 | break | |
73 | elif tfilter in testcase: | |
74 | break | |
75 | else: | |
76 | continue | |
a53c08bc | 77 | # cname = testcase["@classname"] |
49549fe2 CH |
78 | fname = testcase.get("@file", "") |
79 | cname = testcase.get("@classname", "") | |
80 | if not fname and not cname: | |
81 | name = testcase.get("@name", "") | |
82 | if not name: | |
83 | continue | |
84 | # If we had a failure at the module level we could be here. | |
85 | fname = name.replace(".", "/") + ".py" | |
86 | tcname = fname | |
87 | else: | |
88 | if not fname: | |
89 | fname = cname.replace(".", "/") + ".py" | |
90 | if args.files_only or "@name" not in testcase: | |
91 | tcname = fname | |
92 | else: | |
93 | tcname = fname + "::" + testcase["@name"] | |
94 | found_files[tcname] = testcase | |
95 | return found_files | |
96 | ||
97 | ||
49549fe2 CH |
98 | def dump_testcase(testcase): |
99 | expand_keys = ("failure", "error", "skipped") | |
100 | ||
101 | s = "" | |
102 | for key, val in testcase.items(): | |
103 | if isinstance(val, str) or isinstance(val, float) or isinstance(val, int): | |
104 | s += "{}: {}\n".format(key, val) | |
160910ec CH |
105 | elif isinstance(val, list): |
106 | for k2, v2 in enumerate(val): | |
107 | s += "{}: {}\n".format(k2, v2) | |
49549fe2 CH |
108 | else: |
109 | for k2, v2 in val.items(): | |
110 | s += "{}: {}\n".format(k2, v2) | |
111 | return s | |
112 | ||
113 | ||
114 | def main(): | |
115 | parser = argparse.ArgumentParser() | |
a53c08bc CH |
116 | parser.add_argument( |
117 | "-A", | |
118 | "--save", | |
119 | action="store_true", | |
120 | help="Save /tmp/topotests{,.xml} in --rundir if --rundir does not yet exist", | |
121 | ) | |
122 | parser.add_argument( | |
123 | "-F", | |
124 | "--files-only", | |
125 | action="store_true", | |
126 | help="print test file names rather than individual full testcase names", | |
127 | ) | |
128 | parser.add_argument( | |
129 | "-S", | |
130 | "--select", | |
131 | default="fe", | |
132 | help="select results combination of letters: 'e'rrored 'f'ailed 'p'assed 's'kipped.", | |
133 | ) | |
134 | parser.add_argument( | |
135 | "-r", | |
136 | "--results", | |
137 | help="xml results file or directory containing xml results file", | |
138 | ) | |
49549fe2 CH |
139 | parser.add_argument("--rundir", help=argparse.SUPPRESS) |
140 | parser.add_argument( | |
141 | "-E", | |
142 | "--enumerate", | |
143 | action="store_true", | |
144 | help="enumerate each item (results scoped)", | |
145 | ) | |
a53c08bc | 146 | parser.add_argument("-T", "--test", help="print testcase at enumeration") |
49549fe2 CH |
147 | parser.add_argument( |
148 | "--errmsg", action="store_true", help="print testcase error message" | |
149 | ) | |
150 | parser.add_argument( | |
151 | "--errtext", action="store_true", help="print testcase error text" | |
152 | ) | |
a53c08bc | 153 | parser.add_argument("--time", action="store_true", help="print testcase run times") |
49549fe2 CH |
154 | |
155 | parser.add_argument("-s", "--summary", action="store_true", help="print summary") | |
156 | parser.add_argument("-v", "--verbose", action="store_true", help="be verbose") | |
157 | args = parser.parse_args() | |
158 | ||
159 | if args.save and args.results and not os.path.exists(args.results): | |
160 | if not os.path.exists("/tmp/topotests"): | |
a53c08bc | 161 | logging.critical('No "/tmp/topotests" directory to save') |
49549fe2 CH |
162 | sys.exit(1) |
163 | subprocess.run(["mv", "/tmp/topotests", args.results]) | |
47e52c47 CH |
164 | if "SUDO_USER" in os.environ: |
165 | subprocess.run(["chown", "-R", os.environ["SUDO_USER"], args.results]) | |
49549fe2 CH |
166 | # # Old location for results |
167 | # if os.path.exists("/tmp/topotests.xml", args.results): | |
168 | # subprocess.run(["mv", "/tmp/topotests.xml", args.results]) | |
169 | ||
a53c08bc CH |
170 | assert ( |
171 | args.test is None or not args.files_only | |
172 | ), "Can't have both --files and --test" | |
49549fe2 CH |
173 | |
174 | results = {} | |
175 | ttfiles = [] | |
176 | if args.rundir: | |
177 | basedir = os.path.realpath(args.rundir) | |
178 | os.chdir(basedir) | |
179 | ||
180 | newfiles = glob.glob("tt-group-*/topotests.xml") | |
181 | if newfiles: | |
182 | ttfiles.extend(newfiles) | |
183 | if os.path.exists("topotests.xml"): | |
184 | ttfiles.append("topotests.xml") | |
185 | else: | |
186 | if args.results: | |
187 | if os.path.exists(os.path.join(args.results, "topotests.xml")): | |
188 | args.results = os.path.join(args.results, "topotests.xml") | |
189 | if not os.path.exists(args.results): | |
190 | logging.critical("%s doesn't exist", args.results) | |
191 | sys.exit(1) | |
192 | ttfiles = [args.results] | |
3f264d54 CH |
193 | elif os.path.exists("/tmp/topotests/topotests.xml"): |
194 | ttfiles.append("/tmp/topotests/topotests.xml") | |
49549fe2 | 195 | |
3f264d54 CH |
196 | if not ttfiles: |
197 | if os.path.exists("/tmp/topotests.xml"): | |
198 | ttfiles.append("/tmp/topotests.xml") | |
49549fe2 CH |
199 | |
200 | for f in ttfiles: | |
201 | m = re.match(r"tt-group-(\d+)/topotests.xml", f) | |
202 | group = int(m.group(1)) if m else 0 | |
203 | with open(f) as xml_file: | |
204 | results[group] = xmltodict.parse(xml_file.read())["testsuites"]["testsuite"] | |
205 | ||
206 | filters = [] | |
207 | if "e" in args.select: | |
208 | filters.append("error") | |
209 | if "f" in args.select: | |
210 | filters.append("failure") | |
211 | if "s" in args.select: | |
212 | filters.append("skipped") | |
213 | if "p" in args.select: | |
214 | filters.append(None) | |
215 | ||
216 | found_files = get_filtered(filters, results, args) | |
217 | if found_files: | |
218 | if args.test is not None: | |
219 | if args.test == "all": | |
220 | keys = found_files.keys() | |
221 | else: | |
222 | keys = [list(found_files.keys())[int(args.test)]] | |
223 | for key in keys: | |
224 | testcase = found_files[key] | |
225 | if args.errtext: | |
226 | if "error" in testcase: | |
227 | errmsg = testcase["error"]["#text"] | |
228 | elif "failure" in testcase: | |
229 | errmsg = testcase["failure"]["#text"] | |
230 | else: | |
231 | errmsg = "none found" | |
232 | s = "{}: {}".format(key, errmsg) | |
233 | elif args.time: | |
234 | text = testcase["@time"] | |
235 | s = "{}: {}".format(text, key) | |
236 | elif args.errmsg: | |
237 | if "error" in testcase: | |
238 | errmsg = testcase["error"]["@message"] | |
239 | elif "failure" in testcase: | |
240 | errmsg = testcase["failure"]["@message"] | |
241 | else: | |
242 | errmsg = "none found" | |
243 | s = "{}: {}".format(key, errmsg) | |
244 | else: | |
245 | s = dump_testcase(testcase) | |
246 | print(s) | |
247 | elif filters: | |
248 | if args.enumerate: | |
249 | print( | |
250 | "\n".join(["{} {}".format(i, x) for i, x in enumerate(found_files)]) | |
251 | ) | |
252 | else: | |
253 | print("\n".join(found_files)) | |
254 | ||
255 | if args.summary: | |
256 | print_summary(results, args) | |
257 | ||
258 | ||
259 | if __name__ == "__main__": | |
260 | main() |