• 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 argparse
19from functools import cached_property
20from pathlib import Path
21from typing import Any, cast
22
23from runner.common_exceptions import InvalidConfiguration
24from runner.logger import Log
25from runner.options.macros import Macros, ParameterNotFound
26from runner.options.options import IOptions
27from runner.options.options_collections import CollectionsOptions
28from runner.options.options_ets import ETSOptions
29from runner.options.options_general import GeneralOptions
30from runner.options.options_groups import GroupsOptions
31from runner.options.options_test_lists import TestListsOptions
32from runner.utils import check_int, convert_underscore, extract_parameter_name
33
34_LOGGER = Log.get_logger(__file__)
35
36
37class TestSuiteOptions(IOptions):
38    __DATA = "data"
39    __TEST_SUITE = "test-suite"
40
41    __PROPERTIES = "properties"
42    __COLLECTIONS = "collections"
43    __LIST_ROOT = "list-root"
44    __TEST_ROOT = "test-root"
45
46    __PARAMETERS = "parameters"
47    __EXTENSION = "extension"
48    __DEFAULT_EXTENSION = "sts"
49    __FILTER = "filter"
50    __DEFAULT_FILTER = "*"
51    __LOAD_RUNTIMES = "load-runtimes"
52    __DEFAULT_LOAD_RUNTIMES = "ets"
53    __REPEATS = "repeats"
54    __DEFAULT_REPEATS = 1
55    __REPEATS_BY_TIME = "repeats-by-time"
56    __DEFAULT_REPEATS_BY_TIME = 0
57    __WITH_JS = "with-js"
58    __DEFAULT_WITH_JS = False
59    __WORK_DIR = "work-dir"
60
61    def __init__(self, args: dict[str, Any], parent: IOptions):  # type: ignore[explicit-any]
62        super().__init__(None)
63        self.__name: str = cast(str, args[self.__TEST_SUITE])
64        if not isinstance(parent, GeneralOptions):
65            raise InvalidConfiguration(
66                "Incorrect configuration: test suite parent is not GeneralOptions")
67        self._parent: GeneralOptions = parent
68        self.__parameters: dict[str, Any] = self.__process_parameters(args) # type: ignore[explicit-any]
69        self.__data = args[f"{self.__name}.data"]
70        self.__default_list_root: Path = self._parent.static_core_root / 'tests' / 'tests-u-runner' / 'test-lists'
71        self.__list_root: str | None = self.__data[self.__LIST_ROOT] \
72            if self.__data[self.__LIST_ROOT] else str(self.__default_list_root)
73        self.__test_root: str | None = self.__data[self.__TEST_ROOT] \
74            if self.__data[self.__TEST_ROOT] else None
75        self.__collections: list[CollectionsOptions] = []
76        self.test_lists = TestListsOptions(self.__parameters)
77        self.ets = ETSOptions(self.__parameters)
78        self.groups = GroupsOptions(self.__parameters)
79        self.__expand_macros_in_parameters()
80        self.__fill_collections()
81        self.__expand_macros_collections(self._parent)
82
83    def __str__(self) -> str:
84        return self._to_str(indent=1)
85
86    @property
87    def list_root(self) -> Path:
88        if self.__list_root is None:
89            raise InvalidConfiguration("List-root is not specified")
90        return Path(self.__list_root)
91
92    @property
93    def test_root(self) -> Path:
94        if self.__test_root is None:
95            raise InvalidConfiguration("Test-root is not specified")
96        return Path(self.__test_root)
97
98    @property
99    def work_dir(self) -> str:
100        if self.__WORK_DIR not in self.__parameters:
101            raise InvalidConfiguration("work-dir is not specified")
102        return str(self.__parameters[self.__WORK_DIR])
103
104    @staticmethod
105    def add_cli_args(parser: argparse.ArgumentParser) -> None:
106        parser.add_argument(
107            f'--{TestSuiteOptions.__FILTER}', '-f', action='store',
108            default=TestSuiteOptions.__DEFAULT_FILTER,
109            help=f'test filter wildcard. By default \'{TestSuiteOptions.__DEFAULT_FILTER}\'')
110        repeats_group = parser.add_mutually_exclusive_group(required=False)
111        repeats_group.add_argument(
112            f'--{TestSuiteOptions.__REPEATS}', action='store',
113            type=lambda arg: check_int(arg, f"--{TestSuiteOptions.__REPEATS}", is_zero_allowed=False),
114            default=TestSuiteOptions.__DEFAULT_REPEATS,
115            help=f'how many times to repeat the suite entirely. By default {TestSuiteOptions.__DEFAULT_REPEATS}')
116        repeats_group.add_argument(
117            f'--{TestSuiteOptions.__REPEATS_BY_TIME}', action='store',
118            type=lambda arg: check_int(arg, f"--{TestSuiteOptions.__REPEATS_BY_TIME}", is_zero_allowed=True),
119            default=TestSuiteOptions.__DEFAULT_REPEATS_BY_TIME,
120            help=f'number of seconds during which the suite is repeated. '
121                 f'Number of repeats is always integer. By default {TestSuiteOptions.__DEFAULT_REPEATS_BY_TIME}')
122        parser.add_argument(
123            f'--{TestSuiteOptions.__WITH_JS}', action='store_true',
124            default=TestSuiteOptions.__WITH_JS,
125            help='enable JS-related tests')
126
127        TestListsOptions.add_cli_args(parser)
128        ETSOptions.add_cli_args(parser)
129        GroupsOptions.add_cli_args(parser)
130
131    @staticmethod
132    def __fill_collection(content: dict) -> dict:
133        for _, prop_value in content.items():
134            if isinstance(prop_value, dict):
135                TestSuiteOptions.__fill_collection_props(prop_value)
136        return content
137
138    @staticmethod
139    def __fill_collection_props(prop_value: dict) -> None:
140        for sub_key, sub_value in prop_value.items():
141            if isinstance(sub_value, list):
142                prop_value[sub_key] = " ".join(sub_value)
143
144    @cached_property
145    def suite_name(self) -> str:
146        return self.__name
147
148    @cached_property
149    def collections(self) -> list[CollectionsOptions]:
150        return self.__collections
151
152    @cached_property
153    def repeats(self) -> int:
154        return int(self.__parameters.get(self.__REPEATS, self.__DEFAULT_REPEATS))
155
156    @cached_property
157    def repeats_by_time(self) -> int:
158        return cast(int, self.__parameters.get(self.__REPEATS_BY_TIME, self.__DEFAULT_REPEATS_BY_TIME))
159
160    @cached_property
161    def filter(self) -> str:
162        return str(self.__parameters.get(self.__FILTER, self.__DEFAULT_FILTER))
163
164    @cached_property
165    def parameters(self) -> dict[str, Any]: # type: ignore[explicit-any]
166        return self.__parameters
167
168    def extension(self, collection: CollectionsOptions | None = None) -> str:
169        return str(self.get_parameter(self.__EXTENSION, self.__DEFAULT_EXTENSION, collection))
170
171    def with_js(self, collection: CollectionsOptions | None = None) -> bool:
172        return bool(self.get_parameter(self.__WITH_JS, self.__DEFAULT_WITH_JS, collection))
173
174    def load_runtimes(self, collection: CollectionsOptions | None = None) -> str:
175        return str(self.get_parameter(self.__LOAD_RUNTIMES, self.__DEFAULT_LOAD_RUNTIMES, collection))
176
177    def get_parameter(self, key: str, default: Any | None = None,   # type: ignore[explicit-any]
178                      collection: CollectionsOptions | None = None) -> Any | None:
179        if collection is not None:
180            value = collection.get_parameter(key, None)
181            if value is not None:
182                return value
183        return self.__parameters.get(key, default)
184
185    def is_defined_in_collections(self, key: str) -> bool:
186        key = extract_parameter_name(key)
187        for collection in self.__collections:
188            if key in collection.parameters:
189                return True
190        return False
191
192    def get_command_line(self) -> str:
193        options = [
194            f'--test-root={self.test_root}' if self.test_root is not None else '',
195            f'--list-root={self.list_root}' if self.list_root is not None else '',
196        ]
197        return ' '.join(options)
198
199    def __fill_collections(self) -> None:
200        if self.__COLLECTIONS not in self.__data:
201            self.__collections.append(CollectionsOptions("", self.__data, self))
202            return
203        for coll_name, content in self.__data[self.__COLLECTIONS].items():
204            if content:
205                content = self.__fill_collection(content)
206            self.__collections.append(CollectionsOptions(coll_name, content, self))
207
208    def __expand_macros_collections(self, parent: IOptions) -> None:
209        for collection in self.__collections:
210            for coll_param, coll_value in collection.parameters.items():
211                try:
212                    new_coll_value = Macros.correct_macro(coll_value, self)
213                except ParameterNotFound:
214                    new_coll_value = Macros.correct_macro(coll_value, parent)
215
216                if new_coll_value != coll_value:
217                    collection.parameters[coll_param] = new_coll_value
218
219    def __process_parameters(self, args: dict[str, Any]) -> dict[str, Any]:  # type: ignore[explicit-any]
220        result: dict[str, Any] = {}  # type: ignore[explicit-any]
221        for param_name, param_value in args.items():
222            if param_name.startswith(f"{self.__name}.{self.__PARAMETERS}."):
223                param_name = convert_underscore(param_name[(len(self.__name) + len(self.__PARAMETERS) + 2):])
224                result[param_name] = param_value
225        return result
226
227    def __expand_macros_in_parameters(self) -> None:
228        if self.__list_root is not None:
229            self.__list_root = Macros.expand_macros_in_path(self.__list_root, self)
230        if self.__test_root is not None:
231            self.__test_root = Macros.expand_macros_in_path(self.__test_root, self)
232        for param_name, param_value in self.__parameters.items():
233            if isinstance(param_value, str):
234                self.__parameters[param_name] = Macros.correct_macro(param_value, self)
235