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, 367 UnicodeError) as exception: 368 LOG.exception(exception, exc_info=False) 369 370 @classmethod 371 def _params_pre_processing(cls, para_list): 372 if len(para_list) <= 1 or ( 373 len(para_list) > 1 and "-" in str(para_list[1])): 374 para_list.insert(1, "empty") 375 for index, param in enumerate(para_list): 376 if param == "--retry": 377 if index + 1 == len(para_list): 378 para_list.append("retry_previous_command") 379 elif "-" in str(para_list[index + 1]): 380 para_list.insert(index + 1, "retry_previous_command") 381 elif param == "-->": 382 para_list[index] = "!%s" % param 383 384 @staticmethod 385 def _parse_combination_param(combination_value): 386 # sample: size:xxx1;exclude-annotation:xxx 387 parse_result = {} 388 key_value_pairs = str(combination_value).split(";") 389 for key_value_pair in key_value_pairs: 390 key, value = key_value_pair.split(":", 1) 391 if not value: 392 raise ParamError("'%s' no value" % key) 393 value_list = str(value).split(",") 394 exist_list = parse_result.get(key, []) 395 exist_list.extend(value_list) 396 parse_result[key] = exist_list 397 return parse_result 398 399 400 @classmethod 401 def _process_command_version(cls, para_list): 402 display_version_info(para_list) 403 return 404 405 406 @classmethod 407 def _process_command_help(cls, para_list): 408 if para_list[0] == ToolCommandType.TOOLCMD_KEY_HELP: 409 display_help_info(para_list) 410 else: 411 LOG.error("Wrong help command.") 412 return 413 414 @classmethod 415 def _process_command_show(cls, para_list, productform="phone"): 416 if para_list[0] == ToolCommandType.TOOLCMD_KEY_SHOW: 417 display_show_info(para_list, productform) 418 else: 419 LOG.error("Wrong show command.") 420 return 421 422 @classmethod 423 def _process_command_gen(cls, command, options): 424 if command == ToolCommandType.TOOLCMD_KEY_GEN: 425 Gen().process_command_gen(options) 426 else: 427 LOG.error("Wrong gen command.") 428 return 429 430 @classmethod 431 def _process_command_run(cls, command, options): 432 if command == ToolCommandType.TOOLCMD_KEY_RUN: 433 Run().process_command_run(command, options) 434 else: 435 LOG.error("Wrong run command.") 436 return 437 438 @classmethod 439 def _process_command_device(cls, command): 440 if command == ToolCommandType.TOOLCMD_KEY_LIST: 441 env_manager = EnvironmentManager() 442 env_manager.list_devices() 443 else: 444 LOG.error("Wrong list command.") 445 return 446 447 @classmethod 448 def _process_command_quit(cls, command): 449 if command == ToolCommandType.TOOLCMD_KEY_QUIT: 450 env_manager = EnvironmentManager() 451 env_manager.env_stop() 452 sys.exit(0) 453 else: 454 LOG.error("Wrong exit command.") 455 return 456 457 @classmethod 458 def _build_version(cls, product_form): 459 is_build_version = UserConfigManager().get_user_config_flag( 460 "build", "version") 461 462 project_root_path = sys.source_code_root_path 463 if project_root_path == "": 464 return True 465 466 build_result = True 467 if is_lite_product(product_form, sys.source_code_root_path): 468 if not is_build_version: 469 return True 470 from core.build.build_lite_manager import BuildLiteManager 471 build_lite_manager = BuildLiteManager(project_root_path) 472 build_result = build_lite_manager.build_version(product_form) 473 else: 474 from core.build.build_manager import BuildManager 475 build_manager = BuildManager() 476 if is_build_version: 477 build_result = build_manager.build_version(project_root_path, 478 product_form) 479 return build_result 480 481 482@dataclass 483class ConfigConst(object): 484 action = "action" 485 task = "task" 486 testlist = "testlist" 487 testfile = "testfile" 488 testcase = "testcase" 489 testdict = "testdict" 490 device_sn = "device_sn" 491 report_path = "report_path" 492 resource_path = "resource_path" 493 testcases_path = "testcases_path" 494 testargs = "testargs" 495 pass_through = "pass_through" 496 test_environment = "test_environment" 497 exectype = "exectype" 498 testtype = "testtype" 499 testdriver = "testdriver" 500 retry = "retry" 501 session = "session" 502 dry_run = "dry_run" 503 reboot_per_module = "reboot_per_module" 504 check_device = "check_device" 505 configfile = "config" 506 repeat = "repeat" 507 subsystems = "subsystems" 508 parts = "parts" 509 510 # Runtime Constant 511 history_report_path = "history_report_path" 512 product_info = "product_info" 513 task_state = "task_state" 514 recover_state = "recover_state" 515 need_kit_setup = "need_kit_setup" 516 task_kits = "task_kits" 517 module_kits = "module_kits" 518 spt = "spt" 519 version = "version" 520 component_mapper = "_component_mapper" 521 component_base_kit = "component_base_kit" 522 support_component = "support_component" 523 524 525############################################################################## 526############################################################################## 527