Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 1 | """Template test runner class for running TOSA tests.""" |
Jeremy Johnson | f0348ea | 2023-09-27 16:10:59 +0100 | [diff] [blame] | 2 | # Copyright (c) 2020-2023, ARM Limited. |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 3 | # SPDX-License-Identifier: Apache-2.0 |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 4 | import json |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 5 | from enum import IntEnum |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 6 | |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 7 | import schemavalidation.schemavalidation as sch |
| 8 | from checker.color_print import LogColors |
| 9 | from checker.color_print import print_color |
| 10 | from checker.color_print import set_print_in_color |
| 11 | from checker.tosa_result_checker import set_print_result |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 12 | from checker.tosa_result_checker import test_check |
| 13 | from json2fbbin import json2fbbin |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 14 | from runner.tosa_test_presets import TOSA_REFCOMPLIANCE_RUNNER |
| 15 | |
| 16 | |
| 17 | def isComplianceModeDotProduct(testDesc): |
| 18 | """Checks the test descriptor for DOT_PRODUCT compliance mode.""" |
| 19 | if ( |
| 20 | "meta" in testDesc |
| 21 | and "compliance" in testDesc["meta"] |
| 22 | and "tensors" in testDesc["meta"]["compliance"] |
| 23 | ): |
| 24 | for _, t in testDesc["meta"]["compliance"]["tensors"].items(): |
| 25 | if "mode" in t and t["mode"] == "DOT_PRODUCT": |
| 26 | return True |
| 27 | return False |
| 28 | |
| 29 | |
| 30 | def getRunnerResultFilePath(resultFilePath, sutModule): |
| 31 | """Return the result file path with the runner specific naming.""" |
| 32 | return resultFilePath.with_suffix(f".{sutModule}{resultFilePath.suffix}") |
| 33 | |
| 34 | |
| 35 | def getBoundsResultFilePath(resultFilePath, sutModule=None): |
| 36 | """Return the bounds result file with/without runner specific naming.""" |
| 37 | boundsFilePath = resultFilePath.parent / f"bounds_{resultFilePath.name}" |
| 38 | if sutModule is not None: |
| 39 | boundsFilePath = boundsFilePath.with_suffix( |
| 40 | f".{sutModule}{boundsFilePath.suffix}" |
| 41 | ) |
| 42 | return boundsFilePath |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 43 | |
Kevin Cheng | 550ccc5 | 2021-03-03 11:21:43 -0800 | [diff] [blame] | 44 | |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 45 | class TosaTestInvalid(Exception): |
| 46 | """Exception raised for errors loading test description. |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 47 | |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 48 | Attributes: |
| 49 | path - full path to missing test description file |
| 50 | exception = underlying exception |
| 51 | """ |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 52 | |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 53 | def __init__(self, path, exception): |
| 54 | """Initialize test not found error.""" |
| 55 | self.path = path |
| 56 | self.exception = exception |
| 57 | self.message = "Invalid test, could not read test description {}: {}".format( |
| 58 | self.path, str(self.exception) |
| 59 | ) |
| 60 | super().__init__(self.message) |
Kevin Cheng | 550ccc5 | 2021-03-03 11:21:43 -0800 | [diff] [blame] | 61 | |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 62 | |
| 63 | class TosaTestRunner: |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 64 | """TOSA Test Runner template class for systems under test.""" |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 65 | |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 66 | def __init__(self, args, runnerArgs, testDirPath): |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 67 | """Initialize and load JSON meta data file.""" |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 68 | self.args = args |
| 69 | self.runnerArgs = runnerArgs |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 70 | self.testDir = str(testDirPath) |
| 71 | self.testDirPath = testDirPath |
| 72 | self.testName = self.testDirPath.name |
Jeremy Johnson | 9c2fe6e | 2023-10-04 16:55:04 +0100 | [diff] [blame^] | 73 | self.verify_lib_path = args.verify_lib_path |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 74 | |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 75 | set_print_in_color(not args.no_color) |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 76 | # Stop the result checker printing anything - we will do it |
| 77 | set_print_result(False) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 78 | |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 79 | # Check if we want to run binary and if its already converted |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 80 | descFilePath = testDirPath / "desc.json" |
| 81 | descBinFilePath = testDirPath / "desc_binary.json" |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 82 | if args.binary: |
| 83 | if descBinFilePath.is_file(): |
| 84 | descFilePath = descBinFilePath |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 85 | |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 86 | try: |
| 87 | # Load the json test file |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 88 | with descFilePath.open("r") as fd: |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 89 | self.testDesc = json.load(fd) |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 90 | # Validate the json with the schema |
| 91 | sch.TestDescSchemaValidator().validate_config(self.testDesc) |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 92 | except Exception as e: |
| 93 | raise TosaTestInvalid(str(descFilePath), e) |
| 94 | |
| 95 | # Convert to binary if needed |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 96 | tosaFilePath = testDirPath / self.testDesc["tosa_file"] |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 97 | if args.binary and tosaFilePath.suffix == ".json": |
| 98 | # Convert tosa JSON to binary |
| 99 | json2fbbin.json_to_fbbin( |
Jeremy Johnson | f0348ea | 2023-09-27 16:10:59 +0100 | [diff] [blame] | 100 | args.flatc_path, |
| 101 | args.schema_path, |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 102 | tosaFilePath, |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 103 | testDirPath, |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 104 | ) |
| 105 | # Write new desc_binary file |
| 106 | self.testDesc["tosa_file"] = tosaFilePath.stem + ".tosa" |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 107 | with descBinFilePath.open("w") as fd: |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 108 | json.dump(self.testDesc, fd, indent=2) |
| 109 | descFilePath = descBinFilePath |
| 110 | |
| 111 | # Set location of desc.json (or desc_binary.json) file in use |
| 112 | self.descFile = str(descFilePath) |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 113 | self.descFilePath = descFilePath |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 114 | |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 115 | # Check for compliance mode - need to run refmodel to get results |
| 116 | if "meta" in self.testDesc and "compliance" in self.testDesc["meta"]: |
| 117 | self.complianceMode = True |
| 118 | if "expected_result" in self.testDesc: |
| 119 | if self.args.verbose: |
| 120 | print("Warning: fixing conflicting compliance mode in test.desc") |
| 121 | self.testDesc.pop("expected_result") |
| 122 | else: |
| 123 | self.complianceMode = False |
| 124 | |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 125 | def skipTest(self): |
Jeremy Johnson | 8858862 | 2022-07-12 16:42:29 +0100 | [diff] [blame] | 126 | """Check if the test is skipped due to test type or profile selection.""" |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 127 | expectedFailure = self.testDesc["expected_failure"] |
| 128 | if self.args.test_type == "negative" and not expectedFailure: |
Jeremy Johnson | 8858862 | 2022-07-12 16:42:29 +0100 | [diff] [blame] | 129 | return True, "non-negative type" |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 130 | elif self.args.test_type == "positive" and expectedFailure: |
Jeremy Johnson | 8858862 | 2022-07-12 16:42:29 +0100 | [diff] [blame] | 131 | return True, "non-positive type" |
| 132 | if self.args.profile: |
| 133 | profile = self.testDesc["profile"] if "profile" in self.testDesc else [] |
| 134 | if self.args.profile not in profile: |
| 135 | return True, "non-{} profile".format(self.args.profile) |
| 136 | return False, "" |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 137 | |
| 138 | def runTestGraph(self): |
| 139 | """Override with function that calls system under test.""" |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 140 | pass |
| 141 | |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 142 | def testResult(self, tosaGraphResult, graphMessage=None): |
| 143 | """Work out test result based on graph result and output files.""" |
| 144 | expectedFailure = self.testDesc["expected_failure"] |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 145 | print_check_result = False |
| 146 | |
| 147 | sutModule = self.__module__ |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 148 | |
| 149 | if tosaGraphResult == TosaTestRunner.TosaGraphResult.TOSA_VALID: |
| 150 | if expectedFailure: |
| 151 | result = TosaTestRunner.Result.UNEXPECTED_PASS |
| 152 | resultMessage = "Expected failure test incorrectly passed" |
| 153 | else: |
| 154 | # Work through all the results produced by the testing, assuming success |
| 155 | # but overriding this with any failures found |
| 156 | result = TosaTestRunner.Result.EXPECTED_PASS |
| 157 | messages = [] |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 158 | |
| 159 | # Go through each output result checking it |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 160 | for resultNum, resultFileName in enumerate(self.testDesc["ofm_file"]): |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 161 | resultFilePath = self.testDirPath / resultFileName |
| 162 | |
| 163 | # Work out the file to check against (if any) |
| 164 | if self.complianceMode and sutModule != TOSA_REFCOMPLIANCE_RUNNER: |
| 165 | conformanceFilePath = getRunnerResultFilePath( |
| 166 | resultFilePath, TOSA_REFCOMPLIANCE_RUNNER |
| 167 | ) |
| 168 | if isComplianceModeDotProduct(self.testDesc): |
| 169 | conformanceBoundsPath = getBoundsResultFilePath( |
| 170 | resultFilePath, TOSA_REFCOMPLIANCE_RUNNER |
| 171 | ) |
| 172 | else: |
| 173 | # Not expecting a bounds file for this test |
| 174 | conformanceBoundsPath = None |
| 175 | elif "expected_result_file" in self.testDesc: |
| 176 | conformanceBoundsPath = None |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 177 | try: |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 178 | conformanceFilePath = ( |
| 179 | self.testDirPath |
| 180 | / self.testDesc["expected_result_file"][resultNum] |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 181 | ) |
| 182 | except IndexError: |
| 183 | result = TosaTestRunner.Result.INTERNAL_ERROR |
| 184 | msg = "Internal error: Missing expected_result_file {} in {}".format( |
| 185 | resultNum, self.descFile |
| 186 | ) |
| 187 | messages.append(msg) |
| 188 | print(msg) |
| 189 | break |
| 190 | else: |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 191 | # Nothing to check against |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 192 | conformanceFilePath = None |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 193 | conformanceBoundsPath = None |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 194 | |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 195 | if conformanceFilePath: |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 196 | print_check_result = True # Result from checker |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 197 | chkResult, tolerance, msg = test_check( |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 198 | conformanceFilePath, |
| 199 | resultFilePath, |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 200 | test_name=self.testName, |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 201 | test_desc=self.testDesc, |
| 202 | bnd_result_path=conformanceBoundsPath, |
| 203 | ofm_name=self.testDesc["ofm_name"][resultNum], |
| 204 | verify_lib_path=self.verify_lib_path, |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 205 | ) |
| 206 | # Change EXPECTED_PASS assumption if we have any failures |
| 207 | if chkResult != 0: |
| 208 | result = TosaTestRunner.Result.UNEXPECTED_FAILURE |
| 209 | messages.append(msg) |
| 210 | if self.args.verbose: |
| 211 | print(msg) |
| 212 | else: |
| 213 | # No conformance file to verify, just check results file exists |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 214 | if not resultFilePath.is_file(): |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 215 | result = TosaTestRunner.Result.UNEXPECTED_FAILURE |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 216 | msg = f"Results file is missing: {resultFilePath}" |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 217 | messages.append(msg) |
| 218 | print(msg) |
| 219 | |
Jeremy Johnson | e4b08ff | 2022-09-15 10:38:17 +0100 | [diff] [blame] | 220 | if resultFilePath.is_file(): |
| 221 | # Move the resultFilePath to allow subsequent system under |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 222 | # tests to create them and to test they have been created |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 223 | # and to enable compliance testing against refmodel results |
| 224 | resultFilePath.rename( |
| 225 | getRunnerResultFilePath(resultFilePath, sutModule) |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 226 | ) |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 227 | if ( |
| 228 | isComplianceModeDotProduct(self.testDesc) |
| 229 | and sutModule == TOSA_REFCOMPLIANCE_RUNNER |
| 230 | ): |
| 231 | boundsFilePath = getBoundsResultFilePath(resultFilePath) |
| 232 | if boundsFilePath.is_file(): |
| 233 | boundsFilePath = boundsFilePath.rename( |
| 234 | getBoundsResultFilePath(resultFilePath, sutModule) |
| 235 | ) |
| 236 | else: |
| 237 | result = TosaTestRunner.Result.INTERNAL_ERROR |
| 238 | msg = f"Internal error: Missing expected dot product compliance bounds file {boundsFilePath}" |
| 239 | messages.append(msg) |
| 240 | print(msg) |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 241 | |
| 242 | resultMessage = "\n".join(messages) if len(messages) > 0 else None |
| 243 | else: |
| 244 | if ( |
| 245 | expectedFailure |
| 246 | and tosaGraphResult == TosaTestRunner.TosaGraphResult.TOSA_ERROR |
| 247 | ): |
| 248 | result = TosaTestRunner.Result.EXPECTED_FAILURE |
| 249 | resultMessage = None |
| 250 | else: |
| 251 | result = TosaTestRunner.Result.UNEXPECTED_FAILURE |
| 252 | resultMessage = graphMessage |
| 253 | |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 254 | status = "Result" if print_check_result else "Result code" |
| 255 | if ( |
| 256 | result == TosaTestRunner.Result.EXPECTED_FAILURE |
| 257 | or result == TosaTestRunner.Result.EXPECTED_PASS |
| 258 | ): |
| 259 | print_color(LogColors.GREEN, f"{sutModule}: {status} PASS {self.testName}") |
| 260 | else: |
| 261 | print_color(LogColors.RED, f"{sutModule}: {status} FAIL {self.testName}") |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 262 | |
| 263 | return result, resultMessage |
| 264 | |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 265 | class Result(IntEnum): |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 266 | """Test result codes.""" |
| 267 | |
Eric Kunze | e5e2676 | 2020-10-13 16:11:07 -0700 | [diff] [blame] | 268 | EXPECTED_PASS = 0 |
| 269 | EXPECTED_FAILURE = 1 |
| 270 | UNEXPECTED_PASS = 2 |
| 271 | UNEXPECTED_FAILURE = 3 |
| 272 | INTERNAL_ERROR = 4 |
Jeremy Johnson | be1a940 | 2021-12-15 17:14:56 +0000 | [diff] [blame] | 273 | SKIPPED = 5 |
| 274 | |
| 275 | class TosaGraphResult(IntEnum): |
| 276 | """The tosa_graph_result codes.""" |
| 277 | |
| 278 | TOSA_VALID = 0 |
| 279 | TOSA_UNPREDICTABLE = 1 |
| 280 | TOSA_ERROR = 2 |
| 281 | OTHER_ERROR = 3 |