blob: 4281fc2b185ff1c29b0548f7fbcf686b8f844387 [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,
112 "--filter",
113 operator,
114 "-o",
115 str(op_build_dir),
116 "--seed",
Jeremy Johnson93d43902022-09-27 12:26:14 +0100117 str(args.random_seed),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100118 ]
119
Jeremy Johnson1271c442023-09-05 11:39:26 +0100120 if "lazy_data_gen" in supports and args.lazy_data_generation:
121 build_cmd_base.append("--lazy-data-generation")
122
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000123 build_cmds_list = []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100124
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000125 if test_type in ["positive", "both"]:
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100126 # Append extra parameters and run test generator for each set of parameters.
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000127 for arglist in gen_args_list:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000128 build_cmd_pos_test = build_cmd_base.copy()
129 build_cmd_pos_test.extend(["--test-type", "positive"])
130 build_cmd_pos_test.extend(arglist)
131 build_cmds_list.append(build_cmd_pos_test)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100132
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000133 if test_type in ["negative", "both"]:
Jeremy Johnson35396f22023-01-04 17:05:25 +0000134 # Get target-dtypes options and any filter string to limit tests
Jeremy Johnson93d43902022-09-27 12:26:14 +0100135 target_dtypes_args = []
Jeremy Johnson35396f22023-01-04 17:05:25 +0000136 filter_str = None
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000137 for arglist in gen_args_list:
Jeremy Johnson93d43902022-09-27 12:26:14 +0100138 idx = 0
139 while idx < len(arglist):
140 if arglist[idx] == "--target-dtype":
141 if arglist[idx + 1] not in target_dtypes_args:
142 target_dtypes_args.extend(arglist[idx : idx + 2])
143 idx += 1 # skip over option (and then argument below)
Jeremy Johnson35396f22023-01-04 17:05:25 +0000144 elif arglist[idx] == "--filter":
145 filter_str = arglist[idx + 1]
146 idx += 1 # skip over option (and then argument below)
Jeremy Johnson93d43902022-09-27 12:26:14 +0100147 idx += 1
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000148 build_cmd_neg_test = build_cmd_base.copy()
Jeremy Johnson35396f22023-01-04 17:05:25 +0000149 if filter_str:
150 build_cmd_neg_test.extend(["--filter", filter_str])
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000151 build_cmd_neg_test.extend(["--test-type", "negative"])
Jeremy Johnson93d43902022-09-27 12:26:14 +0100152 # Limit sizes of negative tests
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000153 dim_range = gen_neg_dim_range if gen_neg_dim_range is not None else "1,16"
154
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000155 build_cmd_neg_test.extend(["--tensor-dim-range", dim_range])
156 build_cmd_neg_test.extend(target_dtypes_args)
157 build_cmds_list.append(build_cmd_neg_test)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100158
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000159 logger.debug(f"Creating {operator} tests with {len(build_cmds_list)} parameter(s)")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100160 error = False
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000161 for i, cmd in enumerate(build_cmds_list):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100162 try:
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100163 _run_sh_command(args, args.ref_model_path.parent.absolute(), cmd)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100164 logger.info(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000165 f"{operator} test batch {(i+1)}/{len(build_cmds_list)} created successfully"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100166 )
167 except Exception as e:
168 logger.error(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000169 f"{operator} test batch {(i+1)}/{len(build_cmds_list)} unsuccessful, skipping"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100170 )
171 logger.error(f" build_op_tests error: {e} ")
172 error = True
173 if error:
174 raise (GenConformanceError())
175
176 return op_build_dir
177
178
179def _check_to_include_test(profile, test_name, exclude_negative_tests=False):
180 """Check test name for exclusions, return False to indicate excluded."""
181 excludes = ["ERRORIF"] if exclude_negative_tests else []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100182
183 for exclusion in excludes:
184 if f"_{exclusion}_" in test_name:
185 return False
186 return True
187
188
189def _get_all_tests_list(
190 profile, test_root_dir, operator, exclude_negative_tests=False, include_all=False
191):
192 """Create test list based on tests in the test_dir."""
193 test_dir = test_root_dir / operator
194 if not test_dir.is_dir():
195 # Tests are split into multiple dirs, for example: conv2d_1x1, conv2d_3x3
196 test_dir = test_root_dir
197 directories = [
198 tdir for tdir in test_dir.glob("*") if tdir.name.startswith(operator)
199 ]
200 else:
201 directories = [test_dir]
202
203 tests = []
204 for tdir in directories:
205 tests.extend(
206 [
207 test
208 for test in tdir.glob("*")
209 if include_all
210 or _check_to_include_test(profile, test.name, exclude_negative_tests)
211 ]
212 )
213 return tests
214
215
Jeremy Johnson1271c442023-09-05 11:39:26 +0100216def generate_results(args, profile, operator, op_build_dir, supports=[], tests=None):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100217 """Run tests on reference model and save result to the test directory."""
Jeremy Johnson1271c442023-09-05 11:39:26 +0100218 if "lazy_data_gen" in supports and args.lazy_data_generation:
219 logger.info("Skipping running tests due to lazy data gen")
220 return
221
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100222 num_cores = args.num_cores
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100223
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100224 # Use the test runner
225 ref_cmd_base = [
226 "tosa_verif_run_tests",
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100227 "--ref-model-path",
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100228 str(args.ref_model_path.absolute()),
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100229 "--schema-path",
230 str(args.schema_path.absolute()),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100231 "-j",
232 str(num_cores),
233 "-v",
234 "-t",
235 ]
236 ref_cmds = []
237
238 if not tests:
239 # Do not need to run ERRORIF tests as they don't have result files
240 tests = _get_all_tests_list(
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100241 profile, op_build_dir, operator, exclude_negative_tests=True
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100242 )
243
244 for test in tests:
Jeremy Johnsone2b5e872023-09-14 17:02:09 +0100245 desc = test / "desc.json"
246 with desc.open("r") as fd:
247 test_desc = json.load(fd)
248 if "meta" in test_desc and "compliance" in test_desc["meta"]:
249 logger.info(
250 f"Skipping generating results for new compliance test - {str(test)}"
251 )
252 continue
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100253 ref_cmd = ref_cmd_base.copy()
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100254 ref_cmd.append(str(test.absolute()))
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100255 ref_cmds.append(ref_cmd)
256
257 fail_string = "UNEXPECTED_FAILURE"
258 failed_counter = 0
259
260 job_pool = mp.Pool(args.num_cores)
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100261 sh_partial = partial(_run_sh_command, args, args.ref_model_path.parent.absolute())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100262 pool_results = job_pool.map(sh_partial, ref_cmds)
263 job_pool.close()
264 job_pool.join()
265
266 # Use captured output for run_sh_command to work out if test passed.
267 for i, rc in enumerate(pool_results):
268 if fail_string in str(rc[0]):
269 logger.error(f"Test {i+1}/{len(ref_cmds)}: {ref_cmds[i][-1]} failed.")
270 failed_counter += 1
271 else:
272 logger.info(f"Test {i+1}/{len(ref_cmds)}: {ref_cmds[i][-1]} passed.")
273
274 logger.info(f"{len(ref_cmds)-failed_counter}/{len(ref_cmds)} tests passed")
275 logger.info("Ran tests on model and saved results of passing tests")
276
277
278def convert_tests(
279 args,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100280 profile,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100281 operator,
282 op_build_dir,
283 output_dir,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100284 op_profiles_list,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100285 supports=[],
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100286 tests=None,
287 group=None,
288 trim_op_subdir=False,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000289 tags=None,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100290):
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100291 """Convert/copy tests to output directory."""
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100292 if group:
293 output_dir = output_dir / group
294
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100295 c2c_args_base = ["--strict"]
296 c2c_args_base.extend(["--schema-path", str(args.schema_path)])
297 c2c_args_base.extend(["--flatc-path", str(args.flatc_path)])
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100298 c2c_args_base.extend(["--output-type", args.output_type])
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100299 # This op maybe in more than one profile - e.g. tosa_bi and tosa_mi
300 # even if we are only producing tests for tosa_mi
301 for op_profile in op_profiles_list:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000302 c2c_args_base.extend(["--profile", op_profile])
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000303 if tags is not None:
304 for tag in tags:
305 c2c_args_base.extend(["--tag", tag])
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100306 if args.framework_schema:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000307 c2c_args_base.extend(["--framework-schema", str(args.framework_schema)])
Jeremy Johnson1271c442023-09-05 11:39:26 +0100308 if "lazy_data_gen" in supports and args.lazy_data_generation:
309 c2c_args_base.append("--lazy-data-generation")
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000310 c2c_args_base.append("--output-directory")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100311
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000312 c2c_args_list = []
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100313
314 if not tests:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100315 tests = _get_all_tests_list(profile, op_build_dir, operator)
316 logger.info(f"Converting all {profile} profile tests")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100317
318 # Controls if we copy the tests in their operator sub-directory or not
319 output_dir_relative_pos = -1 if trim_op_subdir else -2
320 for test in tests:
321 logger.info(f"Test chosen: {test}")
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000322 c2c_args = c2c_args_base.copy()
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100323 full_output_directory = output_dir / test.relative_to(
324 *test.parts[:output_dir_relative_pos]
325 )
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000326 c2c_args.append(str(full_output_directory))
327 c2c_args.append(str(test))
328 c2c_args_list.append(c2c_args)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100329
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000330 if len(c2c_args_list) == 0:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000331 logger.error(
332 f"No tests found for {operator}. Nothing to convert in {op_build_dir}"
333 )
334 raise (GenConformanceError())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100335
336 job_pool = mp.Pool(args.num_cores)
337
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000338 pool_results = job_pool.map(c2c_main, c2c_args_list)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100339 job_pool.close()
340 job_pool.join()
341
342 failed_counter = 0
343 for i, result in enumerate(pool_results):
344 if result != 0:
345 logger.error(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000346 f"test {i+1}/{len(c2c_args_list)}: {c2c_args_list[i][-1]} failed to convert."
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100347 )
348 failed_counter += 1
349 else:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000350 logger.info(
351 f"test {i+1}/{len(c2c_args_list)}: {c2c_args_list[i][-1]} converted"
352 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100353 logger.info(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000354 f"{len(c2c_args_list)-failed_counter}/{len(c2c_args_list)} tests successfully converted"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100355 )
356
357 if failed_counter > 0:
358 logger.error(f"Stopping due to {failed_counter} test conversion errors")
359 raise (GenConformanceError())
360
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100361 logger.info("Converted/copied tests and saved to output directory")
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100362
363 return output_dir
364
365
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100366def get_op_tests_selection(
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000367 args,
368 profile,
369 operator,
370 op_build_dir,
371 selection_config,
372 negative=False,
373 ignore_missing=False,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100374):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100375 """Use test picker to get subsection of tests generated."""
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000376 # Need a full copy of the config as the selector updates it
377 config = copy.deepcopy(selection_config)
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100378 logger.info("Choosing {} tests".format(("negative" if negative else "positive")))
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100379 try:
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100380 op = Operator.registry[operator](
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000381 op_build_dir, config, negative=negative, ignore_missing=ignore_missing
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100382 )
383 except KeyError:
384 logger.error(f"{operator} operator is not supported by test_select")
385 raise (GenConformanceError())
386
387 return op.select_tests()
388
389
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100390def check_op_tests(args, profile, operator, output_dir):
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100391 """Move test folders than contain files larger than 30MB to new directory."""
392 destination_dir = str(args.output_dir) + "_large_files"
393
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100394 tests = _get_all_tests_list(profile, output_dir, operator, include_all=True)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100395 if not tests:
396 logger.error(
397 f"Couldn't find any tests to size check for {operator} in {output_dir}"
398 )
399 raise (GenConformanceError())
400
401 for tdir in tests:
402 move_dir = False
403 test_files = [file for file in tdir.glob("*")]
404 for file in test_files:
405 file_size = os.stat(file).st_size / 1024**2
406 if file_size > 30:
407 move_dir = True
408
409 if move_dir:
410 move_destination = destination_dir / tdir.relative_to(output_dir)
411 logger.warning(
412 f"{tdir.relative_to(output_dir)} contains files that are too large (>30MB), test moved to new folder: {destination_dir}"
413 )
414
415 if move_destination.is_dir():
416 logger.warning(
417 f"{move_destination} directory already exists, deleting existing."
418 )
419 shutil.rmtree(str(move_destination))
420 shutil.move(str(tdir), move_destination)
421
422
423def copy_rename_framework_tests(args, operator, test_picks):
424 """Copy framework tests into new folder and rename them if needed.
425
426 The tests are renamed to match the framework operator names if an
427 alternate name has been used instead.
428 """
429 framework_tests_dir = args.framework_tests_dir
430 new_tests_dir = args.build_dir / "frameworks" / operator
431 os.makedirs(new_tests_dir, exist_ok=True)
432
433 # Get the framework tests operator name
434 if "alternate_names" in test_picks[operator]:
435 alternate_names = test_picks[operator]["alternate_names"]
436 else:
437 alternate_names = [operator]
438
439 # Get the alternate named test directories for the operator
440 for alt_name in alternate_names:
441 test_prefix = f"test_{alt_name}"
442 test_dirs = list(framework_tests_dir.glob(f"{test_prefix}_*"))
443
444 # Copy tests to new directory and rename to match framework operator names
445 # - if there is just 1 alternate name, replace the full test prefix
446 # test_add_... -> add_...
447 # - if there are multiple alternate names, just replace the "test"
448 # test_concatv2_... -> concatenation_concatv2_...
449 old_prefix = test_prefix if len(alternate_names) == 1 else "test"
450
451 for tdir in test_dirs:
452 new_test_name = tdir.name.replace(old_prefix, operator)
453 copy_destination = new_tests_dir / new_test_name
454 logger.debug(f"copying test folder {tdir} to {copy_destination}")
455 copy_tree(str(tdir), str(copy_destination))
456
457 logger.info(f"Copied and renamed {len(test_dirs)} framework test folders")
458 return new_tests_dir.parent
459
460
461def get_framework_tests_selection(args, operator, test_picks, op_build_dir):
462 """Get the list of pre-chosen tests with relative paths."""
463 try:
464 tests = test_picks[operator]["tests"]
465 except KeyError:
466 logger.error(f"Framework test selection not defined for {operator} operator")
467 raise (GenConformanceError())
468
469 test_paths = [op_build_dir / operator / test for test in tests]
470 return test_paths
471
472
473def parse_args(argv=None):
474 """Parse the arguments."""
475 parser = argparse.ArgumentParser()
Jeremy Johnson88588622022-07-12 16:42:29 +0100476 profiles = list(PROFILE_OPS_INFO.keys())
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100477 profiles.append(PROFILES_ALL)
Jeremy Johnson88588622022-07-12 16:42:29 +0100478 parser.add_argument(
479 "--profile",
480 dest="profile",
481 choices=profiles,
482 default=profiles[0],
483 type=str,
484 help=f"TOSA profile (default is {profiles[0]})",
485 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100486 parser.add_argument(
487 "--operators",
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100488 "--op",
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100489 type=str,
490 nargs="*",
491 help="The operator(s) to create tests for, if not supplied all tests will be created",
492 )
493 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +0100494 "--unit-tests",
495 dest="unit_tests",
496 choices=["operator", "framework", "both"],
497 default="operator",
498 type=str,
499 help="Which unit tests are produced (default is operator)",
500 )
501 parser.add_argument(
502 "--test-type",
503 dest="test_type",
504 choices=["positive", "negative", "both"],
505 default="both",
506 type=str,
507 help="Type of tests produced (default is both)",
508 )
509 parser.add_argument(
Jeremy Johnson1271c442023-09-05 11:39:26 +0100510 "--lazy-data-generation",
511 action="store_true",
512 help="Enable lazy data generation (only for tosa-mi)",
513 )
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100514 rm_group = parser.add_mutually_exclusive_group(required=True)
515 rm_group.add_argument(
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100516 "--ref-model-directory",
517 dest="ref_model_dir",
518 type=Path,
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100519 help="(DEPRECATED - use ref-model-path) Reference Model directory - with build directory",
520 )
521 rm_group.add_argument(
522 "--ref-model-path",
523 dest="ref_model_path",
524 type=Path,
525 help="Path to TOSA reference model executable",
526 )
527 parser.add_argument(
528 "--schema-path",
529 "--operator-fbs",
530 dest="schema_path",
531 type=Path,
532 help=(
533 "Path to TOSA reference model flat buffer schema. Defaults to "
534 f"`{cmf.DEFAULT_REF_MODEL_SCHEMA_PATH}` in parents parent directory of `ref-model-path`"
535 ),
536 )
537 parser.add_argument(
538 "--flatc-path",
539 dest="flatc_path",
540 type=Path,
541 help=(
542 "Path to flatc executable. Defaults to "
543 f"`{cmf.DEFAULT_REF_MODEL_BUILD_FLATC_PATH}` in parent directory of `ref-model-path`"
544 ),
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100545 )
Jeremy Johnson88588622022-07-12 16:42:29 +0100546 parser.add_argument(
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100547 "--test-version",
548 dest="test_version",
549 choices=TEST_VERSIONS,
550 default=TEST_VERSION_LATEST,
551 help=f"Version of the tests to produce (default is {TEST_VERSION_LATEST})",
552 )
553 parser.add_argument(
554 "--output-type",
555 dest="output_type",
556 choices=OUTPUT_TYPES,
557 default=OUTPUT_TYPE_DEFAULT,
558 help=f"Output file type produced (default is {OUTPUT_TYPE_DEFAULT})",
559 )
560 parser.add_argument(
Jeremy Johnson93d43902022-09-27 12:26:14 +0100561 "--seed",
562 dest="random_seed",
563 default=DEFAULT_SEED,
564 type=int,
565 help="Random test seed",
566 )
567 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +0100568 "--framework-tests-directory",
569 dest="framework_tests_dir",
570 type=Path,
571 default=Path.cwd() / "tests",
572 help="The pre-built framework tests directory (default is tests)",
573 )
574 parser.add_argument(
575 "--framework-schema",
576 dest="framework_schema",
577 type=Path,
578 help="Framework flatbuffers schema needed to convert framework models",
579 )
580 parser.add_argument(
581 "--build-directory",
582 dest="build_dir",
583 type=Path,
584 default=Path.cwd() / "conformance_build",
585 help="Temporary build directory for files created during this process (default is conformance_build)",
586 )
587 parser.add_argument(
588 "--output-directory",
589 dest="output_dir",
590 type=Path,
591 default=Path.cwd() / "conformance",
592 help="Output directory (default is conformance)",
593 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100594 script_dir = Path(__file__).parent.absolute()
595 parser.add_argument(
596 "--test-param-json-directory",
597 dest="param_json_dir",
598 type=Path,
599 default=script_dir,
Jeremy Johnson88588622022-07-12 16:42:29 +0100600 help=f"Test parameters (ops info) JSON file directory (default is {script_dir})",
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100601 )
602 parser.add_argument(
603 "--convert-all-tests",
604 action="store_true",
605 help="Converts all tests instead of those picked by test_select",
606 )
607 parser.add_argument(
608 "--keep-large-files",
609 action="store_true",
610 help="Keeps tests that contain files larger than 30MB in output directory",
611 )
612 parser.add_argument(
613 "--capture-output",
614 action="store_true",
615 help="Prints output of running sh commands",
616 )
617 parser.add_argument(
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100618 "-j",
619 dest="num_cores",
620 type=int,
621 default=6,
622 help="Number of simultaneous jobs to split the tasks into for multiprocessing",
623 )
624 parser.add_argument(
625 "-v",
626 dest="verbosity",
627 action="count",
628 default=0,
629 help="Verbosity (can be used multiple times for more details)",
630 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100631 args = parser.parse_args(argv)
632
633 return args
634
635
636def main():
637 args = parse_args()
638
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100639 if args.ref_model_dir is not None:
640 # Assume the ref model exe path based on the ref model directory
641 args.ref_model_path = cmf.find_tosa_file(
642 cmf.TosaFileType.REF_MODEL, args.ref_model_dir, False
643 )
644 if not args.ref_model_path.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100645 logger.error(
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100646 f"Missing reference model binary (--ref-model-path): {args.ref_model_path}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100647 )
648 return 2
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100649
650 if args.schema_path is None:
651 args.schema_path = cmf.find_tosa_file(
652 cmf.TosaFileType.SCHEMA, args.ref_model_path
653 )
654 if not args.schema_path.is_file():
655 logger.error(
656 f"Missing reference model schema (--schema-path): {args.schema_path}"
657 )
658 return 2
659
660 if args.flatc_path is None:
661 args.flatc_path = cmf.find_tosa_file(
662 cmf.TosaFileType.FLATC, args.ref_model_path
663 )
664 if not args.flatc_path.is_file():
665 logger.error(f"Missing flatc binary (--flatc-path): {args.flatc_path}")
666 return 2
667
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100668 if args.unit_tests in ["framework", "both"]:
Jeremy Johnsonc1d1c632023-08-02 17:21:36 +0100669 logger.warning(
670 "DEPRECATION - Framework tests are not part of TOSA conformance testing"
671 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100672 if not args.framework_schema:
673 logger.error(
674 "Need to supply location of Framework flatbuffers schema via --framework-schema"
675 )
676 return 2
677 if not args.framework_tests_dir.is_dir():
678 logger.error(
679 f"Missing or invalid framework tests directory: {args.framework_tests_dir}"
680 )
681 return 2
682
683 loglevels = (logging.WARNING, logging.INFO, logging.DEBUG)
684 loglevel = loglevels[min(args.verbosity, len(loglevels) - 1)]
685 logger.setLevel(loglevel)
686 # Set other loggers the same
687 logging.getLogger("test_select").setLevel(loglevel)
688 logging.getLogger("convert2conformance").setLevel(loglevel)
689
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100690 print(f"Output directory: {args.output_dir}")
691
Jeremy Johnson93d43902022-09-27 12:26:14 +0100692 if args.random_seed != DEFAULT_SEED:
693 logger.warning(
694 "Random test seed changed from default, tests will not match official conformance"
695 )
696
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100697 args.build_dir = args.build_dir.resolve()
698 logger.debug(f"Creating build directory: {args.build_dir}")
699 args.build_dir.mkdir(parents=True, exist_ok=True)
700
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100701 # TODO: For tosa-mi should really generate tosa-bi profile as well
702 # - for now leave it as subset instead of as superset (for testing)
703 if args.profile == PROFILES_ALL:
704 profiles = list(PROFILE_OPS_INFO.keys())
705 else:
706 profiles = [args.profile]
707
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100708 try:
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100709 for profile in profiles:
710 print(f"Creating conformance tests for TOSA {profile} profile")
711 # Framework unit tests
712 if args.unit_tests in ["framework", "both"]:
713 logger.debug("Creating FRAMEWORK unit tests")
714 test_picks_file = (
715 args.param_json_dir / PROFILE_OPS_INFO[profile]["framework_tests"]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100716 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100717 try:
718 with open(test_picks_file, "r") as fd:
719 test_picks = json.load(fd)
720 except Exception as e:
721 logger.error(
722 f"Couldn't load framework tests info - {test_picks_file}: {e}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100723 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100724 return 1
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100725
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100726 operators = args.operators
727 if not operators:
728 # Create tests for all the operators
729 operators = list(test_picks.keys())
730
731 root_output_dir = (
732 args.output_dir / "frameworks" / "tflite" / "operators"
733 )
734 for op in operators:
735 logger.info(f"FRAMEWORK OP: {op}")
736 if op not in test_picks:
737 logger.warning(
738 f"Framework op {op} not found in {test_picks_file} - skipping"
739 )
740 continue
741
742 op_profiles_list = test_picks[op]["profile"]
743 if (
744 args.profile != PROFILES_ALL
745 and args.profile not in op_profiles_list
746 ):
747 # Skip this operator as not part of the profile chosen
748 logger.debug(f"Skipping {op} as not part of {args.profile}")
749 continue
750
751 logger.debug(f"Copying and renaming {op}")
752 framework_test_dir = copy_rename_framework_tests(
753 args, op, test_picks
754 )
755
756 if args.convert_all_tests:
757 logger.debug("Running and converting all framework tests")
758 framework_tests = None # Don't select any
759 else:
760 logger.debug("Running and converting selected framework tests")
761 framework_tests = get_framework_tests_selection(
762 args, op, test_picks, framework_test_dir
763 )
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100764 convert_tests(
765 args,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100766 profile,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100767 op,
768 framework_test_dir,
769 root_output_dir,
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100770 op_profiles_list,
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100771 tests=framework_tests,
772 trim_op_subdir=True,
773 )
774
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100775 # Operator unit tests
776 if args.unit_tests in ["operator", "both"]:
777 logger.debug("Creating OPERATOR unit tests")
778 test_params_file = (
779 args.param_json_dir
780 / PROFILE_OPS_INFO[profile]["operator_test_params"]
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100781 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100782 try:
783 with open(test_params_file, "r") as fd:
784 test_params = json.load(fd)
785 except Exception as e:
786 logger.error(
787 f"Couldn't load operator test params - {test_params_file}: {e}"
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100788 )
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100789 return 1
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100790
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100791 operators = args.operators
792 if not operators:
793 # Create tests for all the operators
794 operators = list(test_params.keys())
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100795
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100796 for op in operators:
797 logger.info(f"OPERATOR: {op}")
798 if op not in test_params:
799 logger.warning(
800 f"{op} operator parameters not found in {test_params_file} - skipping"
801 )
802 continue
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100803
Jeremy Johnson9c2fe6e2023-10-04 16:55:04 +0100804 if args.test_version == TEST_VERSION_V0_60_0 and op in ("dim",):
805 logger.warning(f"{op} is not in {args.test_version} - skipping")
806 continue
807
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100808 op_profiles_list = test_params[op]["profile"]
809 if (
810 args.profile != PROFILES_ALL
811 and args.profile not in op_profiles_list
812 ):
813 # Skip this operator as not part of the profile chosen
814 logger.debug(f"Skipping {op} as not part of {args.profile}")
815 continue
816
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100817 operator_group = test_params[op]["group"]
818 root_output_dir = args.output_dir / "operators"
Jeremy Johnson1271c442023-09-05 11:39:26 +0100819 supports = (
820 test_params[op]["support_for"]
821 if "support_for" in test_params[op]
822 else []
823 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000824
825 # Iterate through the generation groups selecting tests from each
826 for gen_name, gen_dict in test_params[op]["generation"].items():
827 no_neg_tests = (
828 "no_negative_tests" in gen_dict
829 and gen_dict["no_negative_tests"] == "true"
830 )
831
832 if no_neg_tests:
833 if args.test_type == "negative":
834 logger.info(
835 f"No negative tests for {op} / generation group {gen_name}"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100836 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000837 continue
838 # Only produce positive tests
839 test_type = "positive"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100840 else:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000841 test_type = args.test_type
842
843 gen_neg_dim_range = (
844 gen_dict["negative_dim_range"]
845 if "negative_dim_range" in gen_dict
846 else None
847 )
848
849 ignore_missing = gen_name != STANDARD_GENERATOR_GROUP
850 tags = (
851 [gen_name] if gen_name != STANDARD_GENERATOR_GROUP else None
852 )
853
854 op_build_dir = build_op_tests(
855 args,
856 test_type,
857 profile,
858 op,
859 gen_name,
860 gen_dict["generator_args"],
861 gen_neg_dim_range,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100862 supports=supports,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000863 )
864
Jeremy Johnson0c716862023-04-13 17:18:19 +0100865 # Work out which selection criteria we are using
866 if "selector" in gen_dict:
867 selector_name = gen_dict["selector"]
868 if selector_name not in test_params[op]["selection"]:
869 logger.warn(
870 f"Could not find {selector_name} in selection dict for {op} - using default"
871 )
872 selector_name = "default"
873 else:
874 selector_name = "default"
875 if selector_name not in test_params[op]["selection"]:
876 logger.error(
877 f"Could not find {selector_name} in selection dict for {op}"
878 )
879 raise (GenConformanceError())
880
881 # Selection criteria
882 selection_config = test_params[op]["selection"][selector_name]
883
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100884 if args.convert_all_tests:
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000885 logger.debug(f"Running and converting all {op} tests")
Jeremy Johnson1271c442023-09-05 11:39:26 +0100886 generate_results(
887 args, profile, op, op_build_dir, supports=supports
888 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000889 operator_test_list = None
890 else:
891 logger.debug(
892 f"Running and converting selection of {op} tests"
Jeremy Johnsone4b08ff2022-09-15 10:38:17 +0100893 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000894 if test_type in ["positive", "both"]:
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100895 if (
896 "all" in selection_config
897 and selection_config["all"] == "true"
898 ):
899 # Just get all the positive tests
900 tests_gen, tests_gen2 = tee(
901 _get_all_tests_list(
902 profile,
903 op_build_dir,
904 op,
905 exclude_negative_tests=True,
906 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000907 )
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100908 else:
909 # Get a selection of positive tests
910 tests_gen, tests_gen2 = tee(
911 get_op_tests_selection(
912 args,
913 profile,
914 op,
915 op_build_dir,
916 selection_config,
917 ignore_missing=ignore_missing,
918 )
919 )
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000920 generate_results(
Jeremy Johnson1271c442023-09-05 11:39:26 +0100921 args,
922 profile,
923 op,
924 op_build_dir,
925 supports=supports,
926 tests=tests_gen,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000927 )
928 operator_test_list = list(tests_gen2)
929 else:
930 operator_test_list = []
931 if test_type in ["negative", "both"]:
932 operator_test_list.extend(
933 get_op_tests_selection(
934 args,
935 profile,
936 op,
937 op_build_dir,
938 selection_config,
939 negative=True,
940 )
941 )
942 output_dir = convert_tests(
943 args,
944 profile,
945 op,
946 op_build_dir,
947 root_output_dir,
948 op_profiles_list,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100949 supports=supports,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000950 tests=operator_test_list,
951 group=operator_group,
952 tags=tags,
953 )
954 if not args.keep_large_files:
955 check_op_tests(args, profile, op, output_dir)
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100956 except GenConformanceError:
957 return 1
958
959 return 0
960
961
962if __name__ == "__main__":
963 exit(main())