1#!/usr/bin/env python3 2# coding=utf-8 3 4# 5# Copyright (c) 2021 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 threading 20 21from xdevice import DeviceOsType 22from xdevice import ProductForm 23from xdevice import ReportException 24from xdevice import IDevice 25from xdevice import platform_logger 26from xdevice import Plugin 27from xdevice import exec_cmd 28from xdevice import ConfigConst 29 30from xdevice_extension._core import utils 31from xdevice_extension._core.environment.dmlib import HdcHelper 32from xdevice_extension._core.exception import HdcError 33from xdevice_extension._core.environment.dmlib import CollectingOutputReceiver 34from xdevice_extension._core.environment.device_state import \ 35 DeviceAllocationState 36from xdevice_extension._core.utils import check_path_legal 37from xdevice_extension._core.utils import convert_serial 38from xdevice_extension._core.constants import DeviceConnectorType 39 40__all__ = ["Device"] 41TIMEOUT = 90 * 1000 42RETRY_ATTEMPTS = 2 43DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000 44BACKGROUND_TIME = 2 * 60 * 1000 45LOG = platform_logger("Device") 46 47 48def perform_device_action(func): 49 def device_action(self, *args, **kwargs): 50 if not self.get_recover_state(): 51 LOG.debug("device %s %s is false" % (self.device_sn, 52 ConfigConst.recover_state)) 53 return 54 # avoid infinite recursion, such as device reboot 55 abort_on_exception = bool(kwargs.get("abort_on_exception", False)) 56 if abort_on_exception: 57 result = func(self, *args, **kwargs) 58 return result 59 60 tmp = int(kwargs.get("retry", RETRY_ATTEMPTS)) 61 retry = tmp + 1 if tmp > 0 else 1 62 exception = None 63 for _ in range(retry): 64 try: 65 result = func(self, *args, **kwargs) 66 return result 67 except ReportException as error: 68 self.log.exception("Generate report error!", exc_info=False) 69 exception = error 70 except (ConnectionResetError, ConnectionRefusedError) as error: 71 self.log.error("error type: %s, error: %s" % 72 (error.__class__.__name__, error)) 73 if self.usb_type == DeviceConnectorType.hdc: 74 cmd = "hdc reset" 75 self.log.info("re-execute hdc reset") 76 exec_cmd(cmd) 77 if not self.recover_device(): 78 LOG.debug("set device %s %s false" % ( 79 self.device_sn, ConfigConst.recover_state)) 80 self.set_recover_state(False) 81 raise error 82 exception = error 83 except HdcError as error: 84 self.log.error("error type: %s, error: %s" % 85 (error.__class__.__name__, error)) 86 if not self.recover_device(): 87 LOG.debug("set device %s %s false" % ( 88 self.device_sn, ConfigConst.recover_state)) 89 self.set_recover_state(False) 90 raise error 91 exception = error 92 except Exception as error: 93 self.log.exception("error type: %s, error: %s" % ( 94 error.__class__.__name__, error), exc_info=False) 95 exception = error 96 97 return device_action 98 99 100@Plugin(type=Plugin.DEVICE, id=DeviceOsType.default) 101class Device(IDevice): 102 """ 103 Class representing a device. 104 105 Each object of this class represents one device in xDevice, 106 including handles to hdc, fastboot, and test agent. 107 108 Attributes: 109 device_sn: A string that's the serial number of the device. 110 """ 111 112 device_sn = None 113 host = None 114 port = None 115 usb_type = None 116 is_timeout = False 117 device_hilog_proc = None 118 device_os_type = DeviceOsType.default 119 test_device_state = None 120 device_allocation_state = DeviceAllocationState.available 121 label = None 122 log = platform_logger("Device") 123 device_state_monitor = None 124 reboot_timeout = 2 * 60 * 1000 125 hilog_file_pipe = None 126 127 model_dict = { 128 'default': ProductForm.phone, 129 'car': ProductForm.car, 130 'tv': ProductForm.television, 131 'watch': ProductForm.watch, 132 'tablet': ProductForm.tablet 133 } 134 135 def __init__(self): 136 self.extend_value = {} 137 self.device_lock = threading.RLock() 138 139 def __eq__(self, other): 140 return self.device_sn == other.__get_serial__() and \ 141 self.device_os_type == other.device_os_type 142 143 def __set_serial__(self, device_sn=""): 144 self.device_sn = device_sn 145 return self.device_sn 146 147 def __get_serial__(self): 148 return self.device_sn 149 150 def get(self, key=None, default=None): 151 if not key: 152 return default 153 value = getattr(self, key, None) 154 if value: 155 return value 156 else: 157 return self.extend_value.get(key, default) 158 159 def recover_device(self): 160 if not self.get_recover_state(): 161 LOG.debug("device %s %s is false, cannot recover device" % ( 162 self.device_sn, ConfigConst.recover_state)) 163 return 164 165 LOG.debug("wait device %s to recover" % self.device_sn) 166 return self.device_state_monitor.wait_for_device_available() 167 168 def get_device_type(self): 169 self.label = self.model_dict.get("default", None) 170 171 def get_property(self, prop_name, retry=RETRY_ATTEMPTS, 172 abort_on_exception=False): 173 """ 174 Hdc command, ddmlib function. 175 """ 176 command = "param get %s" % prop_name 177 stdout = self.execute_shell_command( 178 command, timeout=5 * 1000, output_flag=False, retry=retry, 179 abort_on_exception=abort_on_exception).strip() 180 if stdout: 181 LOG.debug(stdout) 182 return stdout 183 184 @perform_device_action 185 def hdc_command(self, command, **kwargs): 186 timeout = int(kwargs.get("timeout", TIMEOUT)) / 1000 187 error_print = bool(kwargs.get("error_print", True)) 188 join_result = bool(kwargs.get("join_result", False)) 189 timeout_msg = '' if timeout == 300.0 else \ 190 " with timeout %ss" % timeout 191 if self.usb_type == DeviceConnectorType.hdc: 192 LOG.debug("%s execute command hdc %s%s" % ( 193 convert_serial(self.device_sn), command, timeout_msg)) 194 cmd = ["hdc_std", "-t", self.device_sn] 195 if isinstance(command, list): 196 cmd.extend(command) 197 else: 198 command = command.strip() 199 cmd.extend(command.split(" ")) 200 result = exec_cmd(cmd, timeout, error_print, join_result) 201 if not result: 202 return result 203 for line in str(result).split("\n"): 204 if line.strip(): 205 LOG.debug(line.strip()) 206 return result 207 208 @perform_device_action 209 def execute_shell_command(self, command, timeout=TIMEOUT, 210 receiver=None, **kwargs): 211 if not receiver: 212 collect_receiver = CollectingOutputReceiver() 213 HdcHelper.execute_shell_command( 214 self, command, timeout=timeout, 215 receiver=collect_receiver, **kwargs) 216 return collect_receiver.output 217 else: 218 return HdcHelper.execute_shell_command( 219 self, command, timeout=timeout, 220 receiver=receiver, **kwargs) 221 222 def execute_shell_cmd_background(self, command, timeout=TIMEOUT, 223 receiver=None): 224 status = HdcHelper.execute_shell_command(self, command, 225 timeout=timeout, 226 receiver=receiver) 227 228 self.wait_for_device_not_available(DEFAULT_UNAVAILABLE_TIMEOUT) 229 self.device_state_monitor.wait_for_device_available(BACKGROUND_TIME) 230 cmd = "target mount" \ 231 if self.usb_type == DeviceConnectorType.hdc else "remount" 232 self.hdc_command(cmd) 233 self.start_catch_device_log() 234 return status 235 236 def wait_for_device_not_available(self, wait_time): 237 return self.device_state_monitor.wait_for_device_not_available( 238 wait_time) 239 240 def _wait_for_device_online(self, wait_time=None): 241 return self.device_state_monitor.wait_for_device_online(wait_time) 242 243 def _do_reboot(self): 244 HdcHelper.reboot(self) 245 if not self.wait_for_device_not_available( 246 DEFAULT_UNAVAILABLE_TIMEOUT): 247 LOG.error("Did not detect device {} becoming unavailable " 248 "after reboot".format(convert_serial(self.device_sn))) 249 250 def _reboot_until_online(self): 251 self._do_reboot() 252 self._wait_for_device_online() 253 254 def reboot(self): 255 self._reboot_until_online() 256 self.device_state_monitor.wait_for_device_available( 257 self.reboot_timeout) 258 self.enable_hdc_root() 259 self.start_catch_device_log() 260 261 @perform_device_action 262 def install_package(self, package_path, command=""): 263 if package_path is None: 264 raise HdcError( 265 "install package: package path cannot be None!") 266 return HdcHelper.install_package(self, package_path, command) 267 268 @perform_device_action 269 def uninstall_package(self, package_name): 270 return HdcHelper.uninstall_package(self, package_name) 271 272 @perform_device_action 273 def push_file(self, local, remote, **kwargs): 274 """ 275 Push a single file. 276 The top directory won't be created if is_create is False (by default) 277 and vice versa 278 """ 279 if local is None: 280 raise HdcError("XDevice Local path cannot be None!") 281 282 remote_is_dir = kwargs.get("remote_is_dir", False) 283 if remote_is_dir: 284 ret = self.execute_shell_command("test -d %s && echo 0" % remote) 285 if not (ret != "" and len(str(ret).split()) != 0 and 286 str(ret).split()[0] == "0"): 287 self.execute_shell_command("mkdir -p %s" % remote) 288 289 is_create = kwargs.get("is_create", False) 290 timeout = kwargs.get("timeout", TIMEOUT) 291 HdcHelper.push_file(self, local, remote, is_create=is_create, 292 timeout=timeout) 293 294 @perform_device_action 295 def pull_file(self, remote, local, **kwargs): 296 """ 297 Pull a single file. 298 The top directory won't be created if is_create is False (by default) 299 and vice versa 300 """ 301 302 is_create = kwargs.get("is_create", False) 303 timeout = kwargs.get("timeout", TIMEOUT) 304 HdcHelper.pull_file(self, remote, local, is_create=is_create, 305 timeout=timeout) 306 307 def is_hdc_root(self): 308 output = self.execute_shell_command("id") 309 return "uid=0(root)" in output 310 311 def enable_hdc_root(self): 312 if self.is_hdc_root(): 313 return True 314 for index in range(3): 315 cmd = "smode" \ 316 if self.usb_type == DeviceConnectorType.hdc else "root" 317 output = self.hdc_command(cmd) 318 if self.is_hdc_root(): 319 return True 320 LOG.debug( 321 "hdc root on %s unsuccessful on attempt %d. " 322 "Output: %s" % ( 323 convert_serial(self.device_sn), index, output)) 324 return False 325 326 def is_directory(self, path): 327 path = check_path_legal(path) 328 output = self.execute_shell_command("ls -ld {}".format(path)) 329 if output and output.startswith('d'): 330 return True 331 return False 332 333 def is_file_exist(self, file_path): 334 file_path = check_path_legal(file_path) 335 output = self.execute_shell_command("ls {}".format(file_path)) 336 if output and "No such file or directory" not in output: 337 return True 338 return False 339 340 def start_catch_device_log(self, hilog_file_pipe=None): 341 """ 342 Starts hdc log for each device in separate subprocesses and save 343 the logs in files. 344 """ 345 if hilog_file_pipe: 346 self.hilog_file_pipe = hilog_file_pipe 347 self._start_catch_device_log() 348 349 def stop_catch_device_log(self): 350 """ 351 Stops all hdc log subprocesses. 352 """ 353 self._stop_catch_device_log() 354 355 def _start_catch_device_log(self): 356 if self.hilog_file_pipe: 357 command = "hilog" 358 if self.usb_type == DeviceConnectorType.hdc: 359 cmd = ['hdc_std', "-t", self.device_sn, "shell", command] 360 LOG.info("execute command: %s" % " ".join(cmd).replace( 361 self.device_sn, convert_serial(self.device_sn))) 362 self.device_hilog_proc = utils.start_standing_subprocess( 363 cmd, self.hilog_file_pipe) 364 365 def _stop_catch_device_log(self): 366 if self.device_hilog_proc: 367 utils.stop_standing_subprocess(self.device_hilog_proc) 368 self.device_hilog_proc = None 369 self.hilog_file_pipe = None 370 371 def get_recover_result(self, retry=RETRY_ATTEMPTS): 372 command = "param get sys.boot_completed" 373 stdout = self.execute_shell_command(command, timeout=5 * 1000, 374 output_flag=False, retry=retry, 375 abort_on_exception=True).strip() 376 if stdout: 377 LOG.debug(stdout) 378 return stdout 379 380 def set_recover_state(self, state): 381 with self.device_lock: 382 setattr(self, ConfigConst.recover_state, state) 383 384 def get_recover_state(self, default_state=True): 385 with self.device_lock: 386 state = getattr(self, ConfigConst.recover_state, default_state) 387 return state 388 389 def close(self): 390 pass 391