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