1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3 4# Copyright (c) 2021-2024 Huawei Device Co., Ltd. 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18from __future__ import annotations 19 20from unittest import TestCase 21from os import path, makedirs 22from pathlib import Path 23from typing import Tuple, Optional, Sequence, List 24from runner.utils import compare_files 25 26from runner.plugins.ets.ets_templates.test_metadata import get_metadata, TestMetadata 27from runner.enum_types.configuration_kind import ConfigurationKind 28from runner.enum_types.fail_kind import FailKind 29from runner.enum_types.params import TestEnv, TestReport, Params 30from runner.test_file_based import TestFileBased 31 32 33class TestETS(TestFileBased): 34 def __init__(self, test_env: TestEnv, test_path: str, flags: List[str], test_id: str) -> None: 35 TestFileBased.__init__(self, test_env, test_path, flags, test_id) 36 37 self.main_entry_point = "ETSGLOBAL::main" 38 self.metadata: TestMetadata = get_metadata(Path(test_path)) 39 if self.metadata.package is not None: 40 self.main_entry_point = f"{self.metadata.package}.{self.main_entry_point}" 41 42 # If test fails it contains reason (of FailKind enum) of first failed step 43 # It's supposed if the first step is failed then no step is executed further 44 self.fail_kind = None 45 46 self.bytecode_path = test_env.work_dir.intermediate 47 makedirs(self.bytecode_path, exist_ok=True) 48 self.test_abc = path.join(self.bytecode_path, f"{self.test_id}.abc") 49 self.test_an = path.join(self.bytecode_path, f"{self.test_id}.an") 50 makedirs(path.dirname(self.test_abc), exist_ok=True) 51 52 @property 53 def is_negative_runtime(self) -> bool: 54 """ True if a test is expected to fail on ark """ 55 negative_runtime_metadata = self.metadata.tags.negative and not self.metadata.tags.compile_only 56 return negative_runtime_metadata or path.basename(self.path).startswith("n.") 57 58 @property 59 def is_negative_compile(self) -> bool: 60 """ True if a test is expected to fail on es2panda """ 61 return self.metadata.tags.negative and self.metadata.tags.compile_only 62 63 @property 64 def is_compile_only(self) -> bool: 65 """ True if a test should be run only on es2panda """ 66 return self.metadata.tags.compile_only 67 68 @property 69 def is_valid_test(self) -> bool: 70 """ True if a test is valid """ 71 return not self.metadata.tags.not_a_test 72 73 @property 74 def ark_extra_options(self) -> List[str]: 75 return self.metadata.ark_options 76 77 @property 78 def ark_timeout(self) -> int: 79 return self.metadata.timeout if self.metadata.timeout else super().ark_timeout 80 81 @property 82 def dependent_files(self) -> Sequence[TestETS]: 83 if not self.metadata.files: 84 return [] 85 86 tests = [] 87 for file in self.metadata.files: 88 test_path = Path(self.path).parent / Path(file) 89 current_test_id = Path(self.test_id) 90 test = self.__class__(self.test_env, str(test_path), self.flags, str(current_test_id.parent / Path(file))) 91 92 test_abc_name = f'{current_test_id.stem}_{Path(test.test_abc).name}' 93 test_an_name = f'{current_test_id.stem}_{Path(test.test_an).name}' 94 95 test.test_abc = str(Path(test.test_abc).parent / Path(test_abc_name)) 96 test.test_an = str(Path(test.test_abc).parent / Path(test_an_name)) 97 98 tests.append(test) 99 return tests 100 101 @property 102 def runtime_args(self) -> List[str]: 103 if not self.dependent_files: 104 return super().runtime_args 105 return self.add_boot_panda_files(super().runtime_args) 106 107 @property 108 def verifier_args(self) -> List[str]: 109 if not self.dependent_files: 110 return super().verifier_args 111 return self.add_boot_panda_files(super().verifier_args) 112 113 def add_boot_panda_files(self, args: List[str]) -> List[str]: 114 dep_files_args = [] 115 for arg in args: 116 name = '--boot-panda-files' 117 if name in arg: 118 _, value = arg.split('=') 119 dep_files_args.append(f'{name}={":".join([value] + [dt.test_abc for dt in self.dependent_files])}') 120 else: 121 dep_files_args.append(arg) 122 return dep_files_args 123 124 # pylint: disable=too-many-return-statements 125 def do_run(self) -> TestETS: 126 for test in self.dependent_files: 127 test.do_run() 128 129 if not self.is_valid_test and not self.is_compile_only: 130 return self 131 132 if not self.is_valid_test and self.is_compile_only: 133 self._run_compiler(self.test_abc) 134 return self 135 136 if self.test_env.config.ets.compare_files: 137 return self._run_compare_mode() 138 self.passed, self.report, self.fail_kind = self._run_compiler(self.test_abc) 139 140 if not self.passed or (self.passed and self.is_compile_only): 141 return self 142 143 # Run verifier if required 144 if self.test_env.config.verifier.enable: 145 self.passed, self.report, self.fail_kind = self._run_verifier(self.test_abc) 146 if not self.passed: 147 return self 148 149 # Run aot if required 150 if self.test_env.conf_kind in [ConfigurationKind.AOT, ConfigurationKind.AOT_FULL]: 151 self.passed, self.report, self.fail_kind = self.run_aot( 152 self.test_an, 153 [test.test_abc for test in list(self.dependent_files) + [self]], 154 lambda o, e, rc: rc == 0 and path.exists(self.test_an) and path.getsize(self.test_an) > 0 155 ) 156 157 if not self.passed: 158 return self 159 160 self.passed, self.report, self.fail_kind = self.run_runtime( 161 self.test_an, 162 self.test_abc, 163 lambda _, _2, rc: self._runtime_result_validator(rc)) 164 return self 165 166 def _run_compare_mode(self) -> TestETS: 167 files = [] 168 iterations = self.test_env.config.ets.compare_files_iterations 169 for i in range(iterations): 170 if i != 0: 171 test_ets = self.bytecode_path / self.test_id 172 stem, suffix_ets = test_ets.stem, test_ets.suffix 173 suffix_abc = ".abc" 174 test_abc = test_ets.parent / Path(f'{stem}_{str(i + 1)}{suffix_ets}{suffix_abc}') 175 else: 176 test_abc = Path(self.test_abc) 177 178 files.append(test_abc) 179 self.passed, self.report, self.fail_kind = self._run_compiler(str(test_abc)) 180 if not self.passed: 181 return self 182 183 self.passed = compare_files(files) 184 if not self.passed: 185 self.fail_kind = FailKind.COMPARE_FAIL 186 return self 187 188 def _runtime_result_validator(self, return_code: int) -> bool: 189 """ 190 :return: True if test is successful, False if failed 191 """ 192 if self.is_negative_runtime: 193 return return_code != 0 194 195 return return_code == 0 196 197 def _run_compiler(self, test_abc: str) -> Tuple[bool, TestReport, Optional[FailKind]]: 198 es2panda_flags = [] 199 if not self.is_valid_test: 200 es2panda_flags.append('--ets-module') 201 es2panda_flags.extend(self.test_env.es2panda_args) 202 es2panda_flags.append(f"--output={test_abc}") 203 es2panda_flags.append(self.path) 204 205 params = Params( 206 executor=self.test_env.es2panda, 207 flags=es2panda_flags, 208 env=self.test_env.cmd_env, 209 timeout=self.test_env.config.es2panda.timeout, 210 fail_kind_fail=FailKind.ES2PANDA_FAIL, 211 fail_kind_timeout=FailKind.ES2PANDA_TIMEOUT, 212 fail_kind_other=FailKind.ES2PANDA_OTHER, 213 ) 214 215 passed, report, fail_kind = self.run_one_step( 216 name="es2panda", 217 params=params, 218 result_validator=lambda _, _2, rc: self._validate_compiler(rc, test_abc) 219 ) 220 return passed, report, fail_kind 221 222 def _validate_compiler(self, return_code: int, output_path: str) -> bool: 223 if self.is_negative_compile: 224 return return_code != 0 225 return return_code == 0 and path.exists(output_path) and path.getsize(output_path) > 0 226 227 def _run_verifier(self, test_abc: str) -> Tuple[bool, TestReport, Optional[FailKind]]: 228 TestCase().assertTrue(path.exists(self.test_env.verifier), \ 229 f"Verifier binary '{self.test_env.verifier}' is absent or not set") 230 config_path = self.test_env.config.verifier.config 231 if config_path is None: 232 config_path = path.join(path.dirname(__file__), 'ets-verifier.config') 233 234 verifier_flags = list(self.verifier_args) 235 verifier_flags.append(f"--config-file={config_path}") 236 verifier_flags.append(test_abc) 237 238 params = Params( 239 executor=self.test_env.verifier, 240 flags=verifier_flags, 241 env=self.test_env.cmd_env, 242 timeout=self.test_env.config.verifier.timeout, 243 fail_kind_fail=FailKind.VERIFIER_FAIL, 244 fail_kind_timeout=FailKind.VERIFIER_TIMEOUT, 245 fail_kind_other=FailKind.VERIFIER_OTHER, 246 ) 247 return self.run_one_step( 248 name="verifier", 249 params=params, 250 result_validator=lambda _, _2, rc: rc == 0, 251 ) 252