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