1#!/usr/bin/env python3 2# 3# Copyright 2019 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from abc import ABC 18from datetime import datetime 19import inspect 20import logging 21import os 22import pathlib 23import shutil 24import signal 25import socket 26import subprocess 27import time 28from typing import List 29 30import grpc 31 32from acts import asserts 33from acts import utils 34from acts.context import get_current_context 35from acts.controllers.adb import AdbProxy 36from acts.controllers.adb import AdbError 37from acts.controllers.adb_lib.error import AdbCommandError 38 39from google.protobuf import empty_pb2 as empty_proto 40 41from cert.async_subprocess_logger import AsyncSubprocessLogger 42from cert.logging_client_interceptor import LoggingClientInterceptor 43from cert.os_utils import get_gd_root 44from cert.os_utils import read_crash_snippet_and_log_tail 45from cert.os_utils import is_subprocess_alive 46from cert.os_utils import make_ports_available 47from cert.os_utils import TerminalColor 48from facade import rootservice_pb2_grpc as facade_rootservice_pb2_grpc 49from hal import hal_facade_pb2_grpc 50from hci.facade import hci_facade_pb2_grpc 51from hci.facade import acl_manager_facade_pb2_grpc 52from hci.facade import controller_facade_pb2_grpc 53from hci.facade import le_acl_manager_facade_pb2_grpc 54from hci.facade import le_advertising_manager_facade_pb2_grpc 55from hci.facade import le_initiator_address_facade_pb2_grpc 56from hci.facade import le_scanning_manager_facade_pb2_grpc 57from l2cap.classic import facade_pb2_grpc as l2cap_facade_pb2_grpc 58from l2cap.le import facade_pb2_grpc as l2cap_le_facade_pb2_grpc 59from iso import facade_pb2_grpc as iso_facade_pb2_grpc 60from neighbor.facade import facade_pb2_grpc as neighbor_facade_pb2_grpc 61from security import facade_pb2_grpc as security_facade_pb2_grpc 62from shim.facade import facade_pb2_grpc as shim_facade_pb2_grpc 63 64MOBLY_CONTROLLER_CONFIG_NAME = "GdDevice" 65ACTS_CONTROLLER_REFERENCE_NAME = "gd_devices" 66 67 68def create(configs): 69 if not configs: 70 raise Exception("Configuration is empty") 71 elif not isinstance(configs, list): 72 raise Exception("Configuration should be a list") 73 return get_instances_with_configs(configs) 74 75 76def destroy(devices): 77 for device in devices: 78 try: 79 device.teardown() 80 except: 81 logging.exception("[%s] Failed to clean up properly due to" % device.label) 82 83 84def get_info(devices): 85 return [] 86 87 88def get_instances_with_configs(configs): 89 print(configs) 90 devices = [] 91 for config in configs: 92 resolved_cmd = [] 93 for arg in config["cmd"]: 94 logging.debug(arg) 95 resolved_cmd.append(replace_vars(arg, config)) 96 verbose_mode = bool(config.get('verbose_mode', False)) 97 if config.get("serial_number"): 98 device = GdAndroidDevice(config["grpc_port"], config["grpc_root_server_port"], config["signal_port"], 99 resolved_cmd, config["label"], MOBLY_CONTROLLER_CONFIG_NAME, config["name"], 100 config["serial_number"], verbose_mode) 101 else: 102 device = GdHostOnlyDevice(config["grpc_port"], config["grpc_root_server_port"], config["signal_port"], 103 resolved_cmd, config["label"], MOBLY_CONTROLLER_CONFIG_NAME, config["name"], 104 verbose_mode) 105 device.setup() 106 devices.append(device) 107 return devices 108 109 110def replace_vars(string, config): 111 serial_number = config.get("serial_number") 112 if serial_number is None: 113 serial_number = "" 114 rootcanal_port = config.get("rootcanal_port") 115 if rootcanal_port is None: 116 rootcanal_port = "" 117 if serial_number == "DUT" or serial_number == "CERT": 118 raise Exception("Did you forget to configure the serial number?") 119 return string.replace("$GD_ROOT", get_gd_root()) \ 120 .replace("$(grpc_port)", config.get("grpc_port")) \ 121 .replace("$(grpc_root_server_port)", config.get("grpc_root_server_port")) \ 122 .replace("$(rootcanal_port)", rootcanal_port) \ 123 .replace("$(signal_port)", config.get("signal_port")) \ 124 .replace("$(serial_number)", serial_number) 125 126 127class GdDeviceBase(ABC): 128 """ 129 Base GD device class that covers common traits which assumes that the 130 device must be driven by a driver-like backing process that takes following 131 command line arguments: 132 --grpc-port: main entry port for facade services 133 --root-server-port: management port for starting and stopping services 134 --btsnoop: path to btsnoop HCI log 135 --signal-port: signaling port to indicate that backing process is started 136 --rootcanal-port: root-canal HCI port, optional 137 """ 138 139 WAIT_CHANNEL_READY_TIMEOUT_SECONDS = 10 140 141 def __init__(self, grpc_port: str, grpc_root_server_port: str, signal_port: str, cmd: List[str], label: str, 142 type_identifier: str, name: str, verbose_mode: bool): 143 """Base GD device, common traits for both device based and host only GD 144 cert tests 145 :param grpc_port: main gRPC service port 146 :param grpc_root_server_port: gRPC root server port 147 :param signal_port: signaling port for backing process start up 148 :param cmd: list of arguments to run in backing process 149 :param label: device label used in logs 150 :param type_identifier: device type identifier used in logs 151 :param name: name of device used in logs 152 """ 153 # Must be at the first line of __init__ method 154 values = locals() 155 arguments = [values[arg] for arg in inspect.getfullargspec(GdDeviceBase.__init__).args if arg != "verbose_mode"] 156 asserts.assert_true(all(arguments), "All arguments to GdDeviceBase must not be None nor empty") 157 asserts.assert_true(all(cmd), "cmd list should not have None nor empty component") 158 self.verbose_mode = verbose_mode 159 self.grpc_root_server_port = int(grpc_root_server_port) 160 self.grpc_port = int(grpc_port) 161 self.signal_port = int(signal_port) 162 self.name = name 163 self.type_identifier = type_identifier 164 self.label = label 165 # logging.log_path only exists when this is used in an ACTS test run. 166 self.log_path_base = get_current_context().get_full_output_path() 167 self.test_runner_base_path = \ 168 get_current_context().get_base_output_path() 169 self.backing_process_log_path = os.path.join(self.log_path_base, 170 '%s_%s_backing_logs.txt' % (self.type_identifier, self.label)) 171 if "--btsnoop=" not in " ".join(cmd): 172 cmd.append("--btsnoop=%s" % os.path.join(self.log_path_base, '%s_btsnoop_hci.log' % self.label)) 173 if "--btsnooz=" not in " ".join(cmd): 174 cmd.append("--btsnooz=%s" % os.path.join(self.log_path_base, '%s_btsnooz_hci.log' % self.label)) 175 if "--btconfig=" not in " ".join(cmd): 176 cmd.append("--btconfig=%s" % os.path.join(self.log_path_base, '%s_bt_config.conf' % self.label)) 177 self.cmd = cmd 178 self.environment = os.environ.copy() 179 if "cert" in self.label: 180 self.terminal_color = TerminalColor.BLUE 181 else: 182 self.terminal_color = TerminalColor.YELLOW 183 184 def setup(self): 185 """Set up this device for test, must run before using this device 186 - After calling this, teardown() must be called when test finishes 187 - Should be executed after children classes' setup() methods 188 :return: 189 """ 190 # Ensure signal port is available 191 # signal port is the only port that always listen on the host machine 192 asserts.assert_true( 193 make_ports_available([self.signal_port]), "[%s] Failed to make signal port available" % self.label) 194 # Start backing process 195 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as signal_socket: 196 # Setup signaling socket 197 signal_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 198 signal_socket.bind(("localhost", self.signal_port)) 199 signal_socket.listen(1) 200 signal_socket.settimeout(300) # 5 minute timeout for blocking socket operations 201 202 # Start backing process 203 logging.debug("Running %s" % " ".join(self.cmd)) 204 self.backing_process = subprocess.Popen( 205 self.cmd, 206 cwd=get_gd_root(), 207 env=self.environment, 208 stdout=subprocess.PIPE, 209 stderr=subprocess.STDOUT, 210 universal_newlines=True) 211 asserts.assert_true(self.backing_process, msg="Cannot start backing_process at " + " ".join(self.cmd)) 212 asserts.assert_true( 213 is_subprocess_alive(self.backing_process), 214 msg="backing_process stopped immediately after running " + " ".join(self.cmd)) 215 216 # Wait for process to be ready 217 logging.debug("Waiting for backing_process accept.") 218 signal_socket.accept() 219 220 self.backing_process_logger = AsyncSubprocessLogger( 221 self.backing_process, [self.backing_process_log_path], 222 log_to_stdout=self.verbose_mode, 223 tag=self.label, 224 color=self.terminal_color) 225 226 # Setup gRPC management channels 227 self.grpc_root_server_channel = grpc.insecure_channel("localhost:%d" % self.grpc_root_server_port) 228 self.grpc_channel = grpc.insecure_channel("localhost:%d" % self.grpc_port) 229 230 if self.verbose_mode: 231 self.grpc_channel = grpc.intercept_channel(self.grpc_channel, LoggingClientInterceptor(self.label)) 232 233 # Establish services from facades 234 self.rootservice = facade_rootservice_pb2_grpc.RootFacadeStub(self.grpc_root_server_channel) 235 self.hal = hal_facade_pb2_grpc.HciHalFacadeStub(self.grpc_channel) 236 self.controller_read_only_property = facade_rootservice_pb2_grpc.ReadOnlyPropertyStub(self.grpc_channel) 237 self.hci = hci_facade_pb2_grpc.HciFacadeStub(self.grpc_channel) 238 self.l2cap = l2cap_facade_pb2_grpc.L2capClassicModuleFacadeStub(self.grpc_channel) 239 self.l2cap_le = l2cap_le_facade_pb2_grpc.L2capLeModuleFacadeStub(self.grpc_channel) 240 self.iso = iso_facade_pb2_grpc.IsoModuleFacadeStub(self.grpc_channel) 241 self.hci_acl_manager = acl_manager_facade_pb2_grpc.AclManagerFacadeStub(self.grpc_channel) 242 self.hci_le_acl_manager = le_acl_manager_facade_pb2_grpc.LeAclManagerFacadeStub(self.grpc_channel) 243 self.hci_le_initiator_address = le_initiator_address_facade_pb2_grpc.LeInitiatorAddressFacadeStub( 244 self.grpc_channel) 245 self.hci_controller = controller_facade_pb2_grpc.ControllerFacadeStub(self.grpc_channel) 246 self.hci_controller.GetMacAddressSimple = lambda: self.hci_controller.GetMacAddress(empty_proto.Empty()).address 247 self.hci_controller.GetLocalNameSimple = lambda: self.hci_controller.GetLocalName(empty_proto.Empty()).name 248 self.hci_le_advertising_manager = le_advertising_manager_facade_pb2_grpc.LeAdvertisingManagerFacadeStub( 249 self.grpc_channel) 250 self.hci_le_scanning_manager = le_scanning_manager_facade_pb2_grpc.LeScanningManagerFacadeStub( 251 self.grpc_channel) 252 self.neighbor = neighbor_facade_pb2_grpc.NeighborFacadeStub(self.grpc_channel) 253 self.security = security_facade_pb2_grpc.SecurityModuleFacadeStub(self.grpc_channel) 254 self.shim = shim_facade_pb2_grpc.ShimFacadeStub(self.grpc_channel) 255 256 def get_crash_snippet_and_log_tail(self): 257 if is_subprocess_alive(self.backing_process): 258 return None, None 259 260 return read_crash_snippet_and_log_tail(self.backing_process_log_path) 261 262 def teardown(self): 263 """Tear down this device and clean up any resources. 264 - Must be called after setup() 265 - Should be executed before children classes' teardown() 266 :return: 267 """ 268 self.grpc_channel.close() 269 self.grpc_root_server_channel.close() 270 stop_signal = signal.SIGINT 271 self.backing_process.send_signal(stop_signal) 272 try: 273 return_code = self.backing_process.wait(timeout=self.WAIT_CHANNEL_READY_TIMEOUT_SECONDS) 274 except subprocess.TimeoutExpired: 275 logging.error("[%s] Failed to interrupt backing process via SIGINT, sending SIGKILL" % self.label) 276 stop_signal = signal.SIGKILL 277 self.backing_process.kill() 278 try: 279 return_code = self.backing_process.wait(timeout=self.WAIT_CHANNEL_READY_TIMEOUT_SECONDS) 280 except subprocess.TimeoutExpired: 281 logging.error("Failed to kill backing process") 282 return_code = -65536 283 if return_code not in [-stop_signal, 0]: 284 logging.error("backing process %s stopped with code: %d" % (self.label, return_code)) 285 self.backing_process_logger.stop() 286 287 def wait_channel_ready(self): 288 future = grpc.channel_ready_future(self.grpc_channel) 289 try: 290 future.result(timeout=self.WAIT_CHANNEL_READY_TIMEOUT_SECONDS) 291 except grpc.FutureTimeoutError: 292 asserts.fail("[%s] wait channel ready timeout" % self.label) 293 294 295class GdHostOnlyDevice(GdDeviceBase): 296 """ 297 Host only device where the backing process is running on the host machine 298 """ 299 300 def __init__(self, grpc_port: str, grpc_root_server_port: str, signal_port: str, cmd: List[str], label: str, 301 type_identifier: str, name: str, verbose_mode: bool): 302 super().__init__(grpc_port, grpc_root_server_port, signal_port, cmd, label, MOBLY_CONTROLLER_CONFIG_NAME, name, 303 verbose_mode) 304 # Enable LLVM code coverage output for host only tests 305 self.backing_process_profraw_path = pathlib.Path(self.log_path_base).joinpath( 306 "%s_%s_backing_coverage.profraw" % (self.type_identifier, self.label)) 307 self.environment["LLVM_PROFILE_FILE"] = str(self.backing_process_profraw_path) 308 llvm_binutils = pathlib.Path(get_gd_root()).joinpath("llvm_binutils").joinpath("bin") 309 llvm_symbolizer = llvm_binutils.joinpath("llvm-symbolizer") 310 if llvm_symbolizer.is_file(): 311 self.environment["ASAN_SYMBOLIZER_PATH"] = llvm_symbolizer 312 else: 313 logging.warning("[%s] Cannot find LLVM symbolizer at %s" % (self.label, str(llvm_symbolizer))) 314 315 def teardown(self): 316 super().teardown() 317 self.generate_coverage_report() 318 319 def generate_coverage_report(self): 320 if not self.backing_process_profraw_path.is_file(): 321 logging.info("[%s] Skip coverage report as there is no profraw file at %s" % 322 (self.label, str(self.backing_process_profraw_path))) 323 return 324 try: 325 if self.backing_process_profraw_path.stat().st_size <= 0: 326 logging.info("[%s] Skip coverage report as profraw file is empty at %s" % 327 (self.label, str(self.backing_process_profraw_path))) 328 return 329 except OSError: 330 logging.info("[%s] Skip coverage report as profraw file is inaccessible at %s" % 331 (self.label, str(self.backing_process_profraw_path))) 332 return 333 llvm_binutils = pathlib.Path(get_gd_root()).joinpath("llvm_binutils").joinpath("bin") 334 llvm_profdata = llvm_binutils.joinpath("llvm-profdata") 335 if not llvm_profdata.is_file(): 336 logging.info( 337 "[%s] Skip coverage report as llvm-profdata is not found at %s" % (self.label, str(llvm_profdata))) 338 return 339 llvm_cov = llvm_binutils.joinpath("llvm-cov") 340 if not llvm_cov.is_file(): 341 logging.info("[%s] Skip coverage report as llvm-cov is not found at %s" % (self.label, str(llvm_cov))) 342 return 343 logging.info("[%s] Generating coverage report" % self.label) 344 profdata_path = pathlib.Path(self.test_runner_base_path).joinpath( 345 "%s_%s_backing_process_coverage.profdata" % (self.type_identifier, self.label)) 346 profdata_path_tmp = pathlib.Path(self.test_runner_base_path).joinpath( 347 "%s_%s_backing_process_coverage_tmp.profdata" % (self.type_identifier, self.label)) 348 # Merge with existing profdata if possible 349 profdata_cmd = [str(llvm_profdata), "merge", "-sparse", str(self.backing_process_profraw_path)] 350 if profdata_path.is_file(): 351 profdata_cmd.append(str(profdata_path)) 352 profdata_cmd += ["-o", str(profdata_path_tmp)] 353 result = subprocess.run(profdata_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 354 if result.returncode != 0: 355 logging.warning("[%s] Failed to index profdata, cmd result: %r" % (self.label, result)) 356 profdata_path.unlink(missing_ok=True) 357 return 358 shutil.move(profdata_path_tmp, profdata_path) 359 coverage_result_path = pathlib.Path(self.test_runner_base_path).joinpath( 360 "%s_%s_backing_process_coverage.json" % (self.type_identifier, self.label)) 361 with coverage_result_path.open("w") as coverage_result_file: 362 result = subprocess.run( 363 [str(llvm_cov), "export", "--format=text", "--instr-profile", profdata_path, self.cmd[0]], 364 stderr=subprocess.PIPE, 365 stdout=coverage_result_file, 366 cwd=os.path.join(get_gd_root())) 367 if result.returncode != 0: 368 logging.warning("[%s] Failed to generated coverage report, cmd result: %r" % (self.label, result)) 369 coverage_result_path.unlink(missing_ok=True) 370 return 371 coverage_summary_path = pathlib.Path(self.test_runner_base_path).joinpath( 372 "%s_%s_backing_process_coverage_summary.txt" % (self.type_identifier, self.label)) 373 with coverage_summary_path.open("w") as coverage_summary_file: 374 result = subprocess.run( 375 [llvm_cov, "report", "--instr-profile", profdata_path, self.cmd[0]], 376 stderr=subprocess.PIPE, 377 stdout=coverage_summary_file, 378 cwd=os.path.join(get_gd_root())) 379 if result.returncode != 0: 380 logging.warning("[%s] Failed to generated coverage summary, cmd result: %r" % (self.label, result)) 381 coverage_summary_path.unlink(missing_ok=True) 382 383 def setup(self): 384 # Ensure ports are available 385 # Only check on host only test, for Android devices, these ports will 386 # be opened on Android device and host machine ports will be occupied 387 # by sshd or adb forwarding 388 asserts.assert_true( 389 make_ports_available((self.grpc_port, self.grpc_root_server_port)), 390 "[%s] Failed to make backing process ports available" % self.label) 391 super().setup() 392 393 394class GdAndroidDevice(GdDeviceBase): 395 """Real Android device where the backing process is running on it 396 """ 397 398 WAIT_FOR_DEVICE_TIMEOUT_SECONDS = 180 399 400 def __init__(self, grpc_port: str, grpc_root_server_port: str, signal_port: str, cmd: List[str], label: str, 401 type_identifier: str, name: str, serial_number: str, verbose_mode: bool): 402 super().__init__(grpc_port, grpc_root_server_port, signal_port, cmd, label, type_identifier, name, verbose_mode) 403 asserts.assert_true(serial_number, "serial_number must not be None nor empty") 404 self.serial_number = serial_number 405 self.adb = AdbProxy(serial_number) 406 407 def setup(self): 408 logging.info("Setting up device %s %s" % (self.label, self.serial_number)) 409 asserts.assert_true(self.adb.ensure_root(), "device %s cannot run as root", self.serial_number) 410 411 # Try freeing ports and ignore results 412 self.cleanup_port_forwarding() 413 self.sync_device_time() 414 415 # Set up port forwarding or reverse or die 416 self.tcp_forward_or_die(self.grpc_port, self.grpc_port) 417 self.tcp_forward_or_die(self.grpc_root_server_port, self.grpc_root_server_port) 418 self.tcp_reverse_or_die(self.signal_port, self.signal_port) 419 420 # Push test binaries 421 self.ensure_verity_disabled() 422 self.push_or_die(os.path.join(get_gd_root(), "target", "bluetooth_stack_with_facade"), "system/bin") 423 self.push_or_die(os.path.join(get_gd_root(), "target", "libbluetooth_gd.so"), "system/lib64") 424 self.push_or_die(os.path.join(get_gd_root(), "target", "libgrpc++_unsecure.so"), "system/lib64") 425 426 try: 427 self.adb.shell("rm /data/misc/bluetooth/logs/btsnoop_hci.log") 428 except AdbCommandError as error: 429 logging.warning("Failed to remove old btsnoop log: " + str(error)) 430 431 try: 432 self.adb.shell("rm /data/misc/bluetooth/logs/btsnooz_hci.log") 433 except AdbCommandError as error: 434 logging.warning("Failed to remove old btsnooz log: " + str(error)) 435 436 try: 437 self.adb.shell("rm /data/misc/bluedroid/bt_config.conf") 438 except AdbCommandError as error: 439 logging.warning("Failed to remove old bt config: " + str(error)) 440 441 try: 442 self.adb.shell("rm /data/misc/bluedroid/bt_config.bak") 443 except AdbCommandError as error: 444 logging.warning("Failed to remove back up config: " + str(error)) 445 446 self.ensure_no_output(self.adb.shell("svc bluetooth disable")) 447 448 # Start logcat logging 449 self.logcat_output_path = os.path.join( 450 self.log_path_base, '%s_%s_%s_logcat_logs.txt' % (self.type_identifier, self.label, self.serial_number)) 451 self.logcat_cmd = ["adb", "-s", self.serial_number, "logcat", "-T", "1", "-v", "year", "-v", "uid"] 452 logging.debug("Running %s", " ".join(self.logcat_cmd)) 453 self.logcat_process = subprocess.Popen( 454 self.logcat_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) 455 asserts.assert_true(self.logcat_process, msg="Cannot start logcat_process at " + " ".join(self.logcat_cmd)) 456 asserts.assert_true( 457 is_subprocess_alive(self.logcat_process), 458 msg="logcat_process stopped immediately after running " + " ".join(self.logcat_cmd)) 459 self.logcat_logger = AsyncSubprocessLogger( 460 self.logcat_process, [self.logcat_output_path], 461 log_to_stdout=self.verbose_mode, 462 tag="%s_%s" % (self.label, self.serial_number), 463 color=self.terminal_color) 464 465 # Done run parent setup 466 logging.info("Done preparation for %s, starting backing process" % self.serial_number) 467 super().setup() 468 469 def teardown(self): 470 super().teardown() 471 stop_signal = signal.SIGINT 472 self.logcat_process.send_signal(stop_signal) 473 try: 474 return_code = self.logcat_process.wait(timeout=self.WAIT_CHANNEL_READY_TIMEOUT_SECONDS) 475 except subprocess.TimeoutExpired: 476 logging.error("[%s_%s] Failed to interrupt logcat process via SIGINT, sending SIGKILL" % 477 (self.label, self.serial_number)) 478 stop_signal = signal.SIGKILL 479 self.logcat_process.kill() 480 try: 481 return_code = self.logcat_process.wait(timeout=self.WAIT_CHANNEL_READY_TIMEOUT_SECONDS) 482 except subprocess.TimeoutExpired: 483 logging.error("Failed to kill logcat_process %s %s" % (self.label, self.serial_number)) 484 return_code = -65536 485 if return_code not in [-stop_signal, 0]: 486 logging.error("logcat_process %s_%s stopped with code: %d" % (self.label, self.serial_number, return_code)) 487 self.logcat_logger.stop() 488 self.cleanup_port_forwarding() 489 self.pull_logs(self.log_path_base) 490 491 def pull_logs(self, base_dir): 492 try: 493 self.adb.pull("/data/misc/bluetooth/logs/btsnoop_hci.log %s" % os.path.join( 494 base_dir, "%s_btsnoop_hci.log" % self.label)) 495 self.adb.pull( 496 "/data/misc/bluedroid/bt_config.conf %s" % os.path.join(base_dir, "%s_bt_config.conf" % self.label)) 497 self.adb.pull( 498 "/data/misc/bluedroid/bt_config.bak %s" % os.path.join(base_dir, "%s_bt_config.bak" % self.label)) 499 except AdbCommandError as error: 500 logging.warning("Failed to pull logs from device: " + str(error)) 501 502 def cleanup_port_forwarding(self): 503 try: 504 self.adb.remove_tcp_forward(self.grpc_port) 505 except AdbError as error: 506 logging.warning("Failed to cleanup gRPC port: " + str(error)) 507 508 try: 509 self.adb.remove_tcp_forward(self.grpc_root_server_port) 510 except AdbError as error: 511 logging.warning("Failed to cleanup gRPC server port: " + str(error)) 512 513 try: 514 self.adb.reverse("--remove tcp:%d" % self.signal_port) 515 except AdbError as error: 516 logging.warning("Failed to cleanup signal port: " + str(error)) 517 518 @staticmethod 519 def ensure_no_output(result): 520 """ 521 Ensure a command has not output 522 """ 523 asserts.assert_true( 524 result is None or len(result) == 0, msg="command returned something when it shouldn't: %s" % result) 525 526 def sync_device_time(self): 527 self.adb.shell("settings put global auto_time 0") 528 self.adb.shell("settings put global auto_time_zone 0") 529 device_tz = self.adb.shell("date +%z") 530 asserts.assert_true(device_tz, "date +%z must return device timezone, " 531 "but returned {} instead".format(device_tz)) 532 host_tz = time.strftime("%z") 533 if device_tz != host_tz: 534 target_timezone = utils.get_timezone_olson_id() 535 logging.debug("Device timezone %s does not match host timezone %s, " 536 "syncing them by setting timezone to %s" % (device_tz, host_tz, target_timezone)) 537 self.adb.shell("setprop persist.sys.timezone %s" % target_timezone) 538 self.reboot() 539 device_tz = self.adb.shell("date +%z") 540 asserts.assert_equal( 541 host_tz, device_tz, "Device timezone %s still does not match host " 542 "timezone %s after reset" % (device_tz, host_tz)) 543 self.adb.shell("date %s" % time.strftime("%m%d%H%M%Y.%S")) 544 datetime_format = "%Y-%m-%dT%H:%M:%S%z" 545 try: 546 device_time = datetime.strptime(self.adb.shell("date +'%s'" % datetime_format), datetime_format) 547 except ValueError: 548 asserts.fail("Failed to get time after sync") 549 return 550 # Include ADB delay that might be longer in SSH environment 551 max_delta_seconds = 3 552 host_time = datetime.now(tz=device_time.tzinfo) 553 asserts.assert_almost_equal( 554 (device_time - host_time).total_seconds(), 555 0, 556 msg="Device time %s and host time %s off by >%dms after sync" % 557 (device_time.isoformat(), host_time.isoformat(), int(max_delta_seconds * 1000)), 558 delta=max_delta_seconds) 559 560 def push_or_die(self, src_file_path, dst_file_path, push_timeout=300): 561 """Pushes a file to the Android device 562 563 Args: 564 src_file_path: The path to the file to install. 565 dst_file_path: The destination of the file. 566 push_timeout: How long to wait for the push to finish in seconds 567 """ 568 out = self.adb.push('%s %s' % (src_file_path, dst_file_path), timeout=push_timeout) 569 if 'error' in out: 570 asserts.fail('Unable to push file %s to %s due to %s' % (src_file_path, dst_file_path, out)) 571 572 def tcp_forward_or_die(self, host_port, device_port, num_retry=1): 573 """ 574 Forward a TCP port from host to device or fail 575 :param host_port: host port, int, 0 for adb to assign one 576 :param device_port: device port, int 577 :param num_retry: number of times to reboot and retry this before dying 578 :return: host port int 579 """ 580 error_or_port = self.adb.tcp_forward(host_port, device_port) 581 if not error_or_port: 582 logging.debug("host port %d was already forwarded" % host_port) 583 return host_port 584 if not isinstance(error_or_port, int): 585 if num_retry > 0: 586 # If requested, reboot an retry 587 num_retry -= 1 588 logging.warning( 589 "[%s] Failed to TCP forward host port %d to " 590 "device port %d, num_retries left is %d" % (self.label, host_port, device_port, num_retry)) 591 self.reboot() 592 return self.tcp_forward_or_die(host_port, device_port, num_retry=num_retry) 593 asserts.fail( 594 'Unable to forward host port %d to device port %d, error %s' % (host_port, device_port, error_or_port)) 595 return error_or_port 596 597 def tcp_reverse_or_die(self, device_port, host_port, num_retry=1): 598 """ 599 Forward a TCP port from device to host or fail 600 :param device_port: device port, int, 0 for adb to assign one 601 :param host_port: host port, int 602 :param num_retry: number of times to reboot and retry this before dying 603 :return: device port int 604 """ 605 error_or_port = self.adb.reverse("tcp:%d tcp:%d" % (device_port, host_port)) 606 if not error_or_port: 607 logging.debug("device port %d was already reversed" % device_port) 608 return device_port 609 try: 610 error_or_port = int(error_or_port) 611 except ValueError: 612 if num_retry > 0: 613 # If requested, reboot an retry 614 num_retry -= 1 615 logging.warning( 616 "[%s] Failed to TCP reverse device port %d to " 617 "host port %d, num_retries left is %d" % (self.label, device_port, host_port, num_retry)) 618 self.reboot() 619 return self.tcp_reverse_or_die(device_port, host_port, num_retry=num_retry) 620 asserts.fail( 621 'Unable to reverse device port %d to host port %d, error %s' % (device_port, host_port, error_or_port)) 622 return error_or_port 623 624 def ensure_verity_disabled(self): 625 """Ensures that verity is enabled. 626 627 If verity is not enabled, this call will reboot the phone. Note that 628 this only works on debuggable builds. 629 """ 630 logging.debug("Disabling verity and remount for %s", self.serial_number) 631 # The below properties will only exist if verity has been enabled. 632 system_verity = self.adb.getprop('partition.system.verified') 633 vendor_verity = self.adb.getprop('partition.vendor.verified') 634 if system_verity or vendor_verity: 635 self.adb.disable_verity() 636 self.reboot() 637 self.adb.remount() 638 self.adb.wait_for_device(timeout=self.WAIT_FOR_DEVICE_TIMEOUT_SECONDS) 639 640 def reboot(self, timeout_minutes=15.0): 641 """Reboots the device. 642 643 Reboot the device, wait for device to complete booting. 644 """ 645 logging.debug("Rebooting %s", self.serial_number) 646 self.adb.reboot() 647 648 timeout_start = time.time() 649 timeout = timeout_minutes * 60 650 # Android sometimes return early after `adb reboot` is called. This 651 # means subsequent calls may make it to the device before the reboot 652 # goes through, return false positives for getprops such as 653 # sys.boot_completed. 654 while time.time() < timeout_start + timeout: 655 try: 656 self.adb.get_state() 657 time.sleep(.1) 658 except AdbError: 659 # get_state will raise an error if the device is not found. We 660 # want the device to be missing to prove the device has kicked 661 # off the reboot. 662 break 663 minutes_left = timeout_minutes - (time.time() - timeout_start) / 60.0 664 self.wait_for_boot_completion(timeout_minutes=minutes_left) 665 asserts.assert_true(self.adb.ensure_root(), "device %s cannot run as root after reboot", self.serial_number) 666 667 def wait_for_boot_completion(self, timeout_minutes=15.0): 668 """ 669 Waits for Android framework to broadcast ACTION_BOOT_COMPLETED. 670 :param timeout_minutes: number of minutes to wait 671 """ 672 timeout_start = time.time() 673 timeout = timeout_minutes * 60 674 675 self.adb.wait_for_device(timeout=self.WAIT_FOR_DEVICE_TIMEOUT_SECONDS) 676 while time.time() < timeout_start + timeout: 677 try: 678 completed = self.adb.getprop("sys.boot_completed") 679 if completed == '1': 680 return 681 except AdbError: 682 # adb shell calls may fail during certain period of booting 683 # process, which is normal. Ignoring these errors. 684 pass 685 time.sleep(5) 686 asserts.fail(msg='Device %s booting process timed out.' % self.serial_number) 687