• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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