blob: 54cb7b26f4491a0c4bf23a75932ae48219cff036 [file] [log] [blame]
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +00001"""TOSA verification runner script."""
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +01002# Copyright (c) 2020-2023, ARM Limited.
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +00003# SPDX-License-Identifier: Apache-2.0
4import argparse
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +00005import importlib
Jeremy Johnsone2b5e872023-09-14 17:02:09 +01006import json
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +00007import os
8import queue
9import threading
10import traceback
11from datetime import datetime
12from pathlib import Path
13
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +010014import conformance.model_files as cmf
Jeremy Johnsone2b5e872023-09-14 17:02:09 +010015import runner.tosa_test_presets as ttp
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +000016from json2numpy import json2numpy
17from runner.tosa_test_runner import TosaTestInvalid
18from runner.tosa_test_runner import TosaTestRunner
19from xunit import xunit
20
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +000021
22def parseArgs(argv):
23 """Parse the arguments and return the settings."""
24 parser = argparse.ArgumentParser()
25 group = parser.add_mutually_exclusive_group(required=True)
26 group.add_argument(
27 "-t",
28 "--test",
29 dest="test",
30 type=str,
31 nargs="+",
32 help="Test(s) to run",
33 )
34 group.add_argument(
35 "-T",
36 "--test-list",
37 dest="test_list_file",
38 type=Path,
39 help="File containing list of tests to run (one per line)",
40 )
41 parser.add_argument(
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +010042 "-r",
43 "--recursive",
44 dest="recursive_tests",
45 action="store_true",
46 help="Recursively search for tests",
47 )
48 parser.add_argument(
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +000049 "--ref-model-path",
50 dest="ref_model_path",
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +010051 type=Path,
52 help="Path to TOSA reference model executable",
53 )
54 parser.add_argument(
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +010055 "--verify-lib-path",
56 dest="verify_lib_path",
57 type=Path,
58 help=(
59 "Path to TOSA verify library. Defaults to "
60 "the library in the directory of `ref-model-path`"
61 ),
62 )
63 parser.add_argument(
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +010064 "--operator-fbs",
65 "--schema-path",
66 dest="schema_path",
67 type=Path,
68 help=(
69 "Path to TOSA reference model flat buffer schema. Defaults to "
70 f"`{cmf.DEFAULT_REF_MODEL_SCHEMA_PATH}` in parents parent directory of `ref-model-path`"
71 ),
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +000072 )
73 parser.add_argument(
74 "--flatc-path",
75 dest="flatc_path",
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +010076 type=Path,
77 help=(
78 "Path to flatc executable. Defaults to "
79 f"`{cmf.DEFAULT_REF_MODEL_BUILD_FLATC_PATH}` in parent directory of `ref-model-path`"
80 ),
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +000081 )
82 parser.add_argument(
83 "--ref-debug",
84 dest="ref_debug",
85 default="",
86 type=str,
87 help="Reference debug flag (low, med, high)",
88 )
89 parser.add_argument(
90 "--ref-intermediates",
91 dest="ref_intermediates",
92 default=0,
93 type=int,
94 help="Reference model dumps intermediate tensors",
95 )
96 parser.add_argument(
97 "-b",
98 "--binary",
99 dest="binary",
100 action="store_true",
101 help="Convert to using binary flatbuffers instead of JSON",
102 )
103 parser.add_argument(
104 "-v", "--verbose", dest="verbose", action="count", help="Verbose operation"
105 )
106 parser.add_argument(
107 "-j", "--jobs", dest="jobs", type=int, default=1, help="Number of parallel jobs"
108 )
109 parser.add_argument(
110 "--sut-module",
111 "-s",
112 dest="sut_module",
113 type=str,
114 nargs="+",
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100115 default=[ttp.TOSA_REFMODEL_RUNNER],
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000116 help="System under test module to load (derives from TosaTestRunner). May be repeated",
117 )
118 parser.add_argument(
119 "--sut-module-args",
120 dest="sut_module_args",
121 type=str,
122 nargs="+",
123 default=[],
124 help="System under test module arguments. Use sutmodulename:argvalue to pass an argument. May be repeated.",
125 )
126 parser.add_argument(
127 "--xunit-file",
128 dest="xunit_file",
129 type=str,
130 default="result.xml",
131 help="XUnit output file",
132 )
133 parser.add_argument(
134 "--test-type",
135 dest="test_type",
136 type=str,
137 default="both",
138 choices=["positive", "negative", "both"],
Jeremy Johnson88588622022-07-12 16:42:29 +0100139 help="Filter tests based on expected failure status",
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000140 )
Jeremy Johnson015c3552022-02-23 12:15:03 +0000141 parser.add_argument(
142 "--no-color",
143 "--no-colour",
144 dest="no_color",
145 action="store_true",
146 help="Disable color output",
147 )
Jeremy Johnson88588622022-07-12 16:42:29 +0100148 parser.add_argument(
149 "--profile",
150 dest="profile",
151 type=str,
152 choices=["tosa-bi", "tosa-mi"],
153 help="Filter tests based on profile",
154 )
Jerry Gea793f462023-04-11 00:05:02 +0000155 parser.add_argument(
156 "--tosa_level",
157 dest="tosa_level",
158 default="EIGHTK",
159 type=str,
160 help="A TOSA level defines operator parameter ranges that an implementation shall support."
161 "Config tosa_level for running the reference model only. Default is EIGHTK",
162 )
Tai Lya4d748b2023-03-28 22:06:56 +0000163 parser.add_argument(
164 "-p",
165 "--precise-mode",
166 dest="precise_mode",
167 action="store_true",
168 help="Run the reference model in precise mode (FP64)",
169 )
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000170
171 args = parser.parse_args(argv)
172
173 # Autodetect CPU count
174 if args.jobs <= 0:
175 args.jobs = os.cpu_count()
176
177 return args
178
179
180EXCLUSION_PREFIX = ["test", "model", "desc"]
181
182
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100183def convert2Numpy(test_path):
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000184 """Convert all the JSON numpy files back into binary numpy."""
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100185 jsons = test_path.glob("*.json")
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100186 for j in jsons:
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000187 for exclude in EXCLUSION_PREFIX:
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100188 if j.name.startswith(exclude):
189 j = None
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100190 break
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100191 if j:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100192 # debug print(f"Converting {json}")
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100193 json2numpy.json_to_npy(j)
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000194
195
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100196def workerThread(task_queue, runnerList, complianceRunner, args, result_queue):
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000197 """Worker thread that runs the next test from the queue."""
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100198 complianceRunnerList = runnerList.copy()
199 complianceRunnerList.insert(0, (complianceRunner, []))
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000200 while True:
201 try:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100202 test_path = task_queue.get(block=False)
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000203 except queue.Empty:
204 break
205
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100206 if test_path is None:
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000207 break
208
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100209 try:
210 # Check for compliance test
211 desc = test_path / "desc.json"
212 with desc.open("r") as fd:
213 j = json.load(fd)
214 compliance = "compliance" in j["meta"]
215 except Exception:
216 compliance = False
217
218 if compliance:
219 # Run compliance first to create output files!
220 currentRunners = complianceRunnerList
221 else:
222 currentRunners = runnerList
223
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000224 msg = ""
225 converted = False
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100226 for runnerModule, runnerArgs in currentRunners:
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000227 try:
228 start_time = datetime.now()
229 # Set up system under test runner
230 runnerName = runnerModule.__name__
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100231 runner = runnerModule.TosaSUTRunner(args, runnerArgs, test_path)
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000232
Jeremy Johnson88588622022-07-12 16:42:29 +0100233 skip, reason = runner.skipTest()
234 if skip:
235 msg = "Skipping {} test".format(reason)
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100236 print("{} {}".format(msg, test_path))
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000237 rc = TosaTestRunner.Result.SKIPPED
238 else:
239 # Convert JSON data files into numpy format on first pass
240 if not converted:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100241 convert2Numpy(test_path)
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000242 converted = True
243
244 if args.verbose:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100245 print(
246 "Running runner {} with test {}".format(
247 runnerName, test_path
248 )
249 )
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000250 try:
251 grc, gmsg = runner.runTestGraph()
252 rc, msg = runner.testResult(grc, gmsg)
253 except Exception as e:
254 msg = "System Under Test error: {}".format(e)
255 print(msg)
256 print(
257 "".join(
258 traceback.format_exception(
259 etype=type(e), value=e, tb=e.__traceback__
260 )
261 )
262 )
263 rc = TosaTestRunner.Result.INTERNAL_ERROR
264 except Exception as e:
265 msg = "Internal error: {}".format(e)
266 print(msg)
267 if not isinstance(e, TosaTestInvalid):
268 # Show stack trace on unexpected exceptions
269 print(
270 "".join(
271 traceback.format_exception(
272 etype=type(e), value=e, tb=e.__traceback__
273 )
274 )
275 )
276 rc = TosaTestRunner.Result.INTERNAL_ERROR
277 finally:
278 end_time = datetime.now()
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100279 result_queue.put(
280 (runnerName, test_path, rc, msg, end_time - start_time)
281 )
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000282
283 task_queue.task_done()
284
285 return True
286
287
288def loadSUTRunnerModules(args):
289 """Load in the system under test modules.
290
291 Returns a list of tuples of (runner_module, [argument list])
292 """
293 runnerList = []
294 # Remove any duplicates from the list
295 sut_module_list = list(set(args.sut_module))
296 for r in sut_module_list:
297 if args.verbose:
298 print("Loading module {}".format(r))
299
300 runner = importlib.import_module(r)
301
302 # Look for arguments associated with this runner
303 runnerArgPrefix = "{}:".format(r)
304 runnerArgList = []
305 for a in args.sut_module_args:
306 if a.startswith(runnerArgPrefix):
307 runnerArgList.append(a[len(runnerArgPrefix) :])
308 runnerList.append((runner, runnerArgList))
309
310 return runnerList
311
312
313def createXUnitResults(xunitFile, runnerList, resultLists, verbose):
314 """Create the xunit results file."""
315 xunit_result = xunit.xunit_results()
316
317 for runnerModule, _ in runnerList:
318 # Create test suite per system under test (runner)
319 runner = runnerModule.__name__
320 xunit_suite = xunit_result.create_suite(runner)
321
322 # Sort by test name
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100323 for test_path, rc, msg, time_delta in sorted(
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000324 resultLists[runner], key=lambda tup: tup[0]
325 ):
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100326 test_name = str(test_path)
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000327 xt = xunit.xunit_test(test_name, runner)
328
329 xt.time = str(
330 float(time_delta.seconds) + (float(time_delta.microseconds) * 1e-6)
331 )
332
333 testMsg = rc.name if not msg else "{}: {}".format(rc.name, msg)
334
335 if (
336 rc == TosaTestRunner.Result.EXPECTED_PASS
337 or rc == TosaTestRunner.Result.EXPECTED_FAILURE
338 ):
339 if verbose:
340 print("{} {} ({})".format(rc.name, test_name, runner))
341 elif rc == TosaTestRunner.Result.SKIPPED:
342 xt.skipped()
343 if verbose:
344 print("{} {} ({})".format(rc.name, test_name, runner))
345 else:
346 xt.failed(testMsg)
347 print("{} {} ({})".format(rc.name, test_name, runner))
348
349 xunit_suite.tests.append(xt)
350
351 xunit_result.write_results(xunitFile)
352
353
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100354def getTestsInPath(path):
355 # Recursively find any tests in this directory
356 desc_path = path / "desc.json"
357 if desc_path.is_file():
358 return [path]
359 elif path.is_dir():
360 path_list = []
361 for p in path.glob("*"):
362 path_list.extend(getTestsInPath(p))
363 return path_list
364 else:
365 return []
366
367
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000368def main(argv=None):
369 """Start worker threads to do the testing and outputs the results."""
370 args = parseArgs(argv)
371
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100372 # Set up some defaults
373 if args.ref_model_path is None:
374 args.ref_model_path = cmf.find_tosa_file(
375 cmf.TosaFileType.REF_MODEL, Path("reference_model"), False
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000376 )
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100377 if args.verify_lib_path is None:
378 args.verify_lib_path = cmf.find_tosa_file(
379 cmf.TosaFileType.VERIFY_LIBRARY, args.ref_model_path
380 )
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100381 if args.flatc_path is None:
382 args.flatc_path = cmf.find_tosa_file(
383 cmf.TosaFileType.FLATC, args.ref_model_path
384 )
385 if args.schema_path is None:
386 args.schema_path = cmf.find_tosa_file(
387 cmf.TosaFileType.SCHEMA, args.ref_model_path
388 )
389
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100390 # Always check as it will be needed for compliance
391 if not args.ref_model_path.is_file():
392 print(
393 f"Argument error: Reference Model not found - ({str(args.ref_model_path)})"
394 )
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000395 exit(2)
396
397 if args.test_list_file:
398 try:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100399 with args.test_list_file.open("r") as f:
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000400 args.test = f.read().splitlines()
401 except Exception as e:
402 print(
403 "Argument error: Cannot read list of tests in {}\n{}".format(
404 args.test_list_file, e
405 )
406 )
407 exit(2)
408
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100409 # Load in the runner modules and the ref model compliance module
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000410 runnerList = loadSUTRunnerModules(args)
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100411 complianceRunner = importlib.import_module(ttp.TOSA_REFCOMPLIANCE_RUNNER)
412 # Create a separate reporting runner list as the compliance runner may not
413 # be always run - depends on compliance testing
414 fullRunnerList = runnerList + [(complianceRunner, [])]
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000415
416 threads = []
417 taskQueue = queue.Queue()
418 resultQueue = queue.Queue()
419
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100420 for tdir in args.test:
421 tpath = Path(tdir)
422 if tpath.is_file():
423 if tpath.name != "README":
424 print(
425 "Warning: Skipping test {} as not a valid directory".format(tpath)
426 )
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000427 else:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100428 if args.recursive_tests:
429 tpath_list = getTestsInPath(tpath)
430 else:
431 tpath_list = [tpath]
432
433 for t in tpath_list:
434 taskQueue.put((t))
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000435
436 print(
437 "Running {} tests on {} system{} under test".format(
438 taskQueue.qsize(), len(runnerList), "s" if len(runnerList) > 1 else ""
439 )
440 )
441
442 for i in range(args.jobs):
443 t = threading.Thread(
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100444 target=workerThread,
445 args=(taskQueue, runnerList, complianceRunner, args, resultQueue),
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000446 )
447 t.setDaemon(True)
448 t.start()
449 threads.append(t)
450
451 taskQueue.join()
452
453 # Set up results lists for each system under test
454 resultLists = {}
455 results = {}
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100456 for runnerModule, _ in fullRunnerList:
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000457 runner = runnerModule.__name__
458 resultLists[runner] = []
459 results[runner] = [0] * len(TosaTestRunner.Result)
460
461 while True:
462 try:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100463 runner, test_path, rc, msg, time_delta = resultQueue.get(block=False)
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000464 resultQueue.task_done()
465 except queue.Empty:
466 break
467
468 # Limit error messages to make results easier to digest
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100469 if msg and len(msg) > ttp.MAX_XUNIT_TEST_MESSAGE:
470 half = int(ttp.MAX_XUNIT_TEST_MESSAGE / 2)
471 trimmed = len(msg) - ttp.MAX_XUNIT_TEST_MESSAGE
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000472 msg = "{} ...\nskipped {} bytes\n... {}".format(
473 msg[:half], trimmed, msg[-half:]
474 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100475 resultLists[runner].append((test_path, rc, msg, time_delta))
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000476 results[runner][rc] += 1
477
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100478 createXUnitResults(args.xunit_file, fullRunnerList, resultLists, args.verbose)
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000479
480 # Print out results for each system under test
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100481 for runnerModule, _ in fullRunnerList:
Jeremy Johnsonbe1a9402021-12-15 17:14:56 +0000482 runner = runnerModule.__name__
483 resultSummary = []
484 for result in TosaTestRunner.Result:
485 resultSummary.append(
486 "{} {}".format(results[runner][result], result.name.lower())
487 )
488 print("Totals ({}): {}".format(runner, ", ".join(resultSummary)))
489
490 return 0
491
492
493if __name__ == "__main__":
494 exit(main())