blob: 692e79acb8e73fc63ec7265f33984ccff427d35e [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.
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +010011- Tests are converted to JSON and/or copied and saved to desired output directory.
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010012"""
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
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +010029from convert2conformance.convert2conformance import OUTPUT_TYPE_DEFAULT
30from convert2conformance.convert2conformance import OUTPUT_TYPES
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010031from distutils.dir_util import copy_tree
32
33logging.basicConfig()
34logger = logging.getLogger("tosa_verif_conformance_generator")
35
36# Configuration for each TOSA profile
37PROFILE_OPS_INFO = {
Jeremy Johnson88588622022-07-12 16:42:29 +010038 "tosa-bi": {
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010039 "operator_test_params": "tosa_base_profile_ops_info.json",
40 "framework_tests": "tosa_base_profile_framework_ops_info.json",
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +010041 },
42 "tosa-mi": {
43 # Note: This is just the extra tests not in the base profile!
44 "operator_test_params": "tosa_main_profile_ops_info.json",
45 "framework_tests": "tosa_main_profile_framework_ops_info.json",
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +010046 },
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010047}
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +010048PROFILES_ALL = "all"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010049
Jeremy Johnson93d43902022-09-27 12:26:14 +010050DEFAULT_SEED = 42
51
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000052# When there is a dictionary of generator argument lists (groups) only the
53# standard group will have negative tests generated for it
54STANDARD_GENERATOR_GROUP = "standard"
55
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +010056TEST_VERSION_LATEST = "latest"
57TEST_VERSION_V0_60_0 = "v0.60.0"
58TEST_VERSIONS = (TEST_VERSION_LATEST, TEST_VERSION_V0_60_0)
59
Jeremy Johnson0ecfa372022-06-30 14:27:56 +010060
61class GenConformanceError(Exception):
62 """Generation error reporting exception."""
63
64 pass
65
66
67def _run_sh_command(args, cwd, full_cmd):
68 """Run an external command and capture stdout/stderr."""
69 # Quote the command line for printing
70 full_cmd_esc = [shlex.quote(x) for x in full_cmd]
71 if args.capture_output:
72 logger.debug(f"Command: {full_cmd_esc}")
73
74 rc = subprocess.run(
75 full_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd
76 )
77
78 if args.capture_output:
79 stdout = rc.stdout.decode("utf-8")
80 logger.debug(f"stdout: \n{stdout}")
81 if rc.returncode != 0:
82
83 raise Exception(
84 "Error running command: {}.\n{}".format(
85 " ".join(full_cmd_esc), rc.stderr.decode("utf-8")
86 )
87 )
88 return (rc.stdout, rc.stderr)
89
90
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000091def build_op_tests(
Jeremy Johnson1271c442023-09-05 11:39:26 +010092 args,
93 test_type,
94 profile,
95 operator,
96 group,
97 gen_args_list,
98 gen_neg_dim_range,
99 supports=[],
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000100):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100101 """Build tests for a given operator.
102
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000103 Builds a set of tests based on the given generator arguments list
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100104
105 Returns operator output directory
106 """
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100107 build_tests_cmd = "tosa_verif_build_tests"
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000108 op_build_dir = args.build_dir / profile / group
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100109
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000110 build_cmd_base = [
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100111 build_tests_cmd,
Jeremy Johnson65ba8092023-10-09 16:31:13 +0100112 "--generate-lib-path",
113 str(args.generate_lib_path),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100114 "--filter",
115 operator,
116 "-o",
117 str(op_build_dir),
118 "--seed",
Jeremy Johnson93d43902022-09-27 12:26:14 +0100119 str(args.random_seed),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100120 ]
121
Jeremy Johnson1271c442023-09-05 11:39:26 +0100122 if "lazy_data_gen" in supports and args.lazy_data_generation:
123 build_cmd_base.append("--lazy-data-generation")
124
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000125 build_cmds_list = []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100126
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000127 if test_type in ["positive", "both"]:
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100128 # Append extra parameters and run test generator for each set of parameters.
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000129 for arglist in gen_args_list:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000130 build_cmd_pos_test = build_cmd_base.copy()
131 build_cmd_pos_test.extend(["--test-type", "positive"])
132 build_cmd_pos_test.extend(arglist)
133 build_cmds_list.append(build_cmd_pos_test)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100134
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000135 if test_type in ["negative", "both"]:
Jeremy Johnson35396f22023-01-04 17:05:25 +0000136 # Get target-dtypes options and any filter string to limit tests
Jeremy Johnson93d43902022-09-27 12:26:14 +0100137 target_dtypes_args = []
Jeremy Johnson35396f22023-01-04 17:05:25 +0000138 filter_str = None
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000139 for arglist in gen_args_list:
Jeremy Johnson93d43902022-09-27 12:26:14 +0100140 idx = 0
141 while idx < len(arglist):
142 if arglist[idx] == "--target-dtype":
143 if arglist[idx + 1] not in target_dtypes_args:
144 target_dtypes_args.extend(arglist[idx : idx + 2])
145 idx += 1 # skip over option (and then argument below)
Jeremy Johnson35396f22023-01-04 17:05:25 +0000146 elif arglist[idx] == "--filter":
147 filter_str = arglist[idx + 1]
148 idx += 1 # skip over option (and then argument below)
Jeremy Johnson93d43902022-09-27 12:26:14 +0100149 idx += 1
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000150 build_cmd_neg_test = build_cmd_base.copy()
Jeremy Johnson35396f22023-01-04 17:05:25 +0000151 if filter_str:
152 build_cmd_neg_test.extend(["--filter", filter_str])
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000153 build_cmd_neg_test.extend(["--test-type", "negative"])
Jeremy Johnson93d43902022-09-27 12:26:14 +0100154 # Limit sizes of negative tests
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000155 dim_range = gen_neg_dim_range if gen_neg_dim_range is not None else "1,16"
156
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000157 build_cmd_neg_test.extend(["--tensor-dim-range", dim_range])
158 build_cmd_neg_test.extend(target_dtypes_args)
159 build_cmds_list.append(build_cmd_neg_test)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100160
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000161 logger.debug(f"Creating {operator} tests with {len(build_cmds_list)} parameter(s)")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100162 error = False
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000163 for i, cmd in enumerate(build_cmds_list):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100164 try:
Jeremy Johnson65ba8092023-10-09 16:31:13 +0100165 _run_sh_command(args, args.ref_model_path.parent, cmd)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100166 logger.info(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000167 f"{operator} test batch {(i+1)}/{len(build_cmds_list)} created successfully"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100168 )
169 except Exception as e:
170 logger.error(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000171 f"{operator} test batch {(i+1)}/{len(build_cmds_list)} unsuccessful, skipping"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100172 )
173 logger.error(f" build_op_tests error: {e} ")
174 error = True
175 if error:
176 raise (GenConformanceError())
177
178 return op_build_dir
179
180
181def _check_to_include_test(profile, test_name, exclude_negative_tests=False):
182 """Check test name for exclusions, return False to indicate excluded."""
183 excludes = ["ERRORIF"] if exclude_negative_tests else []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100184
185 for exclusion in excludes:
186 if f"_{exclusion}_" in test_name:
187 return False
188 return True
189
190
191def _get_all_tests_list(
192 profile, test_root_dir, operator, exclude_negative_tests=False, include_all=False
193):
194 """Create test list based on tests in the test_dir."""
195 test_dir = test_root_dir / operator
196 if not test_dir.is_dir():
197 # Tests are split into multiple dirs, for example: conv2d_1x1, conv2d_3x3
198 test_dir = test_root_dir
199 directories = [
200 tdir for tdir in test_dir.glob("*") if tdir.name.startswith(operator)
201 ]
202 else:
203 directories = [test_dir]
204
205 tests = []
206 for tdir in directories:
207 tests.extend(
208 [
209 test
210 for test in tdir.glob("*")
211 if include_all
212 or _check_to_include_test(profile, test.name, exclude_negative_tests)
213 ]
214 )
215 return tests
216
217
Jeremy Johnson1271c442023-09-05 11:39:26 +0100218def generate_results(args, profile, operator, op_build_dir, supports=[], tests=None):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100219 """Run tests on reference model and save result to the test directory."""
Jeremy Johnson1271c442023-09-05 11:39:26 +0100220 if "lazy_data_gen" in supports and args.lazy_data_generation:
221 logger.info("Skipping running tests due to lazy data gen")
222 return
223
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100224 num_cores = args.num_cores
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100225
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100226 # Use the test runner
227 ref_cmd_base = [
228 "tosa_verif_run_tests",
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100229 "--ref-model-path",
Jeremy Johnson65ba8092023-10-09 16:31:13 +0100230 str(args.ref_model_path),
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100231 "--schema-path",
Jeremy Johnson65ba8092023-10-09 16:31:13 +0100232 str(args.schema_path),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100233 "-j",
234 str(num_cores),
235 "-v",
236 "-t",
237 ]
238 ref_cmds = []
239
240 if not tests:
241 # Do not need to run ERRORIF tests as they don't have result files
242 tests = _get_all_tests_list(
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100243 profile, op_build_dir, operator, exclude_negative_tests=True
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100244 )
245
246 for test in tests:
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100247 desc = test / "desc.json"
248 with desc.open("r") as fd:
249 test_desc = json.load(fd)
250 if "meta" in test_desc and "compliance" in test_desc["meta"]:
251 logger.info(
252 f"Skipping generating results for new compliance test - {str(test)}"
253 )
254 continue
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100255 ref_cmd = ref_cmd_base.copy()
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100256 ref_cmd.append(str(test.absolute()))
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100257 ref_cmds.append(ref_cmd)
258
259 fail_string = "UNEXPECTED_FAILURE"
260 failed_counter = 0
261
262 job_pool = mp.Pool(args.num_cores)
Jeremy Johnson65ba8092023-10-09 16:31:13 +0100263 sh_partial = partial(_run_sh_command, args, args.ref_model_path.parent)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100264 pool_results = job_pool.map(sh_partial, ref_cmds)
265 job_pool.close()
266 job_pool.join()
267
268 # Use captured output for run_sh_command to work out if test passed.
269 for i, rc in enumerate(pool_results):
270 if fail_string in str(rc[0]):
271 logger.error(f"Test {i+1}/{len(ref_cmds)}: {ref_cmds[i][-1]} failed.")
272 failed_counter += 1
273 else:
274 logger.info(f"Test {i+1}/{len(ref_cmds)}: {ref_cmds[i][-1]} passed.")
275
276 logger.info(f"{len(ref_cmds)-failed_counter}/{len(ref_cmds)} tests passed")
277 logger.info("Ran tests on model and saved results of passing tests")
278
279
280def convert_tests(
281 args,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100282 profile,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100283 operator,
284 op_build_dir,
285 output_dir,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100286 op_profiles_list,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100287 supports=[],
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100288 tests=None,
289 group=None,
290 trim_op_subdir=False,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000291 tags=None,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100292):
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100293 """Convert/copy tests to output directory."""
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100294 if group:
295 output_dir = output_dir / group
296
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100297 c2c_args_base = ["--strict"]
298 c2c_args_base.extend(["--schema-path", str(args.schema_path)])
299 c2c_args_base.extend(["--flatc-path", str(args.flatc_path)])
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100300 c2c_args_base.extend(["--output-type", args.output_type])
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100301 # This op maybe in more than one profile - e.g. tosa_bi and tosa_mi
302 # even if we are only producing tests for tosa_mi
303 for op_profile in op_profiles_list:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000304 c2c_args_base.extend(["--profile", op_profile])
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000305 if tags is not None:
306 for tag in tags:
307 c2c_args_base.extend(["--tag", tag])
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100308 if args.framework_schema:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000309 c2c_args_base.extend(["--framework-schema", str(args.framework_schema)])
Jeremy Johnson1271c442023-09-05 11:39:26 +0100310 if "lazy_data_gen" in supports and args.lazy_data_generation:
311 c2c_args_base.append("--lazy-data-generation")
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000312 c2c_args_base.append("--output-directory")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100313
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000314 c2c_args_list = []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100315
316 if not tests:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100317 tests = _get_all_tests_list(profile, op_build_dir, operator)
318 logger.info(f"Converting all {profile} profile tests")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100319
320 # Controls if we copy the tests in their operator sub-directory or not
321 output_dir_relative_pos = -1 if trim_op_subdir else -2
322 for test in tests:
323 logger.info(f"Test chosen: {test}")
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000324 c2c_args = c2c_args_base.copy()
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100325 full_output_directory = output_dir / test.relative_to(
326 *test.parts[:output_dir_relative_pos]
327 )
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000328 c2c_args.append(str(full_output_directory))
329 c2c_args.append(str(test))
330 c2c_args_list.append(c2c_args)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100331
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000332 if len(c2c_args_list) == 0:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000333 logger.error(
334 f"No tests found for {operator}. Nothing to convert in {op_build_dir}"
335 )
336 raise (GenConformanceError())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100337
338 job_pool = mp.Pool(args.num_cores)
339
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000340 pool_results = job_pool.map(c2c_main, c2c_args_list)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100341 job_pool.close()
342 job_pool.join()
343
344 failed_counter = 0
345 for i, result in enumerate(pool_results):
346 if result != 0:
347 logger.error(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000348 f"test {i+1}/{len(c2c_args_list)}: {c2c_args_list[i][-1]} failed to convert."
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100349 )
350 failed_counter += 1
351 else:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000352 logger.info(
353 f"test {i+1}/{len(c2c_args_list)}: {c2c_args_list[i][-1]} converted"
354 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100355 logger.info(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000356 f"{len(c2c_args_list)-failed_counter}/{len(c2c_args_list)} tests successfully converted"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100357 )
358
359 if failed_counter > 0:
360 logger.error(f"Stopping due to {failed_counter} test conversion errors")
361 raise (GenConformanceError())
362
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100363 logger.info("Converted/copied tests and saved to output directory")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100364
365 return output_dir
366
367
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100368def get_op_tests_selection(
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000369 args,
370 profile,
371 operator,
372 op_build_dir,
373 selection_config,
374 negative=False,
375 ignore_missing=False,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100376):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100377 """Use test picker to get subsection of tests generated."""
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000378 # Need a full copy of the config as the selector updates it
379 config = copy.deepcopy(selection_config)
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100380 logger.info("Choosing {} tests".format(("negative" if negative else "positive")))
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100381 try:
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100382 op = Operator.registry[operator](
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000383 op_build_dir, config, negative=negative, ignore_missing=ignore_missing
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100384 )
385 except KeyError:
386 logger.error(f"{operator} operator is not supported by test_select")
387 raise (GenConformanceError())
388
389 return op.select_tests()
390
391
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100392def check_op_tests(args, profile, operator, output_dir):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100393 """Move test folders than contain files larger than 30MB to new directory."""
394 destination_dir = str(args.output_dir) + "_large_files"
395
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100396 tests = _get_all_tests_list(profile, output_dir, operator, include_all=True)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100397 if not tests:
398 logger.error(
399 f"Couldn't find any tests to size check for {operator} in {output_dir}"
400 )
401 raise (GenConformanceError())
402
403 for tdir in tests:
404 move_dir = False
405 test_files = [file for file in tdir.glob("*")]
406 for file in test_files:
407 file_size = os.stat(file).st_size / 1024**2
408 if file_size > 30:
409 move_dir = True
410
411 if move_dir:
412 move_destination = destination_dir / tdir.relative_to(output_dir)
413 logger.warning(
414 f"{tdir.relative_to(output_dir)} contains files that are too large (>30MB), test moved to new folder: {destination_dir}"
415 )
416
417 if move_destination.is_dir():
418 logger.warning(
419 f"{move_destination} directory already exists, deleting existing."
420 )
421 shutil.rmtree(str(move_destination))
422 shutil.move(str(tdir), move_destination)
423
424
425def copy_rename_framework_tests(args, operator, test_picks):
426 """Copy framework tests into new folder and rename them if needed.
427
428 The tests are renamed to match the framework operator names if an
429 alternate name has been used instead.
430 """
431 framework_tests_dir = args.framework_tests_dir
432 new_tests_dir = args.build_dir / "frameworks" / operator
433 os.makedirs(new_tests_dir, exist_ok=True)
434
435 # Get the framework tests operator name
436 if "alternate_names" in test_picks[operator]:
437 alternate_names = test_picks[operator]["alternate_names"]
438 else:
439 alternate_names = [operator]
440
441 # Get the alternate named test directories for the operator
442 for alt_name in alternate_names:
443 test_prefix = f"test_{alt_name}"
444 test_dirs = list(framework_tests_dir.glob(f"{test_prefix}_*"))
445
446 # Copy tests to new directory and rename to match framework operator names
447 # - if there is just 1 alternate name, replace the full test prefix
448 # test_add_... -> add_...
449 # - if there are multiple alternate names, just replace the "test"
450 # test_concatv2_... -> concatenation_concatv2_...
451 old_prefix = test_prefix if len(alternate_names) == 1 else "test"
452
453 for tdir in test_dirs:
454 new_test_name = tdir.name.replace(old_prefix, operator)
455 copy_destination = new_tests_dir / new_test_name
456 logger.debug(f"copying test folder {tdir} to {copy_destination}")
457 copy_tree(str(tdir), str(copy_destination))
458
459 logger.info(f"Copied and renamed {len(test_dirs)} framework test folders")
460 return new_tests_dir.parent
461
462
463def get_framework_tests_selection(args, operator, test_picks, op_build_dir):
464 """Get the list of pre-chosen tests with relative paths."""
465 try:
466 tests = test_picks[operator]["tests"]
467 except KeyError:
468 logger.error(f"Framework test selection not defined for {operator} operator")
469 raise (GenConformanceError())
470
471 test_paths = [op_build_dir / operator / test for test in tests]
472 return test_paths
473
474
475def parse_args(argv=None):
476 """Parse the arguments."""
477 parser = argparse.ArgumentParser()
Jeremy Johnson88588622022-07-12 16:42:29 +0100478 profiles = list(PROFILE_OPS_INFO.keys())
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100479 profiles.append(PROFILES_ALL)
Jeremy Johnson88588622022-07-12 16:42:29 +0100480 parser.add_argument(
481 "--profile",
482 dest="profile",
483 choices=profiles,
484 default=profiles[0],
485 type=str,
486 help=f"TOSA profile (default is {profiles[0]})",
487 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100488 parser.add_argument(
489 "--operators",
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100490 "--op",
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100491 type=str,
492 nargs="*",
493 help="The operator(s) to create tests for, if not supplied all tests will be created",
494 )
495 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +0100496 "--unit-tests",
497 dest="unit_tests",
498 choices=["operator", "framework", "both"],
499 default="operator",
500 type=str,
501 help="Which unit tests are produced (default is operator)",
502 )
503 parser.add_argument(
504 "--test-type",
505 dest="test_type",
506 choices=["positive", "negative", "both"],
507 default="both",
508 type=str,
509 help="Type of tests produced (default is both)",
510 )
511 parser.add_argument(
Jeremy Johnson1271c442023-09-05 11:39:26 +0100512 "--lazy-data-generation",
513 action="store_true",
514 help="Enable lazy data generation (only for tosa-mi)",
515 )
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100516 rm_group = parser.add_mutually_exclusive_group(required=True)
517 rm_group.add_argument(
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100518 "--ref-model-directory",
519 dest="ref_model_dir",
520 type=Path,
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100521 help="(DEPRECATED - use ref-model-path) Reference Model directory - with build directory",
522 )
523 rm_group.add_argument(
524 "--ref-model-path",
525 dest="ref_model_path",
526 type=Path,
527 help="Path to TOSA reference model executable",
528 )
529 parser.add_argument(
Jeremy Johnson65ba8092023-10-09 16:31:13 +0100530 "--generate-lib-path",
531 dest="generate_lib_path",
532 type=Path,
533 help=(
534 "Path to TOSA generate library. Defaults to "
535 "the library in the directory of `ref-model-path`"
536 ),
537 )
538 parser.add_argument(
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100539 "--schema-path",
540 "--operator-fbs",
541 dest="schema_path",
542 type=Path,
543 help=(
544 "Path to TOSA reference model flat buffer schema. Defaults to "
545 f"`{cmf.DEFAULT_REF_MODEL_SCHEMA_PATH}` in parents parent directory of `ref-model-path`"
546 ),
547 )
548 parser.add_argument(
549 "--flatc-path",
550 dest="flatc_path",
551 type=Path,
552 help=(
553 "Path to flatc executable. Defaults to "
554 f"`{cmf.DEFAULT_REF_MODEL_BUILD_FLATC_PATH}` in parent directory of `ref-model-path`"
555 ),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100556 )
Jeremy Johnson88588622022-07-12 16:42:29 +0100557 parser.add_argument(
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100558 "--test-version",
559 dest="test_version",
560 choices=TEST_VERSIONS,
561 default=TEST_VERSION_LATEST,
562 help=f"Version of the tests to produce (default is {TEST_VERSION_LATEST})",
563 )
564 parser.add_argument(
565 "--output-type",
566 dest="output_type",
567 choices=OUTPUT_TYPES,
568 default=OUTPUT_TYPE_DEFAULT,
569 help=f"Output file type produced (default is {OUTPUT_TYPE_DEFAULT})",
570 )
571 parser.add_argument(
Jeremy Johnson93d43902022-09-27 12:26:14 +0100572 "--seed",
573 dest="random_seed",
574 default=DEFAULT_SEED,
575 type=int,
576 help="Random test seed",
577 )
578 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +0100579 "--framework-tests-directory",
580 dest="framework_tests_dir",
581 type=Path,
582 default=Path.cwd() / "tests",
583 help="The pre-built framework tests directory (default is tests)",
584 )
585 parser.add_argument(
586 "--framework-schema",
587 dest="framework_schema",
588 type=Path,
589 help="Framework flatbuffers schema needed to convert framework models",
590 )
591 parser.add_argument(
592 "--build-directory",
593 dest="build_dir",
594 type=Path,
595 default=Path.cwd() / "conformance_build",
596 help="Temporary build directory for files created during this process (default is conformance_build)",
597 )
598 parser.add_argument(
599 "--output-directory",
600 dest="output_dir",
601 type=Path,
602 default=Path.cwd() / "conformance",
603 help="Output directory (default is conformance)",
604 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100605 script_dir = Path(__file__).parent.absolute()
606 parser.add_argument(
607 "--test-param-json-directory",
608 dest="param_json_dir",
609 type=Path,
610 default=script_dir,
Jeremy Johnson88588622022-07-12 16:42:29 +0100611 help=f"Test parameters (ops info) JSON file directory (default is {script_dir})",
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100612 )
613 parser.add_argument(
614 "--convert-all-tests",
615 action="store_true",
616 help="Converts all tests instead of those picked by test_select",
617 )
618 parser.add_argument(
619 "--keep-large-files",
620 action="store_true",
621 help="Keeps tests that contain files larger than 30MB in output directory",
622 )
623 parser.add_argument(
624 "--capture-output",
625 action="store_true",
626 help="Prints output of running sh commands",
627 )
628 parser.add_argument(
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100629 "-j",
630 dest="num_cores",
631 type=int,
632 default=6,
633 help="Number of simultaneous jobs to split the tasks into for multiprocessing",
634 )
635 parser.add_argument(
636 "-v",
637 dest="verbosity",
638 action="count",
639 default=0,
640 help="Verbosity (can be used multiple times for more details)",
641 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100642 args = parser.parse_args(argv)
643
644 return args
645
646
647def main():
648 args = parse_args()
649
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100650 if args.ref_model_dir is not None:
651 # Assume the ref model exe path based on the ref model directory
652 args.ref_model_path = cmf.find_tosa_file(
653 cmf.TosaFileType.REF_MODEL, args.ref_model_dir, False
654 )
655 if not args.ref_model_path.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100656 logger.error(
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100657 f"Missing reference model binary (--ref-model-path): {args.ref_model_path}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100658 )
659 return 2
Jeremy Johnson65ba8092023-10-09 16:31:13 +0100660 args.ref_model_path = args.ref_model_path.absolute()
661
662 if args.generate_lib_path is None:
663 args.generate_lib_path = cmf.find_tosa_file(
664 cmf.TosaFileType.GENERATE_LIBRARY, args.ref_model_path
665 )
666 if not args.generate_lib_path.is_file():
667 logger.error(
668 f"Missing TOSA generate data library (--generate-lib-path): {args.generate_lib_path}"
669 )
670 return 2
671 args.generate_lib_path = args.generate_lib_path.absolute()
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100672
673 if args.schema_path is None:
674 args.schema_path = cmf.find_tosa_file(
675 cmf.TosaFileType.SCHEMA, args.ref_model_path
676 )
677 if not args.schema_path.is_file():
678 logger.error(
679 f"Missing reference model schema (--schema-path): {args.schema_path}"
680 )
681 return 2
Jeremy Johnson65ba8092023-10-09 16:31:13 +0100682 args.schema_path = args.schema_path.absolute()
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100683
684 if args.flatc_path is None:
685 args.flatc_path = cmf.find_tosa_file(
686 cmf.TosaFileType.FLATC, args.ref_model_path
687 )
688 if not args.flatc_path.is_file():
689 logger.error(f"Missing flatc binary (--flatc-path): {args.flatc_path}")
690 return 2
Jeremy Johnson65ba8092023-10-09 16:31:13 +0100691 args.flatc_path = args.flatc_path.absolute()
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100692
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100693 if args.unit_tests in ["framework", "both"]:
Jeremy Johnsonc1d1c632023-08-02 17:21:36 +0100694 logger.warning(
695 "DEPRECATION - Framework tests are not part of TOSA conformance testing"
696 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100697 if not args.framework_schema:
698 logger.error(
699 "Need to supply location of Framework flatbuffers schema via --framework-schema"
700 )
701 return 2
702 if not args.framework_tests_dir.is_dir():
703 logger.error(
704 f"Missing or invalid framework tests directory: {args.framework_tests_dir}"
705 )
706 return 2
707
708 loglevels = (logging.WARNING, logging.INFO, logging.DEBUG)
709 loglevel = loglevels[min(args.verbosity, len(loglevels) - 1)]
710 logger.setLevel(loglevel)
711 # Set other loggers the same
712 logging.getLogger("test_select").setLevel(loglevel)
713 logging.getLogger("convert2conformance").setLevel(loglevel)
714
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100715 print(f"Output directory: {args.output_dir}")
716
Jeremy Johnson93d43902022-09-27 12:26:14 +0100717 if args.random_seed != DEFAULT_SEED:
718 logger.warning(
719 "Random test seed changed from default, tests will not match official conformance"
720 )
721
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100722 args.build_dir = args.build_dir.resolve()
723 logger.debug(f"Creating build directory: {args.build_dir}")
724 args.build_dir.mkdir(parents=True, exist_ok=True)
725
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100726 # TODO: For tosa-mi should really generate tosa-bi profile as well
727 # - for now leave it as subset instead of as superset (for testing)
728 if args.profile == PROFILES_ALL:
729 profiles = list(PROFILE_OPS_INFO.keys())
730 else:
731 profiles = [args.profile]
732
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100733 try:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100734 for profile in profiles:
735 print(f"Creating conformance tests for TOSA {profile} profile")
736 # Framework unit tests
737 if args.unit_tests in ["framework", "both"]:
738 logger.debug("Creating FRAMEWORK unit tests")
739 test_picks_file = (
740 args.param_json_dir / PROFILE_OPS_INFO[profile]["framework_tests"]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100741 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100742 try:
743 with open(test_picks_file, "r") as fd:
744 test_picks = json.load(fd)
745 except Exception as e:
746 logger.error(
747 f"Couldn't load framework tests info - {test_picks_file}: {e}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100748 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100749 return 1
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100750
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100751 operators = args.operators
752 if not operators:
753 # Create tests for all the operators
754 operators = list(test_picks.keys())
755
756 root_output_dir = (
757 args.output_dir / "frameworks" / "tflite" / "operators"
758 )
759 for op in operators:
760 logger.info(f"FRAMEWORK OP: {op}")
761 if op not in test_picks:
762 logger.warning(
763 f"Framework op {op} not found in {test_picks_file} - skipping"
764 )
765 continue
766
767 op_profiles_list = test_picks[op]["profile"]
768 if (
769 args.profile != PROFILES_ALL
770 and args.profile not in op_profiles_list
771 ):
772 # Skip this operator as not part of the profile chosen
773 logger.debug(f"Skipping {op} as not part of {args.profile}")
774 continue
775
776 logger.debug(f"Copying and renaming {op}")
777 framework_test_dir = copy_rename_framework_tests(
778 args, op, test_picks
779 )
780
781 if args.convert_all_tests:
782 logger.debug("Running and converting all framework tests")
783 framework_tests = None # Don't select any
784 else:
785 logger.debug("Running and converting selected framework tests")
786 framework_tests = get_framework_tests_selection(
787 args, op, test_picks, framework_test_dir
788 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100789 convert_tests(
790 args,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100791 profile,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100792 op,
793 framework_test_dir,
794 root_output_dir,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100795 op_profiles_list,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100796 tests=framework_tests,
797 trim_op_subdir=True,
798 )
799
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100800 # Operator unit tests
801 if args.unit_tests in ["operator", "both"]:
802 logger.debug("Creating OPERATOR unit tests")
803 test_params_file = (
804 args.param_json_dir
805 / PROFILE_OPS_INFO[profile]["operator_test_params"]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100806 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100807 try:
808 with open(test_params_file, "r") as fd:
809 test_params = json.load(fd)
810 except Exception as e:
811 logger.error(
812 f"Couldn't load operator test params - {test_params_file}: {e}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100813 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100814 return 1
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100815
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100816 operators = args.operators
817 if not operators:
818 # Create tests for all the operators
819 operators = list(test_params.keys())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100820
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100821 for op in operators:
822 logger.info(f"OPERATOR: {op}")
823 if op not in test_params:
824 logger.warning(
825 f"{op} operator parameters not found in {test_params_file} - skipping"
826 )
827 continue
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100828
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100829 if args.test_version == TEST_VERSION_V0_60_0 and op in ("dim",):
830 logger.warning(f"{op} is not in {args.test_version} - skipping")
831 continue
832
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100833 op_profiles_list = test_params[op]["profile"]
834 if (
835 args.profile != PROFILES_ALL
836 and args.profile not in op_profiles_list
837 ):
838 # Skip this operator as not part of the profile chosen
839 logger.debug(f"Skipping {op} as not part of {args.profile}")
840 continue
841
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100842 operator_group = test_params[op]["group"]
843 root_output_dir = args.output_dir / "operators"
Jeremy Johnson1271c442023-09-05 11:39:26 +0100844 supports = (
845 test_params[op]["support_for"]
846 if "support_for" in test_params[op]
847 else []
848 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000849
850 # Iterate through the generation groups selecting tests from each
851 for gen_name, gen_dict in test_params[op]["generation"].items():
852 no_neg_tests = (
853 "no_negative_tests" in gen_dict
854 and gen_dict["no_negative_tests"] == "true"
855 )
856
857 if no_neg_tests:
858 if args.test_type == "negative":
859 logger.info(
860 f"No negative tests for {op} / generation group {gen_name}"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100861 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000862 continue
863 # Only produce positive tests
864 test_type = "positive"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100865 else:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000866 test_type = args.test_type
867
868 gen_neg_dim_range = (
869 gen_dict["negative_dim_range"]
870 if "negative_dim_range" in gen_dict
871 else None
872 )
873
874 ignore_missing = gen_name != STANDARD_GENERATOR_GROUP
875 tags = (
876 [gen_name] if gen_name != STANDARD_GENERATOR_GROUP else None
877 )
878
879 op_build_dir = build_op_tests(
880 args,
881 test_type,
882 profile,
883 op,
884 gen_name,
885 gen_dict["generator_args"],
886 gen_neg_dim_range,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100887 supports=supports,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000888 )
889
Jeremy Johnson0c716862023-04-13 17:18:19 +0100890 # Work out which selection criteria we are using
891 if "selector" in gen_dict:
892 selector_name = gen_dict["selector"]
893 if selector_name not in test_params[op]["selection"]:
894 logger.warn(
895 f"Could not find {selector_name} in selection dict for {op} - using default"
896 )
897 selector_name = "default"
898 else:
899 selector_name = "default"
900 if selector_name not in test_params[op]["selection"]:
901 logger.error(
902 f"Could not find {selector_name} in selection dict for {op}"
903 )
904 raise (GenConformanceError())
905
906 # Selection criteria
907 selection_config = test_params[op]["selection"][selector_name]
908
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100909 if args.convert_all_tests:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000910 logger.debug(f"Running and converting all {op} tests")
Jeremy Johnson1271c442023-09-05 11:39:26 +0100911 generate_results(
912 args, profile, op, op_build_dir, supports=supports
913 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000914 operator_test_list = None
915 else:
916 logger.debug(
917 f"Running and converting selection of {op} tests"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100918 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000919 if test_type in ["positive", "both"]:
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100920 if (
921 "all" in selection_config
922 and selection_config["all"] == "true"
923 ):
924 # Just get all the positive tests
925 tests_gen, tests_gen2 = tee(
926 _get_all_tests_list(
927 profile,
928 op_build_dir,
929 op,
930 exclude_negative_tests=True,
931 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000932 )
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100933 else:
934 # Get a selection of positive tests
935 tests_gen, tests_gen2 = tee(
936 get_op_tests_selection(
937 args,
938 profile,
939 op,
940 op_build_dir,
941 selection_config,
942 ignore_missing=ignore_missing,
943 )
944 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000945 generate_results(
Jeremy Johnson1271c442023-09-05 11:39:26 +0100946 args,
947 profile,
948 op,
949 op_build_dir,
950 supports=supports,
951 tests=tests_gen,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000952 )
953 operator_test_list = list(tests_gen2)
954 else:
955 operator_test_list = []
956 if test_type in ["negative", "both"]:
957 operator_test_list.extend(
958 get_op_tests_selection(
959 args,
960 profile,
961 op,
962 op_build_dir,
963 selection_config,
964 negative=True,
965 )
966 )
967 output_dir = convert_tests(
968 args,
969 profile,
970 op,
971 op_build_dir,
972 root_output_dir,
973 op_profiles_list,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100974 supports=supports,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000975 tests=operator_test_list,
976 group=operator_group,
977 tags=tags,
978 )
979 if not args.keep_large_files:
980 check_op_tests(args, profile, op, output_dir)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100981 except GenConformanceError:
982 return 1
983
984 return 0
985
986
987if __name__ == "__main__":
988 exit(main())