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