blob: c9a0b3a25df1cb9ee09a91709db96f1a89ebb064 [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
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +010026import conformance.model_files as cmf
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010027from conformance.test_select import Operator
28from convert2conformance.convert2conformance import main as c2c_main
29from distutils.dir_util import copy_tree
30
31logging.basicConfig()
32logger = logging.getLogger("tosa_verif_conformance_generator")
33
34# Configuration for each TOSA profile
35PROFILE_OPS_INFO = {
Jeremy Johnson88588622022-07-12 16:42:29 +010036 "tosa-bi": {
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010037 "operator_test_params": "tosa_base_profile_ops_info.json",
38 "framework_tests": "tosa_base_profile_framework_ops_info.json",
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +010039 },
40 "tosa-mi": {
41 # Note: This is just the extra tests not in the base profile!
42 "operator_test_params": "tosa_main_profile_ops_info.json",
43 "framework_tests": "tosa_main_profile_framework_ops_info.json",
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +010044 },
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010045}
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +010046PROFILES_ALL = "all"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010047
Jeremy Johnson93d43902022-09-27 12:26:14 +010048DEFAULT_SEED = 42
49
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000050# When there is a dictionary of generator argument lists (groups) only the
51# standard group will have negative tests generated for it
52STANDARD_GENERATOR_GROUP = "standard"
53
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010054
55class GenConformanceError(Exception):
56 """Generation error reporting exception."""
57
58 pass
59
60
61def _run_sh_command(args, cwd, full_cmd):
62 """Run an external command and capture stdout/stderr."""
63 # Quote the command line for printing
64 full_cmd_esc = [shlex.quote(x) for x in full_cmd]
65 if args.capture_output:
66 logger.debug(f"Command: {full_cmd_esc}")
67
68 rc = subprocess.run(
69 full_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd
70 )
71
72 if args.capture_output:
73 stdout = rc.stdout.decode("utf-8")
74 logger.debug(f"stdout: \n{stdout}")
75 if rc.returncode != 0:
76
77 raise Exception(
78 "Error running command: {}.\n{}".format(
79 " ".join(full_cmd_esc), rc.stderr.decode("utf-8")
80 )
81 )
82 return (rc.stdout, rc.stderr)
83
84
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000085def build_op_tests(
Jeremy Johnson1271c442023-09-05 11:39:26 +010086 args,
87 test_type,
88 profile,
89 operator,
90 group,
91 gen_args_list,
92 gen_neg_dim_range,
93 supports=[],
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000094):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010095 """Build tests for a given operator.
96
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000097 Builds a set of tests based on the given generator arguments list
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010098
99 Returns operator output directory
100 """
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100101 build_tests_cmd = "tosa_verif_build_tests"
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000102 op_build_dir = args.build_dir / profile / group
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100103
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000104 build_cmd_base = [
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100105 build_tests_cmd,
106 "--filter",
107 operator,
108 "-o",
109 str(op_build_dir),
110 "--seed",
Jeremy Johnson93d43902022-09-27 12:26:14 +0100111 str(args.random_seed),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100112 ]
113
Jeremy Johnson1271c442023-09-05 11:39:26 +0100114 if "lazy_data_gen" in supports and args.lazy_data_generation:
115 build_cmd_base.append("--lazy-data-generation")
116
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000117 build_cmds_list = []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100118
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000119 if test_type in ["positive", "both"]:
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100120 # Append extra parameters and run test generator for each set of parameters.
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000121 for arglist in gen_args_list:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000122 build_cmd_pos_test = build_cmd_base.copy()
123 build_cmd_pos_test.extend(["--test-type", "positive"])
124 build_cmd_pos_test.extend(arglist)
125 build_cmds_list.append(build_cmd_pos_test)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100126
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000127 if test_type in ["negative", "both"]:
Jeremy Johnson35396f22023-01-04 17:05:25 +0000128 # Get target-dtypes options and any filter string to limit tests
Jeremy Johnson93d43902022-09-27 12:26:14 +0100129 target_dtypes_args = []
Jeremy Johnson35396f22023-01-04 17:05:25 +0000130 filter_str = None
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000131 for arglist in gen_args_list:
Jeremy Johnson93d43902022-09-27 12:26:14 +0100132 idx = 0
133 while idx < len(arglist):
134 if arglist[idx] == "--target-dtype":
135 if arglist[idx + 1] not in target_dtypes_args:
136 target_dtypes_args.extend(arglist[idx : idx + 2])
137 idx += 1 # skip over option (and then argument below)
Jeremy Johnson35396f22023-01-04 17:05:25 +0000138 elif arglist[idx] == "--filter":
139 filter_str = arglist[idx + 1]
140 idx += 1 # skip over option (and then argument below)
Jeremy Johnson93d43902022-09-27 12:26:14 +0100141 idx += 1
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000142 build_cmd_neg_test = build_cmd_base.copy()
Jeremy Johnson35396f22023-01-04 17:05:25 +0000143 if filter_str:
144 build_cmd_neg_test.extend(["--filter", filter_str])
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000145 build_cmd_neg_test.extend(["--test-type", "negative"])
Jeremy Johnson93d43902022-09-27 12:26:14 +0100146 # Limit sizes of negative tests
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000147 dim_range = gen_neg_dim_range if gen_neg_dim_range is not None else "1,16"
148
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000149 build_cmd_neg_test.extend(["--tensor-dim-range", dim_range])
150 build_cmd_neg_test.extend(target_dtypes_args)
151 build_cmds_list.append(build_cmd_neg_test)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100152
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000153 logger.debug(f"Creating {operator} tests with {len(build_cmds_list)} parameter(s)")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100154 error = False
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000155 for i, cmd in enumerate(build_cmds_list):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100156 try:
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100157 _run_sh_command(args, args.ref_model_path.parent.absolute(), cmd)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100158 logger.info(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000159 f"{operator} test batch {(i+1)}/{len(build_cmds_list)} created successfully"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100160 )
161 except Exception as e:
162 logger.error(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000163 f"{operator} test batch {(i+1)}/{len(build_cmds_list)} unsuccessful, skipping"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100164 )
165 logger.error(f" build_op_tests error: {e} ")
166 error = True
167 if error:
168 raise (GenConformanceError())
169
170 return op_build_dir
171
172
173def _check_to_include_test(profile, test_name, exclude_negative_tests=False):
174 """Check test name for exclusions, return False to indicate excluded."""
175 excludes = ["ERRORIF"] if exclude_negative_tests else []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100176
177 for exclusion in excludes:
178 if f"_{exclusion}_" in test_name:
179 return False
180 return True
181
182
183def _get_all_tests_list(
184 profile, test_root_dir, operator, exclude_negative_tests=False, include_all=False
185):
186 """Create test list based on tests in the test_dir."""
187 test_dir = test_root_dir / operator
188 if not test_dir.is_dir():
189 # Tests are split into multiple dirs, for example: conv2d_1x1, conv2d_3x3
190 test_dir = test_root_dir
191 directories = [
192 tdir for tdir in test_dir.glob("*") if tdir.name.startswith(operator)
193 ]
194 else:
195 directories = [test_dir]
196
197 tests = []
198 for tdir in directories:
199 tests.extend(
200 [
201 test
202 for test in tdir.glob("*")
203 if include_all
204 or _check_to_include_test(profile, test.name, exclude_negative_tests)
205 ]
206 )
207 return tests
208
209
Jeremy Johnson1271c442023-09-05 11:39:26 +0100210def generate_results(args, profile, operator, op_build_dir, supports=[], tests=None):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100211 """Run tests on reference model and save result to the test directory."""
Jeremy Johnson1271c442023-09-05 11:39:26 +0100212 if "lazy_data_gen" in supports and args.lazy_data_generation:
213 logger.info("Skipping running tests due to lazy data gen")
214 return
215
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100216 num_cores = args.num_cores
217 run_tests_cmd = "tosa_verif_run_tests"
218
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100219 ref_cmd_base = ref_cmd = [
220 run_tests_cmd,
221 "--ref-model-path",
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100222 str(args.ref_model_path.absolute()),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100223 "-j",
224 str(num_cores),
225 "-v",
226 "-t",
227 ]
228 ref_cmds = []
229
230 if not tests:
231 # Do not need to run ERRORIF tests as they don't have result files
232 tests = _get_all_tests_list(
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100233 profile, op_build_dir, operator, exclude_negative_tests=True
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100234 )
235
236 for test in tests:
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100237 desc = test / "desc.json"
238 with desc.open("r") as fd:
239 test_desc = json.load(fd)
240 if "meta" in test_desc and "compliance" in test_desc["meta"]:
241 logger.info(
242 f"Skipping generating results for new compliance test - {str(test)}"
243 )
244 continue
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100245 ref_cmd = ref_cmd_base.copy()
246 ref_cmd.append(str(test))
247 ref_cmds.append(ref_cmd)
248
249 fail_string = "UNEXPECTED_FAILURE"
250 failed_counter = 0
251
252 job_pool = mp.Pool(args.num_cores)
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100253 sh_partial = partial(_run_sh_command, args, args.ref_model_path.parent.absolute())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100254 pool_results = job_pool.map(sh_partial, ref_cmds)
255 job_pool.close()
256 job_pool.join()
257
258 # Use captured output for run_sh_command to work out if test passed.
259 for i, rc in enumerate(pool_results):
260 if fail_string in str(rc[0]):
261 logger.error(f"Test {i+1}/{len(ref_cmds)}: {ref_cmds[i][-1]} failed.")
262 failed_counter += 1
263 else:
264 logger.info(f"Test {i+1}/{len(ref_cmds)}: {ref_cmds[i][-1]} passed.")
265
266 logger.info(f"{len(ref_cmds)-failed_counter}/{len(ref_cmds)} tests passed")
267 logger.info("Ran tests on model and saved results of passing tests")
268
269
270def convert_tests(
271 args,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100272 profile,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100273 operator,
274 op_build_dir,
275 output_dir,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100276 op_profiles_list,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100277 supports=[],
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100278 tests=None,
279 group=None,
280 trim_op_subdir=False,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000281 tags=None,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100282):
283 """Convert tests to JSON and save to output directory."""
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100284 if group:
285 output_dir = output_dir / group
286
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100287 c2c_args_base = ["--strict"]
288 c2c_args_base.extend(["--schema-path", str(args.schema_path)])
289 c2c_args_base.extend(["--flatc-path", str(args.flatc_path)])
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100290 # This op maybe in more than one profile - e.g. tosa_bi and tosa_mi
291 # even if we are only producing tests for tosa_mi
292 for op_profile in op_profiles_list:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000293 c2c_args_base.extend(["--profile", op_profile])
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000294 if tags is not None:
295 for tag in tags:
296 c2c_args_base.extend(["--tag", tag])
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100297 if args.framework_schema:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000298 c2c_args_base.extend(["--framework-schema", str(args.framework_schema)])
Jeremy Johnson1271c442023-09-05 11:39:26 +0100299 if "lazy_data_gen" in supports and args.lazy_data_generation:
300 c2c_args_base.append("--lazy-data-generation")
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000301 c2c_args_base.append("--output-directory")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100302
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000303 c2c_args_list = []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100304
305 if not tests:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100306 tests = _get_all_tests_list(profile, op_build_dir, operator)
307 logger.info(f"Converting all {profile} profile tests")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100308
309 # Controls if we copy the tests in their operator sub-directory or not
310 output_dir_relative_pos = -1 if trim_op_subdir else -2
311 for test in tests:
312 logger.info(f"Test chosen: {test}")
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000313 c2c_args = c2c_args_base.copy()
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100314 full_output_directory = output_dir / test.relative_to(
315 *test.parts[:output_dir_relative_pos]
316 )
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000317 c2c_args.append(str(full_output_directory))
318 c2c_args.append(str(test))
319 c2c_args_list.append(c2c_args)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100320
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000321 if len(c2c_args_list) == 0:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000322 logger.error(
323 f"No tests found for {operator}. Nothing to convert in {op_build_dir}"
324 )
325 raise (GenConformanceError())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100326
327 job_pool = mp.Pool(args.num_cores)
328
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000329 pool_results = job_pool.map(c2c_main, c2c_args_list)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100330 job_pool.close()
331 job_pool.join()
332
333 failed_counter = 0
334 for i, result in enumerate(pool_results):
335 if result != 0:
336 logger.error(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000337 f"test {i+1}/{len(c2c_args_list)}: {c2c_args_list[i][-1]} failed to convert."
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100338 )
339 failed_counter += 1
340 else:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000341 logger.info(
342 f"test {i+1}/{len(c2c_args_list)}: {c2c_args_list[i][-1]} converted"
343 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100344 logger.info(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000345 f"{len(c2c_args_list)-failed_counter}/{len(c2c_args_list)} tests successfully converted"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100346 )
347
348 if failed_counter > 0:
349 logger.error(f"Stopping due to {failed_counter} test conversion errors")
350 raise (GenConformanceError())
351
352 logger.info("Converted tests to JSON and saved to output directory")
353
354 return output_dir
355
356
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100357def get_op_tests_selection(
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000358 args,
359 profile,
360 operator,
361 op_build_dir,
362 selection_config,
363 negative=False,
364 ignore_missing=False,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100365):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100366 """Use test picker to get subsection of tests generated."""
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000367 # Need a full copy of the config as the selector updates it
368 config = copy.deepcopy(selection_config)
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100369 logger.info("Choosing {} tests".format(("negative" if negative else "positive")))
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100370 try:
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100371 op = Operator.registry[operator](
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000372 op_build_dir, config, negative=negative, ignore_missing=ignore_missing
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100373 )
374 except KeyError:
375 logger.error(f"{operator} operator is not supported by test_select")
376 raise (GenConformanceError())
377
378 return op.select_tests()
379
380
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100381def check_op_tests(args, profile, operator, output_dir):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100382 """Move test folders than contain files larger than 30MB to new directory."""
383 destination_dir = str(args.output_dir) + "_large_files"
384
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100385 tests = _get_all_tests_list(profile, output_dir, operator, include_all=True)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100386 if not tests:
387 logger.error(
388 f"Couldn't find any tests to size check for {operator} in {output_dir}"
389 )
390 raise (GenConformanceError())
391
392 for tdir in tests:
393 move_dir = False
394 test_files = [file for file in tdir.glob("*")]
395 for file in test_files:
396 file_size = os.stat(file).st_size / 1024**2
397 if file_size > 30:
398 move_dir = True
399
400 if move_dir:
401 move_destination = destination_dir / tdir.relative_to(output_dir)
402 logger.warning(
403 f"{tdir.relative_to(output_dir)} contains files that are too large (>30MB), test moved to new folder: {destination_dir}"
404 )
405
406 if move_destination.is_dir():
407 logger.warning(
408 f"{move_destination} directory already exists, deleting existing."
409 )
410 shutil.rmtree(str(move_destination))
411 shutil.move(str(tdir), move_destination)
412
413
414def copy_rename_framework_tests(args, operator, test_picks):
415 """Copy framework tests into new folder and rename them if needed.
416
417 The tests are renamed to match the framework operator names if an
418 alternate name has been used instead.
419 """
420 framework_tests_dir = args.framework_tests_dir
421 new_tests_dir = args.build_dir / "frameworks" / operator
422 os.makedirs(new_tests_dir, exist_ok=True)
423
424 # Get the framework tests operator name
425 if "alternate_names" in test_picks[operator]:
426 alternate_names = test_picks[operator]["alternate_names"]
427 else:
428 alternate_names = [operator]
429
430 # Get the alternate named test directories for the operator
431 for alt_name in alternate_names:
432 test_prefix = f"test_{alt_name}"
433 test_dirs = list(framework_tests_dir.glob(f"{test_prefix}_*"))
434
435 # Copy tests to new directory and rename to match framework operator names
436 # - if there is just 1 alternate name, replace the full test prefix
437 # test_add_... -> add_...
438 # - if there are multiple alternate names, just replace the "test"
439 # test_concatv2_... -> concatenation_concatv2_...
440 old_prefix = test_prefix if len(alternate_names) == 1 else "test"
441
442 for tdir in test_dirs:
443 new_test_name = tdir.name.replace(old_prefix, operator)
444 copy_destination = new_tests_dir / new_test_name
445 logger.debug(f"copying test folder {tdir} to {copy_destination}")
446 copy_tree(str(tdir), str(copy_destination))
447
448 logger.info(f"Copied and renamed {len(test_dirs)} framework test folders")
449 return new_tests_dir.parent
450
451
452def get_framework_tests_selection(args, operator, test_picks, op_build_dir):
453 """Get the list of pre-chosen tests with relative paths."""
454 try:
455 tests = test_picks[operator]["tests"]
456 except KeyError:
457 logger.error(f"Framework test selection not defined for {operator} operator")
458 raise (GenConformanceError())
459
460 test_paths = [op_build_dir / operator / test for test in tests]
461 return test_paths
462
463
464def parse_args(argv=None):
465 """Parse the arguments."""
466 parser = argparse.ArgumentParser()
Jeremy Johnson88588622022-07-12 16:42:29 +0100467 profiles = list(PROFILE_OPS_INFO.keys())
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100468 profiles.append(PROFILES_ALL)
Jeremy Johnson88588622022-07-12 16:42:29 +0100469 parser.add_argument(
470 "--profile",
471 dest="profile",
472 choices=profiles,
473 default=profiles[0],
474 type=str,
475 help=f"TOSA profile (default is {profiles[0]})",
476 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100477 parser.add_argument(
478 "--operators",
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100479 "--op",
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100480 type=str,
481 nargs="*",
482 help="The operator(s) to create tests for, if not supplied all tests will be created",
483 )
484 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +0100485 "--unit-tests",
486 dest="unit_tests",
487 choices=["operator", "framework", "both"],
488 default="operator",
489 type=str,
490 help="Which unit tests are produced (default is operator)",
491 )
492 parser.add_argument(
493 "--test-type",
494 dest="test_type",
495 choices=["positive", "negative", "both"],
496 default="both",
497 type=str,
498 help="Type of tests produced (default is both)",
499 )
500 parser.add_argument(
Jeremy Johnson1271c442023-09-05 11:39:26 +0100501 "--lazy-data-generation",
502 action="store_true",
503 help="Enable lazy data generation (only for tosa-mi)",
504 )
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100505 rm_group = parser.add_mutually_exclusive_group(required=True)
506 rm_group.add_argument(
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100507 "--ref-model-directory",
508 dest="ref_model_dir",
509 type=Path,
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100510 help="(DEPRECATED - use ref-model-path) Reference Model directory - with build directory",
511 )
512 rm_group.add_argument(
513 "--ref-model-path",
514 dest="ref_model_path",
515 type=Path,
516 help="Path to TOSA reference model executable",
517 )
518 parser.add_argument(
519 "--schema-path",
520 "--operator-fbs",
521 dest="schema_path",
522 type=Path,
523 help=(
524 "Path to TOSA reference model flat buffer schema. Defaults to "
525 f"`{cmf.DEFAULT_REF_MODEL_SCHEMA_PATH}` in parents parent directory of `ref-model-path`"
526 ),
527 )
528 parser.add_argument(
529 "--flatc-path",
530 dest="flatc_path",
531 type=Path,
532 help=(
533 "Path to flatc executable. Defaults to "
534 f"`{cmf.DEFAULT_REF_MODEL_BUILD_FLATC_PATH}` in parent directory of `ref-model-path`"
535 ),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100536 )
Jeremy Johnson88588622022-07-12 16:42:29 +0100537 parser.add_argument(
Jeremy Johnson93d43902022-09-27 12:26:14 +0100538 "--seed",
539 dest="random_seed",
540 default=DEFAULT_SEED,
541 type=int,
542 help="Random test seed",
543 )
544 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +0100545 "--framework-tests-directory",
546 dest="framework_tests_dir",
547 type=Path,
548 default=Path.cwd() / "tests",
549 help="The pre-built framework tests directory (default is tests)",
550 )
551 parser.add_argument(
552 "--framework-schema",
553 dest="framework_schema",
554 type=Path,
555 help="Framework flatbuffers schema needed to convert framework models",
556 )
557 parser.add_argument(
558 "--build-directory",
559 dest="build_dir",
560 type=Path,
561 default=Path.cwd() / "conformance_build",
562 help="Temporary build directory for files created during this process (default is conformance_build)",
563 )
564 parser.add_argument(
565 "--output-directory",
566 dest="output_dir",
567 type=Path,
568 default=Path.cwd() / "conformance",
569 help="Output directory (default is conformance)",
570 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100571 script_dir = Path(__file__).parent.absolute()
572 parser.add_argument(
573 "--test-param-json-directory",
574 dest="param_json_dir",
575 type=Path,
576 default=script_dir,
Jeremy Johnson88588622022-07-12 16:42:29 +0100577 help=f"Test parameters (ops info) JSON file directory (default is {script_dir})",
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100578 )
579 parser.add_argument(
580 "--convert-all-tests",
581 action="store_true",
582 help="Converts all tests instead of those picked by test_select",
583 )
584 parser.add_argument(
585 "--keep-large-files",
586 action="store_true",
587 help="Keeps tests that contain files larger than 30MB in output directory",
588 )
589 parser.add_argument(
590 "--capture-output",
591 action="store_true",
592 help="Prints output of running sh commands",
593 )
594 parser.add_argument(
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100595 "-j",
596 dest="num_cores",
597 type=int,
598 default=6,
599 help="Number of simultaneous jobs to split the tasks into for multiprocessing",
600 )
601 parser.add_argument(
602 "-v",
603 dest="verbosity",
604 action="count",
605 default=0,
606 help="Verbosity (can be used multiple times for more details)",
607 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100608 args = parser.parse_args(argv)
609
610 return args
611
612
613def main():
614 args = parse_args()
615
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100616 if args.ref_model_dir is not None:
617 # Assume the ref model exe path based on the ref model directory
618 args.ref_model_path = cmf.find_tosa_file(
619 cmf.TosaFileType.REF_MODEL, args.ref_model_dir, False
620 )
621 if not args.ref_model_path.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100622 logger.error(
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100623 f"Missing reference model binary (--ref-model-path): {args.ref_model_path}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100624 )
625 return 2
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100626
627 if args.schema_path is None:
628 args.schema_path = cmf.find_tosa_file(
629 cmf.TosaFileType.SCHEMA, args.ref_model_path
630 )
631 if not args.schema_path.is_file():
632 logger.error(
633 f"Missing reference model schema (--schema-path): {args.schema_path}"
634 )
635 return 2
636
637 if args.flatc_path is None:
638 args.flatc_path = cmf.find_tosa_file(
639 cmf.TosaFileType.FLATC, args.ref_model_path
640 )
641 if not args.flatc_path.is_file():
642 logger.error(f"Missing flatc binary (--flatc-path): {args.flatc_path}")
643 return 2
644
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100645 if args.unit_tests in ["framework", "both"]:
Jeremy Johnsonc1d1c632023-08-02 17:21:36 +0100646 logger.warning(
647 "DEPRECATION - Framework tests are not part of TOSA conformance testing"
648 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100649 if not args.framework_schema:
650 logger.error(
651 "Need to supply location of Framework flatbuffers schema via --framework-schema"
652 )
653 return 2
654 if not args.framework_tests_dir.is_dir():
655 logger.error(
656 f"Missing or invalid framework tests directory: {args.framework_tests_dir}"
657 )
658 return 2
659
660 loglevels = (logging.WARNING, logging.INFO, logging.DEBUG)
661 loglevel = loglevels[min(args.verbosity, len(loglevels) - 1)]
662 logger.setLevel(loglevel)
663 # Set other loggers the same
664 logging.getLogger("test_select").setLevel(loglevel)
665 logging.getLogger("convert2conformance").setLevel(loglevel)
666
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100667 print(f"Output directory: {args.output_dir}")
668
Jeremy Johnson93d43902022-09-27 12:26:14 +0100669 if args.random_seed != DEFAULT_SEED:
670 logger.warning(
671 "Random test seed changed from default, tests will not match official conformance"
672 )
673
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100674 args.build_dir = args.build_dir.resolve()
675 logger.debug(f"Creating build directory: {args.build_dir}")
676 args.build_dir.mkdir(parents=True, exist_ok=True)
677
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100678 # TODO: For tosa-mi should really generate tosa-bi profile as well
679 # - for now leave it as subset instead of as superset (for testing)
680 if args.profile == PROFILES_ALL:
681 profiles = list(PROFILE_OPS_INFO.keys())
682 else:
683 profiles = [args.profile]
684
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100685 try:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100686 for profile in profiles:
687 print(f"Creating conformance tests for TOSA {profile} profile")
688 # Framework unit tests
689 if args.unit_tests in ["framework", "both"]:
690 logger.debug("Creating FRAMEWORK unit tests")
691 test_picks_file = (
692 args.param_json_dir / PROFILE_OPS_INFO[profile]["framework_tests"]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100693 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100694 try:
695 with open(test_picks_file, "r") as fd:
696 test_picks = json.load(fd)
697 except Exception as e:
698 logger.error(
699 f"Couldn't load framework tests info - {test_picks_file}: {e}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100700 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100701 return 1
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100702
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100703 operators = args.operators
704 if not operators:
705 # Create tests for all the operators
706 operators = list(test_picks.keys())
707
708 root_output_dir = (
709 args.output_dir / "frameworks" / "tflite" / "operators"
710 )
711 for op in operators:
712 logger.info(f"FRAMEWORK OP: {op}")
713 if op not in test_picks:
714 logger.warning(
715 f"Framework op {op} not found in {test_picks_file} - skipping"
716 )
717 continue
718
719 op_profiles_list = test_picks[op]["profile"]
720 if (
721 args.profile != PROFILES_ALL
722 and args.profile not in op_profiles_list
723 ):
724 # Skip this operator as not part of the profile chosen
725 logger.debug(f"Skipping {op} as not part of {args.profile}")
726 continue
727
728 logger.debug(f"Copying and renaming {op}")
729 framework_test_dir = copy_rename_framework_tests(
730 args, op, test_picks
731 )
732
733 if args.convert_all_tests:
734 logger.debug("Running and converting all framework tests")
735 framework_tests = None # Don't select any
736 else:
737 logger.debug("Running and converting selected framework tests")
738 framework_tests = get_framework_tests_selection(
739 args, op, test_picks, framework_test_dir
740 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100741 convert_tests(
742 args,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100743 profile,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100744 op,
745 framework_test_dir,
746 root_output_dir,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100747 op_profiles_list,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100748 tests=framework_tests,
749 trim_op_subdir=True,
750 )
751
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100752 # Operator unit tests
753 if args.unit_tests in ["operator", "both"]:
754 logger.debug("Creating OPERATOR unit tests")
755 test_params_file = (
756 args.param_json_dir
757 / PROFILE_OPS_INFO[profile]["operator_test_params"]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100758 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100759 try:
760 with open(test_params_file, "r") as fd:
761 test_params = json.load(fd)
762 except Exception as e:
763 logger.error(
764 f"Couldn't load operator test params - {test_params_file}: {e}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100765 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100766 return 1
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100767
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100768 operators = args.operators
769 if not operators:
770 # Create tests for all the operators
771 operators = list(test_params.keys())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100772
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100773 for op in operators:
774 logger.info(f"OPERATOR: {op}")
775 if op not in test_params:
776 logger.warning(
777 f"{op} operator parameters not found in {test_params_file} - skipping"
778 )
779 continue
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100780
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100781 op_profiles_list = test_params[op]["profile"]
782 if (
783 args.profile != PROFILES_ALL
784 and args.profile not in op_profiles_list
785 ):
786 # Skip this operator as not part of the profile chosen
787 logger.debug(f"Skipping {op} as not part of {args.profile}")
788 continue
789
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100790 operator_group = test_params[op]["group"]
791 root_output_dir = args.output_dir / "operators"
Jeremy Johnson1271c442023-09-05 11:39:26 +0100792 supports = (
793 test_params[op]["support_for"]
794 if "support_for" in test_params[op]
795 else []
796 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000797
798 # Iterate through the generation groups selecting tests from each
799 for gen_name, gen_dict in test_params[op]["generation"].items():
800 no_neg_tests = (
801 "no_negative_tests" in gen_dict
802 and gen_dict["no_negative_tests"] == "true"
803 )
804
805 if no_neg_tests:
806 if args.test_type == "negative":
807 logger.info(
808 f"No negative tests for {op} / generation group {gen_name}"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100809 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000810 continue
811 # Only produce positive tests
812 test_type = "positive"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100813 else:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000814 test_type = args.test_type
815
816 gen_neg_dim_range = (
817 gen_dict["negative_dim_range"]
818 if "negative_dim_range" in gen_dict
819 else None
820 )
821
822 ignore_missing = gen_name != STANDARD_GENERATOR_GROUP
823 tags = (
824 [gen_name] if gen_name != STANDARD_GENERATOR_GROUP else None
825 )
826
827 op_build_dir = build_op_tests(
828 args,
829 test_type,
830 profile,
831 op,
832 gen_name,
833 gen_dict["generator_args"],
834 gen_neg_dim_range,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100835 supports=supports,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000836 )
837
Jeremy Johnson0c716862023-04-13 17:18:19 +0100838 # Work out which selection criteria we are using
839 if "selector" in gen_dict:
840 selector_name = gen_dict["selector"]
841 if selector_name not in test_params[op]["selection"]:
842 logger.warn(
843 f"Could not find {selector_name} in selection dict for {op} - using default"
844 )
845 selector_name = "default"
846 else:
847 selector_name = "default"
848 if selector_name not in test_params[op]["selection"]:
849 logger.error(
850 f"Could not find {selector_name} in selection dict for {op}"
851 )
852 raise (GenConformanceError())
853
854 # Selection criteria
855 selection_config = test_params[op]["selection"][selector_name]
856
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100857 if args.convert_all_tests:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000858 logger.debug(f"Running and converting all {op} tests")
Jeremy Johnson1271c442023-09-05 11:39:26 +0100859 generate_results(
860 args, profile, op, op_build_dir, supports=supports
861 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000862 operator_test_list = None
863 else:
864 logger.debug(
865 f"Running and converting selection of {op} tests"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100866 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000867 if test_type in ["positive", "both"]:
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100868 if (
869 "all" in selection_config
870 and selection_config["all"] == "true"
871 ):
872 # Just get all the positive tests
873 tests_gen, tests_gen2 = tee(
874 _get_all_tests_list(
875 profile,
876 op_build_dir,
877 op,
878 exclude_negative_tests=True,
879 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000880 )
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100881 else:
882 # Get a selection of positive tests
883 tests_gen, tests_gen2 = tee(
884 get_op_tests_selection(
885 args,
886 profile,
887 op,
888 op_build_dir,
889 selection_config,
890 ignore_missing=ignore_missing,
891 )
892 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000893 generate_results(
Jeremy Johnson1271c442023-09-05 11:39:26 +0100894 args,
895 profile,
896 op,
897 op_build_dir,
898 supports=supports,
899 tests=tests_gen,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000900 )
901 operator_test_list = list(tests_gen2)
902 else:
903 operator_test_list = []
904 if test_type in ["negative", "both"]:
905 operator_test_list.extend(
906 get_op_tests_selection(
907 args,
908 profile,
909 op,
910 op_build_dir,
911 selection_config,
912 negative=True,
913 )
914 )
915 output_dir = convert_tests(
916 args,
917 profile,
918 op,
919 op_build_dir,
920 root_output_dir,
921 op_profiles_list,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100922 supports=supports,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000923 tests=operator_test_list,
924 group=operator_group,
925 tags=tags,
926 )
927 if not args.keep_large_files:
928 check_op_tests(args, profile, op, output_dir)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100929 except GenConformanceError:
930 return 1
931
932 return 0
933
934
935if __name__ == "__main__":
936 exit(main())