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