1#!/usr/bin/env python3 2# -- coding: utf-8 -- 3# 4# Copyright (c) 2024-2025 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 18import re 19from glob import glob 20from os import path 21from pathlib import Path 22from typing import cast 23 24from runner import utils 25from runner.common_exceptions import InvalidConfiguration 26from runner.enum_types.configuration_kind import ( 27 BuildTypeKind, 28 ConfigurationKind, 29 SanitizerKind, 30) 31from runner.enum_types.params import TestEnv 32from runner.logger import Log 33from runner.options.step import Step, StepKind 34from runner.utils import correct_path, detect_architecture, detect_operating_system 35 36_LOGGER = Log.get_logger(__file__) 37 38 39class TestLists: 40 def __init__(self, list_root: Path, test_env: TestEnv): 41 self.list_root = list_root 42 self.config = test_env.config 43 self.explicit_list: Path | None = ( 44 correct_path(self.list_root, self.config.test_suite.test_lists.explicit_list) 45 if self.config.test_suite.test_lists.explicit_list is not None and self.list_root is not None 46 else None 47 ) 48 self.explicit_test: Path | None = None 49 self.excluded_lists: list[Path] = [] 50 self.ignored_lists: list[Path] = [] 51 52 self.cache: list[str] = self.__cmake_cache() 53 self.sanitizer = self.search_sanitizer() 54 self.architecture = detect_architecture() 55 self.operating_system = detect_operating_system() 56 self.build_type = self.search_build_type() 57 self.conf_kind = self.detect_conf() 58 59 @staticmethod 60 def __search_option_in_list(option: str, arg_list: list[str] | None) -> list[str]: 61 if arg_list is None: 62 return [] 63 return [arg for arg in arg_list if arg.startswith(option)] 64 65 @staticmethod 66 def __to_bool(value: str | None) -> bool | None: 67 true_list = ("on", "true") 68 false_list = ("on", "false") 69 return value in true_list if value and value in true_list + false_list else None 70 71 def collect_excluded_test_lists(self, extra_list: list[str] | None = None, 72 test_name: str | None = None) -> None: 73 self.excluded_lists.extend(self.collect_test_lists("excluded", extra_list, test_name)) 74 75 def collect_ignored_test_lists(self, extra_list: list[str] | None = None, 76 test_name: str | None = None) -> None: 77 self.ignored_lists.extend(self.collect_test_lists("ignored", extra_list, test_name)) 78 79 def collect_test_lists( 80 self, 81 kind: str, extra_lists: list[str] | None = None, 82 test_name: str | None = None 83 ) -> list[Path]: 84 test_lists = extra_lists[:] if extra_lists else [] 85 test_name = test_name if test_name else self.config.test_suite.suite_name 86 87 short_template_name = f"{test_name}*-{kind}*.txt" 88 conf_kind = self.conf_kind.value.upper() 89 interpreter = self.get_interpreter().upper() 90 full_template_name = f"{test_name}.*-{kind}" + \ 91 f"(-{self.operating_system.value})?" \ 92 f"(-{self.architecture.value})?" \ 93 f"(-{conf_kind})?" \ 94 f"(-{interpreter})?" 95 if self.sanitizer != SanitizerKind.NONE: 96 full_template_name += f"(-{self.sanitizer.value})?" 97 opt_level = self.opt_level() 98 if opt_level is not None: 99 full_template_name += f"(-OL{opt_level})?" 100 if self.debug_info(): 101 full_template_name += "(-DI)?" 102 if self.is_full_ast_verifier(): 103 full_template_name += "(-FULLASTV)?" 104 if self.conf_kind == ConfigurationKind.JIT and self.is_jit_with_repeats(): 105 full_template_name += "(-(repeats|REPEATS))?" 106 gc_type = cast(str, self.config.workflow.get_parameter('gc-type', 'g1-gc')).upper() 107 full_template_name += f"(-({gc_type}))?" 108 full_template_name += f"(-{self.build_type.value})?" 109 full_template_name += ".txt" 110 full_pattern = re.compile(full_template_name) 111 112 def is_matched(file: str) -> bool: 113 file = file.split(path.sep)[-1] 114 match = full_pattern.match(file) 115 return match is not None 116 117 glob_expression = path.join(self.list_root, f"**/{short_template_name}") 118 test_lists.extend(filter( 119 is_matched, 120 glob(glob_expression, recursive=True) 121 )) 122 123 _LOGGER.all(f"Loading {kind} test lists: {test_lists}") 124 125 return [Path(test_list) for test_list in test_lists] 126 127 def search_build_type(self) -> BuildTypeKind: 128 value = cast(str, self.__search("CMAKE_BUILD_TYPE")) 129 if value == "fastverify": 130 value = "fast-verify" 131 return BuildTypeKind.is_value(value, option_name="from cmake CMAKE_BUILD_TYPE") 132 133 def search_sanitizer(self) -> SanitizerKind: 134 is_ubsan = self.__to_bool(self.__search("PANDA_ENABLE_UNDEFINED_BEHAVIOR_SANITIZER")) 135 is_asan = self.__to_bool(self.__search("PANDA_ENABLE_ADDRESS_SANITIZER")) 136 is_tsan = self.__to_bool(self.__search("PANDA_ENABLE_THREAD_SANITIZER")) 137 if is_asan or is_ubsan: 138 return SanitizerKind.ASAN 139 if is_tsan: 140 return SanitizerKind.TSAN 141 return SanitizerKind.NONE 142 143 def is_aot(self) -> Step | None: 144 return next((step for step in self.config.workflow.steps if step.step_kind == StepKind.AOT), None) 145 146 def is_aot_full(self, step: Step) -> bool: 147 return len(self.__search_option_in_list("--compiler-inline-full-intrinsics=true", step.args)) > 0 148 149 def is_aot_pgo(self, step: Step) -> bool: 150 return len(self.__search_option_in_list("--paoc-use-profile:path=", step.args)) > 0 151 152 def is_jit(self) -> bool: 153 jit = str(self.config.workflow.get_parameter("compiler-enable-jit")) 154 return jit.lower() == "true" 155 156 def is_jit_with_repeats(self) -> bool: 157 jit_with_repeats = cast(str | None, self.config.workflow.get_parameter("with-repeats")) 158 return utils.to_bool(jit_with_repeats) if jit_with_repeats is not None else False 159 160 def get_interpreter(self) -> str: 161 result: list[str] | str | None = self.config.workflow.get_parameter("ark-args") 162 ark_args: list[str] = [] if result is None else result if isinstance(result, list) else [result] 163 is_int = self.__search_option_in_list("--interpreter-type", ark_args) 164 if is_int: 165 return is_int[0].split('=')[-1] 166 return "default" 167 168 def detect_conf(self) -> ConfigurationKind: 169 if (step := self.is_aot()) is not None: 170 if self.is_aot_full(step): 171 return ConfigurationKind.AOT_FULL 172 if self.is_aot_pgo(step): 173 return ConfigurationKind.AOT_PGO 174 return ConfigurationKind.AOT 175 176 if self.is_jit(): 177 return ConfigurationKind.JIT 178 179 return ConfigurationKind.INT 180 181 def opt_level(self) -> int | None: 182 level = self.config.workflow.get_parameter("opt-level") 183 return int(level) if level is not None else None 184 185 def debug_info(self) -> bool: 186 args = self.config.workflow.get_parameter("es2panda-extra-args") 187 return len(self.__search_option_in_list("--es2panda-debug-info", args)) > 0 188 189 def is_full_ast_verifier(self) -> bool: 190 args = self.config.workflow.get_parameter("es2panda-extra-args") 191 return len(self.__search_option_in_list("--verifier-all-checks", args)) > 0 192 193 def __cmake_cache(self) -> list[str]: 194 cmake_cache_txt = "CMakeCache.txt" 195 cmake_cache: Path = self.config.general.build / cmake_cache_txt 196 if not cmake_cache.exists(): 197 raise InvalidConfiguration( 198 f"Incorrect build folder {self.config.general.build}. Cannot find '{cmake_cache_txt}' file") 199 with open(cmake_cache, encoding="utf-8") as file_handler: 200 cache = [line.strip() 201 for line in file_handler.readlines() 202 if line.strip() and not line.strip().startswith("#") and not line.strip().startswith("//")] 203 return sorted(cache) 204 205 def __search(self, variable: str) -> str | None: 206 found: list[str] = [var for var in self.cache if var.startswith(variable)] 207 return str(found[0].split("=")[-1].lower()) if found else None 208