1#!/usr/bin/env python3 2# 3# Copyright 2021 - 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 17import asyncio 18import importlib 19import logging 20import os 21import signal 22import subprocess 23 24from blueberry.tests.gd.cert.async_subprocess_logger import AsyncSubprocessLogger 25from blueberry.tests.gd.cert.context import get_current_context 26from blueberry.tests.gd.cert.os_utils import get_gd_root 27from blueberry.tests.gd.cert.os_utils import get_gd_root 28from blueberry.tests.gd.cert.os_utils import read_crash_snippet_and_log_tail 29from blueberry.tests.gd.cert.os_utils import is_subprocess_alive 30from blueberry.tests.gd.cert.os_utils import make_ports_available 31from blueberry.tests.gd.cert.os_utils import TerminalColor 32from blueberry.tests.gd.cert.tracelogger import TraceLogger 33from blueberry.tests.gd.cert.truth import assertThat 34from blueberry.tests.topshim.lib.adapter_client import AdapterClient 35from blueberry.tests.topshim.lib.async_closable import asyncSafeClose 36from blueberry.tests.topshim.lib.gatt_client import GattClient 37from blueberry.tests.topshim.lib.hf_client_client import HfClientClient 38from blueberry.tests.topshim.lib.hfp_client import HfpClient 39from blueberry.tests.topshim.lib.security_client import SecurityClient 40from blueberry.tests.topshim.lib.topshim_device import TopshimDevice 41 42from mobly import asserts 43from mobly import base_test 44 45CONTROLLER_CONFIG_NAME = "GdDevice" 46 47 48def _setup_class_core(verbose_mode, log_path_base, controller_configs): 49 info = {} 50 info['controller_configs'] = controller_configs 51 52 # Start root-canal if needed 53 info['rootcanal_running'] = False 54 info['rootcanal_logpath'] = None 55 info['rootcanal_process'] = None 56 info['rootcanal_logger'] = None 57 if 'rootcanal' not in info['controller_configs']: 58 return 59 info['rootcanal_running'] = True 60 # Get root canal binary 61 rootcanal = os.path.join(get_gd_root(), "root-canal") 62 info['rootcanal'] = rootcanal 63 info['rootcanal_exist'] = os.path.isfile(rootcanal) 64 if not os.path.isfile(rootcanal): 65 return info 66 # Get root canal log 67 rootcanal_logpath = os.path.join(log_path_base, 'rootcanal_logs.txt') 68 info['rootcanal_logpath'] = rootcanal_logpath 69 # Make sure ports are available 70 rootcanal_config = info['controller_configs']['rootcanal'] 71 rootcanal_test_port = int(rootcanal_config.get("test_port", "6401")) 72 rootcanal_hci_port = int(rootcanal_config.get("hci_port", "6402")) 73 rootcanal_link_layer_port = int(rootcanal_config.get("link_layer_port", "6403")) 74 75 info['make_rootcanal_ports_available'] = make_ports_available( 76 (rootcanal_test_port, rootcanal_hci_port, rootcanal_link_layer_port)) 77 if not make_ports_available((rootcanal_test_port, rootcanal_hci_port, rootcanal_link_layer_port)): 78 return info 79 80 # Start root canal process 81 rootcanal_cmd = [rootcanal, str(rootcanal_test_port), str(rootcanal_hci_port), str(rootcanal_link_layer_port)] 82 info['rootcanal_cmd'] = rootcanal_cmd 83 84 rootcanal_process = subprocess.Popen(rootcanal_cmd, 85 cwd=get_gd_root(), 86 env=os.environ.copy(), 87 stdout=subprocess.PIPE, 88 stderr=subprocess.STDOUT, 89 universal_newlines=True) 90 91 info['rootcanal_process'] = rootcanal_process 92 if rootcanal_process: 93 info['is_rootcanal_process_started'] = True 94 else: 95 info['is_rootcanal_process_started'] = False 96 return info 97 info['is_subprocess_alive'] = is_subprocess_alive(rootcanal_process) 98 if not is_subprocess_alive(rootcanal_process): 99 info['is_subprocess_alive'] = False 100 return info 101 102 info['rootcanal_logger'] = AsyncSubprocessLogger(rootcanal_process, [rootcanal_logpath], 103 log_to_stdout=verbose_mode, 104 tag="rootcanal", 105 color=TerminalColor.MAGENTA) 106 107 # Modify the device config to include the correct root-canal port 108 for gd_device_config in info['controller_configs'].get("GdDevice"): 109 gd_device_config["rootcanal_port"] = str(rootcanal_hci_port) 110 111 return info 112 113 114def _teardown_class_core(rootcanal_running, rootcanal_process, rootcanal_logger, subprocess_wait_timeout_seconds): 115 if rootcanal_running: 116 stop_signal = signal.SIGINT 117 rootcanal_process.send_signal(stop_signal) 118 try: 119 return_code = rootcanal_process.wait(timeout=subprocess_wait_timeout_seconds) 120 except subprocess.TimeoutExpired: 121 logging.error("Failed to interrupt root canal via SIGINT, sending SIGKILL") 122 stop_signal = signal.SIGKILL 123 rootcanal_process.kill() 124 try: 125 return_code = rootcanal_process.wait(timeout=subprocess_wait_timeout_seconds) 126 except subprocess.TimeoutExpired: 127 logging.error("Failed to kill root canal") 128 return_code = -65536 129 if return_code != 0 and return_code != -stop_signal: 130 logging.error("rootcanal stopped with code: %d" % return_code) 131 rootcanal_logger.stop() 132 133 134def dump_crashes_core(dut, cert, rootcanal_running, rootcanal_process, rootcanal_logpath): 135 dut_crash, dut_log_tail = dut.get_crash_snippet_and_log_tail() 136 cert_crash, cert_log_tail = cert.get_crash_snippet_and_log_tail() 137 rootcanal_crash = None 138 rootcanal_log_tail = None 139 if rootcanal_running and not is_subprocess_alive(rootcanal_process): 140 rootcanal_crash, roocanal_log_tail = read_crash_snippet_and_log_tail(rootcanal_logpath) 141 142 crash_detail = "" 143 if dut_crash or cert_crash or rootcanal_crash: 144 if rootcanal_crash: 145 crash_detail += "rootcanal crashed:\n\n%s\n\n" % rootcanal_crash 146 if dut_crash: 147 crash_detail += "dut stack crashed:\n\n%s\n\n" % dut_crash 148 if cert_crash: 149 crash_detail += "cert stack crashed:\n\n%s\n\n" % cert_crash 150 else: 151 if rootcanal_log_tail: 152 crash_detail += "rootcanal log tail:\n\n%s\n\n" % rootcanal_log_tail 153 if dut_log_tail: 154 crash_detail += "dut log tail:\n\n%s\n\n" % dut_log_tail 155 if cert_log_tail: 156 crash_detail += "cert log tail:\n\n%s\n\n" % cert_log_tail 157 158 return crash_detail 159 160 161class TopshimBaseTest(base_test.BaseTestClass): 162 163 __dut = None 164 __cert = None 165 166 async def __setup_adapter(self): 167 dut_adapter = AdapterClient(port=self.dut_port) 168 cert_adapter = AdapterClient(port=self.cert_port) 169 started = await dut_adapter._verify_adapter_started() 170 assertThat(started).isTrue() 171 started = started and await cert_adapter._verify_adapter_started() 172 assertThat(started).isTrue() 173 self.__dut = TopshimDevice(dut_adapter, GattClient(port=self.dut_port), 174 SecurityClient(dut_adapter, port=self.dut_port), 175 HfpClient(port=self.dut_port), 176 HfClientClient(port=self.dut_port)) 177 self.__cert = TopshimDevice(cert_adapter, GattClient(port=self.cert_port), 178 SecurityClient(cert_adapter, port=self.cert_port), 179 HfpClient(port=self.cert_port), 180 HfClientClient(port=self.cert_port)) 181 return started 182 183 async def __teardown_adapter(self): 184 await asyncSafeClose(self.__dut) 185 await asyncSafeClose(self.__cert) 186 187 def dut(self): 188 """ 189 Get a handle on the DUT device 190 """ 191 return self.__dut 192 193 def cert(self): 194 """ 195 Get a handle on the CERT device 196 """ 197 return self.__cert 198 199 def setup_class(self): 200 """ 201 Configure rootcanal and setup test parameters 202 """ 203 self.log = TraceLogger(logging.getLogger()) 204 self.log_path_base = get_current_context().get_full_output_path() 205 self.verbose_mode = bool(self.user_params.get('verbose_mode', False)) 206 for config in self.controller_configs[CONTROLLER_CONFIG_NAME]: 207 config['verbose_mode'] = self.verbose_mode 208 209 self.info = _setup_class_core(verbose_mode=self.verbose_mode, 210 log_path_base=self.log_path_base, 211 controller_configs=self.controller_configs) 212 self.rootcanal_running = self.info['rootcanal_running'] 213 self.rootcanal_logpath = self.info['rootcanal_logpath'] 214 self.rootcanal_process = self.info['rootcanal_process'] 215 self.rootcanal_logger = self.info['rootcanal_logger'] 216 217 asserts.assert_true(self.info['rootcanal_exist'], "Root canal does not exist at %s" % self.info['rootcanal']) 218 asserts.assert_true(self.info['make_rootcanal_ports_available'], "Failed to make root canal ports available") 219 220 self.log.debug("Running %s" % " ".join(self.info['rootcanal_cmd'])) 221 asserts.assert_true(self.info['is_rootcanal_process_started'], 222 msg="Cannot start root-canal at " + str(self.info['rootcanal'])) 223 asserts.assert_true(self.info['is_subprocess_alive'], msg="root-canal stopped immediately after running") 224 225 self.controller_configs = self.info['controller_configs'] 226 227 controllers = self.register_controller(importlib.import_module('blueberry.tests.topshim.lib.topshim_device')) 228 self.cert_port = controllers[0].grpc_port 229 self.dut_port = controllers[1].grpc_port 230 asyncio.set_event_loop(asyncio.new_event_loop()) 231 asyncio.get_event_loop().run_until_complete(self.__setup_adapter()) 232 233 def teardown_class(self): 234 _teardown_class_core(rootcanal_running=self.rootcanal_running, 235 rootcanal_process=self.rootcanal_process, 236 rootcanal_logger=self.rootcanal_logger, 237 subprocess_wait_timeout_seconds=1) 238 asyncio.get_event_loop().run_until_complete(self.__teardown_adapter()) 239