blob: 2d9dad343669a4c0ccfcaddd1fd5086ea0be467d [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"]:
575 if not args.framework_schema:
576 logger.error(
577 "Need to supply location of Framework flatbuffers schema via --framework-schema"
578 )
579 return 2
580 if not args.framework_tests_dir.is_dir():
581 logger.error(
582 f"Missing or invalid framework tests directory: {args.framework_tests_dir}"
583 )
584 return 2
585
586 loglevels = (logging.WARNING, logging.INFO, logging.DEBUG)
587 loglevel = loglevels[min(args.verbosity, len(loglevels) - 1)]
588 logger.setLevel(loglevel)
589 # Set other loggers the same
590 logging.getLogger("test_select").setLevel(loglevel)
591 logging.getLogger("convert2conformance").setLevel(loglevel)
592
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100593 print(f"Output directory: {args.output_dir}")
594
Jeremy Johnson93d43902022-09-27 12:26:14 +0100595 if args.random_seed != DEFAULT_SEED:
596 logger.warning(
597 "Random test seed changed from default, tests will not match official conformance"
598 )
599
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100600 args.build_dir = args.build_dir.resolve()
601 logger.debug(f"Creating build directory: {args.build_dir}")
602 args.build_dir.mkdir(parents=True, exist_ok=True)
603
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100604 # TODO: For tosa-mi should really generate tosa-bi profile as well
605 # - for now leave it as subset instead of as superset (for testing)
606 if args.profile == PROFILES_ALL:
607 profiles = list(PROFILE_OPS_INFO.keys())
608 else:
609 profiles = [args.profile]
610
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100611 try:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100612 for profile in profiles:
613 print(f"Creating conformance tests for TOSA {profile} profile")
614 # Framework unit tests
615 if args.unit_tests in ["framework", "both"]:
616 logger.debug("Creating FRAMEWORK unit tests")
617 test_picks_file = (
618 args.param_json_dir / PROFILE_OPS_INFO[profile]["framework_tests"]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100619 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100620 try:
621 with open(test_picks_file, "r") as fd:
622 test_picks = json.load(fd)
623 except Exception as e:
624 logger.error(
625 f"Couldn't load framework tests info - {test_picks_file}: {e}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100626 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100627 return 1
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100628
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100629 operators = args.operators
630 if not operators:
631 # Create tests for all the operators
632 operators = list(test_picks.keys())
633
634 root_output_dir = (
635 args.output_dir / "frameworks" / "tflite" / "operators"
636 )
637 for op in operators:
638 logger.info(f"FRAMEWORK OP: {op}")
639 if op not in test_picks:
640 logger.warning(
641 f"Framework op {op} not found in {test_picks_file} - skipping"
642 )
643 continue
644
645 op_profiles_list = test_picks[op]["profile"]
646 if (
647 args.profile != PROFILES_ALL
648 and args.profile not in op_profiles_list
649 ):
650 # Skip this operator as not part of the profile chosen
651 logger.debug(f"Skipping {op} as not part of {args.profile}")
652 continue
653
654 logger.debug(f"Copying and renaming {op}")
655 framework_test_dir = copy_rename_framework_tests(
656 args, op, test_picks
657 )
658
659 if args.convert_all_tests:
660 logger.debug("Running and converting all framework tests")
661 framework_tests = None # Don't select any
662 else:
663 logger.debug("Running and converting selected framework tests")
664 framework_tests = get_framework_tests_selection(
665 args, op, test_picks, framework_test_dir
666 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100667 convert_tests(
668 args,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100669 profile,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100670 op,
671 framework_test_dir,
672 root_output_dir,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100673 op_profiles_list,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100674 tests=framework_tests,
675 trim_op_subdir=True,
676 )
677
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100678 # Operator unit tests
679 if args.unit_tests in ["operator", "both"]:
680 logger.debug("Creating OPERATOR unit tests")
681 test_params_file = (
682 args.param_json_dir
683 / PROFILE_OPS_INFO[profile]["operator_test_params"]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100684 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100685 try:
686 with open(test_params_file, "r") as fd:
687 test_params = json.load(fd)
688 except Exception as e:
689 logger.error(
690 f"Couldn't load operator test params - {test_params_file}: {e}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100691 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100692 return 1
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100693
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100694 operators = args.operators
695 if not operators:
696 # Create tests for all the operators
697 operators = list(test_params.keys())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100698
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100699 for op in operators:
700 logger.info(f"OPERATOR: {op}")
701 if op not in test_params:
702 logger.warning(
703 f"{op} operator parameters not found in {test_params_file} - skipping"
704 )
705 continue
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100706
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100707 op_profiles_list = test_params[op]["profile"]
708 if (
709 args.profile != PROFILES_ALL
710 and args.profile not in op_profiles_list
711 ):
712 # Skip this operator as not part of the profile chosen
713 logger.debug(f"Skipping {op} as not part of {args.profile}")
714 continue
715
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100716 operator_group = test_params[op]["group"]
717 root_output_dir = args.output_dir / "operators"
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000718
719 # Iterate through the generation groups selecting tests from each
720 for gen_name, gen_dict in test_params[op]["generation"].items():
721 no_neg_tests = (
722 "no_negative_tests" in gen_dict
723 and gen_dict["no_negative_tests"] == "true"
724 )
725
726 if no_neg_tests:
727 if args.test_type == "negative":
728 logger.info(
729 f"No negative tests for {op} / generation group {gen_name}"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100730 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000731 continue
732 # Only produce positive tests
733 test_type = "positive"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100734 else:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000735 test_type = args.test_type
736
737 gen_neg_dim_range = (
738 gen_dict["negative_dim_range"]
739 if "negative_dim_range" in gen_dict
740 else None
741 )
742
743 ignore_missing = gen_name != STANDARD_GENERATOR_GROUP
744 tags = (
745 [gen_name] if gen_name != STANDARD_GENERATOR_GROUP else None
746 )
747
748 op_build_dir = build_op_tests(
749 args,
750 test_type,
751 profile,
752 op,
753 gen_name,
754 gen_dict["generator_args"],
755 gen_neg_dim_range,
756 )
757
758 if args.convert_all_tests:
759 logger.debug(f"Running and converting all {op} tests")
760 generate_results(args, profile, op, op_build_dir)
761 operator_test_list = None
762 else:
763 logger.debug(
764 f"Running and converting selection of {op} tests"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100765 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000766 # Work out which selection criteria we are using
767 if "selector" in gen_dict:
768 selector_name = gen_dict["selector"]
769 if selector_name not in test_params[op]["selection"]:
770 logger.warn(
771 f"Could not find {selector_name} in selection dict for {op} - using default"
772 )
773 selector_name = "default"
774 else:
775 selector_name = "default"
776 if selector_name not in test_params[op]["selection"]:
777 logger.error(
778 f"Could not find {selector_name} in selection dict for {op}"
779 )
780 raise (GenConformanceError())
781
782 # Selection criteria
783 selection_config = test_params[op]["selection"][
784 selector_name
785 ]
786 if test_type in ["positive", "both"]:
787 tests_gen, tests_gen2 = tee(
788 get_op_tests_selection(
789 args,
790 profile,
791 op,
792 op_build_dir,
793 selection_config,
794 ignore_missing=ignore_missing,
795 )
796 )
797 generate_results(
798 args, profile, op, op_build_dir, tests_gen
799 )
800 operator_test_list = list(tests_gen2)
801 else:
802 operator_test_list = []
803 if test_type in ["negative", "both"]:
804 operator_test_list.extend(
805 get_op_tests_selection(
806 args,
807 profile,
808 op,
809 op_build_dir,
810 selection_config,
811 negative=True,
812 )
813 )
814 output_dir = convert_tests(
815 args,
816 profile,
817 op,
818 op_build_dir,
819 root_output_dir,
820 op_profiles_list,
821 tests=operator_test_list,
822 group=operator_group,
823 tags=tags,
824 )
825 if not args.keep_large_files:
826 check_op_tests(args, profile, op, output_dir)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100827 except GenConformanceError:
828 return 1
829
830 return 0
831
832
833if __name__ == "__main__":
834 exit(main())