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