blob: 555cb500208d8b0964cf9f45d59df8c5884867de [file] [log] [blame]
Jeremy Johnson6179c212022-01-13 13:46:35 +00001#!/usr/bin/env python3
2# Copyright (c) 2021-2022, ARM Limited.
3# SPDX-License-Identifier: Apache-2.0
4"""This script converts generated tests into conformance tests.
5
6It can convert a framework unit test or a reference model unit test.
7It expects the tests have been already run on the reference model
8so it can capture the result as the expected result.
9"""
10import argparse
11import json
12import logging
13import os
Jeremy Johnson1271c442023-09-05 11:39:26 +010014import shutil
Jeremy Johnson6179c212022-01-13 13:46:35 +000015from pathlib import Path
16from typing import Optional
17
18from json2fbbin.json2fbbin import fbbin_to_json
19from json2numpy.json2numpy import npy_to_json
Jeremy Johnson1271c442023-09-05 11:39:26 +010020from schemavalidation.schemavalidation import TestDescSchemaValidator
Jeremy Johnson6179c212022-01-13 13:46:35 +000021
22logging.basicConfig(level=logging.INFO)
23logger = logging.getLogger("convert2conformance")
24
25LOCATION_REF_MODEL_SCHEMA = Path("thirdparty/serialization_lib/schema/tosa.fbs")
26LOCATION_REF_MODEL_FLATC = Path(
27 "build/thirdparty/serialization_lib/third_party/flatbuffers/flatc"
28)
29
30NAME_FLATBUFFER_DIR = ["flatbuffer-", "_FW_"]
31NAME_DESC_FILENAME = "desc.json"
32NAME_CONFORMANCE_RESULT_PREFIX = "Conformance-"
33NAME_REFMODEL_RUN_RESULT_SUFFIX = ".runner.tosa_refmodel_sut_run.npy"
34
Jeremy Johnson88588622022-07-12 16:42:29 +010035PROFILES_LIST = ["tosa-bi", "tosa-mi"]
36
Jeremy Johnson6179c212022-01-13 13:46:35 +000037
38def parse_args(argv):
39 """Parse the arguments."""
Jeremy Johnson1271c442023-09-05 11:39:26 +010040 # Set prog for when we are called via tosa_verif_conformance_generator
41 parser = argparse.ArgumentParser(prog="convert2conformance")
Jeremy Johnson6179c212022-01-13 13:46:35 +000042 parser.add_argument(
43 "test_dir",
44 default=Path.cwd(),
45 type=Path,
46 nargs="?",
47 help="The test directory to convert (default is CWD)",
48 )
49 parser.add_argument(
50 "--ref-model-directory",
51 dest="ref_model_dir",
52 type=Path,
53 required=True,
54 help="Reference Model directory (must be pre-built)",
55 )
56 parser.add_argument(
57 "--output-directory",
58 dest="output_dir",
59 type=Path,
60 default=Path.cwd() / "conformance",
61 help="Output directory (default is conformance in CWD)",
62 )
63 parser.add_argument(
64 "--framework",
65 dest="framework",
66 choices=["tflite"],
67 default="tflite",
68 help="Framework to convert (default tflite)",
69 )
70 parser.add_argument(
71 "--framework-schema",
72 dest="framework_schema",
73 type=Path,
74 help="Framework schema needed to convert framework models",
75 )
76 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +010077 "--profile",
78 dest="profile",
79 choices=PROFILES_LIST,
80 action="append",
81 required=True,
82 help="Profiles this test is suitable for. May be repeated",
83 )
84 parser.add_argument(
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000085 "--tag",
Jeremy Johnson76c6a552023-09-11 09:30:02 +010086 dest="tags",
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000087 action="append",
88 type=str,
Jeremy Johnson76c6a552023-09-11 09:30:02 +010089 help="Optional string tag to mark this test with. May be repeated",
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000090 )
91 parser.add_argument(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +000092 "--strict",
93 dest="strict",
94 action="store_true",
95 help="Output directory must not contain the same test directory",
96 )
97 parser.add_argument(
Jeremy Johnson1271c442023-09-05 11:39:26 +010098 "--lazy-data-generation",
99 action="store_true",
100 help="Enable lazy data generation (only for tosa-mi)",
101 )
102 parser.add_argument(
Jeremy Johnson6179c212022-01-13 13:46:35 +0000103 "-v", "--verbose", dest="verbose", action="store_true", help="Verbose operation"
104 )
105 args = parser.parse_args(argv)
Jeremy Johnson88588622022-07-12 16:42:29 +0100106
Jeremy Johnson6179c212022-01-13 13:46:35 +0000107 return args
108
109
110def find_ref_model_artifacts(path: Path):
111 """Check the location of the flatc compiler and schema artifacts."""
112 flatc = path / LOCATION_REF_MODEL_FLATC
113 schema = path / LOCATION_REF_MODEL_SCHEMA
114 if not flatc.is_file():
115 raise Exception(
116 f"flatc not found in {flatc}\nHave you built the flatbuffers compiler?"
117 )
118 if not schema.is_file():
119 raise Exception(
120 f"TOSA schema not found at {schema}\nHave you checked out the submodules?"
121 )
122 return flatc, schema
123
124
125def find_framework_artifacts(framework: str, schema_path: Path, desc_file: Path):
126 """Check that any required schema has been supplied for conversion."""
127 if framework == "tflite":
128 if not schema_path:
129 raise Exception("the following arguments are required: --framework-schema")
130 elif not schema_path.is_file():
131 raise Exception(f"framework schema not found at {schema_path}")
132 model = desc_file.parent.parent / "model.tflite"
133 if not model.is_file():
134 raise Exception(f"Model file not found at {model}")
135 return schema_path, model
136 return None, None
137
138
139def get_framework_name(name_array: list, framework: str):
140 """Get the framework conversion directory name."""
141 name = ""
142 for part in name_array:
143 if part == "_FW_":
144 part = framework
145 name = f"{name}{part}"
146 return name
147
148
149def convert_flatbuffer_file(flatc: Path, schema: Path, model_file: Path, output: Path):
150 """Convert the flatbuffer binary into JSON."""
151 try:
152 fbbin_to_json(flatc, schema, model_file, output)
153 except Exception as e:
154 logger.error(f"Failed to convert flatbuffer binary:\n{e}")
155 return None
156
157 if model_file.name == "model.tflite":
158 file_name = "model-tflite.json"
159 os.rename(output / "model.json", output / file_name)
160 else:
161 file_name = model_file.stem + ".json"
162 return output / file_name
163
164
165def convert_numpy_file(n_file: Path, output: Path, outname: Optional[str] = None):
166 """Convert a numpy file into a JSON file."""
167 j_file = output / (outname if outname else (n_file.stem + ".json"))
168 npy_to_json(n_file, j_file)
169 return j_file
170
171
172def update_desc_json(
Jeremy Johnson88588622022-07-12 16:42:29 +0100173 test_dir: Path,
174 test_desc,
175 output_dir: Optional[Path] = None,
176 create_result=True,
177 profiles=None,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000178 tags=None,
Jeremy Johnson6179c212022-01-13 13:46:35 +0000179):
180 """Update the desc.json format for conformance and optionally create result."""
181 ofm_files = []
182 cfm_files = []
183 if not output_dir:
184 output_dir = test_dir
185 for index, ofm in enumerate(test_desc["ofm_file"]):
186 ofm_path = test_dir / ofm
187 if not test_desc["expected_failure"]:
188 cfm = NAME_CONFORMANCE_RESULT_PREFIX + test_desc["ofm_name"][index]
189 if create_result:
190 if ofm_path.is_file():
191 # Use the desc.json name
192 ofm_refmodel = ofm_path
193 else:
194 # Adjust for renaming due to tosa_verif_run_tests
195 ofm_refmodel = ofm_path.with_suffix(NAME_REFMODEL_RUN_RESULT_SUFFIX)
196 # Create conformance result
197 if ofm_refmodel.is_file():
198 convert_numpy_file(ofm_refmodel, output_dir, outname=cfm + ".json")
199 else:
200 logger.error(f"Missing result file {ofm_path}")
201 return None
Jeremy Johnson1271c442023-09-05 11:39:26 +0100202 cfm_files.append(cfm + ".npy")
Jeremy Johnson6179c212022-01-13 13:46:35 +0000203 # Remove path and "ref-"/"ref_model_" from output filenames
204 ofm_files.append(strip_ref_output_name(ofm_path.name))
205
206 # Rewrite output file names as they can be relative, but keep them npys
207 test_desc["ofm_file"] = ofm_files
Jeremy Johnson1271c442023-09-05 11:39:26 +0100208 if not test_desc["expected_failure"] and cfm_files:
209 # Output expected result file for conformance if expected pass and we
210 # have some files!
Jeremy Johnson6179c212022-01-13 13:46:35 +0000211 test_desc["expected_result_file"] = cfm_files
Jeremy Johnson88588622022-07-12 16:42:29 +0100212
213 # Add supported profiles
214 if profiles is None:
215 # Assume base profile
216 profiles = [PROFILES_LIST[0]]
217 test_desc["profile"] = profiles
218
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000219 # Add tags (if any)
220 if tags is not None:
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100221 test_desc["tags"] = tags
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000222
Jeremy Johnson6179c212022-01-13 13:46:35 +0000223 return test_desc
224
225
226def strip_ref_output_name(name):
227 """Remove mentions of reference from output files."""
228 if name.startswith("ref-"):
229 name = name[4:]
230 if name.startswith("ref_model_"):
231 name = name[10:]
232 return name
233
234
235def main(argv=None):
236 """Convert the given directory to a conformance test."""
237 args = parse_args(argv)
238 # Verbosity
239 if args.verbose:
240 logger.setLevel(logging.DEBUG)
241
242 # Check we can get the files we need
243 try:
244 flatc, schema = find_ref_model_artifacts(args.ref_model_dir)
245 except Exception as err:
246 logger.error(err)
247 return 2
248
249 # Work out where the desc.json file is
250 desc_filename = args.test_dir / NAME_DESC_FILENAME
251 framework_conversion = False
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100252 test_type_desc = "unknown"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000253 if desc_filename.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100254 logger.debug("Found TOSA operator unit test")
255 test_type_desc = "TOSA operator"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000256 else:
257 desc_filename = (
258 args.test_dir
259 / get_framework_name(NAME_FLATBUFFER_DIR, args.framework)
260 / NAME_DESC_FILENAME
261 )
262 if desc_filename.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100263 logger.debug(f"Found framework unit test for {args.framework}")
264 test_type_desc = f"{args.framework}"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000265 framework_conversion = True
266 else:
267 logger.error(f"Could not find {NAME_DESC_FILENAME} in {args.test_dir}")
268 return 2
269 logger.debug(f"desc.json file: {desc_filename}")
270
271 # Check for required files for framework conversion
272 if framework_conversion:
273 try:
274 framework_schema, framework_filename = find_framework_artifacts(
275 args.framework, args.framework_schema, desc_filename
276 )
277 except Exception as err:
278 logger.error(err)
279 return 2
280 else:
281 framework_schema, framework_filename = None, None
282
283 # Open the meta desc.json file
284 with open(desc_filename, mode="r") as fd:
285 test_desc = json.load(fd)
286
287 if "tosa_file" not in test_desc:
288 logger.error(f"Unsupported desc.json file found {desc_filename}")
289 return 2
290
291 # Dictionary fix
292 if "ifm_name" not in test_desc:
293 logger.warn("Old format desc.json file found - attempting to fix up")
294 test_desc["ifm_name"] = test_desc["ifm_placeholder"]
295 del test_desc["ifm_placeholder"]
296
297 # Make the output directory if needed
298 try:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000299 args.output_dir.mkdir(parents=True, exist_ok=(not args.strict))
Jeremy Johnson6179c212022-01-13 13:46:35 +0000300 except FileExistsError:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000301 if args.strict:
302 logger.error(f"{args.output_dir} already exists")
303 else:
304 logger.error(f"{args.output_dir} is not a directory")
Jeremy Johnson6179c212022-01-13 13:46:35 +0000305 return 2
306
307 # Convert the TOSA flatbuffer binary
308 tosa_filename = desc_filename.parent / test_desc["tosa_file"]
309 tosa_filename = convert_flatbuffer_file(
310 flatc, schema, tosa_filename, args.output_dir
311 )
312 if not tosa_filename:
313 # Failed to convert the file, json2fbbin will have printed an error
314 return 1
315 else:
316 # Replace binary with JSON name
317 test_desc["tosa_file"] = tosa_filename.name
318
319 if framework_conversion and framework_filename:
320 # Convert the framework flatbuffer binary
321 framework_filename = convert_flatbuffer_file(
322 flatc, framework_schema, framework_filename, args.output_dir
323 )
324 if not framework_filename:
325 # Failed to convert the file, json2fbbin will have printed an error
326 return 1
327
328 # Convert input files to JSON
329 ifm_files = []
330 for file in test_desc["ifm_file"]:
Jeremy Johnson1271c442023-09-05 11:39:26 +0100331 if file:
Jeremy Johnson6179c212022-01-13 13:46:35 +0000332 path = desc_filename.parent / file
Jeremy Johnson6179c212022-01-13 13:46:35 +0000333 ifm_files.append(path.name)
Jeremy Johnson1271c442023-09-05 11:39:26 +0100334 if path.is_file():
335 convert_numpy_file(path, args.output_dir)
336 else:
337 if not args.lazy_data_generation:
338 logger.error(f"Missing input file {path.name}")
339 return 1
340
Jeremy Johnson6179c212022-01-13 13:46:35 +0000341 # Rewrite input file names to make sure the paths are correct,
342 # but keep them numpys as the test runner will convert them back
343 # before giving them to the SUT
344 test_desc["ifm_file"] = ifm_files
345
Jeremy Johnson1271c442023-09-05 11:39:26 +0100346 # Check for cpp files for data-generator/verifier
347 cpp_files = args.test_dir.glob("*.cpp")
348 for cpp in cpp_files:
349 shutil.copy(str(cpp), str(args.output_dir))
350
Jeremy Johnson6179c212022-01-13 13:46:35 +0000351 # Update desc.json and convert result files to JSON
352 test_desc = update_desc_json(
Jeremy Johnson88588622022-07-12 16:42:29 +0100353 desc_filename.parent,
354 test_desc,
355 output_dir=args.output_dir,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100356 create_result=(not args.lazy_data_generation),
Jeremy Johnson88588622022-07-12 16:42:29 +0100357 profiles=args.profile,
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100358 tags=args.tags,
Jeremy Johnson6179c212022-01-13 13:46:35 +0000359 )
360 if not test_desc:
361 # Error from conversion/update
362 return 1
363
Jeremy Johnson1271c442023-09-05 11:39:26 +0100364 # Validate the desc.json schema
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100365 try:
366 TestDescSchemaValidator().validate_config(test_desc)
367 except Exception as e:
368 logger.error(e)
369 return 1
Jeremy Johnson1271c442023-09-05 11:39:26 +0100370
Jeremy Johnson6179c212022-01-13 13:46:35 +0000371 # Output new desc.json
372 new_desc_filename = args.output_dir / NAME_DESC_FILENAME
373 with open(new_desc_filename, "w") as fd:
374 json.dump(test_desc, fd, indent=2)
375
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100376 logger.info(f"Converted {test_type_desc} test to {args.output_dir}")
Jeremy Johnson6179c212022-01-13 13:46:35 +0000377 return 0
378
379
380if __name__ == "__main__":
381 exit(main())