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