• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 os
20import re
21import subprocess
22import zipfile
23import stat
24import time
25import json
26from dataclasses import dataclass
27from tempfile import TemporaryDirectory
28from tempfile import NamedTemporaryFile
29from multiprocessing import Process
30from multiprocessing import Queue
31
32from xdevice import ITestKit
33from xdevice import platform_logger
34from xdevice import Plugin
35from xdevice import ParamError
36from xdevice import get_file_absolute_path
37from xdevice import get_config_value
38from xdevice import exec_cmd
39from xdevice import ConfigConst
40from xdevice import AppInstallError
41from xdevice import convert_serial
42from xdevice import check_path_legal
43from xdevice import modify_props
44from xdevice import get_app_name_by_tool
45from xdevice import remount
46from xdevice import disable_keyguard
47from xdevice import get_class
48from xdevice import get_cst_time
49
50from ohos.constants import CKit
51from ohos.environment.dmlib import HdcHelper
52from ohos.environment.dmlib import CollectingOutputReceiver
53
54__all__ = ["STSKit", "CommandKit", "PushKit", "PropertyCheckKit", "ShellKit", "WifiKit",
55           "ConfigKit", "AppInstallKit", "ComponentKit", "PermissionKit",
56           "junit_dex_para_parse", "SmartPerfKit"]
57
58MAX_WAIT_COUNT = 4
59TARGET_SDK_VERSION = 22
60
61LOG = platform_logger("Kit")
62
63
64@Plugin(type=Plugin.TEST_KIT, id=CKit.command)
65class CommandKit(ITestKit):
66
67    def __init__(self):
68        self.run_command = []
69        self.teardown_command = []
70        self.paths = ""
71
72    def __check_config__(self, config):
73        self.paths = get_config_value('paths', config)
74        self.teardown_command = get_config_value('teardown', config)
75        self.run_command = get_config_value('shell', config)
76
77    def __setup__(self, device, **kwargs):
78        del kwargs
79        LOG.debug("CommandKit setup, device:{}, params:{}".
80                  format(device, self.get_plugin_config().__dict__))
81        if len(self.run_command) == 0:
82            LOG.info("No setup_command to run, skipping!")
83            return
84        for command in self.run_command:
85            self._run_command(command, device)
86
87    def __teardown__(self, device):
88        LOG.debug("CommandKit teardown: device:{}, params:{}".format
89                  (device, self.get_plugin_config().__dict__))
90        if len(self.teardown_command) == 0:
91            LOG.info("No teardown_command to run, skipping!")
92            return
93        for command in self.teardown_command:
94            self._run_command(command, device)
95
96    def _run_command(self, command, device):
97
98        command_type = command.get("name").strip()
99        command_value = command.get("value")
100
101        if command_type == "reboot":
102            device.reboot()
103        elif command_type == "install":
104            LOG.debug("Trying to install package {}".format(command_value))
105            package = get_file_absolute_path(command_value, self.paths)
106            if not package or not os.path.exists(package):
107                LOG.error(
108                    "The package {} to be installed does not exist".format(
109                        package))
110
111            result = device.install_package(package)
112            if not result.startswith("Success") and "successfully" not in result:
113                raise AppInstallError(
114                    "Failed to install %s on %s. Reason:%s" %
115                    (package, device.__get_serial__(), result))
116            LOG.debug("Installed package finished {}".format(package))
117        elif command_type == "uninstall":
118            LOG.debug("Trying to uninstall package {}".format(command_value))
119            package = get_file_absolute_path(command_value, self.paths)
120            app_name = get_app_name_by_tool(package, self.paths)
121            if app_name:
122                result = device.uninstall_package(app_name)
123                if not result.startswith("Success"):
124                    LOG.error("error uninstalling package %s %s" %
125                              (device.__get_serial__(), result))
126            LOG.debug("uninstall package finished {}".format(app_name))
127        elif command_type == "pull":
128            files = command_value.split("->")
129            remote = files[0].strip()
130            local = files[1].strip()
131            device.pull_file(remote, local)
132        elif command_type == "push":
133            files = command_value.split("->")
134            src = files[0].strip()
135            dst = files[1].strip() if files[1].strip().startswith("/") else \
136                files[1].strip() + Props.dest_root
137            LOG.debug(
138                "Trying to push the file local {} to remote{}".format(
139                    src, dst))
140            real_src_path = get_file_absolute_path(src, self.paths)
141            if not real_src_path or not os.path.exists(real_src_path):
142                LOG.error(
143                    "The src file {} to be pushed does not exist".format(src))
144            device.push_file(real_src_path, dst)
145            LOG.debug("Push file finished from {} to {}".format(src, dst))
146        elif command_type == "shell":
147            device.execute_shell_command(command_value)
148
149
150@Plugin(type=Plugin.TEST_KIT, id=CKit.sts)
151class STSKit(ITestKit):
152    def __init__(self):
153        self.sts_version = ""
154        self.throw_error = ""
155
156    def __check_config__(self, config):
157        self.sts_version = get_config_value('sts-version', config)
158        self.throw_error = get_config_value('throw-error', config)
159        if len(self.sts_version) < 1:
160            raise TypeError(
161                "The sts_version: {} is invalid".format(self.sts_version))
162
163    def __setup__(self, device, **kwargs):
164        del kwargs
165        LOG.debug("STSKit setup, device:{}, params:{}".
166                  format(device, self.get_plugin_config().__dict__))
167        device_spl = device.get_property(Props.security_patch)
168        if device_spl is None or device_spl == "":
169            LOG.error("The device security {} is invalid".format(device_spl))
170            raise ParamError(
171                "The device security patch version {} is invalid".format(
172                    device_spl))
173        rex = '^[a-zA-Z\\d\\.]+_([\\d]+-[\\d]+)$'
174        match = re.match(rex, self.sts_version)
175        if match is None:
176            LOG.error("The sts version {} does match the rule".format(
177                self.sts_version))
178            raise ParamError("The sts version {} does match the rule".format(
179                self.sts_version))
180        sts_version_date_user = match.group(1).join("-01")
181        sts_version_date_kernel = match.group(1).join("-05")
182        if device_spl in [sts_version_date_user, sts_version_date_kernel]:
183            LOG.info(
184                "The device SPL version {} match the sts version {}".format(
185                    device_spl, self.sts_version))
186        else:
187            err_msg = "The device SPL version {} does not match the sts " \
188                      "version {}".format(device_spl, self.sts_version)
189            LOG.error(err_msg)
190            raise ParamError(err_msg)
191
192    def __teardown__(self, device):
193        LOG.debug("STSKit teardown: device:{}, params:{}".format
194                  (device, self.get_plugin_config().__dict__))
195
196
197@Plugin(type=Plugin.TEST_KIT, id=CKit.push)
198class PushKit(ITestKit):
199    def __init__(self):
200        self.pre_push = ""
201        self.push_list = ""
202        self.post_push = ""
203        self.is_uninstall = ""
204        self.paths = ""
205        self.pushed_file = []
206        self.abort_on_push_failure = True
207        self.teardown_push = ""
208
209    def __check_config__(self, config):
210        self.pre_push = get_config_value('pre-push', config)
211        self.push_list = get_config_value('push', config)
212        self.post_push = get_config_value('post-push', config)
213        self.teardown_push = get_config_value('teardown-push', config)
214        self.is_uninstall = get_config_value('uninstall', config,
215                                             is_list=False, default=True)
216        self.abort_on_push_failure = get_config_value(
217            'abort-on-push-failure', config, is_list=False, default=True)
218        if isinstance(self.abort_on_push_failure, str):
219            self.abort_on_push_failure = False if \
220                self.abort_on_push_failure.lower() == "false" else True
221
222        self.paths = get_config_value('paths', config)
223        self.pushed_file = []
224
225    def __setup__(self, device, **kwargs):
226        del kwargs
227        LOG.debug("PushKit setup, device:{}".format(device.device_sn))
228        for command in self.pre_push:
229            run_command(device, command)
230        dst = None
231        remount(device)
232        for push_info in self.push_list:
233            files = re.split('->|=>', push_info)
234            if len(files) != 2:
235                LOG.error("The push spec is invalid: {}".format(push_info))
236                continue
237            src = files[0].strip()
238            dst = files[1].strip() if files[1].strip().startswith("/") else \
239                files[1].strip() + Props.dest_root
240            LOG.debug(
241                "Trying to push the file local {} to remote {}".format(src,
242                                                                       dst))
243
244            try:
245                real_src_path = get_file_absolute_path(src, self.paths)
246            except ParamError as error:
247                if self.abort_on_push_failure:
248                    raise error
249                else:
250                    LOG.warning(error, error_no=error.error_no)
251                    continue
252            # hdc don't support push directory now
253            if os.path.isdir(real_src_path):
254                device.connector_command("shell mkdir {}".format(dst))
255                for root, _, files in os.walk(real_src_path):
256                    for file in files:
257                        device.push_file("{}".format(os.path.join(root, file)),
258                                         "{}".format(dst))
259                        LOG.debug(
260                            "Push file finished from {} to {}".format(
261                                os.path.join(root, file), dst))
262                        self.pushed_file.append(os.path.join(dst, file))
263            else:
264                if device.is_directory(dst):
265                    dst = os.path.join(dst, os.path.basename(real_src_path))
266                    if dst.find("\\") > -1:
267                        dst_paths = dst.split("\\")
268                        dst = "/".join(dst_paths)
269                device.push_file("{}".format(real_src_path),
270                                 "{}".format(dst))
271                LOG.debug("Push file finished from {} to {}".format(src, dst))
272                self.pushed_file.append(dst)
273        for command in self.post_push:
274            run_command(device, command)
275        return self.pushed_file, dst
276
277    def add_pushed_dir(self, src, dst):
278        for root, _, files in os.walk(src):
279            for file_path in files:
280                self.pushed_file.append(
281                    os.path.join(root, file_path).replace(src, dst))
282
283    def __teardown__(self, device):
284        LOG.debug("PushKit teardown: device:{}".format(device.device_sn))
285        for command in self.teardown_push:
286            run_command(device, command)
287        if self.is_uninstall:
288            remount(device)
289            for file_name in self.pushed_file:
290                LOG.debug("Trying to remove file {}".format(file_name))
291                file_name = file_name.replace("\\", "/")
292
293                for _ in range(
294                        Props.trying_remove_maximum_times):
295                    collect_receiver = CollectingOutputReceiver()
296                    file_name = check_path_legal(file_name)
297                    device.execute_shell_command("rm -rf {}".format(
298                        file_name), receiver=collect_receiver,
299                        output_flag=False)
300                    if not collect_receiver.output:
301                        LOG.debug(
302                            "Removed file {} successfully".format(file_name))
303                        break
304                    else:
305                        LOG.error("Removed file {} successfully".
306                                  format(collect_receiver.output))
307                else:
308                    LOG.error("Failed to remove file {}".format(file_name))
309
310    def __add_pushed_file__(self, device, src, dst):
311        if device.is_directory(dst):
312            dst = dst + os.path.basename(src) if dst.endswith(
313                "/") else dst + "/" + os.path.basename(src)
314        self.pushed_file.append(dst)
315
316    def __add_dir_pushed_files__(self, device, src, dst):
317        if device.file_exist(device, dst):
318            for _, dirs, files in os.walk(src):
319                for file_path in files:
320                    if dst.endswith("/"):
321                        dst = "%s%s" % (dst, os.path.basename(file_path))
322                    else:
323                        dst = "%s/%s" % (dst, os.path.basename(file_path))
324                    self.pushed_file.append(dst)
325                for dir_name in dirs:
326                    self.__add_dir_pushed_files__(device, dir_name, dst)
327        else:
328            self.pushed_file.append(dst)
329
330
331@Plugin(type=Plugin.TEST_KIT, id=CKit.propertycheck)
332class PropertyCheckKit(ITestKit):
333    def __init__(self):
334        self.prop_name = ""
335        self.expected_value = ""
336        self.throw_error = ""
337
338    def __check_config__(self, config):
339        self.prop_name = get_config_value('property-name', config,
340                                          is_list=False)
341        self.expected_value = get_config_value('expected-value', config,
342                                               is_list=False)
343        self.throw_error = get_config_value('throw-error', config,
344                                            is_list=False)
345
346    def __setup__(self, device, **kwargs):
347        del kwargs
348        LOG.debug("PropertyCheckKit setup, device:{}".format(device.device_sn))
349        if not self.prop_name:
350            LOG.warning("The option of property-name not setting")
351            return
352        prop_value = device.get_property(self.prop_name)
353        if not prop_value:
354            LOG.warning(
355                "The property {} not found on device, cannot check the value".
356                    format(self.prop_name))
357            return
358
359        if prop_value != self.expected_value:
360            msg = "The value found for property {} is {}, not same with the " \
361                  "expected {}".format(self.prop_name, prop_value,
362                                       self.expected_value)
363            LOG.warning(msg)
364            if self.throw_error and self.throw_error.lower() == 'true':
365                raise Exception(msg)
366
367    @classmethod
368    def __teardown__(cls, device):
369        LOG.debug("PropertyCheckKit teardown: device:{}".format(
370            device.device_sn))
371
372
373@Plugin(type=Plugin.TEST_KIT, id=CKit.shell)
374class ShellKit(ITestKit):
375    def __init__(self):
376        self.command_list = []
377        self.tear_down_command = []
378        self.paths = None
379
380    def __check_config__(self, config):
381        self.command_list = get_config_value('run-command', config)
382        self.tear_down_command = get_config_value('teardown-command', config)
383        self.tear_down_local_command = get_config_value('teardown-localcommand', config)
384        self.paths = get_config_value('paths', config)
385
386    def __setup__(self, device, **kwargs):
387        del kwargs
388        LOG.debug("ShellKit setup, device:{}".format(device.device_sn))
389        if len(self.command_list) == 0:
390            LOG.info("No setup_command to run, skipping!")
391            return
392        for command in self.command_list:
393            run_command(device, command)
394
395    def __teardown__(self, device):
396        LOG.debug("ShellKit teardown: device:{}".format(device.device_sn))
397        if len(self.tear_down_command) == 0:
398            LOG.info("No teardown_command to run, skipping!")
399        else:
400            for command in self.tear_down_command:
401                run_command(device, command)
402        if len(self.tear_down_local_command) == 0:
403            LOG.info("No teardown-localcommand to run, skipping!")
404        else:
405            for command in self.tear_down_local_command:
406                subprocess.run(command)
407
408
409@Plugin(type=Plugin.TEST_KIT, id=CKit.wifi)
410class WifiKit(ITestKit):
411    def __init__(self):
412        self.certfilename = ""
413        self.certpassword = ""
414        self.wifiname = ""
415        self.paths = ""
416
417    def __check_config__(self, config):
418        self.certfilename = get_config_value(
419            'certfilename', config, False,
420            default=None)
421        self.certpassword = get_config_value(
422            'certpassword', config, False,
423            default=None)
424        self.wifiname = get_config_value(
425            'wifiname', config, False,
426            default=None)
427        self.paths = get_config_value('paths', config)
428
429    def __setup__(self, device, **kwargs):
430        request = kwargs.get("request", None)
431        if not request:
432            LOG.error("WifiKit need input request")
433            return
434        testargs = request.get("testargs", {})
435        self.certfilename = \
436            testargs.pop("certfilename", [self.certfilename])[0]
437        self.wifiname = \
438            testargs.pop("wifiname", [self.wifiname])[0]
439        self.certpassword = \
440            testargs.pop("certpassword", [self.certpassword])[0]
441        del kwargs
442        LOG.debug("WifiKit setup, device:{}".format(device.device_sn))
443
444        try:
445            wifi_app_path = get_file_absolute_path(
446                Props.Paths.service_wifi_app_path, self.paths)
447        except ParamError as _:
448            wifi_app_path = None
449
450        if wifi_app_path is None:
451            LOG.error("The resource wifi app file does not exist!")
452            return
453
454        try:
455            pfx_path = get_file_absolute_path(
456                "tools/wifi/%s" % self.certfilename
457            ) if self.certfilename else None
458        except ParamError as _:
459            pfx_path = None
460
461        if pfx_path is None:
462            LOG.error("The resource wifi pfx file does not exist!")
463            return
464        pfx_dest_path = \
465            "/storage/emulated/0/%s" % self.certfilename
466        if self.wifiname is None:
467            LOG.error("The wifi name is not given!")
468            return
469        if self.certpassword is None:
470            LOG.error("The wifi password is not given!")
471            return
472
473        device.install_package(wifi_app_path, command="-r")
474        device.push_file(pfx_path, pfx_dest_path)
475        device.execute_shell_command("svc wifi enable")
476        for _ in range(Props.maximum_connect_wifi_times):
477            connect_wifi_cmd = Props.connect_wifi_cmd % (
478                pfx_dest_path,
479                self.certpassword,
480                self.wifiname
481            )
482            if device.execute_shell_command(connect_wifi_cmd):
483                LOG.info("Connect wifi successfully")
484                break
485        else:
486            LOG.error("Connect wifi failed")
487
488    @classmethod
489    def __teardown__(cls, device):
490        LOG.debug("WifiKit teardown: device:{}".format(device.device_sn))
491        LOG.info("Disconnect wifi")
492        device.execute_shell_command("svc wifi disable")
493
494
495@dataclass
496class Props:
497    @dataclass
498    class Paths:
499        system_build_prop_path = "/%s/%s" % ("system", "build.prop")
500        service_wifi_app_path = "tools/wifi/%s" % "Service-wifi.app"
501
502    dest_root = "/%s/%s/" % ("data", "data")
503    mnt_external_storage = "EXTERNAL_STORAGE"
504    trying_remove_maximum_times = 3
505    maximum_connect_wifi_times = 3
506    connect_wifi_cmd = "am instrument -e request \"{module:Wifi, " \
507                       "method:connectWifiByCertificate, params:{'certPath':" \
508                       "'%s'," \
509                       "'certPassword':'%s'," \
510                       "'wifiName':'%s'}}\"  " \
511                       "-w com.xdeviceservice.service/.MainInstrumentation"
512    security_patch = "ro.build.version.security_patch"
513
514
515@Plugin(type=Plugin.TEST_KIT, id=CKit.config)
516class ConfigKit(ITestKit):
517    def __init__(self):
518        self.is_connect_wifi = ""
519        self.is_disconnect_wifi = ""
520        self.wifi_kit = WifiKit()
521        self.min_external_store_space = ""
522        self.is_disable_dialing = ""
523        self.is_test_harness = ""
524        self.is_audio_silent = ""
525        self.is_disable_dalvik_verifier = ""
526        self.build_prop_list = ""
527        self.is_enable_hook = ""
528        self.cust_prop_file = ""
529        self.is_prop_changed = False
530        self.local_system_prop_file = ""
531        self.cust_props = ""
532        self.is_reboot_delay = ""
533        self.is_remount = ""
534        self.local_cust_prop_file = {}
535
536    def __check_config__(self, config):
537        self.is_connect_wifi = get_config_value('connect-wifi', config,
538                                                is_list=False, default=False)
539        self.is_disconnect_wifi = get_config_value(
540            'disconnect-wifi-after-test', config, is_list=False, default=True)
541        self.wifi_kit = WifiKit()
542        self.min_external_store_space = get_config_value(
543            'min-external-store-space', config)
544        self.is_disable_dialing = get_config_value('disable-dialing', config)
545        self.is_test_harness = get_config_value('set-test-harness', config)
546        self.is_audio_silent = get_config_value('audio-silent', config)
547        self.is_disable_dalvik_verifier = get_config_value(
548            'disable-dalvik-verifier', config)
549        self.build_prop_list = get_config_value('build-prop', config)
550        self.cust_prop_file = get_config_value('cust-prop-file', config)
551        self.cust_props = get_config_value('cust-prop', config)
552        self.is_enable_hook = get_config_value('enable-hook', config)
553        self.is_reboot_delay = get_config_value('reboot-delay', config)
554        self.is_remount = get_config_value('remount', config, default=True)
555        self.local_system_prop_file = NamedTemporaryFile(prefix='build',
556                                                         suffix='.prop',
557                                                         delete=False).name
558
559    def __setup__(self, device, **kwargs):
560        del kwargs
561        LOG.debug("ConfigKit setup, device:{}".format(device.device_sn))
562        if self.is_remount:
563            remount(device)
564        self.is_prop_changed = self.modify_system_prop(device)
565        self.is_prop_changed = self.modify_cust_prop(
566            device) or self.is_prop_changed
567
568        keep_screen_on(device)
569        if self.is_enable_hook:
570            pass
571        if self.is_prop_changed:
572            device.reboot()
573
574    def __teardown__(self, device):
575        LOG.debug("ConfigKit teardown: device:{}".format(device.device_sn))
576        if self.is_remount:
577            remount(device)
578        if self.is_connect_wifi and self.is_disconnect_wifi:
579            self.wifi_kit.__teardown__(device)
580        if self.is_prop_changed:
581            device.push_file(self.local_system_prop_file,
582                             Props.Paths.system_build_prop_path)
583            device.execute_shell_command(
584                " ".join(["chmod 644", Props.Paths.system_build_prop_path]))
585            os.remove(self.local_system_prop_file)
586
587            for target_file, temp_file in self.local_cust_prop_file.items():
588                device.push_file(temp_file, target_file)
589                device.execute_shell_command(
590                    " ".join(["chmod 644", target_file]))
591                os.remove(temp_file)
592
593    def modify_system_prop(self, device):
594        prop_changed = False
595        new_props = {}
596        if self.is_disable_dialing:
597            new_props['ro.telephony.disable-call'] = 'true'
598        if self.is_test_harness:
599            new_props['ro.monkey'] = '1'
600            new_props['ro.test_harness'] = '1'
601        if self.is_audio_silent:
602            new_props['ro.audio.silent'] = '1'
603        if self.is_disable_dalvik_verifier:
604            new_props['dalvik.vm.dexopt-flags'] = 'v=n'
605        for prop in self.build_prop_list:
606            if prop is None or prop.find("=") < 0 or len(prop.split("=")) != 2:
607                LOG.warning("The build prop:{} not match the format "
608                            "'key=value'".format(prop))
609                continue
610            new_props[prop.split("=")[0]] = prop.split("=")[1]
611        if new_props:
612            prop_changed = modify_props(device, self.local_system_prop_file,
613                                        Props.Paths.system_build_prop_path,
614                                        new_props)
615        return prop_changed
616
617    def modify_cust_prop(self, device):
618        prop_changed = False
619        cust_files = {}
620        new_props = {}
621        for cust_prop_file in self.cust_prop_file:
622            # the correct format should be "CustName:/cust/prop/absolutepath"
623            if len(cust_prop_file.split(":")) != 2:
624                LOG.error(
625                    "The value %s of option cust-prop-file is incorrect" %
626                    cust_prop_file)
627                continue
628            cust_files[cust_prop_file.split(":")[0]] = \
629                cust_prop_file.split(":")[1]
630        for prop in self.cust_props:
631            # the correct format should be "CustName:key=value"
632            prop_infos = re.split(r'[:|=]', prop)
633            if len(prop_infos) != 3:
634                LOG.error(
635                    "The value {} of option cust-prop is incorrect".format(
636                        prop))
637                continue
638            file_name, key, value = prop_infos
639            if file_name not in cust_files:
640                LOG.error(
641                    "The custName {} must be in cust-prop-file option".format(
642                        file_name))
643                continue
644            props = new_props.setdefault(file_name, {})
645            props[key] = value
646
647        for name in new_props.keys():
648            cust_file = cust_files.get(name)
649            temp_cust_file = NamedTemporaryFile(prefix='cust', suffix='.prop',
650                                                delete=False).name
651            self.local_cust_prop_file[cust_file] = temp_cust_file
652            try:
653                prop_changed = modify_props(device, temp_cust_file, cust_file,
654                                            new_props[name]) or prop_changed
655            except KeyError:
656                LOG.error("Get props error.")
657                continue
658
659        return prop_changed
660
661
662@Plugin(type=Plugin.TEST_KIT, id=CKit.app_install)
663class AppInstallKit(ITestKit):
664    def __init__(self):
665        self.app_list = ""
666        self.app_list_name = ""
667        self.is_clean = ""
668        self.alt_dir = ""
669        self.ex_args = ""
670        self.installed_app = set()
671        self.paths = ""
672        self.is_pri_app = ""
673        self.pushed_hap_file = set()
674        self.env_index_list = None
675
676    def __check_config__(self, options):
677        self.app_list = get_config_value('test-file-name', options)
678        self.app_list_name = get_config_value('test-file-packName', options)
679        self.is_clean = get_config_value('cleanup-apps', options, False)
680        self.alt_dir = get_config_value('alt-dir', options, False)
681        if self.alt_dir and self.alt_dir.startswith("resource/"):
682            self.alt_dir = self.alt_dir[len("resource/"):]
683        self.ex_args = get_config_value('install-arg', options, False)
684        self.installed_app = set()
685        self.paths = get_config_value('paths', options)
686        self.is_pri_app = get_config_value('install-as-privapp', options,
687                                           False, default=False)
688        self.env_index_list = get_config_value('env-index', options)
689
690    def __setup__(self, device, **kwargs):
691        del kwargs
692        LOG.debug("AppInstallKit setup, device:{}".format(device.device_sn))
693        if len(self.app_list) == 0:
694            LOG.info("No app to install, skipping!")
695            return
696        for app in self.app_list:
697            if self.alt_dir:
698                app_file = get_file_absolute_path(app, self.paths,
699                                                  self.alt_dir)
700            else:
701                app_file = get_file_absolute_path(app, self.paths)
702            if app_file is None:
703                LOG.error("The app file {} does not exist".format(app))
704                continue
705            device.connector_command("install \"{}\"".format(app_file))
706            self.installed_app.add(app_file)
707
708    def __teardown__(self, device):
709        LOG.debug("AppInstallKit teardown: device:{}".format(device.device_sn))
710        if self.is_clean and str(self.is_clean).lower() == "true":
711            if self.app_list_name and len(self.app_list_name) > 0:
712                for app_name in self.app_list_name:
713                    result = device.uninstall_package(app_name)
714                    if result and (result.startswith("Success") or "successfully" in result):
715                        LOG.debug("uninstalling package Success. result is %s" %
716                                  result)
717                    else:
718                        LOG.warning("Error uninstalling package %s %s" %
719                                    (device.__get_serial__(), result))
720            else:
721                for app in self.installed_app:
722                    app_name = get_app_name(app)
723                    if app_name:
724                        result = device.uninstall_package(app_name)
725                        if result and (result.startswith("Success") or "successfully" in result):
726                            LOG.debug("uninstalling package Success. result is %s" %
727                                      result)
728                        else:
729                            LOG.warning("Error uninstalling package %s %s" %
730                                        (device.__get_serial__(), result))
731                    else:
732                        LOG.warning("Can't find app name for %s" % app)
733        if self.is_pri_app:
734            remount(device)
735        for pushed_file in self.pushed_hap_file:
736            device.execute_shell_command("rm -r %s" % pushed_file)
737
738    def install_hap(self, device, hap_file):
739        if self.is_pri_app:
740            LOG.info("Install hap as privileged app {}".format(hap_file))
741            hap_name = os.path.basename(hap_file).replace(".hap", "")
742            try:
743                with TemporaryDirectory(prefix=hap_name) as temp_dir:
744                    zif_file = zipfile.ZipFile(hap_file)
745                    zif_file.extractall(path=temp_dir)
746                    entry_app = os.path.join(temp_dir, "Entry.app")
747                    push_dest_dir = os.path.join("/system/priv-app/", hap_name)
748                    device.execute_shell_command("rm -rf " + push_dest_dir,
749                                                 output_flag=False)
750                    device.push_file(entry_app, os.path.join(
751                        push_dest_dir + os.path.basename(entry_app)))
752                    device.push_file(hap_file, os.path.join(
753                        push_dest_dir + os.path.basename(hap_file)))
754                    self.pushed_hap_file.add(os.path.join(
755                        push_dest_dir + os.path.basename(hap_file)))
756                    device.reboot()
757            except RuntimeError as exception:
758                msg = "Install hap app failed withe error {}".format(exception)
759                LOG.error(msg)
760                raise Exception(msg)
761            except Exception as exception:
762                msg = "Install hap app failed withe exception {}".format(
763                    exception)
764                LOG.error(msg)
765                raise Exception(msg)
766            finally:
767                zif_file.close()
768        else:
769            push_dest = "/%s" % "sdcard"
770            push_dest = "%s/%s" % (push_dest, os.path.basename(hap_file))
771            device.push_file(hap_file, push_dest)
772            self.pushed_hap_file.add(push_dest)
773            output = device.execute_shell_command("bm install -p " + push_dest)
774            if not output.startswith("Success") and not "successfully" in output:
775                output = output.strip()
776                if "[ERROR_GET_BUNDLE_INSTALLER_FAILED]" not in output.upper():
777                    raise AppInstallError(
778                        "Failed to install %s on %s. Reason:%s" %
779                        (push_dest, device.__get_serial__(), output))
780                else:
781                    LOG.info("'[ERROR_GET_BUNDLE_INSTALLER_FAILED]' occurs, "
782                             "retry install hap")
783                    exec_out = self.retry_install_hap(
784                        device, "bm install -p " + push_dest)
785                    if not exec_out.startswith("Success") and not "successfully" in output:
786                        raise AppInstallError(
787                            "Retry failed,Can't install %s on %s. Reason:%s" %
788                            (push_dest, device.__get_serial__(), exec_out))
789            else:
790                LOG.debug("Install %s success" % push_dest)
791
792    @classmethod
793    def retry_install_hap(cls, device, command):
794        real_command = [HdcHelper.CONNECTOR_NAME, "-t", str(device.device_sn), "-s",
795                        "tcp:%s:%s" % (str(device.host), str(device.port)),
796                        "shell", command]
797        message = "%s execute command: %s" % \
798                  (convert_serial(device.device_sn), " ".join(real_command))
799        LOG.info(message)
800        exec_out = ""
801        for wait_count in range(1, MAX_WAIT_COUNT):
802            LOG.debug("Retry times:%s, wait %ss" %
803                      (wait_count, (wait_count * 10)))
804            time.sleep(wait_count * 10)
805            exec_out = exec_cmd(real_command)
806            if exec_out and exec_out.startswith("Success"):
807                break
808        if not exec_out:
809            exec_out = "System is not in %s" % ["Windows", "Linux", "Darwin"]
810        LOG.info("Retry install hap result is: [%s]" % exec_out.strip())
811        return exec_out
812
813
814@Plugin(type=Plugin.TEST_KIT, id=CKit.component)
815class ComponentKit(ITestKit):
816
817    def __init__(self):
818        self._white_list_file = ""
819        self._white_list = ""
820        self._cap_file = ""
821        self.paths = ""
822        self.cache_subsystem = set()
823        self.cache_part = set()
824
825    def __check_config__(self, config):
826        self._white_list_file =\
827            get_config_value('white-list', config, is_list=False)
828        self._cap_file = get_config_value('cap-file', config, is_list=False)
829        self.paths = get_config_value('paths', config)
830
831    def __setup__(self, device, **kwargs):
832        if hasattr(device, ConfigConst.support_component):
833            return
834        if device.label in ["phone", "watch", "car", "tv", "tablet", "ivi"]:
835            command = "cat %s" % self._cap_file
836            result = device.execute_shell_command(command)
837            part_set = set()
838            subsystem_set = set()
839            if "{" in result:
840                for item in json.loads(result).get("components", []):
841                    part_set.add(item.get("component", ""))
842            subsystems, parts = self.get_white_list()
843            part_set.update(parts)
844            subsystem_set.update(subsystems)
845            setattr(device, ConfigConst.support_component,
846                    (subsystem_set, part_set))
847            self.cache_subsystem.update(subsystem_set)
848            self.cache_part.update(part_set)
849
850    def get_cache(self):
851        return self.cache_subsystem, self.cache_part
852
853    def get_white_list(self):
854        if not self._white_list and self._white_list_file:
855            self._white_list = self._parse_white_list()
856        return self._white_list
857
858    def _parse_white_list(self):
859        subsystem = set()
860        part = set()
861        white_json_file = os.path.normpath(self._white_list_file)
862        if not os.path.isabs(white_json_file):
863            white_json_file = \
864                get_file_absolute_path(white_json_file, self.paths)
865        if os.path.isfile(white_json_file):
866            subsystem_list = list()
867            flags = os.O_RDONLY
868            modes = stat.S_IWUSR | stat.S_IRUSR
869            with os.fdopen(os.open(white_json_file, flags, modes),
870                           "r") as file_content:
871                json_result = json.load(file_content)
872                if "subsystems" in json_result.keys():
873                    subsystem_list.extend(json_result["subsystems"])
874                for subsystem_item_list in subsystem_list:
875                    for key, value in subsystem_item_list.items():
876                        if key == "subsystem":
877                            subsystem.add(value)
878                        elif key == "components":
879                            for component_item in value:
880                                if "component" in component_item.keys():
881                                    part.add(
882                                        component_item["component"])
883
884        return subsystem, part
885
886    def __teardown__(self, device):
887        if hasattr(device, ConfigConst.support_component):
888            setattr(device, ConfigConst.support_component, None)
889        self._white_list_file = ""
890        self._white_list = ""
891        self._cap_file = ""
892        self.cache_subsystem.clear()
893        self.cache_part.clear()
894        self.cache_device.clear()
895
896
897@Plugin(type=Plugin.TEST_KIT , id=CKit.permission)
898class PermissionKit(ITestKit):
899    def __init__(self):
900        self.package_name_list = None
901        self.permission_list = None
902
903    def __check_config__(self, config):
904        self.package_name_list = \
905            get_config_value('package-names', config, True, [])
906        self.permission_list = \
907            get_config_value('permissions', config, True, [])
908
909    def __setup__(self, device, **kwargs):
910        if not self.package_name_list or not self.permission_list:
911            LOG.warning("Please check parameters of permission kit in json")
912            return
913        for index in range(len(self.package_name_list)):
914            cur_name = self.package_name_list[index]
915            token_id = self._get_token_id(device, cur_name)
916            if not token_id:
917                LOG.warning("Not found accessTokenId of '{}'".format(cur_name))
918                continue
919            for permission in self.permission_list[index]:
920                command = "atm perm -g -i {} -p {}".format(token_id,
921                                                           permission)
922                out = device.execute_shell_command(command)
923                LOG.debug("Set permission result: {}".format(out))
924
925    def __teardown__(self, device):
926        pass
927
928    def _get_token_id(self, device, pkg_name):
929        # shell bm dump -n
930        dump_command = "bm dump -n {}".format(pkg_name)
931        content = device.execute_shell_command(dump_command)
932        if not content or not str(content).startswith(pkg_name):
933            return ""
934        content = content[len(pkg_name) + len(":\n"):]
935        dump_dict = json.loads(content)
936        if "userInfo" not in dump_dict.keys():
937            return ""
938        user_info_dict = dump_dict["userInfo"][0]
939        if "accessTokenId" not in user_info_dict.keys():
940            return ""
941        else:
942            return user_info_dict["accessTokenId"]
943
944
945def keep_screen_on(device):
946    device.execute_shell_command("svc power stayon true")
947
948
949def run_command(device, command):
950    LOG.debug("The command:{} is running".format(command))
951    stdout = None
952    if command.strip() == "remount":
953        remount(device)
954    elif command.strip() == "reboot":
955        device.reboot()
956    elif command.strip() == "reboot-delay":
957        pass
958    elif command.strip().endswith("&"):
959        device.execute_shell_in_daemon(command.strip())
960    else:
961        stdout = device.execute_shell_command(command)
962    LOG.debug("Run command result: %s" % (stdout if stdout else ""))
963    return stdout
964
965
966def junit_dex_para_parse(device, junit_paras, prefix_char="--"):
967    """To parse the para of junit
968    Args:
969        device: the device running
970        junit_paras: the para dict of junit
971        prefix_char: the prefix char of parsed cmd
972    Returns:
973        the new para using in a command like -e testFile xxx
974        -e coverage true...
975    """
976    ret_str = []
977    path = "/%s/%s/%s" % ("data", "local", "ajur")
978    include_file = "%s/%s" % (path, "includes.txt")
979    exclude_file = "%s/%s" % (path, "excludes.txt")
980
981    if not isinstance(junit_paras, dict):
982        LOG.warning("The para of junit is not the dict format as required")
983        return ""
984    # Disable screen keyguard
985    disable_key_guard = junit_paras.get('disable-keyguard')
986    if not disable_key_guard or disable_key_guard[0].lower() != 'false':
987        disable_keyguard(device)
988
989    for para_name in junit_paras.keys():
990        path = "/%s/%s/%s/" % ("data", "local", "ajur")
991        if para_name.strip() == 'test-file-include-filter':
992            for file_name in junit_paras[para_name]:
993                device.push_file(file_name, include_file)
994                device.execute_shell_command(
995                    'chown -R shell:shell %s' % path)
996            ret_str.append(prefix_char + " ".join(['testFile', include_file]))
997        elif para_name.strip() == "test-file-exclude-filter":
998            for file_name in junit_paras[para_name]:
999                device.push_file(file_name, include_file)
1000                device.execute_shell_command(
1001                    'chown -R shell:shell %s' % path)
1002            ret_str.append(prefix_char + " ".join(['notTestFile',
1003                                                   exclude_file]))
1004        elif para_name.strip() == "test" or para_name.strip() == "class":
1005            result = get_class(junit_paras, prefix_char, para_name.strip())
1006            ret_str.append(result)
1007        elif para_name.strip() == "include-annotation":
1008            ret_str.append(prefix_char + " ".join(
1009                ['annotation', ",".join(junit_paras[para_name])]))
1010        elif para_name.strip() == "exclude-annotation":
1011            ret_str.append(prefix_char + " ".join(
1012                ['notAnnotation', ",".join(junit_paras[para_name])]))
1013        else:
1014            ret_str.append(prefix_char + " ".join(
1015                [para_name, ",".join(junit_paras[para_name])]))
1016
1017    return " ".join(ret_str)
1018
1019
1020def get_app_name(hap_app):
1021    hap_name = os.path.basename(hap_app).replace(".hap", "")
1022    app_name = ""
1023    hap_file_info = None
1024    config_json_file = ""
1025    try:
1026        hap_file_info = zipfile.ZipFile(hap_app)
1027        name_list = ["module.json", "config.json"]
1028        for _, name in enumerate(hap_file_info.namelist()):
1029            if name in name_list:
1030                config_json_file = name
1031                break
1032        config_info = hap_file_info.read(config_json_file).decode('utf-8')
1033        attrs = json.loads(config_info)
1034        if "app" in attrs.keys() and \
1035                "bundleName" in attrs.get("app", dict()).keys():
1036            app_name = attrs["app"]["bundleName"]
1037            LOG.info("Obtain the app name {} from json "
1038                     "successfully".format(app_name))
1039        else:
1040            LOG.debug("Tip: 'app' or 'bundleName' not "
1041                      "in %s.hap/config.json" % hap_name)
1042    except Exception as e:
1043        LOG.error("get app name from hap error: {}".format(e))
1044    finally:
1045        if hap_file_info:
1046            hap_file_info.close()
1047    return app_name
1048
1049
1050@Plugin(type=Plugin.TEST_KIT, id=CKit.smartperf)
1051class SmartPerfKit(ITestKit):
1052    def __init__(self):
1053        self._run_command = ["SP_daemon", "-PKG"]
1054        self._process = None
1055        self._pattern = "order:\\d+ (.+)=(.+)"
1056        self._param_key = ["cpu", "gpu", "ddr", "fps", "pnow", "ram", "temp"]
1057        self.target_name = ""
1058        self._msg_queue = None
1059
1060    def __check_config__(self, config):
1061        self._run_command.append(self.target_name)
1062        if isinstance(config, str):
1063            key_value_pairs = str(config).strip(";")
1064            for key_value_pair in key_value_pairs:
1065                key, value = key_value_pair.split(":", 1)
1066                if key == "num":
1067                    self._run_command.append("-N")
1068                    self._run_command.append(value)
1069                else:
1070                    if key in self._param_key and value == "true":
1071                        self._run_command.append("-" + key[:1])
1072
1073    def _execute(self, msg_queue, cmd_list, xls_file, proc_name):
1074        data = []
1075        while msg_queue.empty():
1076            process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
1077                                       stderr=subprocess.PIPE,
1078                                       shell=False)
1079            rev = process.stdout.read()
1080            data.append((get_cst_time().strftime("%Y-%m-%d-%H-%M-%S"), rev))
1081        self.write_to_file(data, proc_name, xls_file)
1082
1083    def write_to_file(self, data, proc_name, xls_file):
1084        from openpyxl import Workbook
1085        from openpyxl import styles
1086        book = Workbook()
1087        sheet = book.active
1088        sheet.row_dimensions[1].height = 30
1089        sheet.column_dimensions["A"].width = 30
1090        sheet.sheet_properties.tabColor = "1072BA"
1091        alignment = styles.Alignment(horizontal='center', vertical='center')
1092        font = styles.Font(size=15, color="000000", bold=True,
1093                           italic=False, strike=None, underline=None)
1094        names = ["time", "PKG"]
1095        start = True
1096        for _time, content in data:
1097            cur = [_time, proc_name]
1098            rev_list = str(content, "utf-8").split("\n")
1099            if start:
1100                start = False
1101                for rev in rev_list:
1102                    result = re.match(self._pattern, rev)
1103                    if result and result.group(1):
1104                        names.append(result.group(1))
1105                        cur.append(result.group(2))
1106                sheet.append(names)
1107                sheet.append(cur)
1108                for pos in range(1, len(names) + 1):
1109                    cur_cell = sheet.cell(1, pos)
1110                    sheet.column_dimensions[cur_cell.colum_letter].width = 20
1111                    cur_cell.alignment = alignment
1112                    cur_cell.font = font
1113            else:
1114                for rev in rev_list:
1115                    result = re.match(self._pattern, rev)
1116                    if result and result.group(1):
1117                        cur.append(result.group(2))
1118                sheet.append(cur)
1119        book.save(xls_file)
1120
1121    def __setup__(self, device, **kwargs):
1122        request = kwargs.get("request")
1123        folder = os.path.join(request.get_config().report_path, "smart_perf")
1124        if not os.path.exists(folder):
1125            os.mkdir(folder)
1126        file = os.path.join(folder, "{}.xlsx".format(request.get_module_name()))
1127        if device.host != "127.0.0.1":
1128            cmd_list = [HdcHelper.CONNECTOR_NAME, "-s", "{}:{}".format(
1129                device.host, device.port), "-t", device.device_sn, "shell"]
1130        else:
1131            cmd_list = [HdcHelper.CONNECTOR_NAME, "-t", device.device_sn, "shell"]
1132
1133        cmd_list.extend(self._run_command)
1134        LOG.debug("Smart perf command:{}".format(" ".join(cmd_list)))
1135        self._msg_queue = Queue()
1136        self._process = Process(target=self._execute, args=(
1137            self._msg_queue, cmd_list, file, self.target_name))
1138        self._process.start()
1139
1140    def __teardown__(self, device):
1141        if self._process:
1142            if self._msg_queue:
1143                self._msg_queue.put("")
1144                self._msg_queue = None
1145            else:
1146                self._process.terminate()
1147            self._process = None
1148