blob: 0fb5500ebf61a5a11407071274a08fc1b241cc7f [file] [log] [blame]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +01001#!/usr/bin/env python3
Jeremy Johnson35396f22023-01-04 17:05:25 +00002# Copyright (c) 2021-2023, ARM Limited.
Jeremy Johnson0ecfa372022-06-30 14:27:56 +01003# SPDX-License-Identifier: Apache-2.0
4"""Build conformance tests.
5
6Steps:
7- Specific input shapes (or tests) are specified and produced by using the
8 settings in the .json files.
9- Tests are selected to produce a good coverage.
10- Tests are run on the reference model to produce the correct output files.
11- Tests are converted into JSON format and saved to desired output directory.
12"""
13import argparse
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000014import copy
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010015import json
16import logging
17import multiprocessing as mp
18import os
19import shlex
20import shutil
21import subprocess
22from functools import partial
23from itertools import tee
24from pathlib import Path
25
26from conformance.test_select import Operator
27from convert2conformance.convert2conformance import main as c2c_main
28from distutils.dir_util import copy_tree
29
30logging.basicConfig()
31logger = logging.getLogger("tosa_verif_conformance_generator")
32
33# Configuration for each TOSA profile
34PROFILE_OPS_INFO = {
Jeremy Johnson88588622022-07-12 16:42:29 +010035 "tosa-bi": {
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010036 "operator_test_params": "tosa_base_profile_ops_info.json",
37 "framework_tests": "tosa_base_profile_framework_ops_info.json",
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +010038 },
39 "tosa-mi": {
40 # Note: This is just the extra tests not in the base profile!
41 "operator_test_params": "tosa_main_profile_ops_info.json",
42 "framework_tests": "tosa_main_profile_framework_ops_info.json",
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +010043 },
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010044}
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +010045PROFILES_ALL = "all"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010046
47LOCATION_REF_MODEL_BINARY = Path("build/reference_model/tosa_reference_model")
48
Jeremy Johnson93d43902022-09-27 12:26:14 +010049DEFAULT_SEED = 42
50
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000051# When there is a dictionary of generator argument lists (groups) only the
52# standard group will have negative tests generated for it
53STANDARD_GENERATOR_GROUP = "standard"
54
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010055
56class GenConformanceError(Exception):
57 """Generation error reporting exception."""
58
59 pass
60
61
62def _run_sh_command(args, cwd, full_cmd):
63 """Run an external command and capture stdout/stderr."""
64 # Quote the command line for printing
65 full_cmd_esc = [shlex.quote(x) for x in full_cmd]
66 if args.capture_output:
67 logger.debug(f"Command: {full_cmd_esc}")
68
69 rc = subprocess.run(
70 full_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd
71 )
72
73 if args.capture_output:
74 stdout = rc.stdout.decode("utf-8")
75 logger.debug(f"stdout: \n{stdout}")
76 if rc.returncode != 0:
77
78 raise Exception(
79 "Error running command: {}.\n{}".format(
80 " ".join(full_cmd_esc), rc.stderr.decode("utf-8")
81 )
82 )
83 return (rc.stdout, rc.stderr)
84
85
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000086def build_op_tests(
87 args, test_type, profile, operator, group, gen_args_list, gen_neg_dim_range
88):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010089 """Build tests for a given operator.
90
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000091 Builds a set of tests based on the given generator arguments list
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010092
93 Returns operator output directory
94 """
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010095 build_tests_cmd = "tosa_verif_build_tests"
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000096 op_build_dir = args.build_dir / profile / group
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010097
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +000098 build_cmd_base = [
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010099 build_tests_cmd,
100 "--filter",
101 operator,
102 "-o",
103 str(op_build_dir),
104 "--seed",
Jeremy Johnson93d43902022-09-27 12:26:14 +0100105 str(args.random_seed),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100106 ]
107
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000108 build_cmds_list = []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100109
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000110 if test_type in ["positive", "both"]:
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100111 # Append extra parameters and run test generator for each set of parameters.
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000112 for arglist in gen_args_list:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000113 build_cmd_pos_test = build_cmd_base.copy()
114 build_cmd_pos_test.extend(["--test-type", "positive"])
115 build_cmd_pos_test.extend(arglist)
116 build_cmds_list.append(build_cmd_pos_test)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100117
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000118 if test_type in ["negative", "both"]:
Jeremy Johnson35396f22023-01-04 17:05:25 +0000119 # Get target-dtypes options and any filter string to limit tests
Jeremy Johnson93d43902022-09-27 12:26:14 +0100120 target_dtypes_args = []
Jeremy Johnson35396f22023-01-04 17:05:25 +0000121 filter_str = None
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000122 for arglist in gen_args_list:
Jeremy Johnson93d43902022-09-27 12:26:14 +0100123 idx = 0
124 while idx < len(arglist):
125 if arglist[idx] == "--target-dtype":
126 if arglist[idx + 1] not in target_dtypes_args:
127 target_dtypes_args.extend(arglist[idx : idx + 2])
128 idx += 1 # skip over option (and then argument below)
Jeremy Johnson35396f22023-01-04 17:05:25 +0000129 elif arglist[idx] == "--filter":
130 filter_str = arglist[idx + 1]
131 idx += 1 # skip over option (and then argument below)
Jeremy Johnson93d43902022-09-27 12:26:14 +0100132 idx += 1
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000133 build_cmd_neg_test = build_cmd_base.copy()
Jeremy Johnson35396f22023-01-04 17:05:25 +0000134 if filter_str:
135 build_cmd_neg_test.extend(["--filter", filter_str])
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000136 build_cmd_neg_test.extend(["--test-type", "negative"])
Jeremy Johnson93d43902022-09-27 12:26:14 +0100137 # Limit sizes of negative tests
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000138 dim_range = gen_neg_dim_range if gen_neg_dim_range is not None else "1,16"
139
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000140 build_cmd_neg_test.extend(["--tensor-dim-range", dim_range])
141 build_cmd_neg_test.extend(target_dtypes_args)
142 build_cmds_list.append(build_cmd_neg_test)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100143
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000144 logger.debug(f"Creating {operator} tests with {len(build_cmds_list)} parameter(s)")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100145 error = False
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000146 for i, cmd in enumerate(build_cmds_list):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100147 try:
148 _run_sh_command(args, args.ref_model_dir.absolute(), cmd)
149 logger.info(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000150 f"{operator} test batch {(i+1)}/{len(build_cmds_list)} created successfully"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100151 )
152 except Exception as e:
153 logger.error(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000154 f"{operator} test batch {(i+1)}/{len(build_cmds_list)} unsuccessful, skipping"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100155 )
156 logger.error(f" build_op_tests error: {e} ")
157 error = True
158 if error:
159 raise (GenConformanceError())
160
161 return op_build_dir
162
163
164def _check_to_include_test(profile, test_name, exclude_negative_tests=False):
165 """Check test name for exclusions, return False to indicate excluded."""
166 excludes = ["ERRORIF"] if exclude_negative_tests else []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100167
168 for exclusion in excludes:
169 if f"_{exclusion}_" in test_name:
170 return False
171 return True
172
173
174def _get_all_tests_list(
175 profile, test_root_dir, operator, exclude_negative_tests=False, include_all=False
176):
177 """Create test list based on tests in the test_dir."""
178 test_dir = test_root_dir / operator
179 if not test_dir.is_dir():
180 # Tests are split into multiple dirs, for example: conv2d_1x1, conv2d_3x3
181 test_dir = test_root_dir
182 directories = [
183 tdir for tdir in test_dir.glob("*") if tdir.name.startswith(operator)
184 ]
185 else:
186 directories = [test_dir]
187
188 tests = []
189 for tdir in directories:
190 tests.extend(
191 [
192 test
193 for test in tdir.glob("*")
194 if include_all
195 or _check_to_include_test(profile, test.name, exclude_negative_tests)
196 ]
197 )
198 return tests
199
200
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100201def generate_results(args, profile, operator, op_build_dir, tests=None):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100202 """Run tests on reference model and save result to the test directory."""
203 num_cores = args.num_cores
204 run_tests_cmd = "tosa_verif_run_tests"
205
206 ref_model_path = args.ref_model_dir / LOCATION_REF_MODEL_BINARY
207 ref_cmd_base = ref_cmd = [
208 run_tests_cmd,
209 "--ref-model-path",
210 str(ref_model_path.absolute()),
211 "-j",
212 str(num_cores),
213 "-v",
214 "-t",
215 ]
216 ref_cmds = []
217
218 if not tests:
219 # Do not need to run ERRORIF tests as they don't have result files
220 tests = _get_all_tests_list(
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100221 profile, op_build_dir, operator, exclude_negative_tests=True
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100222 )
223
224 for test in tests:
225 ref_cmd = ref_cmd_base.copy()
226 ref_cmd.append(str(test))
227 ref_cmds.append(ref_cmd)
228
229 fail_string = "UNEXPECTED_FAILURE"
230 failed_counter = 0
231
232 job_pool = mp.Pool(args.num_cores)
233 sh_partial = partial(_run_sh_command, args, args.ref_model_dir.absolute())
234 pool_results = job_pool.map(sh_partial, ref_cmds)
235 job_pool.close()
236 job_pool.join()
237
238 # Use captured output for run_sh_command to work out if test passed.
239 for i, rc in enumerate(pool_results):
240 if fail_string in str(rc[0]):
241 logger.error(f"Test {i+1}/{len(ref_cmds)}: {ref_cmds[i][-1]} failed.")
242 failed_counter += 1
243 else:
244 logger.info(f"Test {i+1}/{len(ref_cmds)}: {ref_cmds[i][-1]} passed.")
245
246 logger.info(f"{len(ref_cmds)-failed_counter}/{len(ref_cmds)} tests passed")
247 logger.info("Ran tests on model and saved results of passing tests")
248
249
250def convert_tests(
251 args,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100252 profile,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100253 operator,
254 op_build_dir,
255 output_dir,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100256 op_profiles_list,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100257 tests=None,
258 group=None,
259 trim_op_subdir=False,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000260 tags=None,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100261):
262 """Convert tests to JSON and save to output directory."""
263 ref_model_dir = args.ref_model_dir
264
265 if group:
266 output_dir = output_dir / group
267
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000268 c2c_args_base = ["--strict", "--ref-model-directory", str(ref_model_dir)]
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100269 # This op maybe in more than one profile - e.g. tosa_bi and tosa_mi
270 # even if we are only producing tests for tosa_mi
271 for op_profile in op_profiles_list:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000272 c2c_args_base.extend(["--profile", op_profile])
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000273 if tags is not None:
274 for tag in tags:
275 c2c_args_base.extend(["--tag", tag])
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100276 if args.framework_schema:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000277 c2c_args_base.extend(["--framework-schema", str(args.framework_schema)])
278 c2c_args_base.append("--output-directory")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100279
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000280 c2c_args_list = []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100281
282 if not tests:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100283 tests = _get_all_tests_list(profile, op_build_dir, operator)
284 logger.info(f"Converting all {profile} profile tests")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100285
286 # Controls if we copy the tests in their operator sub-directory or not
287 output_dir_relative_pos = -1 if trim_op_subdir else -2
288 for test in tests:
289 logger.info(f"Test chosen: {test}")
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000290 c2c_args = c2c_args_base.copy()
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100291 full_output_directory = output_dir / test.relative_to(
292 *test.parts[:output_dir_relative_pos]
293 )
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000294 c2c_args.append(str(full_output_directory))
295 c2c_args.append(str(test))
296 c2c_args_list.append(c2c_args)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100297
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000298 if len(c2c_args_list) == 0:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000299 logger.error(
300 f"No tests found for {operator}. Nothing to convert in {op_build_dir}"
301 )
302 raise (GenConformanceError())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100303
304 job_pool = mp.Pool(args.num_cores)
305
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000306 pool_results = job_pool.map(c2c_main, c2c_args_list)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100307 job_pool.close()
308 job_pool.join()
309
310 failed_counter = 0
311 for i, result in enumerate(pool_results):
312 if result != 0:
313 logger.error(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000314 f"test {i+1}/{len(c2c_args_list)}: {c2c_args_list[i][-1]} failed to convert."
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100315 )
316 failed_counter += 1
317 else:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000318 logger.info(
319 f"test {i+1}/{len(c2c_args_list)}: {c2c_args_list[i][-1]} converted"
320 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100321 logger.info(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000322 f"{len(c2c_args_list)-failed_counter}/{len(c2c_args_list)} tests successfully converted"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100323 )
324
325 if failed_counter > 0:
326 logger.error(f"Stopping due to {failed_counter} test conversion errors")
327 raise (GenConformanceError())
328
329 logger.info("Converted tests to JSON and saved to output directory")
330
331 return output_dir
332
333
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100334def get_op_tests_selection(
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000335 args,
336 profile,
337 operator,
338 op_build_dir,
339 selection_config,
340 negative=False,
341 ignore_missing=False,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100342):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100343 """Use test picker to get subsection of tests generated."""
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000344 # Need a full copy of the config as the selector updates it
345 config = copy.deepcopy(selection_config)
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100346 logger.info("Choosing {} tests".format(("negative" if negative else "positive")))
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100347 try:
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100348 op = Operator.registry[operator](
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000349 op_build_dir, config, negative=negative, ignore_missing=ignore_missing
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100350 )
351 except KeyError:
352 logger.error(f"{operator} operator is not supported by test_select")
353 raise (GenConformanceError())
354
355 return op.select_tests()
356
357
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100358def check_op_tests(args, profile, operator, output_dir):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100359 """Move test folders than contain files larger than 30MB to new directory."""
360 destination_dir = str(args.output_dir) + "_large_files"
361
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100362 tests = _get_all_tests_list(profile, output_dir, operator, include_all=True)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100363 if not tests:
364 logger.error(
365 f"Couldn't find any tests to size check for {operator} in {output_dir}"
366 )
367 raise (GenConformanceError())
368
369 for tdir in tests:
370 move_dir = False
371 test_files = [file for file in tdir.glob("*")]
372 for file in test_files:
373 file_size = os.stat(file).st_size / 1024**2
374 if file_size > 30:
375 move_dir = True
376
377 if move_dir:
378 move_destination = destination_dir / tdir.relative_to(output_dir)
379 logger.warning(
380 f"{tdir.relative_to(output_dir)} contains files that are too large (>30MB), test moved to new folder: {destination_dir}"
381 )
382
383 if move_destination.is_dir():
384 logger.warning(
385 f"{move_destination} directory already exists, deleting existing."
386 )
387 shutil.rmtree(str(move_destination))
388 shutil.move(str(tdir), move_destination)
389
390
391def copy_rename_framework_tests(args, operator, test_picks):
392 """Copy framework tests into new folder and rename them if needed.
393
394 The tests are renamed to match the framework operator names if an
395 alternate name has been used instead.
396 """
397 framework_tests_dir = args.framework_tests_dir
398 new_tests_dir = args.build_dir / "frameworks" / operator
399 os.makedirs(new_tests_dir, exist_ok=True)
400
401 # Get the framework tests operator name
402 if "alternate_names" in test_picks[operator]:
403 alternate_names = test_picks[operator]["alternate_names"]
404 else:
405 alternate_names = [operator]
406
407 # Get the alternate named test directories for the operator
408 for alt_name in alternate_names:
409 test_prefix = f"test_{alt_name}"
410 test_dirs = list(framework_tests_dir.glob(f"{test_prefix}_*"))
411
412 # Copy tests to new directory and rename to match framework operator names
413 # - if there is just 1 alternate name, replace the full test prefix
414 # test_add_... -> add_...
415 # - if there are multiple alternate names, just replace the "test"
416 # test_concatv2_... -> concatenation_concatv2_...
417 old_prefix = test_prefix if len(alternate_names) == 1 else "test"
418
419 for tdir in test_dirs:
420 new_test_name = tdir.name.replace(old_prefix, operator)
421 copy_destination = new_tests_dir / new_test_name
422 logger.debug(f"copying test folder {tdir} to {copy_destination}")
423 copy_tree(str(tdir), str(copy_destination))
424
425 logger.info(f"Copied and renamed {len(test_dirs)} framework test folders")
426 return new_tests_dir.parent
427
428
429def get_framework_tests_selection(args, operator, test_picks, op_build_dir):
430 """Get the list of pre-chosen tests with relative paths."""
431 try:
432 tests = test_picks[operator]["tests"]
433 except KeyError:
434 logger.error(f"Framework test selection not defined for {operator} operator")
435 raise (GenConformanceError())
436
437 test_paths = [op_build_dir / operator / test for test in tests]
438 return test_paths
439
440
441def parse_args(argv=None):
442 """Parse the arguments."""
443 parser = argparse.ArgumentParser()
Jeremy Johnson88588622022-07-12 16:42:29 +0100444 profiles = list(PROFILE_OPS_INFO.keys())
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100445 profiles.append(PROFILES_ALL)
Jeremy Johnson88588622022-07-12 16:42:29 +0100446 parser.add_argument(
447 "--profile",
448 dest="profile",
449 choices=profiles,
450 default=profiles[0],
451 type=str,
452 help=f"TOSA profile (default is {profiles[0]})",
453 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100454 parser.add_argument(
455 "--operators",
456 type=str,
457 nargs="*",
458 help="The operator(s) to create tests for, if not supplied all tests will be created",
459 )
460 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +0100461 "--unit-tests",
462 dest="unit_tests",
463 choices=["operator", "framework", "both"],
464 default="operator",
465 type=str,
466 help="Which unit tests are produced (default is operator)",
467 )
468 parser.add_argument(
469 "--test-type",
470 dest="test_type",
471 choices=["positive", "negative", "both"],
472 default="both",
473 type=str,
474 help="Type of tests produced (default is both)",
475 )
476 parser.add_argument(
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100477 "--ref-model-directory",
478 dest="ref_model_dir",
479 type=Path,
480 required=True,
481 help="Reference Model directory (must be pre-built)",
482 )
Jeremy Johnson88588622022-07-12 16:42:29 +0100483 parser.add_argument(
Jeremy Johnson93d43902022-09-27 12:26:14 +0100484 "--seed",
485 dest="random_seed",
486 default=DEFAULT_SEED,
487 type=int,
488 help="Random test seed",
489 )
490 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +0100491 "--framework-tests-directory",
492 dest="framework_tests_dir",
493 type=Path,
494 default=Path.cwd() / "tests",
495 help="The pre-built framework tests directory (default is tests)",
496 )
497 parser.add_argument(
498 "--framework-schema",
499 dest="framework_schema",
500 type=Path,
501 help="Framework flatbuffers schema needed to convert framework models",
502 )
503 parser.add_argument(
504 "--build-directory",
505 dest="build_dir",
506 type=Path,
507 default=Path.cwd() / "conformance_build",
508 help="Temporary build directory for files created during this process (default is conformance_build)",
509 )
510 parser.add_argument(
511 "--output-directory",
512 dest="output_dir",
513 type=Path,
514 default=Path.cwd() / "conformance",
515 help="Output directory (default is conformance)",
516 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100517 script_dir = Path(__file__).parent.absolute()
518 parser.add_argument(
519 "--test-param-json-directory",
520 dest="param_json_dir",
521 type=Path,
522 default=script_dir,
Jeremy Johnson88588622022-07-12 16:42:29 +0100523 help=f"Test parameters (ops info) JSON file directory (default is {script_dir})",
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100524 )
525 parser.add_argument(
526 "--convert-all-tests",
527 action="store_true",
528 help="Converts all tests instead of those picked by test_select",
529 )
530 parser.add_argument(
531 "--keep-large-files",
532 action="store_true",
533 help="Keeps tests that contain files larger than 30MB in output directory",
534 )
535 parser.add_argument(
536 "--capture-output",
537 action="store_true",
538 help="Prints output of running sh commands",
539 )
540 parser.add_argument(
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100541 "-j",
542 dest="num_cores",
543 type=int,
544 default=6,
545 help="Number of simultaneous jobs to split the tasks into for multiprocessing",
546 )
547 parser.add_argument(
548 "-v",
549 dest="verbosity",
550 action="count",
551 default=0,
552 help="Verbosity (can be used multiple times for more details)",
553 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100554 args = parser.parse_args(argv)
555
556 return args
557
558
559def main():
560 args = parse_args()
561
562 if not args.ref_model_dir.is_dir():
563 logger.error(
564 f"Missing or invalid reference model directory: {args.ref_model_dir}"
565 )
566 return 2
567 else:
568 ref_model = args.ref_model_dir / LOCATION_REF_MODEL_BINARY
569 if not ref_model.is_file():
570 logger.error(
571 f"{LOCATION_REF_MODEL_BINARY} not found in {args.ref_model_dir}\nHave you built the reference model?"
572 )
573 return 2
574 if args.unit_tests in ["framework", "both"]:
Jeremy Johnsonc1d1c632023-08-02 17:21:36 +0100575 logger.warning(
576 "DEPRECATION - Framework tests are not part of TOSA conformance testing"
577 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100578 if not args.framework_schema:
579 logger.error(
580 "Need to supply location of Framework flatbuffers schema via --framework-schema"
581 )
582 return 2
583 if not args.framework_tests_dir.is_dir():
584 logger.error(
585 f"Missing or invalid framework tests directory: {args.framework_tests_dir}"
586 )
587 return 2
588
589 loglevels = (logging.WARNING, logging.INFO, logging.DEBUG)
590 loglevel = loglevels[min(args.verbosity, len(loglevels) - 1)]
591 logger.setLevel(loglevel)
592 # Set other loggers the same
593 logging.getLogger("test_select").setLevel(loglevel)
594 logging.getLogger("convert2conformance").setLevel(loglevel)
595
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100596 print(f"Output directory: {args.output_dir}")
597
Jeremy Johnson93d43902022-09-27 12:26:14 +0100598 if args.random_seed != DEFAULT_SEED:
599 logger.warning(
600 "Random test seed changed from default, tests will not match official conformance"
601 )
602
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100603 args.build_dir = args.build_dir.resolve()
604 logger.debug(f"Creating build directory: {args.build_dir}")
605 args.build_dir.mkdir(parents=True, exist_ok=True)
606
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100607 # TODO: For tosa-mi should really generate tosa-bi profile as well
608 # - for now leave it as subset instead of as superset (for testing)
609 if args.profile == PROFILES_ALL:
610 profiles = list(PROFILE_OPS_INFO.keys())
611 else:
612 profiles = [args.profile]
613
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100614 try:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100615 for profile in profiles:
616 print(f"Creating conformance tests for TOSA {profile} profile")
617 # Framework unit tests
618 if args.unit_tests in ["framework", "both"]:
619 logger.debug("Creating FRAMEWORK unit tests")
620 test_picks_file = (
621 args.param_json_dir / PROFILE_OPS_INFO[profile]["framework_tests"]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100622 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100623 try:
624 with open(test_picks_file, "r") as fd:
625 test_picks = json.load(fd)
626 except Exception as e:
627 logger.error(
628 f"Couldn't load framework tests info - {test_picks_file}: {e}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100629 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100630 return 1
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100631
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100632 operators = args.operators
633 if not operators:
634 # Create tests for all the operators
635 operators = list(test_picks.keys())
636
637 root_output_dir = (
638 args.output_dir / "frameworks" / "tflite" / "operators"
639 )
640 for op in operators:
641 logger.info(f"FRAMEWORK OP: {op}")
642 if op not in test_picks:
643 logger.warning(
644 f"Framework op {op} not found in {test_picks_file} - skipping"
645 )
646 continue
647
648 op_profiles_list = test_picks[op]["profile"]
649 if (
650 args.profile != PROFILES_ALL
651 and args.profile not in op_profiles_list
652 ):
653 # Skip this operator as not part of the profile chosen
654 logger.debug(f"Skipping {op} as not part of {args.profile}")
655 continue
656
657 logger.debug(f"Copying and renaming {op}")
658 framework_test_dir = copy_rename_framework_tests(
659 args, op, test_picks
660 )
661
662 if args.convert_all_tests:
663 logger.debug("Running and converting all framework tests")
664 framework_tests = None # Don't select any
665 else:
666 logger.debug("Running and converting selected framework tests")
667 framework_tests = get_framework_tests_selection(
668 args, op, test_picks, framework_test_dir
669 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100670 convert_tests(
671 args,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100672 profile,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100673 op,
674 framework_test_dir,
675 root_output_dir,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100676 op_profiles_list,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100677 tests=framework_tests,
678 trim_op_subdir=True,
679 )
680
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100681 # Operator unit tests
682 if args.unit_tests in ["operator", "both"]:
683 logger.debug("Creating OPERATOR unit tests")
684 test_params_file = (
685 args.param_json_dir
686 / PROFILE_OPS_INFO[profile]["operator_test_params"]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100687 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100688 try:
689 with open(test_params_file, "r") as fd:
690 test_params = json.load(fd)
691 except Exception as e:
692 logger.error(
693 f"Couldn't load operator test params - {test_params_file}: {e}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100694 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100695 return 1
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100696
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100697 operators = args.operators
698 if not operators:
699 # Create tests for all the operators
700 operators = list(test_params.keys())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100701
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100702 for op in operators:
703 logger.info(f"OPERATOR: {op}")
704 if op not in test_params:
705 logger.warning(
706 f"{op} operator parameters not found in {test_params_file} - skipping"
707 )
708 continue
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100709
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100710 op_profiles_list = test_params[op]["profile"]
711 if (
712 args.profile != PROFILES_ALL
713 and args.profile not in op_profiles_list
714 ):
715 # Skip this operator as not part of the profile chosen
716 logger.debug(f"Skipping {op} as not part of {args.profile}")
717 continue
718
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100719 operator_group = test_params[op]["group"]
720 root_output_dir = args.output_dir / "operators"
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000721
722 # Iterate through the generation groups selecting tests from each
723 for gen_name, gen_dict in test_params[op]["generation"].items():
724 no_neg_tests = (
725 "no_negative_tests" in gen_dict
726 and gen_dict["no_negative_tests"] == "true"
727 )
728
729 if no_neg_tests:
730 if args.test_type == "negative":
731 logger.info(
732 f"No negative tests for {op} / generation group {gen_name}"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100733 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000734 continue
735 # Only produce positive tests
736 test_type = "positive"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100737 else:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000738 test_type = args.test_type
739
740 gen_neg_dim_range = (
741 gen_dict["negative_dim_range"]
742 if "negative_dim_range" in gen_dict
743 else None
744 )
745
746 ignore_missing = gen_name != STANDARD_GENERATOR_GROUP
747 tags = (
748 [gen_name] if gen_name != STANDARD_GENERATOR_GROUP else None
749 )
750
751 op_build_dir = build_op_tests(
752 args,
753 test_type,
754 profile,
755 op,
756 gen_name,
757 gen_dict["generator_args"],
758 gen_neg_dim_range,
759 )
760
Jeremy Johnson0c716862023-04-13 17:18:19 +0100761 # Work out which selection criteria we are using
762 if "selector" in gen_dict:
763 selector_name = gen_dict["selector"]
764 if selector_name not in test_params[op]["selection"]:
765 logger.warn(
766 f"Could not find {selector_name} in selection dict for {op} - using default"
767 )
768 selector_name = "default"
769 else:
770 selector_name = "default"
771 if selector_name not in test_params[op]["selection"]:
772 logger.error(
773 f"Could not find {selector_name} in selection dict for {op}"
774 )
775 raise (GenConformanceError())
776
777 # Selection criteria
778 selection_config = test_params[op]["selection"][selector_name]
779
780 if args.convert_all_tests or (
781 "all" in selection_config
782 and selection_config["all"] == "true"
783 ):
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000784 logger.debug(f"Running and converting all {op} tests")
785 generate_results(args, profile, op, op_build_dir)
786 operator_test_list = None
787 else:
788 logger.debug(
789 f"Running and converting selection of {op} tests"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100790 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000791 if test_type in ["positive", "both"]:
792 tests_gen, tests_gen2 = tee(
793 get_op_tests_selection(
794 args,
795 profile,
796 op,
797 op_build_dir,
798 selection_config,
799 ignore_missing=ignore_missing,
800 )
801 )
802 generate_results(
803 args, profile, op, op_build_dir, tests_gen
804 )
805 operator_test_list = list(tests_gen2)
806 else:
807 operator_test_list = []
808 if test_type in ["negative", "both"]:
809 operator_test_list.extend(
810 get_op_tests_selection(
811 args,
812 profile,
813 op,
814 op_build_dir,
815 selection_config,
816 negative=True,
817 )
818 )
819 output_dir = convert_tests(
820 args,
821 profile,
822 op,
823 op_build_dir,
824 root_output_dir,
825 op_profiles_list,
826 tests=operator_test_list,
827 group=operator_group,
828 tags=tags,
829 )
830 if not args.keep_large_files:
831 check_op_tests(args, profile, op, output_dir)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100832 except GenConformanceError:
833 return 1
834
835 return 0
836
837
838if __name__ == "__main__":
839 exit(main())