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("-tf", "--testfile", 249 action="store", 250 type=str, 251 dest="testfile", 252 default="", 253 help="Specify test suites list file" 254 ) 255 parser.add_argument("-res", "--resource", 256 action="store", 257 type=str, 258 dest="resource", 259 default="", 260 help="Specify test resource" 261 ) 262 parser.add_argument("-dp", "--dirpath", 263 action="store", 264 type=str, 265 dest="dirpath", 266 default="", 267 help="Specify fuzz test dirpath" 268 ) 269 parser.add_argument("-fn", "--fuzzername", 270 action="store", 271 type=str, 272 dest="fuzzername", 273 default="", 274 help="Specify fuzzer name" 275 ) 276 277 # 解析部分命令行参数,会返回一个由两个条目构成的元组,其中包含带成员的命名空间(options)和剩余参数字符串的列表(unparsed) 278 cls._params_pre_processing(para_list) 279 (options, unparsed) = parser.parse_known_args(para_list) 280 cls._params_post_processing(options) 281 282 # Set default value 283 options.target_os_name = "OHOS" 284 options.build_variant = "release" 285 options.device_sn = "" 286 options.config = "" 287 options.reportpath = "" 288 options.exectype = "device" 289 options.testdriver = "" 290 291 except SystemExit: 292 valid_param = False 293 parser.print_help() 294 LOG.warning("Parameter parsing systemexit exception.") 295 296 return options, unparsed, valid_param 297 298 def command_parser(self, args): 299 try: 300 # 将用户输入的指令按空格拆分成字符串数组 301 para_list = args.split() 302 options, _, valid_param = self.argument_parser(para_list) 303 if options is None or not valid_param: 304 LOG.warning("options is None.") 305 return 306 307 # 根据命令行的命令选择不同的方法执行 308 command = options.action 309 if command == "": 310 LOG.warning("action is empty.") 311 return 312 313 if "productform" in self.wizard_dic.keys(): 314 productform = self.wizard_dic["productform"] 315 options.productform = productform 316 else: 317 productform = options.productform 318 319 if command.startswith(ToolCommandType.TOOLCMD_KEY_HELP): 320 self._process_command_help(para_list) 321 elif command.startswith(ToolCommandType.TOOLCMD_KEY_SHOW): 322 self._process_command_show(para_list, productform) 323 elif command.startswith(ToolCommandType.TOOLCMD_KEY_GEN): 324 self._process_command_gen(command, options) 325 elif command.startswith(ToolCommandType.TOOLCMD_KEY_RUN): 326 # 保存原始控制命令 327 options.current_raw_cmd = args 328 self._process_command_run(command, options) 329 elif command.startswith(ToolCommandType.TOOLCMD_KEY_QUIT): 330 self._process_command_quit(command) 331 elif command.startswith(ToolCommandType.TOOLCMD_KEY_LIST): 332 self._process_command_device(command) 333 elif command.startswith(ToolCommandType.TOOLCMD_KEY_VERSION): 334 self._process_command_version(command) 335 else: 336 print("The %s command is not supported." % command) 337 except (AttributeError, IOError, IndexError, ImportError, NameError, 338 RuntimeError, SystemError, TypeError, ValueError, 339 UnicodeError) as exception: 340 LOG.exception(exception, exc_info=False) 341 342 @classmethod 343 def _params_pre_processing(cls, para_list): 344 if len(para_list) <= 1 or ( 345 len(para_list) > 1 and "-" in str(para_list[1])): 346 para_list.insert(1, "empty") 347 for index, param in enumerate(para_list): 348 if param == "--retry": 349 if index + 1 == len(para_list): 350 para_list.append("retry_previous_command") 351 elif "-" in str(para_list[index + 1]): 352 para_list.insert(index + 1, "retry_previous_command") 353 elif param == "-->": 354 para_list[index] = "!%s" % param 355 356 @staticmethod 357 def _parse_combination_param(combination_value): 358 # sample: size:xxx1;exclude-annotation:xxx 359 parse_result = {} 360 key_value_pairs = str(combination_value).split(";") 361 for key_value_pair in key_value_pairs: 362 key, value = key_value_pair.split(":", 1) 363 if not value: 364 raise ParamError("'%s' no value" % key) 365 value_list = str(value).split(",") 366 exist_list = parse_result.get(key, []) 367 exist_list.extend(value_list) 368 parse_result[key] = exist_list 369 return parse_result 370 371 372 @classmethod 373 def _process_command_version(cls, para_list): 374 display_version_info(para_list) 375 return 376 377 378 @classmethod 379 def _process_command_help(cls, para_list): 380 if para_list[0] == ToolCommandType.TOOLCMD_KEY_HELP: 381 display_help_info(para_list) 382 else: 383 LOG.error("Wrong help command.") 384 return 385 386 @classmethod 387 def _process_command_show(cls, para_list, productform="phone"): 388 if para_list[0] == ToolCommandType.TOOLCMD_KEY_SHOW: 389 display_show_info(para_list, productform) 390 else: 391 LOG.error("Wrong show command.") 392 return 393 394 @classmethod 395 def _process_command_gen(cls, command, options): 396 if command == ToolCommandType.TOOLCMD_KEY_GEN: 397 Gen().process_command_gen(options) 398 else: 399 LOG.error("Wrong gen command.") 400 return 401 402 @classmethod 403 def _process_command_run(cls, command, options): 404 if command == ToolCommandType.TOOLCMD_KEY_RUN: 405 Run().process_command_run(command, options) 406 else: 407 LOG.error("Wrong run command.") 408 return 409 410 @classmethod 411 def _process_command_device(cls, command): 412 if command == ToolCommandType.TOOLCMD_KEY_LIST: 413 env_manager = EnvironmentManager() 414 env_manager.list_devices() 415 else: 416 LOG.error("Wrong list command.") 417 return 418 419 @classmethod 420 def _process_command_quit(cls, command): 421 if command == ToolCommandType.TOOLCMD_KEY_QUIT: 422 env_manager = EnvironmentManager() 423 env_manager.env_stop() 424 sys.exit(0) 425 else: 426 LOG.error("Wrong exit command.") 427 return 428 429 @classmethod 430 def _build_version(cls, product_form): 431 is_build_version = UserConfigManager().get_user_config_flag( 432 "build", "version") 433 434 project_root_path = sys.source_code_root_path 435 if project_root_path == "": 436 return True 437 438 build_result = True 439 if is_lite_product(product_form, sys.source_code_root_path): 440 if not is_build_version: 441 return True 442 from core.build.build_lite_manager import BuildLiteManager 443 build_lite_manager = BuildLiteManager(project_root_path) 444 build_result = build_lite_manager.build_version(product_form) 445 else: 446 from core.build.build_manager import BuildManager 447 build_manager = BuildManager() 448 if is_build_version: 449 build_result = build_manager.build_version(project_root_path, 450 product_form) 451 return build_result 452 453 454@dataclass 455class ConfigConst(object): 456 action = "action" 457 task = "task" 458 testlist = "testlist" 459 testfile = "testfile" 460 testcase = "testcase" 461 testdict = "testdict" 462 device_sn = "device_sn" 463 report_path = "report_path" 464 resource_path = "resource_path" 465 testcases_path = "testcases_path" 466 testargs = "testargs" 467 pass_through = "pass_through" 468 test_environment = "test_environment" 469 exectype = "exectype" 470 testtype = "testtype" 471 testdriver = "testdriver" 472 retry = "retry" 473 session = "session" 474 dry_run = "dry_run" 475 reboot_per_module = "reboot_per_module" 476 check_device = "check_device" 477 configfile = "config" 478 repeat = "repeat" 479 subsystems = "subsystems" 480 parts = "parts" 481 482 # Runtime Constant 483 history_report_path = "history_report_path" 484 product_info = "product_info" 485 task_state = "task_state" 486 recover_state = "recover_state" 487 need_kit_setup = "need_kit_setup" 488 task_kits = "task_kits" 489 module_kits = "module_kits" 490 spt = "spt" 491 version = "version" 492 component_mapper = "_component_mapper" 493 component_base_kit = "component_base_kit" 494 support_component = "support_component" 495 496 497############################################################################## 498############################################################################## 499