]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/analyze.py
Merge pull request #13506 from anlancs/fix/bfdd-vrf-check
[mirror_frr.git] / tests / topotests / analyze.py
CommitLineData
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
9import argparse
10import glob
49549fe2
CH
11import logging
12import os
49549fe2
CH
13import re
14import subprocess
15import sys
16from collections import OrderedDict
17
18import xmltodict
19
20
21def 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
30def 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
47def 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
55def 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
98def 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
114def 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
259if __name__ == "__main__":
260 main()