• 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 re
20import time
21import os
22import threading
23import platform
24import subprocess
25import sys
26from xdevice import DeviceOsType
27from xdevice import ProductForm
28from xdevice import ReportException
29from xdevice import IDevice
30from xdevice import platform_logger
31from xdevice import Plugin
32from xdevice import exec_cmd
33from xdevice import ConfigConst
34from xdevice import HdcError
35from xdevice import DeviceAllocationState
36from xdevice import TestDeviceState
37from xdevice import convert_serial
38from xdevice import check_path_legal
39from xdevice import start_standing_subprocess
40from xdevice import stop_standing_subprocess
41from xdevice import get_cst_time
42from xdevice import get_device_proc_pid
43
44from ohos.environment.dmlib import HdcHelper
45from ohos.environment.dmlib import CollectingOutputReceiver
46
47__all__ = ["Device"]
48TIMEOUT = 300 * 1000
49RETRY_ATTEMPTS = 2
50DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000
51BACKGROUND_TIME = 2 * 60 * 1000
52LOG = platform_logger("Device")
53DEVICETEST_HAP_PACKAGE_NAME = "com.ohos.devicetest"
54UITEST_NAME = "uitest"
55UITEST_PATH = "/system/bin/uitest"
56UITEST_SHMF = "/data/app/el2/100/base/{}/cache/shmf".format(DEVICETEST_HAP_PACKAGE_NAME)
57UITEST_COMMAND = "{} start-daemon 0123456789 &".format(UITEST_PATH)
58NATIVE_CRASH_PATH = "/data/log/faultlog/temp"
59JS_CRASH_PATH = "/data/log/faultlog/faultlogger"
60ROOT_PATH = "/data/log/faultlog"
61
62
63def perform_device_action(func):
64    def callback_to_outer(device, msg):
65        # callback to decc ui
66        if getattr(device, "callback_method", None):
67            device.callback_method(msg)
68
69    def device_action(self, *args, **kwargs):
70        if not self.get_recover_state():
71            LOG.debug("Device %s %s is false" % (self.device_sn,
72                                                 ConfigConst.recover_state))
73            return None
74        # avoid infinite recursion, such as device reboot
75        abort_on_exception = bool(kwargs.get("abort_on_exception", False))
76        if abort_on_exception:
77            result = func(self, *args, **kwargs)
78            return result
79
80        tmp = int(kwargs.get("retry", RETRY_ATTEMPTS))
81        retry = tmp + 1 if tmp > 0 else 1
82        exception = None
83        for _ in range(retry):
84            try:
85                result = func(self, *args, **kwargs)
86                return result
87            except ReportException as error:
88                self.log.exception("Generate report error!", exc_info=False)
89                exception = error
90            except (ConnectionResetError,              # pylint:disable=undefined-variable
91                    ConnectionRefusedError,            # pylint:disable=undefined-variable
92                    ConnectionAbortedError) as error:  # pylint:disable=undefined-variable
93                self.log.error("error type: %s, error: %s" %
94                               (error.__class__.__name__, error))
95                cmd = "{} target boot".format(HdcHelper.CONNECTOR_NAME)
96                self.log.info("re-execute {} reset".format(HdcHelper.CONNECTOR_NAME))
97                exec_cmd(cmd)
98                callback_to_outer(self, "error:%s, prepare to recover" % error)
99                if not self.recover_device():
100                    LOG.debug("Set device %s %s false" % (
101                        self.device_sn, ConfigConst.recover_state))
102                    self.set_recover_state(False)
103                    callback_to_outer(self, "recover failed")
104                    raise error
105                exception = error
106                callback_to_outer(self, "recover success")
107            except HdcError as error:
108                self.log.error("error type: %s, error: %s" %
109                               (error.__class__.__name__, error))
110                callback_to_outer(self, "error:%s, prepare to recover" % error)
111                if not self.recover_device():
112                    LOG.debug("Set device %s %s false" % (
113                        self.device_sn, ConfigConst.recover_state))
114                    self.set_recover_state(False)
115                    callback_to_outer(self, "recover failed")
116                    raise error
117                exception = error
118                callback_to_outer(self, "recover success")
119            except Exception as error:
120                self.log.exception("error type: %s, error: %s" % (
121                    error.__class__.__name__, error), exc_info=False)
122                exception = error
123        raise exception
124
125    return device_action
126
127
128@Plugin(type=Plugin.DEVICE, id=DeviceOsType.default)
129class Device(IDevice):
130    """
131    Class representing a device.
132
133    Each object of this class represents one device in xDevice,
134    including handles to hdc, fastboot, and test agent (DeviceTest.apk).
135
136    Attributes:
137        device_sn: A string that's the serial number of the device.
138    """
139
140    device_sn = None
141    host = None
142    port = None
143    usb_type = None
144    is_timeout = False
145    device_hilog_proc = None
146    device_os_type = DeviceOsType.default
147    test_device_state = None
148    device_allocation_state = DeviceAllocationState.available
149    label = None
150    log = platform_logger("Device")
151    device_state_monitor = None
152    reboot_timeout = 2 * 60 * 1000
153    _device_log_collector = None
154
155    _proxy = None
156    _is_harmony = None
157    initdevice = True
158    d_port = 8011
159    _uitestdeamon = None
160    rpc_timeout = 300
161    device_id = None
162    reconnecttimes = 0
163    _h_port = None
164    screenshot = False
165    screenshot_fail = True
166    module_package = None
167    module_ablity_name = None
168    _device_log_path = ""
169    _device_report_path = ""
170
171    model_dict = {
172        'default': ProductForm.phone,
173        'car': ProductForm.car,
174        'tv': ProductForm.television,
175        'watch': ProductForm.watch,
176        'tablet': ProductForm.tablet,
177        'nosdcard': ProductForm.phone
178    }
179
180    def __init__(self):
181        self.extend_value = {}
182        self.device_lock = threading.RLock()
183        self.forward_ports = []
184
185    @property
186    def is_hw_root(self):
187        if self.is_harmony:
188            return True
189        return False
190
191    def __eq__(self, other):
192        return self.device_sn == other.__get_serial__() and \
193               self.device_os_type == other.device_os_type
194
195    def __set_serial__(self, device_sn=""):
196        self.device_sn = device_sn
197        return self.device_sn
198
199    def __get_serial__(self):
200        return self.device_sn
201
202    def get(self, key=None, default=None):
203        if not key:
204            return default
205        value = getattr(self, key, None)
206        if value:
207            return value
208        else:
209            return self.extend_value.get(key, default)
210
211    def recover_device(self):
212        if not self.get_recover_state():
213            LOG.debug("Device %s %s is false, cannot recover device" % (
214                self.device_sn, ConfigConst.recover_state))
215            return False
216
217        LOG.debug("Wait device %s to recover" % self.device_sn)
218        return self.device_state_monitor.wait_for_device_available()
219
220    def get_device_type(self):
221        self.label = self.model_dict.get("default", None)
222
223    def get_property(self, prop_name, retry=RETRY_ATTEMPTS,
224                     abort_on_exception=False):
225        """
226        Hdc command, ddmlib function.
227        """
228        command = "param get %s" % prop_name
229        stdout = self.execute_shell_command(command, timeout=5 * 1000,
230                                            output_flag=False,
231                                            retry=retry,
232                                            abort_on_exception=abort_on_exception).strip()
233        if stdout:
234            LOG.debug(stdout)
235        return stdout
236
237    @perform_device_action
238    def connector_command(self, command, **kwargs):
239        timeout = int(kwargs.get("timeout", TIMEOUT)) / 1000
240        error_print = bool(kwargs.get("error_print", True))
241        join_result = bool(kwargs.get("join_result", False))
242        timeout_msg = '' if timeout == 300.0 else \
243            " with timeout %ss" % timeout
244        if self.host != "127.0.0.1":
245            cmd = [HdcHelper.CONNECTOR_NAME, "-s", "{}:{}".format(self.host, self.port),
246                   "-t", self.device_sn]
247        else:
248            cmd = [HdcHelper.CONNECTOR_NAME, "-t", self.device_sn]
249        LOG.debug("{} execute command {} {}{}".format(convert_serial(self.device_sn),
250                                                      HdcHelper.CONNECTOR_NAME,
251                                                      command, timeout_msg))
252        if isinstance(command, list):
253            cmd.extend(command)
254        else:
255            command = command.strip()
256            cmd.extend(command.split(" "))
257        result = exec_cmd(cmd, timeout, error_print, join_result)
258        if not result:
259            return result
260        for line in str(result).split("\n"):
261            if line.strip():
262                LOG.debug(line.strip())
263        return result
264
265    @perform_device_action
266    def execute_shell_command(self, command, timeout=TIMEOUT,
267                              receiver=None, **kwargs):
268        if not receiver:
269            collect_receiver = CollectingOutputReceiver()
270            HdcHelper.execute_shell_command(
271                self, command, timeout=timeout,
272                receiver=collect_receiver, **kwargs)
273            return collect_receiver.output
274        else:
275            return HdcHelper.execute_shell_command(
276                self, command, timeout=timeout,
277                receiver=receiver, **kwargs)
278
279    def execute_shell_cmd_background(self, command, timeout=TIMEOUT,
280                                     receiver=None):
281        status = HdcHelper.execute_shell_command(self, command,
282                                                 timeout=timeout,
283                                                 receiver=receiver)
284
285        self.wait_for_device_not_available(DEFAULT_UNAVAILABLE_TIMEOUT)
286        self.device_state_monitor.wait_for_device_available(BACKGROUND_TIME)
287        cmd = "target mount"
288        self.connector_command(cmd)
289        self.device_log_collector.restart_catch_device_log()
290        return status
291
292    def wait_for_device_not_available(self, wait_time):
293        return self.device_state_monitor.wait_for_device_not_available(
294            wait_time)
295
296    def _wait_for_device_online(self, wait_time=None):
297        return self.device_state_monitor.wait_for_device_online(wait_time)
298
299    def _do_reboot(self):
300        HdcHelper.reboot(self)
301        self.wait_for_boot_completion()
302
303    def _reboot_until_online(self):
304        self._do_reboot()
305        self._wait_for_device_online()
306
307    def reboot(self):
308        self._reboot_until_online()
309        self.device_state_monitor.wait_for_device_available(
310            self.reboot_timeout)
311        self.enable_hdc_root()
312        self.device_log_collector.restart_catch_device_log()
313
314    @perform_device_action
315    def install_package(self, package_path, command=""):
316        if package_path is None:
317            raise HdcError(
318                "install package: package path cannot be None!")
319        return HdcHelper.install_package(self, package_path, command)
320
321    @perform_device_action
322    def uninstall_package(self, package_name):
323        return HdcHelper.uninstall_package(self, package_name)
324
325    @perform_device_action
326    def push_file(self, local, remote, **kwargs):
327        """
328        Push a single file.
329        The top directory won't be created if is_create is False (by default)
330        and vice versa
331        """
332        local = "\"{}\"".format(local)
333        remote = "\"{}\"".format(remote)
334        if local is None:
335            raise HdcError("XDevice Local path cannot be None!")
336
337        remote_is_dir = kwargs.get("remote_is_dir", False)
338        if remote_is_dir:
339            ret = self.execute_shell_command("test -d %s && echo 0" % remote)
340            if not (ret != "" and len(str(ret).split()) != 0 and
341                    str(ret).split()[0] == "0"):
342                self.execute_shell_command("mkdir -p %s" % remote)
343
344        if self.host != "127.0.0.1":
345            self.connector_command("file send {} {}".format(local, remote))
346        else:
347            is_create = kwargs.get("is_create", False)
348            timeout = kwargs.get("timeout", TIMEOUT)
349            HdcHelper.push_file(self, local, remote, is_create=is_create,
350                                timeout=timeout)
351        if not self.is_file_exist(remote):
352            LOG.error("Push %s to %s failed" % (local, remote))
353            raise HdcError("push %s to %s failed" % (local, remote))
354
355    @perform_device_action
356    def pull_file(self, remote, local, **kwargs):
357        """
358        Pull a single file.
359        The top directory won't be created if is_create is False (by default)
360        and vice versa
361        """
362        local = "\"{}\"".format(local)
363        remote = "\"{}\"".format(remote)
364        if self.host != "127.0.0.1":
365            self.connector_command("file recv {} {}".format(remote, local))
366        else:
367            is_create = kwargs.get("is_create", False)
368            timeout = kwargs.get("timeout", TIMEOUT)
369            HdcHelper.pull_file(self, remote, local, is_create=is_create,
370                                timeout=timeout)
371
372    def enable_hdc_root(self):
373        return True
374
375    def is_directory(self, path):
376        path = check_path_legal(path)
377        output = self.execute_shell_command("ls -ld {}".format(path))
378        if output and output.startswith('d'):
379            return True
380        return False
381
382    def is_file_exist(self, file_path):
383        file_path = check_path_legal(file_path)
384        output = self.execute_shell_command("ls {}".format(file_path))
385        if output and "No such file or directory" not in output:
386            return True
387        return False
388
389    def get_recover_result(self, retry=RETRY_ATTEMPTS):
390        command = "param get bootevent.boot.completed"
391        stdout = self.execute_shell_command(command, timeout=5 * 1000,
392                                            output_flag=False, retry=retry,
393                                            abort_on_exception=True).strip()
394        if stdout:
395            LOG.debug(stdout)
396        return stdout
397
398    def set_recover_state(self, state):
399        with self.device_lock:
400            setattr(self, ConfigConst.recover_state, state)
401            if not state:
402                self.test_device_state = TestDeviceState.NOT_AVAILABLE
403                self.device_allocation_state = DeviceAllocationState.unavailable
404
405    def get_recover_state(self, default_state=True):
406        with self.device_lock:
407            state = getattr(self, ConfigConst.recover_state, default_state)
408            return state
409
410    def close(self):
411        self.reconnecttimes = 0
412
413    def reset(self):
414        self.log.debug("start stop rpc")
415        if self._proxy is not None:
416            self._proxy.close()
417        self._proxy = None
418        self.remove_ports()
419        self.stop_harmony_rpc()
420        self.device_log_collector.stop_restart_catch_device_log()
421
422    @property
423    def proxy(self):
424        """The first rpc session initiated on this device. None if there isn't
425        one.
426        """
427        try:
428            if self._proxy is None:
429                self._proxy = self.get_harmony()
430        except Exception as error:
431            self._proxy = None
432            self.log.error("DeviceTest-10012 proxy:%s" % str(error))
433        return self._proxy
434
435    @property
436    def uitestdeamon(self):
437        from devicetest.controllers.uitestdeamon import \
438            UiTestDeamon
439        if self._uitestdeamon is None:
440            self._uitestdeamon = UiTestDeamon(self)
441        return self._uitestdeamon
442
443    @classmethod
444    def set_module_package(cls, module_packag):
445        cls.module_package = module_packag
446
447    @classmethod
448    def set_moudle_ablity_name(cls, module_ablity_name):
449        cls.module_ablity_name = module_ablity_name
450
451    @property
452    def is_harmony(self):
453        if self._is_harmony is not None:
454            return self._is_harmony
455        oh_version = self.execute_shell_command("param get const.product.software.version")
456        self.log.debug("is_harmony, OpenHarmony verison :{}".format(oh_version))
457        self._is_harmony = True
458        return self._is_harmony
459
460    def get_harmony(self):
461        if self.initdevice:
462            self.start_harmony_rpc(re_install_rpc=True)
463        self._h_port = self.get_local_port()
464        cmd = "fport tcp:{} tcp:{}".format(
465            self._h_port, self.d_port)
466        self.connector_command(cmd)
467        self.log.info(
468            "get_proxy d_port:{} {}".format(self._h_port, self.d_port))
469        try:
470            from devicetest.controllers.openharmony import OpenHarmony
471            self._proxy = OpenHarmony(port=self._h_port, addr=self.host, device=self)
472        except Exception as error:
473            self.log.error(' proxy init error: {}.'.format(str(error)))
474        return self._proxy
475
476    def start_uitest(self):
477        share_mem_mode = False
478        # uitest基础版本号,比该版本号大的用共享内存方式进行启动
479        base_version = [3, 2, 2, 2]
480        uitest_version = self.execute_shell_command("{} --version".format(UITEST_PATH))
481        if uitest_version and re.match(r'^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', uitest_version):
482            uitest_version = uitest_version.split(".")
483            for index, _ in enumerate(uitest_version):
484                if int(uitest_version[index]) > base_version[index]:
485                    share_mem_mode = True
486                    break
487        result = ""
488        if share_mem_mode:
489            if not self.is_file_exist(UITEST_SHMF):
490                self.log.debug('Path {} not exist, create it.'.format(UITEST_SHMF))
491                self.execute_shell_command("echo abc > {}".format(UITEST_SHMF))
492                self.execute_shell_command("chmod -R 666 {}".format(UITEST_SHMF))
493            result = self.execute_shell_command("{} start-daemon {} &".format(UITEST_PATH, UITEST_SHMF))
494        else:
495            result = self.execute_shell_command(UITEST_COMMAND)
496        self.log.debug('start uitest, {}'.format(result))
497
498    def start_harmony_rpc(self, port=8080, re_install_rpc=False):
499        if hasattr(sys, ConfigConst.env_pool_cache) \
500                and getattr(sys, ConfigConst.env_pool_cache, False) \
501                and self.is_harmony_rpc_running():
502            self.log.debug('harmony rpc is running')
503            return
504        from devicetest.core.error_message import ErrorMessage
505        if re_install_rpc:
506            try:
507                from devicetest.controllers.openharmony import OpenHarmony
508                OpenHarmony.install_harmony_rpc(self)
509            except (ModuleNotFoundError, ImportError) as error:  # pylint:disable=undefined-variable
510                self.log.debug(str(error))
511                self.log.error('please check devicetest extension module is exist.')
512                raise Exception(ErrorMessage.Error_01437.Topic)
513            except Exception as error:
514                self.log.debug(str(error))
515                self.log.error('root device init RPC error.')
516                raise Exception(ErrorMessage.Error_01437.Topic)
517        self.stop_harmony_rpc()
518        cmd = "aa start -a {}.ServiceAbility -b {}".format(DEVICETEST_HAP_PACKAGE_NAME, DEVICETEST_HAP_PACKAGE_NAME)
519        result = self.execute_shell_command(cmd)
520        self.log.debug('start devicetest ability, {}'.format(result))
521        self.start_uitest()
522        time.sleep(1)
523        if not self.is_harmony_rpc_running():
524            raise Exception("harmony rpc not running")
525
526    def stop_harmony_rpc(self):
527        # 杀掉uitest和devicetest
528        self.kill_all_uitest()
529        self.kill_devicetest_agent()
530
531    def is_harmony_rpc_running(self):
532        if hasattr(self, "oh_type") and getattr(self, "oh_type") == "other":
533            bundle_name = DEVICETEST_HAP_PACKAGE_NAME
534        else:
535            # 由于RK上有字段截断问题,因此做出该适配
536            bundle_name = "com.ohos.device"
537        agent_pid = get_device_proc_pid(device=self, proc_name=bundle_name, double_check=True)
538        uitest_pid = get_device_proc_pid(device=self, proc_name=UITEST_NAME, double_check=True)
539        self.log.debug('is_proc_running: agent pid: {}, uitest pid: {}'.format(agent_pid, uitest_pid))
540        if agent_pid != "" and agent_pid != "":
541            return True
542        return False
543
544    def kill_all_uitest(self):
545        uitest_pid = get_device_proc_pid(device=self, proc_name=UITEST_NAME, double_check=True)
546        self.log.debug('is_proc_running: uitest pid: {}'.format(uitest_pid))
547        if uitest_pid != "":
548            cmd = 'kill %s' % uitest_pid
549            self.execute_shell_command(cmd)
550
551    def kill_devicetest_agent(self):
552        if hasattr(self, "oh_type") and getattr(self, "oh_type") == "other":
553            bundle_name = DEVICETEST_HAP_PACKAGE_NAME
554        else:
555            # 由于RK上有字段截断问题,因此做出该适配
556            bundle_name = "com.ohos.device"
557        agent_pid = get_device_proc_pid(device=self, proc_name=bundle_name, double_check=True)
558        self.log.debug('is_proc_running: agent_pid pid: {}'.format(agent_pid))
559        if agent_pid != "":
560            cmd = 'kill %s' % agent_pid
561            self.execute_shell_command(cmd)
562
563    def install_app(self, remote_path, command):
564        try:
565            ret = self.execute_shell_command(
566                "pm install %s %s" % (command, remote_path))
567            if ret is not None and str(
568                    ret) != "" and "Unknown option: -g" in str(ret):
569                return self.execute_shell_command(
570                    "pm install -r %s" % remote_path)
571            return ret
572        except Exception as error:
573            self.log.error("%s, maybe there has a warning box appears "
574                           "when installing RPC." % error)
575            return False
576
577    def uninstall_app(self, package_name):
578        try:
579            ret = self.execute_shell_command("pm uninstall %s" % package_name)
580            self.log.debug(ret)
581            return ret
582        except Exception as err:
583            self.log.error('DeviceTest-20013 uninstall: %s' % str(err))
584            return False
585
586    def reconnect(self, waittime=60):
587        '''
588        @summary: Reconnect the device.
589        '''
590        if not self.is_harmony:
591            if not self.wait_for_boot_completion(waittime):
592                raise Exception("Reconnect timed out.")
593
594        if self._proxy:
595            self.start_harmony_rpc(re_install_rpc=True)
596            self._h_port = self.get_local_port()
597            cmd = "fport tcp:{} tcp:{}".format(
598                self._h_port, self.d_port)
599            self.connector_command(cmd)
600            try:
601                self._proxy.init(port=self._h_port, addr=self.host, device=self)
602            except Exception as _:
603                time.sleep(3)
604                self._proxy.init(port=self._h_port, addr=self.host, device=self)
605            finally:
606                if self._uitestdeamon is not None:
607                    self._uitestdeamon.init(self)
608
609        if self._proxy:
610            return self._proxy
611        return None
612
613    def wait_for_boot_completion(self, waittime=60 * 15, reconnect=False):
614        """Waits for the device to boot up.
615
616        Returns:
617            True if the device successfully finished booting, False otherwise.
618        """
619        if not self.wait_for_device_not_available(
620                DEFAULT_UNAVAILABLE_TIMEOUT):
621            LOG.error("Did not detect device {} becoming unavailable "
622                      "after reboot".format(convert_serial(self.device_sn)))
623        self._wait_for_device_online()
624        self.device_state_monitor.wait_for_device_available(
625            self.reboot_timeout)
626        return True
627
628    def get_local_port(self):
629        from devicetest.utils.util import get_forward_port
630        host = self.host
631        port = None
632        h_port = get_forward_port(self, host, port)
633        self.forward_ports.append(h_port)
634        self.log.info(
635            "tcp forward port: %s for %s*******" % (str(h_port),
636                                                    self.device_sn[0:4]))
637        return h_port
638
639    def remove_ports(self):
640        if self._uitestdeamon is not None:
641            self._uitestdeamon = None
642        for port in self.forward_ports:
643            cmd = "fport rm tcp:{} tcp:{}".format(
644                port, self.d_port)
645            self.connector_command(cmd)
646        self.forward_ports.clear()
647
648    @classmethod
649    def check_recover_result(cls, recover_result):
650        return "true" in recover_result
651
652    def take_picture(self, name):
653        '''
654        @summary: 截取手机屏幕图片并保存
655        @param  name: 保存的图片名称,通过getTakePicturePath方法获取保存全路径
656        '''
657        path = ""
658        try:
659            temp_path = os.path.join(self._device_log_path, "temp")
660            if not os.path.exists(temp_path):
661                os.makedirs(temp_path)
662            path = os.path.join(temp_path, name)
663            picture_name = os.path.basename(name)
664            out = self.execute_shell_command(
665                "snapshot_display -f /data/local/tmp/{}".format(picture_name))
666            self.log.debug("result: {}".format(out))
667            if "error" in out and "success" not in out:
668                return False
669            else:
670                self.pull_file("/data/local/tmp/{}".format(picture_name), path)
671        except Exception as error:
672            self.log.error("devicetest take_picture: {}".format(str(error)))
673        return path
674
675    def execute_shell_in_daemon(self, command):
676        if self.host != "127.0.0.1":
677            cmd = [HdcHelper.CONNECTOR_NAME, "-s", "{}:{}".format(
678                self.host, self.port), "-t", self.device_sn, "shell"]
679        else:
680            cmd = [HdcHelper.CONNECTOR_NAME, "-t", self.device_sn, "shell"]
681        LOG.debug("{} execute command {} {} in daemon".format(
682            convert_serial(self.device_sn), HdcHelper.CONNECTOR_NAME, command))
683        if isinstance(command, list):
684            cmd.extend(command)
685        else:
686            command = command.strip()
687            cmd.extend(command.split(" "))
688        sys_type = platform.system()
689        process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
690                                   shell=False,
691                                   preexec_fn=None if sys_type == "Windows"
692                                   else os.setsid,
693                                   close_fds=True)
694        return process
695
696    @property
697    def device_log_collector(self):
698        if self._device_log_collector is None:
699            self._device_log_collector = DeviceLogCollector(self)
700        return self._device_log_collector
701
702    def set_device_report_path(self, path):
703        self._device_log_path = path
704
705    def get_device_report_path(self):
706        return self._device_log_path
707
708
709class DeviceLogCollector:
710    hilog_file_address = []
711    log_file_address = []
712    device = None
713    restart_proc = []
714
715    def __init__(self, device):
716        self.device = device
717
718    def restart_catch_device_log(self):
719        from xdevice import FilePermission
720        for _, path in enumerate(self.hilog_file_address):
721            hilog_open = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND,
722                                 FilePermission.mode_755)
723            with os.fdopen(hilog_open, "a") as hilog_file_pipe:
724                _, proc = self.start_catch_device_log(hilog_file_pipe=hilog_file_pipe)
725                self.restart_proc.append(proc)
726
727    def stop_restart_catch_device_log(self):
728        # when device free stop restart log proc
729        for _, proc in enumerate(self.restart_proc):
730            self.stop_catch_device_log(proc)
731        self.restart_proc.clear()
732        self.hilog_file_address.clear()
733        self.log_file_address.clear()
734
735    def start_catch_device_log(self, log_file_pipe=None,
736                               hilog_file_pipe=None):
737        """
738        Starts hdc log for each device in separate subprocesses and save
739        the logs in files.
740        """
741        self._sync_device_time()
742        device_hilog_proc = None
743        if hilog_file_pipe:
744            command = "hilog"
745            if self.device.host != "127.0.0.1":
746                cmd = [HdcHelper.CONNECTOR_NAME, "-s", "{}:{}".format(self.device.host, self.device.port),
747                       "-t", self.device.device_sn, "shell", command]
748            else:
749                cmd = [HdcHelper.CONNECTOR_NAME, "-t", self.device.device_sn, "shell", command]
750            LOG.info("execute command: %s" % " ".join(cmd).replace(
751                self.device.device_sn, convert_serial(self.device.device_sn)))
752            device_hilog_proc = start_standing_subprocess(
753                cmd, hilog_file_pipe)
754        return None, device_hilog_proc
755
756    def stop_catch_device_log(self, proc):
757        """
758        Stops all hdc log subprocesses.
759        """
760        if proc:
761            stop_standing_subprocess(proc)
762            self.device.log.debug("Stop catch device hilog.")
763
764    def start_hilog_task(self, log_size="50M"):
765        self._sync_device_time()
766        self.clear_crash_log()
767        # 先停止一下
768        cmd = "hilog -w stop"
769        out = self.device.execute_shell_command(cmd)
770        # 清空日志
771        cmd = "hilog -r"
772        out = self.device.execute_shell_command(cmd)
773        cmd = "rm -rf /data/log/hilog/*"
774        out = self.device.execute_shell_command(cmd)
775        # 开始日志任务 设置落盘文件个数最大值1000, 单个文件20M,链接https://gitee.com/openharmony/hiviewdfx_hilog
776        cmd = "hilog -w start -l {} -n 1000".format(log_size)
777        out = self.device.execute_shell_command(cmd)
778        LOG.info("Execute command: {}, result is {}".format(cmd, out))
779
780    def stop_hilog_task(self, log_name):
781        cmd = "hilog -w stop"
782        out = self.device.execute_shell_command(cmd)
783        self.device.pull_file("/data/log/hilog/", "{}/log/".format(self.device.get_device_report_path()))
784        try:
785            os.rename("{}/log/hilog".format(self.device.get_device_report_path()),
786                      "{}/log/{}_hilog".format(self.device.get_device_report_path(), log_name))
787        except Exception as e:
788            self.device.log.warning("Rename hilog folder {}_hilog failed. error: {}".format(log_name, e))
789            # 把hilog文件夹下所有文件拉出来 由于hdc不支持整个文件夹拉出只能采用先压缩再拉取文件
790            cmd = "cd /data/log/hilog && tar -zcvf /data/log/{}_hilog.tar.gz *".format(log_name)
791            out = self.device.execute_shell_command(cmd)
792            LOG.info("Execute command: {}, result is {}".format(cmd, out))
793            if "No space left on device" not in out:
794                self.device.pull_file("/data/log/{}_hilog.tar.gz".format(log_name),
795                                      "{}/log/".format(self.device.get_device_report_path()))
796                cmd = "rm -rf /data/log/{}_hilog.tar.gz".format(log_name)
797                out = self.device.execute_shell_command(cmd)
798        # 获取crash日志
799        self.start_get_crash_log(log_name)
800
801    def _get_log(self, log_cmd, *params):
802        def filter_by_name(log_name, args):
803            for starts_name in args:
804                if log_name.startswith(starts_name):
805                    return True
806            return False
807
808        data_list = list()
809        log_name_array = list()
810        log_result = self.device.execute_shell_command(log_cmd)
811        if log_result is not None and len(log_result) != 0:
812            log_name_array = log_result.strip().replace("\r", "").split("\n")
813        for log_name in log_name_array:
814            log_name = log_name.strip()
815            if len(params) == 0 or \
816                    filter_by_name(log_name, params):
817                data_list.append(log_name)
818        return data_list
819
820    def get_cur_crash_log(self, crash_path, log_name):
821        log_name_map = {'cppcrash': NATIVE_CRASH_PATH,
822                        "jscrash": JS_CRASH_PATH,
823                        "SERVICE_BLOCK": ROOT_PATH,
824                        "appfreeze": ROOT_PATH}
825        if not os.path.exists(crash_path):
826            os.makedirs(crash_path)
827        if "Not support std mode" in log_name:
828            return
829
830        def get_log_path(logname):
831            name_array = logname.split("-")
832            if len(name_array) <= 1:
833                return ROOT_PATH
834            return log_name_map.get(name_array[0])
835
836        log_path = get_log_path(log_name)
837        temp_path = "%s/%s" % (log_path, log_name)
838        self.device.pull_file(temp_path, crash_path)
839        LOG.debug("Finish pull file: %s" % log_name)
840
841    def start_get_crash_log(self, task_name):
842        log_array = list()
843        native_crash_cmd = "ls {}".format(NATIVE_CRASH_PATH)
844        js_crash_cmd = '"ls {} | grep jscrash"'.format(JS_CRASH_PATH)
845        block_crash_cmd = '"ls {}"'.format(ROOT_PATH)
846        # 获取crash日志文件
847        log_array.extend(self._get_log(native_crash_cmd, "cppcrash"))
848        log_array.extend(self._get_log(js_crash_cmd, "jscrash"))
849        log_array.extend(self._get_log(block_crash_cmd, "SERVICE_BLOCK", "appfreeze"))
850        LOG.debug("crash log file {}, length is {}".format(str(log_array), str(len(log_array))))
851        crash_path = "{}/log/{}_crash_log/".format(self.device.get_device_report_path(), task_name)
852        for log_name in log_array:
853            log_name = log_name.strip()
854            self.get_cur_crash_log(crash_path, log_name)
855
856    def clear_crash_log(self):
857        clear_block_crash_cmd = "rm -f {}/*".format(ROOT_PATH)
858        clear_native_crash_cmd = "rm -f {}/*".format(NATIVE_CRASH_PATH)
859        clear_debug_crash_cmd = "rm -f {}/debug/*".format(ROOT_PATH)
860        clear_js_crash_cmd = "rm -f {}/*".format(JS_CRASH_PATH)
861        self.device.execute_shell_command(clear_block_crash_cmd)
862        self.device.execute_shell_command(clear_native_crash_cmd)
863        self.device.execute_shell_command(clear_debug_crash_cmd)
864        self.device.execute_shell_command(clear_js_crash_cmd)
865
866    def _sync_device_time(self):
867        # 先同步PC和设备的时间
868        iso_time_format = '%Y-%m-%d %H:%M:%S'
869        cur_time = get_cst_time().strftime(iso_time_format)
870        self.device.execute_shell_command("date '{}'".format(cur_time))
871        self.device.execute_shell_command("hwclock --systohc")
872
873    def add_log_address(self, log_file_address, hilog_file_address):
874        # record to restart catch log when reboot device
875        if log_file_address:
876            self.log_file_address.append(log_file_address)
877        if hilog_file_address:
878            self.hilog_file_address.append(hilog_file_address)
879
880    def remove_log_address(self, log_file_address, hilog_file_address):
881        if log_file_address:
882            self.log_file_address.remove(log_file_address)
883        if hilog_file_address:
884            self.hilog_file_address.remove(hilog_file_address)
885