blob: 3654f9a02cb08ded6ebedbeb9070c53d5eb9c955 [file] [log] [blame]
Jeremy Johnson6179c212022-01-13 13:46:35 +00001#!/usr/bin/env python3
Jeremy Johnsonc63d0f62023-09-14 16:07:00 +01002# Copyright (c) 2021-2023, ARM Limited.
Jeremy Johnson6179c212022-01-13 13:46:35 +00003# 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
Jeremy Johnson6179c212022-01-13 13:46:35 +000025
26NAME_FLATBUFFER_DIR = ["flatbuffer-", "_FW_"]
27NAME_DESC_FILENAME = "desc.json"
28NAME_CONFORMANCE_RESULT_PREFIX = "Conformance-"
29NAME_REFMODEL_RUN_RESULT_SUFFIX = ".runner.tosa_refmodel_sut_run.npy"
30
Jeremy Johnson88588622022-07-12 16:42:29 +010031PROFILES_LIST = ["tosa-bi", "tosa-mi"]
32
Jeremy Johnson6179c212022-01-13 13:46:35 +000033
34def parse_args(argv):
35 """Parse the arguments."""
Jeremy Johnson1271c442023-09-05 11:39:26 +010036 # Set prog for when we are called via tosa_verif_conformance_generator
37 parser = argparse.ArgumentParser(prog="convert2conformance")
Jeremy Johnson6179c212022-01-13 13:46:35 +000038 parser.add_argument(
39 "test_dir",
40 default=Path.cwd(),
41 type=Path,
42 nargs="?",
43 help="The test directory to convert (default is CWD)",
44 )
45 parser.add_argument(
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +010046 "--schema-path",
47 "--operator-fbs",
48 dest="schema_path",
Jeremy Johnson6179c212022-01-13 13:46:35 +000049 type=Path,
50 required=True,
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +010051 help=("Path to reference model schema."),
52 )
53 parser.add_argument(
54 "--flatc-path",
55 dest="flatc_path",
56 type=Path,
57 required=True,
58 help=("Path to flatc executable."),
Jeremy Johnson6179c212022-01-13 13:46:35 +000059 )
60 parser.add_argument(
61 "--output-directory",
62 dest="output_dir",
63 type=Path,
64 default=Path.cwd() / "conformance",
65 help="Output directory (default is conformance in CWD)",
66 )
67 parser.add_argument(
68 "--framework",
69 dest="framework",
70 choices=["tflite"],
71 default="tflite",
72 help="Framework to convert (default tflite)",
73 )
74 parser.add_argument(
75 "--framework-schema",
76 dest="framework_schema",
77 type=Path,
78 help="Framework schema needed to convert framework models",
79 )
80 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +010081 "--profile",
82 dest="profile",
83 choices=PROFILES_LIST,
84 action="append",
85 required=True,
86 help="Profiles this test is suitable for. May be repeated",
87 )
88 parser.add_argument(
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000089 "--tag",
Jeremy Johnson76c6a552023-09-11 09:30:02 +010090 dest="tags",
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000091 action="append",
92 type=str,
Jeremy Johnson76c6a552023-09-11 09:30:02 +010093 help="Optional string tag to mark this test with. May be repeated",
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +000094 )
95 parser.add_argument(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +000096 "--strict",
97 dest="strict",
98 action="store_true",
99 help="Output directory must not contain the same test directory",
100 )
101 parser.add_argument(
Jeremy Johnson1271c442023-09-05 11:39:26 +0100102 "--lazy-data-generation",
103 action="store_true",
104 help="Enable lazy data generation (only for tosa-mi)",
105 )
106 parser.add_argument(
Jeremy Johnson6179c212022-01-13 13:46:35 +0000107 "-v", "--verbose", dest="verbose", action="store_true", help="Verbose operation"
108 )
109 args = parser.parse_args(argv)
Jeremy Johnson88588622022-07-12 16:42:29 +0100110
Jeremy Johnson6179c212022-01-13 13:46:35 +0000111 return args
112
113
Jeremy Johnson6179c212022-01-13 13:46:35 +0000114def find_framework_artifacts(framework: str, schema_path: Path, desc_file: Path):
115 """Check that any required schema has been supplied for conversion."""
116 if framework == "tflite":
117 if not schema_path:
118 raise Exception("the following arguments are required: --framework-schema")
119 elif not schema_path.is_file():
120 raise Exception(f"framework schema not found at {schema_path}")
121 model = desc_file.parent.parent / "model.tflite"
122 if not model.is_file():
123 raise Exception(f"Model file not found at {model}")
124 return schema_path, model
125 return None, None
126
127
128def get_framework_name(name_array: list, framework: str):
129 """Get the framework conversion directory name."""
130 name = ""
131 for part in name_array:
132 if part == "_FW_":
133 part = framework
134 name = f"{name}{part}"
135 return name
136
137
138def convert_flatbuffer_file(flatc: Path, schema: Path, model_file: Path, output: Path):
139 """Convert the flatbuffer binary into JSON."""
140 try:
141 fbbin_to_json(flatc, schema, model_file, output)
142 except Exception as e:
143 logger.error(f"Failed to convert flatbuffer binary:\n{e}")
144 return None
145
146 if model_file.name == "model.tflite":
147 file_name = "model-tflite.json"
148 os.rename(output / "model.json", output / file_name)
149 else:
150 file_name = model_file.stem + ".json"
151 return output / file_name
152
153
154def convert_numpy_file(n_file: Path, output: Path, outname: Optional[str] = None):
155 """Convert a numpy file into a JSON file."""
156 j_file = output / (outname if outname else (n_file.stem + ".json"))
157 npy_to_json(n_file, j_file)
158 return j_file
159
160
161def update_desc_json(
Jeremy Johnson88588622022-07-12 16:42:29 +0100162 test_dir: Path,
163 test_desc,
164 output_dir: Optional[Path] = None,
165 create_result=True,
166 profiles=None,
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000167 tags=None,
Jeremy Johnson6179c212022-01-13 13:46:35 +0000168):
169 """Update the desc.json format for conformance and optionally create result."""
170 ofm_files = []
171 cfm_files = []
172 if not output_dir:
173 output_dir = test_dir
174 for index, ofm in enumerate(test_desc["ofm_file"]):
175 ofm_path = test_dir / ofm
176 if not test_desc["expected_failure"]:
177 cfm = NAME_CONFORMANCE_RESULT_PREFIX + test_desc["ofm_name"][index]
178 if create_result:
179 if ofm_path.is_file():
180 # Use the desc.json name
181 ofm_refmodel = ofm_path
182 else:
183 # Adjust for renaming due to tosa_verif_run_tests
184 ofm_refmodel = ofm_path.with_suffix(NAME_REFMODEL_RUN_RESULT_SUFFIX)
185 # Create conformance result
186 if ofm_refmodel.is_file():
187 convert_numpy_file(ofm_refmodel, output_dir, outname=cfm + ".json")
188 else:
189 logger.error(f"Missing result file {ofm_path}")
190 return None
Jeremy Johnson1271c442023-09-05 11:39:26 +0100191 cfm_files.append(cfm + ".npy")
Jeremy Johnson6179c212022-01-13 13:46:35 +0000192 # Remove path and "ref-"/"ref_model_" from output filenames
193 ofm_files.append(strip_ref_output_name(ofm_path.name))
194
195 # Rewrite output file names as they can be relative, but keep them npys
196 test_desc["ofm_file"] = ofm_files
Jeremy Johnson1271c442023-09-05 11:39:26 +0100197 if not test_desc["expected_failure"] and cfm_files:
198 # Output expected result file for conformance if expected pass and we
199 # have some files!
Jeremy Johnson6179c212022-01-13 13:46:35 +0000200 test_desc["expected_result_file"] = cfm_files
Jeremy Johnson88588622022-07-12 16:42:29 +0100201
202 # Add supported profiles
203 if profiles is None:
204 # Assume base profile
205 profiles = [PROFILES_LIST[0]]
206 test_desc["profile"] = profiles
207
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000208 # Add tags (if any)
209 if tags is not None:
Jeremy Johnsonc63d0f62023-09-14 16:07:00 +0100210 test_desc["tag"] = tags
Jeremy Johnsonfd05bb32023-02-07 16:39:24 +0000211
Jeremy Johnson6179c212022-01-13 13:46:35 +0000212 return test_desc
213
214
215def strip_ref_output_name(name):
216 """Remove mentions of reference from output files."""
217 if name.startswith("ref-"):
218 name = name[4:]
219 if name.startswith("ref_model_"):
220 name = name[10:]
221 return name
222
223
224def main(argv=None):
225 """Convert the given directory to a conformance test."""
226 args = parse_args(argv)
227 # Verbosity
228 if args.verbose:
229 logger.setLevel(logging.DEBUG)
230
231 # Check we can get the files we need
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100232 if not args.flatc_path.is_file():
233 logger.error("flatc not found at %s", args.flatc_path)
234 return 2
235 if not args.schema_path.is_file():
236 logger.error("TOSA schema not found at %s", args.schema_path)
Jeremy Johnson6179c212022-01-13 13:46:35 +0000237 return 2
238
239 # Work out where the desc.json file is
240 desc_filename = args.test_dir / NAME_DESC_FILENAME
241 framework_conversion = False
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100242 test_type_desc = "unknown"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000243 if desc_filename.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100244 logger.debug("Found TOSA operator unit test")
245 test_type_desc = "TOSA operator"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000246 else:
247 desc_filename = (
248 args.test_dir
249 / get_framework_name(NAME_FLATBUFFER_DIR, args.framework)
250 / NAME_DESC_FILENAME
251 )
252 if desc_filename.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100253 logger.debug(f"Found framework unit test for {args.framework}")
254 test_type_desc = f"{args.framework}"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000255 framework_conversion = True
256 else:
257 logger.error(f"Could not find {NAME_DESC_FILENAME} in {args.test_dir}")
258 return 2
259 logger.debug(f"desc.json file: {desc_filename}")
260
261 # Check for required files for framework conversion
262 if framework_conversion:
263 try:
264 framework_schema, framework_filename = find_framework_artifacts(
265 args.framework, args.framework_schema, desc_filename
266 )
267 except Exception as err:
268 logger.error(err)
269 return 2
270 else:
271 framework_schema, framework_filename = None, None
272
273 # Open the meta desc.json file
274 with open(desc_filename, mode="r") as fd:
275 test_desc = json.load(fd)
276
277 if "tosa_file" not in test_desc:
278 logger.error(f"Unsupported desc.json file found {desc_filename}")
279 return 2
280
281 # Dictionary fix
282 if "ifm_name" not in test_desc:
283 logger.warn("Old format desc.json file found - attempting to fix up")
284 test_desc["ifm_name"] = test_desc["ifm_placeholder"]
285 del test_desc["ifm_placeholder"]
286
287 # Make the output directory if needed
288 try:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000289 args.output_dir.mkdir(parents=True, exist_ok=(not args.strict))
Jeremy Johnson6179c212022-01-13 13:46:35 +0000290 except FileExistsError:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000291 if args.strict:
292 logger.error(f"{args.output_dir} already exists")
293 else:
294 logger.error(f"{args.output_dir} is not a directory")
Jeremy Johnson6179c212022-01-13 13:46:35 +0000295 return 2
296
297 # Convert the TOSA flatbuffer binary
298 tosa_filename = desc_filename.parent / test_desc["tosa_file"]
299 tosa_filename = convert_flatbuffer_file(
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100300 args.flatc_path, args.schema_path, tosa_filename, args.output_dir
Jeremy Johnson6179c212022-01-13 13:46:35 +0000301 )
302 if not tosa_filename:
303 # Failed to convert the file, json2fbbin will have printed an error
304 return 1
305 else:
306 # Replace binary with JSON name
307 test_desc["tosa_file"] = tosa_filename.name
308
309 if framework_conversion and framework_filename:
310 # Convert the framework flatbuffer binary
311 framework_filename = convert_flatbuffer_file(
Jeremy Johnsonf0348ea2023-09-27 16:10:59 +0100312 args.flatc_path, framework_schema, framework_filename, args.output_dir
Jeremy Johnson6179c212022-01-13 13:46:35 +0000313 )
314 if not framework_filename:
315 # Failed to convert the file, json2fbbin will have printed an error
316 return 1
317
318 # Convert input files to JSON
319 ifm_files = []
320 for file in test_desc["ifm_file"]:
Jeremy Johnson1271c442023-09-05 11:39:26 +0100321 if file:
Jeremy Johnson6179c212022-01-13 13:46:35 +0000322 path = desc_filename.parent / file
Jeremy Johnson6179c212022-01-13 13:46:35 +0000323 ifm_files.append(path.name)
Jeremy Johnson1271c442023-09-05 11:39:26 +0100324 if path.is_file():
325 convert_numpy_file(path, args.output_dir)
326 else:
327 if not args.lazy_data_generation:
328 logger.error(f"Missing input file {path.name}")
329 return 1
330
Jeremy Johnson6179c212022-01-13 13:46:35 +0000331 # Rewrite input file names to make sure the paths are correct,
332 # but keep them numpys as the test runner will convert them back
333 # before giving them to the SUT
334 test_desc["ifm_file"] = ifm_files
335
Jeremy Johnson1271c442023-09-05 11:39:26 +0100336 # Check for cpp files for data-generator/verifier
337 cpp_files = args.test_dir.glob("*.cpp")
338 for cpp in cpp_files:
339 shutil.copy(str(cpp), str(args.output_dir))
340
Jeremy Johnson6179c212022-01-13 13:46:35 +0000341 # Update desc.json and convert result files to JSON
342 test_desc = update_desc_json(
Jeremy Johnson88588622022-07-12 16:42:29 +0100343 desc_filename.parent,
344 test_desc,
345 output_dir=args.output_dir,
Jeremy Johnson1271c442023-09-05 11:39:26 +0100346 create_result=(not args.lazy_data_generation),
Jeremy Johnson88588622022-07-12 16:42:29 +0100347 profiles=args.profile,
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100348 tags=args.tags,
Jeremy Johnson6179c212022-01-13 13:46:35 +0000349 )
350 if not test_desc:
351 # Error from conversion/update
352 return 1
353
Jeremy Johnson1271c442023-09-05 11:39:26 +0100354 # Validate the desc.json schema
Jeremy Johnson76c6a552023-09-11 09:30:02 +0100355 try:
356 TestDescSchemaValidator().validate_config(test_desc)
357 except Exception as e:
358 logger.error(e)
359 return 1
Jeremy Johnson1271c442023-09-05 11:39:26 +0100360
Jeremy Johnson6179c212022-01-13 13:46:35 +0000361 # Output new desc.json
362 new_desc_filename = args.output_dir / NAME_DESC_FILENAME
363 with open(new_desc_filename, "w") as fd:
364 json.dump(test_desc, fd, indent=2)
365
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100366 logger.info(f"Converted {test_type_desc} test to {args.output_dir}")
Jeremy Johnson6179c212022-01-13 13:46:35 +0000367 return 0
368
369
370if __name__ == "__main__":
371 exit(main())