1#!/usr/bin/env python3 2# coding=utf-8 3 4# 5# Copyright (c) 2022 Huawei Device Co., Ltd. 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19import argparse 20import sys 21import signal 22import platform 23from dataclasses import dataclass 24from core.constants import ToolCommandType 25from core.exception import ParamError 26from xdevice import platform_logger 27from xdevice import EnvironmentManager 28from xdevice._core.utils import SplicingAction 29from core.command.run import Run 30from core.command.gen import Gen 31from core.command.display import display_help_info 32from core.command.display import display_show_info 33from core.command.display import display_version_info 34from core.command.display import show_wizard_mode 35from core.config.config_manager import UserConfigManager 36from core.utils import is_lite_product 37 38try: 39 if platform.system() != 'Windows': 40 import readline 41except ModuleNotFoundError: 42 print("ModuleNotFoundError: readline module is not exist.") 43except ImportError: 44 print("ImportError: libreadline.so is not exist.") 45 46__all__ = ["Console", "ConfigConst"] 47LOG = platform_logger("Console") 48 49############################################################################## 50############################################################################## 51 52 53class Console(object): 54 """ 55 Class representing an console for executing test. 56 Main xDevice console providing user with the interface to interact 57 """ 58 __instance = None 59 wizard_dic = {} 60 61 def __new__(cls, *args, **kwargs): 62 if cls.__instance is None: 63 cls.__instance = super(Console, cls).__new__(cls, *args, **kwargs) 64 return cls.__instance 65 66 def __init__(self): 67 pass 68 69 def handler_ctrl_c(self, signalnum, frame): 70 pass 71 72 def handler_ctrl_z(self, signalnum, frame): 73 pass 74 75 def console(self, args): 76 """ 77 Main xDevice console providing user with the interface to interact 78 """ 79 EnvironmentManager() 80 if args is None or len(args) < 2: 81 self.wizard_dic = show_wizard_mode() 82 print(self.wizard_dic) 83 if self._build_version(self.wizard_dic["productform"]): 84 self._console() 85 else: 86 LOG.error("Build version failed, exit test framework.") 87 else: 88 self.command_parser(" ".join(args[1:])) 89 90 # 命令执行总入口 91 def _console(self): 92 if platform.system() != 'Windows': 93 signal.signal(signal.SIGTSTP, self.handler_ctrl_z) # ctrl+x linux 94 signal.signal(signal.SIGINT, self.handler_ctrl_c) # ctrl+c 95 96 while True: 97 try: 98 # 获取用户命令输入 99 usr_input = input(">>> ") 100 if usr_input == "": 101 continue 102 # 用户输入命令解析 103 self.command_parser(usr_input) 104 except SystemExit: 105 LOG.info("Program exit normally!") 106 return 107 except (IOError, EOFError, KeyboardInterrupt) as error: 108 LOG.exception("Input Error: %s" % error) 109 110 @staticmethod 111 def _parse_combination_param(combination_value): 112 # sample: size:xxx1;exclude-annotation:xxx 113 parse_result = {} 114 key_value_pairs = str(combination_value).split(";") 115 for key_value_pair in key_value_pairs: 116 key, value = key_value_pair.split(":", 1) 117 if not value: 118 raise ParamError("'%s' no value" % key) 119 value_list = str(value).split(",") 120 exist_list = parse_result.get(key, []) 121 exist_list.extend(value_list) 122 parse_result[key] = exist_list 123 return parse_result 124 125 @classmethod 126 def _params_post_processing(self, options): 127 # params post-processing 128 if options.testargs: 129 test_args = self._parse_combination_param(options.testargs) 130 setattr(options, ConfigConst.testargs, test_args) 131 132 # 参数解析方法 133 @classmethod 134 def argument_parser(cls, para_list): 135 """ 136 argument parser 137 """ 138 options = None 139 unparsed = [] 140 valid_param = True 141 parser = None 142 143 try: 144 # argparse是一个Python模块:命令行选项、参数和子命令解析器 145 # 使用argparse的第一步:创建一个ArgumentParser对象,ArgumentParser对象包含将命令行解析成Python数据类型所需的全部信息 146 parser = argparse.ArgumentParser(description="Specify test para.") 147 parser.add_argument("action", type=str.lower, 148 help="Specify action") 149 # Developer test general test parameters 150 parser.add_argument("-p", "--productform", 151 action="store", 152 type=str.lower, 153 dest="productform", 154 default="phone", 155 help="Specified product form" 156 ) 157 parser.add_argument("-t", "--testtype", 158 nargs='*', 159 type=str.upper, 160 dest="testtype", 161 default=["UT"], 162 help="Specify test type(UT,MST,ST,PERF,ALL)" 163 ) 164 parser.add_argument("-ss", "--subsystem", 165 nargs='*', 166 dest="subsystem", 167 default=[], 168 help="Specify test subsystem" 169 ) 170 parser.add_argument("--retry", 171 action="store_true", 172 dest="retry", 173 default=False, 174 help="Specify retry command" 175 ) 176 parser.add_argument("--dryrun", 177 action="store_true", 178 dest="dry_run", 179 help="show retry test case list") 180 parser.add_argument("--repeat", 181 type=int, 182 dest="repeat", 183 default=0, 184 help="Specify number of times that a test is executed" 185 ) 186 parser.add_argument("-hl", "--historylist", 187 action='store_true', 188 dest="historylist", 189 default=False, 190 help="Show last 10 excuted commands except -hl,-rh,-retry" 191 ) 192 parser.add_argument("-rh", "--runhistory", 193 type=int, 194 dest="runhistory", 195 default=0, 196 help="Run history command by history command id" 197 ) 198 parser.add_argument("-tp", "--testpart", 199 nargs='*', 200 dest="testpart", 201 default=[], 202 help="Specify test testpart" 203 ) 204 parser.add_argument("-tm", "--testmodule", 205 action="store", 206 type=str, 207 dest="testmodule", 208 default="", 209 help="Specified test module" 210 ) 211 parser.add_argument("-ts", "--testsuit", 212 action="store", 213 type=str, 214 dest="testsuit", 215 default="", 216 help="Specify test suit" 217 ) 218 parser.add_argument("-ta", "--testargs", 219 action=SplicingAction, 220 type=str, 221 nargs='+', 222 dest=ConfigConst.testargs, 223 default={}, 224 help="Specify test arguments" 225 ) 226 parser.add_argument("-tc", "--testcase", 227 action="store", 228 type=str, 229 dest="testcase", 230 default="", 231 help="Specify test case" 232 ) 233 parser.add_argument("-tl", "--testlevel", 234 action="store", 235 type=str, 236 dest="testlevel", 237 default="", 238 help="Specify test level" 239 ) 240 241 # Developer test extended test parameters 242 parser.add_argument("-cov", "--coverage", 243 action="store_true", 244 dest="coverage", 245 default=False, 246 help="Specify coverage" 247 ) 248 parser.add_argument("-pg", "--pullgcda", 249 action="store_true", 250 dest="pullgcda", 251 default=False, 252 help="Only pull gcda file." 253 ) 254 parser.add_argument("-hlg", "--hidelog", 255 action="store_true", 256 dest="hidelog", 257 default=False, 258 help="Not show task log in console." 259 ) 260 parser.add_argument("-tf", "--testfile", 261 action="store", 262 type=str, 263 dest="testfile", 264 default="", 265 help="Specify test suites list file" 266 ) 267 parser.add_argument("-res", "--resource", 268 action="store", 269 type=str, 270 dest="resource", 271 default="", 272 help="Specify test resource" 273 ) 274 parser.add_argument("-dp", "--dirpath", 275 action="store", 276 type=str, 277 dest="dirpath", 278 default="", 279 help="Specify fuzz test dirpath" 280 ) 281 parser.add_argument("-fn", "--fuzzername", 282 action="store", 283 type=str, 284 dest="fuzzername", 285 default="", 286 help="Specify fuzzer name" 287 ) 288 parser.add_argument("-ra", "--random", 289 action="store", 290 type=str, 291 dest="random", 292 default="", 293 help="Specify random name", 294 choices=["random"] 295 ) 296 parser.add_argument("-pd", "--partdeps", 297 action="store", 298 type=str, 299 dest="partdeps", 300 default="", 301 help="Specify part deps", 302 choices=["partdeps"] 303 ) 304 305 # 解析部分命令行参数,会返回一个由两个条目构成的元组,其中包含带成员的命名空间(options)和剩余参数字符串的列表(unparsed) 306 cls._params_pre_processing(para_list) 307 (options, unparsed) = parser.parse_known_args(para_list) 308 cls._params_post_processing(options) 309 310 # Set default value 311 options.target_os_name = "OHOS" 312 options.build_variant = "release" 313 options.device_sn = "" 314 options.config = "" 315 options.reportpath = "" 316 options.exectype = "device" 317 options.testdriver = "" 318 319 except SystemExit: 320 valid_param = False 321 parser.print_help() 322 LOG.warning("Parameter parsing systemexit exception.") 323 324 return options, unparsed, valid_param 325 326 def command_parser(self, args): 327 try: 328 # 将用户输入的指令按空格拆分成字符串数组 329 para_list = args.split() 330 options, _, valid_param = self.argument_parser(para_list) 331 if options is None or not valid_param: 332 LOG.warning("options is None.") 333 return 334 335 # 根据命令行的命令选择不同的方法执行 336 command = options.action 337 if command == "": 338 LOG.warning("action is empty.") 339 return 340 341 if "productform" in self.wizard_dic.keys(): 342 productform = self.wizard_dic["productform"] 343 options.productform = productform 344 else: 345 productform = options.productform 346 347 if command.startswith(ToolCommandType.TOOLCMD_KEY_HELP): 348 self._process_command_help(para_list) 349 elif command.startswith(ToolCommandType.TOOLCMD_KEY_SHOW): 350 self._process_command_show(para_list, productform) 351 elif command.startswith(ToolCommandType.TOOLCMD_KEY_GEN): 352 self._process_command_gen(command, options) 353 elif command.startswith(ToolCommandType.TOOLCMD_KEY_RUN): 354 # 保存原始控制命令 355 options.current_raw_cmd = args 356 self._process_command_run(command, options) 357 elif command.startswith(ToolCommandType.TOOLCMD_KEY_QUIT): 358 self._process_command_quit(command) 359 elif command.startswith(ToolCommandType.TOOLCMD_KEY_LIST): 360 self._process_command_device(command) 361 elif command.startswith(ToolCommandType.TOOLCMD_KEY_VERSION): 362 self._process_command_version(command) 363 else: 364 print("The %s command is not supported." % command) 365 except (AttributeError, IOError, IndexError, ImportError, NameError, 366 RuntimeError, SystemError, TypeError, ValueError) as exception: 367 LOG.exception(exception, exc_info=False) 368 369 @classmethod 370 def _params_pre_processing(cls, para_list): 371 if len(para_list) <= 1 or ( 372 len(para_list) > 1 and "-" in str(para_list[1])): 373 para_list.insert(1, "empty") 374 for index, param in enumerate(para_list): 375 if param == "--retry": 376 if index + 1 == len(para_list): 377 para_list.append("retry_previous_command") 378 elif "-" in str(para_list[index + 1]): 379 para_list.insert(index + 1, "retry_previous_command") 380 elif param == "-->": 381 para_list[index] = "!%s" % param 382 383 @staticmethod 384 def _parse_combination_param(combination_value): 385 # sample: size:xxx1;exclude-annotation:xxx 386 parse_result = {} 387 key_value_pairs = str(combination_value).split(";") 388 for key_value_pair in key_value_pairs: 389 key, value = key_value_pair.split(":", 1) 390 if not value: 391 raise ParamError("'%s' no value" % key) 392 value_list = str(value).split(",") 393 exist_list = parse_result.get(key, []) 394 exist_list.extend(value_list) 395 parse_result[key] = exist_list 396 return parse_result 397 398 399 @classmethod 400 def _process_command_version(cls, para_list): 401 display_version_info(para_list) 402 return 403 404 405 @classmethod 406 def _process_command_help(cls, para_list): 407 if para_list[0] == ToolCommandType.TOOLCMD_KEY_HELP: 408 display_help_info(para_list) 409 else: 410 LOG.error("Wrong help command.") 411 return 412 413 @classmethod 414 def _process_command_show(cls, para_list, productform="phone"): 415 if para_list[0] == ToolCommandType.TOOLCMD_KEY_SHOW: 416 display_show_info(para_list, productform) 417 else: 418 LOG.error("Wrong show command.") 419 return 420 421 @classmethod 422 def _process_command_gen(cls, command, options): 423 if command == ToolCommandType.TOOLCMD_KEY_GEN: 424 Gen().process_command_gen(options) 425 else: 426 LOG.error("Wrong gen command.") 427 return 428 429 @classmethod 430 def _process_command_run(cls, command, options): 431 if command == ToolCommandType.TOOLCMD_KEY_RUN: 432 Run().process_command_run(command, options) 433 else: 434 LOG.error("Wrong run command.") 435 return 436 437 @classmethod 438 def _process_command_device(cls, command): 439 if command == ToolCommandType.TOOLCMD_KEY_LIST: 440 env_manager = EnvironmentManager() 441 env_manager.list_devices() 442 else: 443 LOG.error("Wrong list command.") 444 return 445 446 @classmethod 447 def _process_command_quit(cls, command): 448 if command == ToolCommandType.TOOLCMD_KEY_QUIT: 449 env_manager = EnvironmentManager() 450 env_manager.env_stop() 451 sys.exit(0) 452 else: 453 LOG.error("Wrong exit command.") 454 return 455 456 @classmethod 457 def _build_version(cls, product_form): 458 is_build_version = UserConfigManager().get_user_config_flag( 459 "build", "version") 460 461 project_root_path = sys.source_code_root_path 462 if project_root_path == "": 463 return True 464 465 build_result = True 466 if is_lite_product(product_form, sys.source_code_root_path): 467 if not is_build_version: 468 return True 469 from core.build.build_lite_manager import BuildLiteManager 470 build_lite_manager = BuildLiteManager(project_root_path) 471 build_result = build_lite_manager.build_version(product_form) 472 else: 473 from core.build.build_manager import BuildManager 474 build_manager = BuildManager() 475 if is_build_version: 476 build_result = build_manager.build_version(project_root_path, 477 product_form) 478 return build_result 479 480 481@dataclass 482class ConfigConst(object): 483 action = "action" 484 task = "task" 485 testlist = "testlist" 486 testfile = "testfile" 487 testcase = "testcase" 488 testdict = "testdict" 489 device_sn = "device_sn" 490 report_path = "report_path" 491 resource_path = "resource_path" 492 testcases_path = "testcases_path" 493 testargs = "testargs" 494 pass_through = "pass_through" 495 test_environment = "test_environment" 496 exectype = "exectype" 497 testtype = "testtype" 498 testdriver = "testdriver" 499 retry = "retry" 500 session = "session" 501 dry_run = "dry_run" 502 reboot_per_module = "reboot_per_module" 503 check_device = "check_device" 504 configfile = "config" 505 repeat = "repeat" 506 subsystems = "subsystems" 507 parts = "parts" 508 509 # Runtime Constant 510 history_report_path = "history_report_path" 511 product_info = "product_info" 512 task_state = "task_state" 513 recover_state = "recover_state" 514 need_kit_setup = "need_kit_setup" 515 task_kits = "task_kits" 516 module_kits = "module_kits" 517 spt = "spt" 518 version = "version" 519 component_mapper = "_component_mapper" 520 component_base_kit = "component_base_kit" 521 support_component = "support_component" 522 523 524############################################################################## 525############################################################################## 526