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