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