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 17import argparse 18import multiprocessing 19from functools import cached_property 20from pathlib import Path 21from typing import Any, cast 22 23from runner.common_exceptions import InvalidConfiguration 24from runner.enum_types.qemu import QemuKind 25from runner.enum_types.verbose_format import VerboseFilter, VerboseKind 26from runner.options.macros import Macros 27from runner.options.options import IOptions 28from runner.options.options_coverage import CoverageOptions 29from runner.options.options_time_report import TimeReportOptions 30from runner.reports.report_format import ReportFormat 31from runner.utils import convert_underscore 32 33 34class GeneralOptions(IOptions): 35 __DEFAULT_PROCESSES = 1 36 __DEFAULT_CHUNKSIZE = 32 37 __DEFAULT_GC_TYPE = "g1-gc" 38 __DEFAULT_GC_BOMBING_FREQUENCY = 0 39 __DEFAULT_HEAP_VERIFIER = "fail_on_verification" 40 __DEFAULT_REPORT_FORMAT = ReportFormat.LOG 41 __DEFAULT_DETAILED_REPORT_FILE = "detailed-report-file" 42 __DEFAULT_VERBOSE = VerboseKind.SILENT 43 __DEFAULT_VERBOSE_FILTER = VerboseFilter.NEW_FAILURES 44 __DEFAULT_QEMU = QemuKind.NONE 45 __DEFAULT_SHOW_PROGRESS = False 46 __DEFAULT_DETAILED_REPORT = False 47 __DEFAULT_REPORT_DIR = "report" 48 __CFG_RUNNER = "runner" 49 50 __VERBOSE = "verbose" 51 __VERBOSE_FILTER = "verbose-filter" 52 __PROCESSES = "processes" 53 __CHUNKSIZE = "chunksize" 54 __DETAILED_REPORT = "detailed-report" 55 __DETAILED_REPORT_FILE = "detailed-report-file" 56 __SHOW_PROGRESS = "show-progress" 57 __QEMU = "qemu" 58 __REPORT_DIR = "report-dir" 59 60 def __init__(self, data: dict[str, Any], parent: IOptions): # type: ignore[explicit-any] 61 super().__init__(data) 62 self.__parameters: dict[str, Any] = {} # type: ignore[explicit-any] 63 self._parent = parent 64 for param_name, param_value in data.items(): 65 if param_name.startswith(self.__CFG_RUNNER): 66 param_name = convert_underscore(param_name.replace(f"{self.__CFG_RUNNER}.", "")) 67 self.__parameters[param_name] = param_value 68 self.time_report = TimeReportOptions(self.__parameters) 69 self.coverage = CoverageOptions(self.__parameters) 70 71 def __str__(self) -> str: 72 return self._to_str(indent=1) 73 74 @staticmethod 75 def add_cli_args(parser: argparse.ArgumentParser) -> None: 76 parser.add_argument( 77 f'--{GeneralOptions.__PROCESSES}', '-j', default=GeneralOptions.__DEFAULT_PROCESSES, 78 help=f'Number of processes to use in parallel. By default {GeneralOptions.__DEFAULT_PROCESSES}. ' 79 'Special value `all` - means to use all available processes') 80 parser.add_argument( 81 f'--{GeneralOptions.__DETAILED_REPORT}', action='store_true', 82 default=GeneralOptions.__DEFAULT_DETAILED_REPORT, 83 help='Create additional detailed report with counting tests for each folder.') 84 parser.add_argument( 85 f'--{GeneralOptions.__DETAILED_REPORT_FILE}', action='store', 86 default=GeneralOptions.__DEFAULT_DETAILED_REPORT_FILE, 87 help='Name of additional detailed report. By default, the report is created at ' 88 '$WorkDir/<suite-name>/report/<suite-name>_detailed-report-file.md , ' 89 'where $WorkDir is the folder specified by the environment variable WORK_DIR') 90 parser.add_argument( 91 f'--{GeneralOptions.__REPORT_DIR}', action='store', 92 default=GeneralOptions.__DEFAULT_REPORT_DIR, 93 help='Name of report folder under $WorkDir. By default, the name is "report".' 94 'The location is "$WorkDir/<suite-name>/<report-dir>", ' 95 'where $WorkDir is the folder specified by the environment variable WORK_DIR') 96 parser.add_argument( 97 f'--{GeneralOptions.__SHOW_PROGRESS}', action='store_true', 98 default=GeneralOptions.__DEFAULT_SHOW_PROGRESS, 99 help='Show progress bar') 100 parser.add_argument( 101 f'--{GeneralOptions.__VERBOSE}', '-v', action='store', 102 default=GeneralOptions.__DEFAULT_VERBOSE, 103 type=lambda arg: VerboseKind.is_value(arg, f"--{GeneralOptions.__VERBOSE}"), 104 help='Enable verbose output. ' 105 f'Possible values one of: {VerboseKind.values()}. Where ' 106 'all - the most detailed output, ' 107 'short - test status and output, ' 108 'silent - only test status for new failed tests (by default)') 109 parser.add_argument( 110 f'--{GeneralOptions.__VERBOSE_FILTER}', action='store', 111 default=GeneralOptions.__DEFAULT_VERBOSE_FILTER, 112 type=lambda arg: VerboseFilter.is_value(arg, f"--{GeneralOptions.__VERBOSE_FILTER}"), 113 help='Filter for what kind of tests to output stdout and stderr. Works only when --verbose option is set.' 114 f'Supported values: {VerboseFilter.values()}. Where ' 115 'all - for all executed tests, ' 116 'ignored - for new failures and tests from ignored test lists both passed and failed. ' 117 'new - only for new failures (by default).') 118 parser.add_argument( 119 f'--{GeneralOptions.__QEMU}', action='store', 120 default=GeneralOptions.__DEFAULT_QEMU, 121 type=lambda arg: QemuKind.is_value(arg, f"--{GeneralOptions.__QEMU}"), 122 help='Launch all binaries in qemu aarch64 (arm64) or arm (arm32)') 123 124 TimeReportOptions.add_cli_args(parser) 125 CoverageOptions.add_cli_args(parser) 126 127 @cached_property 128 def processes(self) -> int: 129 procs = self.__parameters.get(self.__PROCESSES, self.__DEFAULT_PROCESSES) 130 if isinstance(procs, str) and procs.lower() == "all": 131 self.__parameters[self.__PROCESSES] = multiprocessing.cpu_count() 132 return int(self.__parameters.get(self.__PROCESSES, self.__DEFAULT_PROCESSES)) 133 134 @cached_property 135 def build(self) -> Path: 136 build_path = Macros.expand_macros_in_path("${PANDA_BUILD}", self) 137 if build_path is None: 138 raise InvalidConfiguration("Build path is not set.") 139 return Path(build_path).expanduser() 140 141 @cached_property 142 def chunksize(self) -> int: 143 return GeneralOptions.__DEFAULT_CHUNKSIZE 144 145 @cached_property 146 def static_core_root(self) -> Path: 147 runtime_core_path = cast(str, Macros.expand_macros_in_path("${ARKCOMPILER_RUNTIME_CORE_PATH}", self)) 148 static_core_path = Path(runtime_core_path) / "static_core" 149 return static_core_path 150 151 @cached_property 152 def show_progress(self) -> bool: 153 return cast(bool, self.__parameters.get(self.__SHOW_PROGRESS, self.__DEFAULT_SHOW_PROGRESS)) 154 155 @cached_property 156 def report_format(self) -> ReportFormat: 157 return GeneralOptions.__DEFAULT_REPORT_FORMAT 158 159 @cached_property 160 def detailed_report(self) -> bool: 161 return cast(bool, self.__parameters.get(self.__DETAILED_REPORT, self.__DEFAULT_DETAILED_REPORT)) 162 163 @cached_property 164 def detailed_report_file(self) -> Path | None: 165 path_str = self.__parameters.get(self.__DETAILED_REPORT_FILE, self.__DEFAULT_DETAILED_REPORT_FILE) 166 if path_str: 167 return Path(cast(str, path_str)) 168 return None 169 170 @cached_property 171 def report_dir_name(self) -> str: 172 return cast(str, self.__parameters.get(self.__REPORT_DIR, self.__DEFAULT_REPORT_DIR)) 173 174 @cached_property 175 def verbose(self) -> VerboseKind: 176 kind = self.__parameters.get(self.__VERBOSE, self.__DEFAULT_VERBOSE) 177 if isinstance(kind, VerboseKind): 178 return kind 179 raise InvalidConfiguration("Verbose kind has incorrect value") 180 181 @cached_property 182 def verbose_filter(self) -> VerboseFilter: 183 kind = self.__parameters.get(self.__VERBOSE_FILTER, self.__DEFAULT_VERBOSE_FILTER) 184 if isinstance(kind, VerboseFilter): 185 return kind 186 raise InvalidConfiguration("Verbose filter has incorrect value") 187 188 @cached_property 189 def qemu(self) -> QemuKind: 190 kind = self.__parameters.get(self.__QEMU, self.__DEFAULT_QEMU) 191 if isinstance(kind, QemuKind): 192 return kind 193 raise InvalidConfiguration("Qemu kind has incorrect value") 194 195 @cached_property 196 def aot_check(self) -> bool: 197 return False 198 199 def get_command_line(self) -> str: 200 options = [ 201 f'--processes={self.processes}' if self.processes != GeneralOptions.__DEFAULT_PROCESSES else '', 202 f'--chunksize={self.chunksize}' if self.chunksize != GeneralOptions.__DEFAULT_CHUNKSIZE else '', 203 '--show-progress' if self.show_progress else '', 204 f'--verbose={self.verbose.value}' 205 if self.verbose != GeneralOptions.__DEFAULT_VERBOSE else '', 206 f'--verbose-filter={self.verbose_filter.value}' 207 if self.verbose_filter != GeneralOptions.__DEFAULT_VERBOSE_FILTER else '', 208 self.coverage.get_command_line(), 209 '--aot' if self.aot_check else '', 210 self.time_report.get_command_line(), 211 ] 212 return ' '.join(options) 213