• 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#
18import json
19import re
20import time
21import os
22import threading
23import platform
24import subprocess
25import sys
26
27from xdevice import DeviceOsType
28from xdevice import DeviceProperties
29from xdevice import FilePermission
30from xdevice import ParamError
31from xdevice import ProductForm
32from xdevice import ReportException
33from xdevice import IDevice
34from xdevice import platform_logger
35from xdevice import Plugin
36from xdevice import exec_cmd
37from xdevice import ConfigConst
38from xdevice import HdcError
39from xdevice import DeviceAllocationState
40from xdevice import DeviceConnectorType
41from xdevice import TestDeviceState
42from xdevice import convert_serial
43from xdevice import check_path_legal
44from xdevice import start_standing_subprocess
45from xdevice import stop_standing_subprocess
46from xdevice import get_cst_time
47from xdevice import get_file_absolute_path
48from xdevice import Platform
49from xdevice import AppInstallError
50from xdevice import RpcNotRunningError
51from xdevice import Variables
52
53from ohos.constants import Constant
54from ohos.environment.dmlib import HdcHelper
55from ohos.environment.dmlib import CollectingOutputReceiver
56from ohos.utils import parse_strings_key_value
57
58__all__ = ["Device"]
59TIMEOUT = 300 * 1000
60RETRY_ATTEMPTS = 2
61DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000
62BACKGROUND_TIME = 2 * 60 * 1000
63LOG = platform_logger("Device")
64DEVICETEST_HAP_PACKAGE_NAME = "com.ohos.devicetest"
65DEVICE_TEMP_PATH = "/data/local/tmp"
66QUERY_DEVICE_PROP_BIN = "testcases/queryStandard"
67UITEST_NAME = "uitest"
68UITEST_SINGLENESS = "singleness"
69UITEST_PATH = "/system/bin/uitest"
70UITEST_SHMF = "/data/app/el2/100/base/{}/cache/shmf".format(DEVICETEST_HAP_PACKAGE_NAME)
71UITEST_COMMAND = "{} start-daemon 0123456789 &".format(UITEST_PATH)
72NATIVE_CRASH_PATH = "/data/log/faultlog/temp"
73JS_CRASH_PATH = "/data/log/faultlog/faultlogger"
74ROOT_PATH = "/data/log/faultlog"
75LOGLEVEL = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"]
76
77
78def perform_device_action(func):
79    def callback_to_outer(device, msg):
80        # callback to decc ui
81        if getattr(device, "callback_method", None):
82            device.callback_method(msg)
83
84    def device_action(self, *args, **kwargs):
85        if not self.get_recover_state():
86            LOG.debug("Device {} {} is false".format(self.device_sn,
87                                                     ConfigConst.recover_state))
88            return None
89        # avoid infinite recursion, such as device reboot
90        abort_on_exception = bool(kwargs.get("abort_on_exception", False))
91        if abort_on_exception:
92            result = func(self, *args, **kwargs)
93            return result
94
95        tmp = int(kwargs.get("retry", RETRY_ATTEMPTS))
96        retry = tmp + 1 if tmp > 0 else 1
97        exception = None
98        for _ in range(retry):
99            try:
100                result = func(self, *args, **kwargs)
101                return result
102            except ReportException as error:
103                self.log.exception("Generate report error!", exc_info=False)
104                exception = error
105            except (ConnectionResetError,  # pylint:disable=undefined-variable
106                    ConnectionRefusedError,  # pylint:disable=undefined-variable
107                    ConnectionAbortedError) as error:  # pylint:disable=undefined-variable
108                self.log.error("error type: {}, error: {}".format
109                               (error.__class__.__name__, error))
110                # check hdc if is running
111                if not HdcHelper.check_if_hdc_running():
112                    LOG.debug("{} not running, set device {} {} false".format(
113                        HdcHelper.CONNECTOR_NAME, self.device_sn, ConfigConst.recover_state))
114                    self.set_recover_state(False)
115                    callback_to_outer(self, "recover failed")
116                    raise error
117                callback_to_outer(self, "error:{}, prepare to recover".format(error))
118                if not self.recover_device():
119                    LOG.debug("Set device {} {} false".format(
120                        self.device_sn, ConfigConst.recover_state))
121                    self.set_recover_state(False)
122                    callback_to_outer(self, "recover failed")
123                    raise error
124                exception = error
125                callback_to_outer(self, "recover success")
126            except HdcError as error:
127                self.log.error("error type: {}, error: {}".format(error.__class__.__name__, error))
128                callback_to_outer(self, "error:{}, prepare to recover".format(error))
129                if not self.recover_device():
130                    LOG.debug("Set device {} {} false".format(
131                        self.device_sn, ConfigConst.recover_state))
132                    self.set_recover_state(False)
133                    callback_to_outer(self, "recover failed")
134                    raise error
135                exception = error
136                callback_to_outer(self, "recover success")
137            except Exception as error:
138                self.log.exception("error type: {}, error: {}".format(
139                    error.__class__.__name__, error), exc_info=False)
140                exception = error
141        raise exception
142
143    return device_action
144
145
146@Plugin(type=Plugin.DEVICE, id=DeviceOsType.default)
147class Device(IDevice):
148    """
149    Class representing a device.
150
151    Each object of this class represents one device in xDevice,
152    including handles to hdc, fastboot, and test agent (DeviceTest.apk).
153
154    Attributes:
155        device_sn: A string that's the serial number of the device.
156    """
157
158    device_sn = None
159    host = None
160    port = None
161    usb_type = DeviceConnectorType.hdc
162    is_timeout = False
163    device_hilog_proc = None
164    device_os_type = DeviceOsType.default
165    test_device_state = None
166    device_allocation_state = DeviceAllocationState.available
167    label = ProductForm.phone
168    log = platform_logger("Device")
169    device_state_monitor = None
170    reboot_timeout = 5 * 60 * 1000
171    _device_log_collector = None
172
173    _proxy = None
174    _abc_proxy = None
175    _is_abc = False
176    initdevice = True
177    d_port = 8011
178    abc_d_port = 8012
179    _uitestdeamon = None
180    rpc_timeout = 300
181    device_id = None
182    reconnecttimes = 0
183    _h_port = None
184    oh_module_package = None
185    module_ablity_name = None
186    _device_report_path = None
187    test_platform = Platform.ohos
188    _webview = None
189
190    model_dict = {
191        'default': ProductForm.phone,
192        'phone': ProductForm.phone,
193        'car': ProductForm.car,
194        'tv': ProductForm.television,
195        'watch': ProductForm.watch,
196        'tablet': ProductForm.tablet,
197        '2in1': ProductForm._2in1,
198        'nosdcard': ProductForm.phone
199    }
200
201    def __init__(self):
202        self.extend_value = {}
203        self.device_lock = threading.RLock()
204        self.forward_ports = []
205        self.forward_ports_abc = []
206        self.proxy_listener = None
207        self.device_props = {}
208        self.device_description = {}
209
210    def __eq__(self, other):
211        return self.device_sn == other.__get_serial__() and \
212            self.device_os_type == other.device_os_type
213
214    def init_description(self):
215        if self.device_description:
216            return
217        try:
218            self.__add_trusted_root_ca()
219            desc = {
220                DeviceProperties.sn: convert_serial(self.device_sn),
221                DeviceProperties.model: self.get_property("const.product.model"),
222                DeviceProperties.type_: self.get_device_type(),
223                DeviceProperties.platform: "OpenHarmony",
224                DeviceProperties.version: self.get_property("const.product.software.version"),
225                DeviceProperties.others: self.device_props
226            }
227            self.device_description.update(desc)
228        except Exception as e:
229            LOG.error("init device description error")
230            LOG.error(e, exc_info=True)
231
232    def __add_trusted_root_ca(self):
233        self.execute_shell_command("mount -o rw,remount /")
234        local = os.path.join(Variables.temp_dir, Constant.TRUSTED_ROOT_CA)
235        remote = Constant.TRUSTED_ROOT_CA_PATH
236        self.pull_file(remote, local)
237        data = {}
238        if os.path.exists(local):
239            try:
240                with open(local, encoding="utf-8") as json_f:
241                    data = json.load(json_f)
242            except ValueError:
243                pass
244        if Constant.TRUSTED_ROOT_CA_KEY in data.keys():
245            LOG.debug("trusted root ca already exists")
246            return
247        LOG.debug("trusted root ca does not exist, push it")
248        data.update({Constant.TRUSTED_ROOT_CA_KEY: Constant.TRUSTED_ROOT_CA_VAL})
249        content = json.dumps(data, indent=4, separators=(",", ":"))
250        json_fd = os.open(local, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, FilePermission.mode_644)
251        with os.fdopen(json_fd, mode="w", encoding="utf-8") as json_f:
252            json_f.write(content)
253        self.push_file(local, remote)
254
255    def __set_serial__(self, device_sn=""):
256        self.device_sn = device_sn
257        return self.device_sn
258
259    def __get_serial__(self):
260        return self.device_sn
261
262    def extend_device_props(self):
263        if self.device_props:
264            return
265        query_bin_path = ""
266        try:
267            query_bin_path = get_file_absolute_path(QUERY_DEVICE_PROP_BIN)
268        except ParamError as e:
269            LOG.warning(e)
270        if query_bin_path == "":
271            return
272        self.push_file(query_bin_path, DEVICE_TEMP_PATH)
273        file_name = os.path.basename(query_bin_path)
274        cmd = f"cd {DEVICE_TEMP_PATH} && chmod +x {file_name} && ./{file_name}"
275        out = self.execute_shell_command(
276            cmd, time=5 * 1000, output_flag=False, retry=RETRY_ATTEMPTS, abort_on_exception=False).strip()
277        if not out:
278            return
279        LOG.info(out)
280        params = parse_strings_key_value(out)
281        self.device_props.update(params)
282
283    def get(self, key=None, default=None):
284        if not key:
285            return default
286        value = getattr(self, key, None)
287        if value:
288            return value
289        else:
290            return self.extend_value.get(key, default)
291
292    def recover_device(self):
293        if not self.get_recover_state():
294            LOG.debug("Device %s %s is false, cannot recover device" % (
295                self.device_sn, ConfigConst.recover_state))
296            return False
297
298        result = self.device_state_monitor.wait_for_device_available(self.reboot_timeout)
299        if result:
300            self.device_log_collector.restart_catch_device_log()
301        return result
302
303    def get_device_type(self):
304        model = self.get_property("const.product.devicetype",
305                                  abort_on_exception=True)
306        model = "default" if model == "" else model
307        self.label = self.model_dict.get(model, ProductForm.phone)
308        return self.label
309
310    def get_property(self, prop_name, retry=RETRY_ATTEMPTS,
311                     abort_on_exception=False):
312        """
313        Hdc command, ddmlib function.
314        """
315        if not self.get_recover_state():
316            return ""
317        command = "param get %s" % prop_name
318        stdout = self.execute_shell_command(command, timeout=5 * 1000,
319                                            output_flag=False,
320                                            retry=retry,
321                                            abort_on_exception=abort_on_exception).strip()
322        if stdout:
323            LOG.debug(stdout)
324        return stdout
325
326    @perform_device_action
327    def connector_command(self, command, **kwargs):
328        timeout = int(kwargs.get("timeout", TIMEOUT)) / 1000
329        error_print = bool(kwargs.get("error_print", True))
330        join_result = bool(kwargs.get("join_result", False))
331        timeout_msg = '' if timeout == 300.0 else \
332            " with timeout %ss" % timeout
333        if self.host != "127.0.0.1":
334            cmd = [HdcHelper.CONNECTOR_NAME, "-s", "{}:{}".format(self.host, self.port),
335                   "-t", self.device_sn]
336        else:
337            cmd = [HdcHelper.CONNECTOR_NAME, "-t", self.device_sn]
338        LOG.debug("{} execute command {} {}{}".format(convert_serial(self.device_sn),
339                                                      HdcHelper.CONNECTOR_NAME,
340                                                      command, timeout_msg))
341        if isinstance(command, list):
342            cmd.extend(command)
343        else:
344            command = command.strip()
345            cmd.extend(command.split(" "))
346        result = exec_cmd(cmd, timeout, error_print, join_result)
347        if not result:
348            return result
349        is_print = bool(kwargs.get("is_print", True))
350        if is_print:
351            for line in str(result).split("\n"):
352                if line.strip():
353                    LOG.debug(line.strip())
354        return result
355
356    @perform_device_action
357    def execute_shell_command(self, command, timeout=TIMEOUT,
358                              receiver=None, **kwargs):
359        if not receiver:
360            collect_receiver = CollectingOutputReceiver()
361            HdcHelper.execute_shell_command(
362                self, command, timeout=timeout,
363                receiver=collect_receiver, **kwargs)
364            return collect_receiver.output
365        else:
366            return HdcHelper.execute_shell_command(
367                self, command, timeout=timeout,
368                receiver=receiver, **kwargs)
369
370    def execute_shell_cmd_background(self, command, timeout=TIMEOUT,
371                                     receiver=None):
372        status = HdcHelper.execute_shell_command(self, command,
373                                                 timeout=timeout,
374                                                 receiver=receiver)
375
376        self.wait_for_device_not_available(DEFAULT_UNAVAILABLE_TIMEOUT)
377        self.device_state_monitor.wait_for_device_available(BACKGROUND_TIME)
378        cmd = "target mount"
379        self.connector_command(cmd)
380        self.device_log_collector.restart_catch_device_log()
381        return status
382
383    def wait_for_device_not_available(self, wait_time):
384        return self.device_state_monitor.wait_for_device_not_available(
385            wait_time)
386
387    def _wait_for_device_online(self, wait_time=None):
388        return self.device_state_monitor.wait_for_device_online(wait_time)
389
390    def _do_reboot(self):
391        HdcHelper.reboot(self)
392        self.wait_for_boot_completion()
393
394    def _reboot_until_online(self):
395        self._do_reboot()
396
397    def reboot(self):
398        self._reboot_until_online()
399        self.enable_hdc_root()
400        self.device_log_collector.restart_catch_device_log()
401
402    @perform_device_action
403    def install_package(self, package_path, command=""):
404        if package_path is None:
405            raise HdcError(
406                "install package: package path cannot be None!")
407        return HdcHelper.install_package(self, package_path, command)
408
409    @perform_device_action
410    def uninstall_package(self, package_name):
411        return HdcHelper.uninstall_package(self, package_name)
412
413    @perform_device_action
414    def push_file(self, local, remote, **kwargs):
415        """
416        Push a single file.
417        The top directory won't be created if is_create is False (by default)
418        and vice versa
419        """
420        local = "\"{}\"".format(local)
421        remote = "\"{}\"".format(remote)
422        if local is None:
423            raise HdcError("XDevice Local path cannot be None!")
424
425        remote_is_dir = kwargs.get("remote_is_dir", False)
426        if remote_is_dir:
427            ret = self.execute_shell_command("test -d %s && echo 0" % remote)
428            if not (ret != "" and len(str(ret).split()) != 0 and
429                    str(ret).split()[0] == "0"):
430                self.execute_shell_command("mkdir -p %s" % remote)
431
432        if self.host != "127.0.0.1":
433            self.connector_command("file send {} {}".format(local, remote))
434        else:
435            is_create = kwargs.get("is_create", False)
436            timeout = kwargs.get("timeout", TIMEOUT)
437            HdcHelper.push_file(self, local, remote, is_create=is_create,
438                                timeout=timeout)
439        if not self.is_file_exist(remote):
440            LOG.error("Push %s to %s failed" % (local, remote))
441            raise HdcError("push %s to %s failed" % (local, remote))
442
443    @perform_device_action
444    def pull_file(self, remote, local, **kwargs):
445        """
446        Pull a single file.
447        The top directory won't be created if is_create is False (by default)
448        and vice versa
449        """
450        local = "\"{}\"".format(local)
451        remote = "\"{}\"".format(remote)
452        self.connector_command("file recv {} {}".format(remote, local))
453
454    def enable_hdc_root(self):
455        return True
456
457    def is_directory(self, path):
458        path = check_path_legal(path)
459        output = self.execute_shell_command("ls -ld {}".format(path))
460        if output and output.startswith('d'):
461            return True
462        return False
463
464    def is_file_exist(self, file_path):
465        file_path = check_path_legal(file_path)
466        output = self.execute_shell_command("ls {}".format(file_path))
467        if output and "No such file or directory" not in output:
468            return True
469        return False
470
471    def get_recover_result(self, retry=RETRY_ATTEMPTS):
472        command = "param get bootevent.boot.completed"
473        stdout = self.execute_shell_command(command, timeout=5 * 1000,
474                                            output_flag=False, retry=retry,
475                                            abort_on_exception=True).strip()
476        return stdout
477
478    def set_recover_state(self, state):
479        with self.device_lock:
480            setattr(self, ConfigConst.recover_state, state)
481            if not state:
482                self.test_device_state = TestDeviceState.NOT_AVAILABLE
483                self.device_allocation_state = DeviceAllocationState.unavailable
484                # do proxy clean
485                if self.proxy_listener is not None:
486                    if self._abc_proxy or (not self.is_abc and self.proxy):
487                        self.proxy_listener(is_exception=True)
488
489    def get_recover_state(self, default_state=True):
490        with self.device_lock:
491            state = getattr(self, ConfigConst.recover_state, default_state)
492            return state
493
494    def wait_for_boot_completion(self):
495        """Waits for the device to boot up.
496
497        Returns:
498            True if the device successfully finished booting, False otherwise.
499        """
500        return self.device_state_monitor.wait_for_boot_complete(self.reboot_timeout)
501
502    @classmethod
503    def check_recover_result(cls, recover_result):
504        return "true" in recover_result
505
506    @property
507    def device_log_collector(self):
508        if self._device_log_collector is None:
509            self._device_log_collector = DeviceLogCollector(self)
510        return self._device_log_collector
511
512    def close(self):
513        self.reconnecttimes = 0
514
515    def reset(self):
516        self.log.debug("start reset device...")
517        if self._proxy is not None:
518            self._proxy.close()
519        self._proxy = None
520        if self._uitestdeamon is not None:
521            self._uitestdeamon = None
522        if self.is_abc:
523            self.stop_harmony_rpc(kill_all=False)
524        else:
525            self.stop_harmony_rpc(kill_all=True)
526            # do proxy clean
527        if self.proxy_listener is not None:
528            self.proxy_listener(is_exception=False)
529        self.remove_ports()
530        self.device_log_collector.stop_restart_catch_device_log()
531
532    @property
533    def is_abc(self):
534        # _is_abc init in device test driver
535        return self._is_abc
536
537    @property
538    def proxy(self):
539        """The first rpc session initiated on this device. None if there isn't
540        one.
541        """
542        try:
543            if self._proxy is None:
544                # check uitest
545                self.check_uitest_status()
546                self._proxy = self.get_harmony()
547        except AppInstallError as error:
548            raise error
549        except RpcNotRunningError as error:
550            raise error
551        except Exception as error:
552            self._proxy = None
553            self.log.error("DeviceTest-10012 proxy:%s" % str(error))
554        return self._proxy
555
556    @property
557    def abc_proxy(self):
558        """The first rpc session initiated on this device. None if there isn't
559        one.
560        """
561        try:
562            if self._abc_proxy is None:
563                # check uitest
564                self.check_uitest_status()
565                self._abc_proxy = self.get_harmony(start_abc=True)
566        except RpcNotRunningError as error:
567            raise error
568        except Exception as error:
569            self._abc_proxy = None
570            self.log.error("DeviceTest-10012 abc_proxy:%s" % str(error))
571        return self._abc_proxy
572
573    @property
574    def uitestdeamon(self):
575        from devicetest.controllers.uitestdeamon import \
576            UiTestDeamon
577        if self._uitestdeamon is None:
578            self._uitestdeamon = UiTestDeamon(self)
579        return self._uitestdeamon
580
581    @classmethod
582    def set_module_package(cls, module_packag):
583        cls.oh_module_package = module_packag
584
585    @classmethod
586    def set_moudle_ablity_name(cls, module_ablity_name):
587        cls.module_ablity_name = module_ablity_name
588
589    @property
590    def is_oh(self):
591        return True
592
593    def get_harmony(self, start_abc=False):
594        if self.initdevice:
595            if start_abc:
596                self.start_abc_rpc(re_install_rpc=True)
597            else:
598                self.start_harmony_rpc(re_install_rpc=True)
599        self._h_port = self.get_local_port(start_abc)
600        port = self.d_port if not start_abc else self.abc_d_port
601        # clear old port,because abc and fast mode will not remove port
602        cmd = "fport tcp:{} tcp:{}".format(
603            self._h_port, port)
604        self.connector_command(cmd)
605        self.log.info(
606            "get_proxy d_port:{} {}".format(self._h_port, port))
607        rpc_proxy = None
608        try:
609            from devicetest.controllers.openharmony import OpenHarmony
610            rpc_proxy = OpenHarmony(port=self._h_port, addr=self.host, device=self)
611        except Exception as error:
612            self.log.error(' proxy init error: {}.'.format(str(error)))
613        return rpc_proxy
614
615    def start_uitest(self):
616        result = ""
617        if self.is_abc:
618            result = self.execute_shell_command("{} start-daemon singleness &".format(UITEST_PATH))
619        else:
620            share_mem_mode = False
621            # uitest基础版本号,比该版本号大的用共享内存的方式进行启动
622            base_version = [3, 2, 2, 2]
623            uitest_version = self.execute_shell_command("{} --version".format(UITEST_PATH))
624            if uitest_version and re.match(r'^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', uitest_version):
625                uitest_version = uitest_version.split(".")
626                for index, _ in enumerate(uitest_version):
627                    if int(uitest_version[index]) > base_version[index]:
628                        share_mem_mode = True
629                        break
630            if share_mem_mode:
631                if not self.is_file_exist(UITEST_SHMF):
632                    self.log.debug('Path {} not exist, create it.'.format(UITEST_SHMF))
633                    self.execute_shell_command("echo abc > {}".format(UITEST_SHMF))
634                    self.execute_shell_command("chmod -R 666 {}".format(UITEST_SHMF))
635                result = self.execute_shell_command("{} start-daemon {} &".format(UITEST_PATH, UITEST_SHMF))
636            else:
637                result = self.execute_shell_command(UITEST_COMMAND)
638        self.log.debug('start uitest, {}'.format(result))
639
640    def start_harmony_rpc(self, re_install_rpc=False):
641        if self.check_rpc_status(check_abc=False):
642            if (hasattr(sys, ConfigConst.env_pool_cache) and
643                getattr(sys, ConfigConst.env_pool_cache, False)) \
644                    or not re_install_rpc:
645                self.log.debug('Harmony rpc already start!!!!')
646                return
647        from devicetest.core.error_message import ErrorMessage
648        if re_install_rpc:
649            try:
650                from devicetest.controllers.openharmony import OpenHarmony
651                OpenHarmony.install_harmony_rpc(self)
652            except ImportError as error:  # pylint:disable=undefined-variable
653                self.log.debug(str(error))
654                self.log.error('please check devicetest extension module is exist.')
655                raise Exception(ErrorMessage.Error_01437.Topic)
656            except AppInstallError as error:
657                raise error
658            except Exception as error:
659                self.log.debug(str(error))
660                self.log.error('root device init RPC error.')
661                raise Exception(ErrorMessage.Error_01437.Topic)
662        if not self.is_abc:
663            self.stop_harmony_rpc()
664        else:
665            self.log.debug('Abc mode, kill hap if hap is running.')
666            self.stop_harmony_rpc(kill_all=False)
667        cmd = "aa start -a {}.ServiceAbility -b {}".format(DEVICETEST_HAP_PACKAGE_NAME, DEVICETEST_HAP_PACKAGE_NAME)
668        result = self.execute_shell_command(cmd)
669        self.log.debug('start devicetest ability, {}'.format(result))
670        if "successfully" not in result:
671            raise RpcNotRunningError("harmony {} rpc start failed".format("system" if self.is_abc else "normal"),
672                                     error_no=ErrorMessage.Error_01439.Code)
673        if not self.is_abc:
674            self.start_uitest()
675        time.sleep(1)
676        if not self.check_rpc_status(check_abc=False):
677            raise RpcNotRunningError("harmony {} rpc process not found".format("system" if self.is_abc else "normal"),
678                                     error_no=ErrorMessage.Error_01440.Code)
679
680    def start_abc_rpc(self, re_install_rpc=False):
681        if re_install_rpc:
682            try:
683                from devicetest.controllers.openharmony import OpenHarmony
684                OpenHarmony.init_abc_resource(self)
685            except ImportError as error:  # pylint:disable=undefined-variable
686                self.log.debug(str(error))
687                self.log.error('please check devicetest extension module is exist.')
688                raise error
689            except Exception as error:
690                self.log.debug(str(error))
691                self.log.error('root device init abc RPC error.')
692                raise error
693        if self.is_abc and self.check_rpc_status(check_abc=True):
694            self.log.debug('Harmony abc rpc already start!!!!')
695            return
696        self.start_uitest()
697        time.sleep(1)
698        from devicetest.core.error_message import ErrorMessage
699        if not self.check_rpc_status(check_abc=True):
700            raise RpcNotRunningError("harmony abc rpc process not found", error_no=ErrorMessage.Error_01440.Code)
701
702    def stop_harmony_rpc(self, kill_all=True):
703        if not self.get_recover_state():
704            LOG.warning("device state is false, skip stop harmony rpc.")
705            return
706        # only kill devicetest in abc mode, or kill all
707        proc_pids = self.get_devicetest_proc_pid()
708        if not kill_all:
709            proc_pids.pop()
710        for pid in proc_pids:
711            if pid != "":
712                cmd = 'kill {}'.format(pid)
713                self.execute_shell_command(cmd)
714
715    # check uitest if running well, otherwise kill it first
716    def check_uitest_status(self):
717        self.log.debug('Check uitest running status.')
718        proc_pids = self.get_devicetest_proc_pid(print_info=False)
719        if self.is_abc and proc_pids[1] != "":
720            if proc_pids[0] != "":
721                self.execute_shell_command('kill {}'.format(proc_pids[0]))
722            self.execute_shell_command('kill {}'.format(proc_pids[1]))
723            self.log.debug('Uitest is running in normal mode, current mode is abc, wait it exit.')
724        if not self.is_abc and proc_pids[2] != "":
725            if proc_pids[0] != "":
726                self.execute_shell_command('kill {}'.format(proc_pids[0]))
727            self.execute_shell_command('kill {}'.format(proc_pids[2]))
728            self.log.debug('Uitest is running in abc mode, current mode is normal, wait it exit.')
729        self.log.debug('Finish check uitest running status.')
730
731    def get_devicetest_proc_pid(self, print_info=True):
732        uitest = "{} start-daemon".format(UITEST_NAME)
733        cmd = 'ps -ef | grep -E \'{}|{}|{}\''.format(DEVICETEST_HAP_PACKAGE_NAME, UITEST_NAME, UITEST_SINGLENESS)
734        proc_running = self.execute_shell_command(cmd).strip()
735        proc_running = proc_running.split("\n")
736        proc_pids = [""] * 3
737        result = []
738        for data in proc_running:
739            if DEVICETEST_HAP_PACKAGE_NAME in data and "grep" not in data and UITEST_NAME not in data:
740                result.append("{} running status: {}".format(DEVICETEST_HAP_PACKAGE_NAME, data))
741                data = data.split()
742                proc_pids[0] = data[1]
743            if uitest in data and "grep" not in data:
744                if UITEST_SINGLENESS in data:
745                    result.append("{} running status: {}".format(UITEST_SINGLENESS, data))
746                    data = data.split()
747                    proc_pids[2] = data[1]
748                else:
749                    result.append("{} running status: {}".format(UITEST_NAME, data))
750                    data = data.split()
751                    proc_pids[1] = data[1]
752        if print_info:
753            self.log.debug("\n".join(result))
754        return proc_pids
755
756    def is_harmony_rpc_running(self, check_abc=False):
757        proc_pids = self.get_devicetest_proc_pid(print_info=False)
758        if not self.is_abc:
759            self.log.debug('is_proc_running: agent pid: {}, uitest pid: {}'.format(proc_pids[0], proc_pids[1]))
760            if proc_pids[0] != "" and proc_pids[1] != "":
761                return True
762        else:
763            if check_abc:
764                self.log.debug('is_proc_running: uitest pid: {}'.format(proc_pids[2]))
765                if proc_pids[2] != "":
766                    return True
767            else:
768                self.log.debug('is_proc_running: agent pid: {}'.format(proc_pids[0]))
769                if proc_pids[0] != "":
770                    return True
771        return False
772
773    def is_harmony_rpc_socket_running(self, port, check_server=True):
774        out = self.execute_shell_command("netstat -anp | grep {}".format(port))
775        self.log.debug(out)
776        if out:
777            out = out.split("\n")
778            for data in out:
779                if check_server:
780                    if "LISTEN" in data and str(port) in data:
781                        return True
782                else:
783                    if "hdcd" in data and str(port) in data:
784                        return True
785        return False
786
787    def check_rpc_status(self, check_abc=False, check_server=True):
788        port = self.d_port if not check_abc else self.abc_d_port
789        if self.is_harmony_rpc_running(check_abc) and \
790                self.is_harmony_rpc_socket_running(port, check_server=check_server):
791            self.log.debug('Harmony rpc is running!!!! If is check abc: {}'.format(check_abc))
792            return True
793        self.log.debug('Harmony rpc is not running!!!! If is check abc: {}'.format(check_abc))
794        return False
795
796    def install_app(self, remote_path, command):
797        try:
798            ret = self.execute_shell_command(
799                "pm install %s %s" % (command, remote_path))
800            if ret is not None and str(
801                    ret) != "" and "Unknown option: -g" in str(ret):
802                return self.execute_shell_command(
803                    "pm install -r %s" % remote_path)
804            return ret
805        except Exception as error:
806            self.log.error("%s, maybe there has a warning box appears "
807                           "when installing RPC." % error)
808            return False
809
810    def uninstall_app(self, package_name):
811        try:
812            ret = self.execute_shell_command("pm uninstall %s" % package_name)
813            self.log.debug(ret)
814            return ret
815        except Exception as err:
816            self.log.error('DeviceTest-20013 uninstall: %s' % str(err))
817            return False
818
819    def reconnect(self, waittime=60):
820        '''
821        @summary: Reconnect the device.
822        '''
823        if not self.wait_for_boot_completion():
824            self._proxy = None
825            self._uitestdeamon = None
826            raise Exception("Reconnect timed out.")
827        if self._proxy:
828            # do proxy clean
829            if not self.is_abc and self.proxy_listener is not None:
830                self.proxy_listener(is_exception=True)
831            self.start_harmony_rpc(re_install_rpc=True)
832            self._h_port = self.get_local_port(start_abc=False)
833            cmd = "fport tcp:{} tcp:{}".format(
834                self._h_port, self.d_port)
835            self.connector_command(cmd)
836            try:
837                self._proxy.init(port=self._h_port, addr=self.host, device=self)
838            except Exception as _:
839                time.sleep(3)
840                self._proxy.init(port=self._h_port, addr=self.host, device=self)
841
842        if self.is_abc and self._abc_proxy:
843            # do proxy clean
844            if self.proxy_listener is not None:
845                self.proxy_listener(is_exception=True)
846            self.start_abc_rpc(re_install_rpc=True)
847            self._h_port = self.get_local_port(start_abc=True)
848            cmd = "fport tcp:{} tcp:{}".format(
849                self._h_port, self.abc_d_port)
850            self.connector_command(cmd)
851            try:
852                self._abc_proxy.init(port=self._h_port, addr=self.host, device=self)
853            except Exception as _:
854                time.sleep(3)
855                self._abc_proxy.init(port=self._h_port, addr=self.host, device=self)
856
857        if self._uitestdeamon is not None:
858            self._uitestdeamon.init(self)
859
860        if self._proxy:
861            return self._proxy
862        return None
863
864    def get_local_port(self, start_abc):
865        from devicetest.utils.util import get_forward_port
866        host = self.host
867        port = None
868        h_port = get_forward_port(self, host, port)
869        if start_abc:
870            self.forward_ports_abc.append(h_port)
871        else:
872            self.forward_ports.append(h_port)
873        self.log.info("tcp forward port: {} for {}".format(
874            h_port, convert_serial(self.device_sn)))
875        return h_port
876
877    def remove_ports(self):
878        for port in self.forward_ports:
879            cmd = "fport rm tcp:{} tcp:{}".format(
880                port, self.d_port)
881            self.connector_command(cmd)
882        for port in self.forward_ports_abc:
883            cmd = "fport rm tcp:{} tcp:{}".format(
884                port, self.abc_d_port)
885            self.connector_command(cmd)
886        self.forward_ports.clear()
887        self.forward_ports_abc.clear()
888
889    def remove_history_ports(self, port):
890        cmd = "fport ls"
891        res = self.connector_command(cmd, is_print=False)
892        res = res.split("\n")
893        for data in res:
894            if str(port) in data:
895                data = data.split('\t')
896                cmd = "fport rm {}".format(data[0][1:-1])
897                self.connector_command(cmd, is_print=False)
898
899    def take_picture(self, name):
900        '''
901        @summary: 截取手机屏幕图片并保存
902        @param  name: 保存的图片名称,通过getTakePicturePath方法获取保存全路径
903        '''
904        path = ""
905        try:
906            if self._device_report_path is None:
907                from xdevice import EnvPool
908                self._device_report_path = EnvPool.report_path
909            temp_path = os.path.join(self._device_report_path, "temp")
910            if not os.path.exists(temp_path):
911                os.makedirs(temp_path)
912            path = os.path.join(temp_path, name)
913            picture_name = os.path.basename(name)
914            out = self.execute_shell_command("snapshot_display -f /data/local/tmp/{}".format(picture_name))
915            self.log.debug("result: {}".format(out))
916            if "error" in out and "success" not in out:
917                return False
918            else:
919                self.pull_file("/data/local/tmp/{}".format(picture_name), path)
920        except Exception as error:
921            self.log.error("devicetest take_picture: {}".format(str(error)))
922        return path
923
924    def set_device_report_path(self, path):
925        self._device_report_path = path
926
927    def get_device_report_path(self):
928        return self._device_report_path
929
930    def execute_shell_in_daemon(self, command):
931        if self.host != "127.0.0.1":
932            cmd = [HdcHelper.CONNECTOR_NAME, "-s", "{}:{}".format(
933                self.host, self.port), "-t", self.device_sn, "shell"]
934        else:
935            cmd = [HdcHelper.CONNECTOR_NAME, "-t", self.device_sn, "shell"]
936        LOG.debug("{} execute command {} {} in daemon".format(
937            convert_serial(self.device_sn), HdcHelper.CONNECTOR_NAME, command))
938        if isinstance(command, list):
939            cmd.extend(command)
940        else:
941            command = command.strip()
942            cmd.extend(command.split(" "))
943        sys_type = platform.system()
944        process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
945                                   shell=False,
946                                   preexec_fn=None if sys_type == "Windows"
947                                   else os.setsid,
948                                   close_fds=True)
949        return process
950
951    @property
952    def webview(self):
953        from devicetest.controllers.web.webview import WebView
954        if self._webview is None:
955            self._webview = WebView(self)
956        return self._webview
957
958
959class DeviceLogCollector:
960    hilog_file_address = []
961    log_file_address = []
962    device = None
963    restart_proc = []
964    device_log_level = None
965    is_clear = True
966
967    def __init__(self, device):
968        self.device = device
969
970    def _set_device_log_level(self, **kwargs):
971        # set device log level
972        if not self.device_log_level:
973            log_level = kwargs.get("log_level", "INFO")
974            if log_level not in LOGLEVEL:
975                self.device_log_level = "INFO"
976            else:
977                self.device_log_level = log_level
978        cmd = "hilog -b {}".format(self.device_log_level)
979        self.device.execute_shell_command(cmd)
980
981    def restart_catch_device_log(self):
982        self._sync_device_time()
983        for _, path in enumerate(self.hilog_file_address):
984            hilog_open = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND,
985                                 FilePermission.mode_755)
986            with os.fdopen(hilog_open, "a") as hilog_file_pipe:
987                _, proc = self.start_catch_device_log(hilog_file_pipe=hilog_file_pipe)
988                self.restart_proc.append(proc)
989
990    def stop_restart_catch_device_log(self):
991        # when device free stop restart log proc
992        for _, proc in enumerate(self.restart_proc):
993            self.stop_catch_device_log(proc)
994        self.restart_proc.clear()
995        self.hilog_file_address.clear()
996        self.log_file_address.clear()
997
998    def start_catch_device_log(self, log_file_pipe=None,
999                               hilog_file_pipe=None, **kwargs):
1000        """
1001        Starts hdc log for each device in separate subprocesses and save
1002        the logs in files.
1003        """
1004        self._sync_device_time()
1005        self._set_device_log_level(**kwargs)
1006
1007        device_hilog_proc = None
1008        if hilog_file_pipe:
1009            command = "hilog"
1010            if self.device.host != "127.0.0.1":
1011                cmd = [HdcHelper.CONNECTOR_NAME, "-s", "{}:{}".format(self.device.host, self.device.port),
1012                       "-t", self.device.device_sn, command]
1013            else:
1014                cmd = [HdcHelper.CONNECTOR_NAME, "-t", self.device.device_sn, command]
1015            LOG.info("execute command: %s" % " ".join(cmd).replace(
1016                self.device.device_sn, convert_serial(self.device.device_sn)))
1017            device_hilog_proc = start_standing_subprocess(
1018                cmd, hilog_file_pipe)
1019        return None, device_hilog_proc
1020
1021    def stop_catch_device_log(self, proc):
1022        """
1023        Stops all hdc log subprocesses.
1024        """
1025        if proc:
1026            stop_standing_subprocess(proc)
1027            self.device.log.debug("Stop catch device hilog.")
1028
1029    def start_hilog_task(self, **kwargs):
1030        log_size = kwargs.get("log_size", "10M")
1031        log_size = log_size.upper()
1032        if re.search("^\d+[K?]$", log_size) is None \
1033                and re.search("^\d+[M?]$", log_size) is None:
1034            self.device.log.debug("hilog task Invalid size string {}. Use default 10M".format(log_size))
1035            log_size = "10M"
1036        matcher = re.match("^\d+", log_size)
1037        if log_size.endswith("K") and int(matcher.group(0)) < 64:
1038            self.device.log.debug("hilog task file size should be "
1039                                  "in range [64.0K, 512.0M], use min value 64K, now is {}".format(log_size))
1040            log_size = "64K"
1041        if log_size.endswith("M") and int(matcher.group(0)) > 512:
1042            self.device.log.debug("hilog task file size should be "
1043                                  "in range [64.0K, 512.0M], use min value 512M, now is {}".format(log_size))
1044            log_size = "512M"
1045
1046        self._sync_device_time()
1047        self.is_clear = kwargs.get("is_clear", True)
1048        # clear device crash log
1049        self.clear_crash_log()
1050        # stop hilog task
1051        cmd = "hilog -w stop"
1052        self.device.execute_shell_command(cmd)
1053        # 清空日志
1054        cmd = "hilog -r"
1055        self.device.execute_shell_command(cmd)
1056        cmd = "rm -rf /data/log/hilog/*.gz"
1057        # set device log level
1058        self._set_device_log_level(**kwargs)
1059        # 开始日志任务 设置落盘文件个数最大值1000, 单个文件20M,链接https://gitee.com/openharmony/hiviewdfx_hilog
1060        cmd = "hilog -w start -l {} -n 1000".format(log_size)
1061        out = self.device.execute_shell_command(cmd)
1062        LOG.info("Execute command: {}, result is {}".format(cmd, out))
1063        # 开启kmsg日志落盘任务
1064        cmd = "hilog -w start -t kmsg -l {} -n 1000".format(log_size)
1065        out = self.device.execute_shell_command(cmd)
1066        LOG.info("Execute command: {}, result is {}".format(cmd, out))
1067
1068    def stop_hilog_task(self, log_name, **kwargs):
1069        cmd = "hilog -w stop"
1070        self.device.execute_shell_command(cmd)
1071        module_name = kwargs.get("module_name", None)
1072        if module_name:
1073            path = "{}/log/{}".format(self.device.get_device_report_path(), module_name)
1074        else:
1075            path = "{}/log/".format(self.device.get_device_report_path())
1076        if not os.path.exists(path):
1077            os.makedirs(path)
1078        self.device.pull_file("/data/log/hilog/", path)
1079        # HDC不支持创建绝对路径,拉取文件夹出来后重命名文件夹
1080        try:
1081            new_hilog_dir = "{}/log/{}/hilog_{}".format(self.device.get_device_report_path(), module_name, log_name)
1082            os.rename("{}/log/{}/hilog".format(self.device.get_device_report_path(), module_name), new_hilog_dir)
1083            # 拉出来的文件夹权限可能是650,更改为755,解决在线日志浏览报403问题
1084            os.chmod(new_hilog_dir, FilePermission.mode_755)
1085        except Exception as e:
1086            self.device.log.warning("Rename hilog folder {}_hilog failed. error: {}".format(log_name, e))
1087            # 把hilog文件夹下所有文件拉出来 由于hdc不支持整个文件夹拉出只能采用先压缩再拉取文件
1088            cmd = "cd /data/log/hilog && tar -zcvf /data/log/{}_hilog.tar.gz *".format(log_name)
1089            out = self.device.execute_shell_command(cmd)
1090            LOG.info("Execute command: {}, result is {}".format(cmd, out))
1091            if out is not None and "No space left on device" not in out:
1092                self.device.pull_file("/data/log/{}_hilog.tar.gz".format(log_name), path)
1093                cmd = "rm -f /data/log/{}_hilog.tar.gz".format(log_name)
1094                self.device.execute_shell_command(cmd)
1095        # check if clear log
1096        if self.is_clear:
1097            cmd = "rm -f /data/log/hilog/*.gz"
1098            self.device.execute_shell_command(cmd)
1099        # get crash log
1100        self.start_get_crash_log(log_name, module_name=module_name)
1101        # get extra log
1102        self.pull_extra_log_files(log_name, module_name, kwargs.get("extras_dirs", None))
1103
1104    def _get_log(self, log_path, *params):
1105        def filter_by_name(log_name, args):
1106            for starts_name in args:
1107                if log_name.startswith(starts_name):
1108                    return True
1109            return False
1110
1111        data_list = list()
1112        log_name_array = list()
1113        log_result = self.device.execute_shell_command("ls {}".format(log_path))
1114        if log_result is not None and len(log_result) != 0:
1115            log_name_array = log_result.strip().replace("\r", "").split("\n")
1116        for log_name in log_name_array:
1117            log_name = log_name.strip()
1118            if len(params) == 0 or \
1119                    filter_by_name(log_name, params):
1120                data_list.append("{}/{}".format(log_path, log_name))
1121        return data_list
1122
1123    def start_get_crash_log(self, task_name, **kwargs):
1124        def get_cur_crash_log(local_path, device_path):
1125            if not os.path.exists(local_path):
1126                os.makedirs(local_path)
1127            if "Not support std mode" in device_path:
1128                return
1129
1130            self.device.pull_file(device_path, local_path)
1131            LOG.debug("Finish pull file: %s" % device_path)
1132
1133        module_name = kwargs.get("module_name", None)
1134        log_array = list()
1135
1136        # get crash log
1137        log_array.extend(self._get_log(NATIVE_CRASH_PATH, "cppcrash"))
1138        log_array.extend(self._get_log(JS_CRASH_PATH, "jscrash", "appfreeze", "cppcrash"))
1139        log_array.extend(self._get_log(ROOT_PATH, "SERVICE_BLOCK", "appfreeze"))
1140        LOG.debug("crash log file {}, length is {}".format(str(log_array), str(len(log_array))))
1141        if module_name:
1142            crash_path = "{}/log/{}/crash_log_{}/".format(self.device.get_device_report_path(), module_name, task_name)
1143        else:
1144            crash_path = "{}/log/crash_log_{}/".format(self.device.get_device_report_path(), task_name)
1145        for log_name in log_array:
1146            log_name = log_name.strip()
1147            get_cur_crash_log(crash_path, log_name)
1148
1149    def clear_crash_log(self):
1150        if not self.is_clear:
1151            LOG.debug("No need to clear crash log.")
1152            return
1153
1154        def execute_clear_cmd(path: str, prefix: list):
1155            for pre in prefix:
1156                clear_cmd = "rm -f {}/{}*".format(path, pre)
1157                self.device.execute_shell_command(clear_cmd)
1158
1159        execute_clear_cmd(ROOT_PATH, ["SERVICE_BLOCK", "appfreeze"])
1160        execute_clear_cmd(NATIVE_CRASH_PATH, ["cppcrash"])
1161        execute_clear_cmd(JS_CRASH_PATH, ["jscrash", "appfreeze", "cppcrash"])
1162
1163    def _sync_device_time(self):
1164        # 先同步PC和设备的时间
1165        iso_time_format = '%Y-%m-%d %H:%M:%S'
1166        cur_time = get_cst_time().strftime(iso_time_format)
1167        self.device.execute_shell_command("date '{}'".format(cur_time))
1168
1169    def add_log_address(self, log_file_address, hilog_file_address):
1170        # record to restart catch log when reboot device
1171        if log_file_address:
1172            self.log_file_address.append(log_file_address)
1173        if hilog_file_address:
1174            self.hilog_file_address.append(hilog_file_address)
1175
1176    def remove_log_address(self, log_file_address, hilog_file_address):
1177        if log_file_address and log_file_address in self.log_file_address:
1178            self.log_file_address.remove(log_file_address)
1179        if hilog_file_address and hilog_file_address in self.hilog_file_address:
1180            self.hilog_file_address.remove(hilog_file_address)
1181
1182    def pull_extra_log_files(self, task_name, module_name, dirs: str):
1183        if dirs is None or dirs == 'None':
1184            return
1185        dir_list = dirs.split(";")
1186        if len(dir_list) > 0:
1187            extra_log_path = "{}/log/{}/extra_log_{}/".format(self.device.get_device_report_path(),
1188                                                              module_name, task_name)
1189            if not os.path.exists(extra_log_path):
1190                os.makedirs(extra_log_path)
1191            for dir_path in dir_list:
1192                self.device.pull_file(dir_path, extra_log_path)
1193                # check if delete file
1194                if self.device.is_directory(dir_path):
1195                    clear_cmd = "rm -f {}/*".format(dir_path)
1196                else:
1197                    clear_cmd = "rm -f {}*".format(dir_path)
1198                self.device.execute_shell_command(clear_cmd)