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