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