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 importlib 18import logging 19import os 20import signal 21import subprocess 22 23from blueberry.tests.gd.cert.context import get_current_context 24from blueberry.tests.gd.cert.tracelogger import TraceLogger 25from blueberry.tests.gd.cert.async_subprocess_logger import AsyncSubprocessLogger 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 mobly import asserts 33from mobly import base_test 34 35CONTROLLER_CONFIG_NAME = "GdDevice" 36 37 38def setup_test_core(verbose_mode, log_path_base, controller_configs): 39 info = {} 40 info['controller_configs'] = controller_configs 41 42 # Start root-canal if needed 43 info['rootcanal_running'] = False 44 info['rootcanal_logpath'] = None 45 info['rootcanal_process'] = None 46 info['rootcanal_logger'] = None 47 if 'rootcanal' not in info['controller_configs']: 48 return 49 info['rootcanal_running'] = True 50 # Get root canal binary 51 rootcanal = os.path.join(get_gd_root(), "root-canal") 52 info['rootcanal'] = rootcanal 53 info['rootcanal_exist'] = os.path.isfile(rootcanal) 54 if not os.path.isfile(rootcanal): 55 return info 56 # Get root canal log 57 rootcanal_logpath = os.path.join(log_path_base, 'rootcanal_logs.txt') 58 info['rootcanal_logpath'] = rootcanal_logpath 59 # Make sure ports are available 60 rootcanal_config = info['controller_configs']['rootcanal'] 61 rootcanal_test_port = int(rootcanal_config.get("test_port", "6401")) 62 rootcanal_hci_port = int(rootcanal_config.get("hci_port", "6402")) 63 rootcanal_link_layer_port = int(rootcanal_config.get("link_layer_port", "6403")) 64 65 info['make_rootcanal_ports_available'] = make_ports_available((rootcanal_test_port, rootcanal_hci_port, 66 rootcanal_link_layer_port)) 67 if not make_ports_available((rootcanal_test_port, rootcanal_hci_port, rootcanal_link_layer_port)): 68 return info 69 70 # Start root canal process 71 rootcanal_cmd = [rootcanal, str(rootcanal_test_port), str(rootcanal_hci_port), str(rootcanal_link_layer_port)] 72 info['rootcanal_cmd'] = rootcanal_cmd 73 74 rootcanal_process = subprocess.Popen( 75 rootcanal_cmd, 76 cwd=get_gd_root(), 77 env=os.environ.copy(), 78 stdout=subprocess.PIPE, 79 stderr=subprocess.STDOUT, 80 universal_newlines=True) 81 82 info['rootcanal_process'] = rootcanal_process 83 if rootcanal_process: 84 info['is_rootcanal_process_started'] = True 85 else: 86 info['is_rootcanal_process_started'] = False 87 return info 88 info['is_subprocess_alive'] = is_subprocess_alive(rootcanal_process) 89 if not is_subprocess_alive(rootcanal_process): 90 info['is_subprocess_alive'] = False 91 return info 92 93 info['rootcanal_logger'] = AsyncSubprocessLogger( 94 rootcanal_process, [rootcanal_logpath], 95 log_to_stdout=verbose_mode, 96 tag="rootcanal", 97 color=TerminalColor.MAGENTA) 98 99 # Modify the device config to include the correct root-canal port 100 for gd_device_config in info['controller_configs'].get("GdDevice"): 101 gd_device_config["rootcanal_port"] = str(rootcanal_hci_port) 102 103 return info 104 105 106def teardown_class_core(rootcanal_running, rootcanal_process, rootcanal_logger, subprocess_wait_timeout_seconds): 107 if rootcanal_running: 108 stop_signal = signal.SIGINT 109 rootcanal_process.send_signal(stop_signal) 110 try: 111 return_code = rootcanal_process.wait(timeout=subprocess_wait_timeout_seconds) 112 except subprocess.TimeoutExpired: 113 logging.error("Failed to interrupt root canal via SIGINT, sending SIGKILL") 114 stop_signal = signal.SIGKILL 115 rootcanal_process.kill() 116 try: 117 return_code = rootcanal_process.wait(timeout=subprocess_wait_timeout_seconds) 118 except subprocess.TimeoutExpired: 119 logging.error("Failed to kill root canal") 120 return_code = -65536 121 if return_code != 0 and return_code != -stop_signal: 122 logging.error("rootcanal stopped with code: %d" % return_code) 123 rootcanal_logger.stop() 124 125 126def dump_crashes_core(dut, cert, rootcanal_running, rootcanal_process, rootcanal_logpath): 127 dut_crash, dut_log_tail = dut.get_crash_snippet_and_log_tail() 128 cert_crash, cert_log_tail = cert.get_crash_snippet_and_log_tail() 129 rootcanal_crash = None 130 rootcanal_log_tail = None 131 if rootcanal_running and not is_subprocess_alive(rootcanal_process): 132 rootcanal_crash, roocanal_log_tail = read_crash_snippet_and_log_tail(rootcanal_logpath) 133 134 crash_detail = "" 135 if dut_crash or cert_crash or rootcanal_crash: 136 if rootcanal_crash: 137 crash_detail += "rootcanal crashed:\n\n%s\n\n" % rootcanal_crash 138 if dut_crash: 139 crash_detail += "dut stack crashed:\n\n%s\n\n" % dut_crash 140 if cert_crash: 141 crash_detail += "cert stack crashed:\n\n%s\n\n" % cert_crash 142 else: 143 if rootcanal_log_tail: 144 crash_detail += "rootcanal log tail:\n\n%s\n\n" % rootcanal_log_tail 145 if dut_log_tail: 146 crash_detail += "dut log tail:\n\n%s\n\n" % dut_log_tail 147 if cert_log_tail: 148 crash_detail += "cert log tail:\n\n%s\n\n" % cert_log_tail 149 150 return crash_detail 151 152 153class TopshimBaseTest(base_test.BaseTestClass): 154 155 def setup_class(self): 156 super().setup_test() 157 self.log = TraceLogger(logging.getLogger()) 158 self.log_path_base = get_current_context().get_full_output_path() 159 self.verbose_mode = bool(self.user_params.get('verbose_mode', False)) 160 for config in self.controller_configs[CONTROLLER_CONFIG_NAME]: 161 config['verbose_mode'] = self.verbose_mode 162 163 self.info = setup_test_core( 164 verbose_mode=self.verbose_mode, 165 log_path_base=self.log_path_base, 166 controller_configs=self.controller_configs) 167 self.rootcanal_running = self.info['rootcanal_running'] 168 self.rootcanal_logpath = self.info['rootcanal_logpath'] 169 self.rootcanal_process = self.info['rootcanal_process'] 170 self.rootcanal_logger = self.info['rootcanal_logger'] 171 172 asserts.assert_true(self.info['rootcanal_exist'], "Root canal does not exist at %s" % self.info['rootcanal']) 173 asserts.assert_true(self.info['make_rootcanal_ports_available'], "Failed to make root canal ports available") 174 175 self.log.debug("Running %s" % " ".join(self.info['rootcanal_cmd'])) 176 asserts.assert_true( 177 self.info['is_rootcanal_process_started'], msg="Cannot start root-canal at " + str(self.info['rootcanal'])) 178 asserts.assert_true(self.info['is_subprocess_alive'], msg="root-canal stopped immediately after running") 179 180 self.controller_configs = self.info['controller_configs'] 181 182 controllers = self.register_controller( 183 importlib.import_module('blueberry.tests.gd.rust.topshim.facade.topshim_device')) 184 self.cert_port = controllers[0].grpc_port 185 self.dut_port = controllers[1].grpc_port 186 187 def test_empty(self): 188 pass 189 190 def teardown_test(self): 191 return super().teardown_test() 192 193 def teardown_class(self): 194 teardown_class_core( 195 rootcanal_running=self.rootcanal_running, 196 rootcanal_process=self.rootcanal_process, 197 rootcanal_logger=self.rootcanal_logger, 198 subprocess_wait_timeout_seconds=1) 199