• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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