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
17import importlib
18import logging
19import os
20import signal
21import subprocess
22import traceback
23
24from functools import wraps
25from grpc import RpcError
26
27from cert.async_subprocess_logger import AsyncSubprocessLogger
28from cert.os_utils import get_gd_root
29from cert.os_utils import read_crash_snippet_and_log_tail
30from cert.os_utils import is_subprocess_alive
31from cert.os_utils import make_ports_available
32from cert.os_utils import TerminalColor
33from cert.gd_device import MOBLY_CONTROLLER_CONFIG_NAME as CONTROLLER_CONFIG_NAME
34from facade import rootservice_pb2 as facade_rootservice
35
36
37def setup_class_core(dut_module, cert_module, verbose_mode, log_path_base, controller_configs):
38    info = {}
39    info['dut_module'] = dut_module
40    info['cert_module'] = cert_module
41    info['controller_configs'] = controller_configs
42
43    # Start root-canal if needed
44    info['rootcanal_running'] = False
45    info['rootcanal_logpath'] = ""
46    info['rootcanal_process'] = None
47    info['rootcanal_logger'] = None
48    if 'rootcanal' in info['controller_configs']:
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
57        # Get root canal log
58        rootcanal_logpath = os.path.join(log_path_base, 'rootcanal_logs.txt')
59        info['rootcanal_logpath'] = rootcanal_logpath
60        # Make sure ports are available
61        rootcanal_config = info['controller_configs']['rootcanal']
62        rootcanal_test_port = int(rootcanal_config.get("test_port", "6401"))
63        rootcanal_hci_port = int(rootcanal_config.get("hci_port", "6402"))
64        rootcanal_link_layer_port = int(rootcanal_config.get("link_layer_port", "6403"))
65
66        info['make_rootcanal_ports_available'] = make_ports_available((rootcanal_test_port, rootcanal_hci_port,
67                                                                       rootcanal_link_layer_port))
68        if not make_ports_available((rootcanal_test_port, rootcanal_hci_port, rootcanal_link_layer_port)):
69            return info
70
71        # Start root canal process
72        rootcanal_cmd = [rootcanal, str(rootcanal_test_port), str(rootcanal_hci_port), str(rootcanal_link_layer_port)]
73        info['rootcanal_cmd'] = rootcanal_cmd
74
75        rootcanal_process = subprocess.Popen(
76            rootcanal_cmd,
77            cwd=get_gd_root(),
78            env=os.environ.copy(),
79            stdout=subprocess.PIPE,
80            stderr=subprocess.STDOUT,
81            universal_newlines=True)
82
83        info['rootcanal_process'] = rootcanal_process
84        if rootcanal_process:
85            info['is_rootcanal_process_started'] = True
86        else:
87            info['is_rootcanal_process_started'] = False
88            return info
89        info['is_subprocess_alive'] = is_subprocess_alive(rootcanal_process)
90        if not is_subprocess_alive(rootcanal_process):
91            info['is_subprocess_alive'] = False
92            return info
93
94        info['rootcanal_logger'] = AsyncSubprocessLogger(
95            rootcanal_process, [rootcanal_logpath],
96            log_to_stdout=verbose_mode,
97            tag="rootcanal",
98            color=TerminalColor.MAGENTA)
99
100        # Modify the device config to include the correct root-canal port
101        for gd_device_config in info['controller_configs'].get("GdDevice"):
102            gd_device_config["rootcanal_port"] = str(rootcanal_hci_port)
103
104    return info
105
106
107def teardown_class_core(rootcanal_running, rootcanal_process, rootcanal_logger, subprocess_wait_timeout_seconds):
108    if rootcanal_running:
109        stop_signal = signal.SIGINT
110        rootcanal_process.send_signal(stop_signal)
111        try:
112            return_code = rootcanal_process.wait(timeout=subprocess_wait_timeout_seconds)
113        except subprocess.TimeoutExpired:
114            logging.error("Failed to interrupt root canal via SIGINT, sending SIGKILL")
115            stop_signal = signal.SIGKILL
116            rootcanal_process.kill()
117            try:
118                return_code = rootcanal_process.wait(timeout=subprocess_wait_timeout_seconds)
119            except subprocess.TimeoutExpired:
120                logging.error("Failed to kill root canal")
121                return_code = -65536
122        if return_code != 0 and return_code != -stop_signal:
123            logging.error("rootcanal stopped with code: %d" % return_code)
124        rootcanal_logger.stop()
125
126
127def setup_test_core(dut, cert, dut_module, cert_module):
128    dut.rootservice.StartStack(
129        facade_rootservice.StartStackRequest(module_under_test=facade_rootservice.BluetoothModule.Value(dut_module),))
130    cert.rootservice.StartStack(
131        facade_rootservice.StartStackRequest(module_under_test=facade_rootservice.BluetoothModule.Value(cert_module),))
132
133    dut.wait_channel_ready()
134    cert.wait_channel_ready()
135
136
137def teardown_test_core(cert, dut):
138    cert.rootservice.StopStack(facade_rootservice.StopStackRequest())
139    dut.rootservice.StopStack(facade_rootservice.StopStackRequest())
140
141
142def dump_crashes_core(dut, cert, rootcanal_running, rootcanal_process, rootcanal_logpath):
143    dut_crash, dut_log_tail = dut.get_crash_snippet_and_log_tail()
144    cert_crash, cert_log_tail = cert.get_crash_snippet_and_log_tail()
145    rootcanal_crash = None
146    rootcanal_log_tail = None
147    if rootcanal_running and not is_subprocess_alive(rootcanal_process):
148        rootcanal_crash, roocanal_log_tail = read_crash_snippet_and_log_tail(rootcanal_logpath)
149
150    crash_detail = ""
151    if dut_crash or cert_crash or rootcanal_crash:
152        if rootcanal_crash:
153            crash_detail += "rootcanal crashed:\n\n%s\n\n" % rootcanal_crash
154        if dut_crash:
155            crash_detail += "dut stack crashed:\n\n%s\n\n" % dut_crash
156        if cert_crash:
157            crash_detail += "cert stack crashed:\n\n%s\n\n" % cert_crash
158    else:
159        if rootcanal_log_tail:
160            crash_detail += "rootcanal log tail:\n\n%s\n\n" % rootcanal_log_tail
161        if dut_log_tail:
162            crash_detail += "dut log tail:\n\n%s\n\n" % dut_log_tail
163        if cert_log_tail:
164            crash_detail += "cert log tail:\n\n%s\n\n" % cert_log_tail
165
166    return crash_detail
167