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