aboutsummaryrefslogtreecommitdiff
path: root/blueberry
diff options
context:
space:
mode:
authorJizheng Chu <jizhengchu@google.com>2021-06-23 13:40:12 -0700
committerJizheng Chu <jizhengchu@google.com>2021-06-25 12:36:19 -0700
commitedb4ee24785b47ec4ea565ed9aae25ebe7f89cdd (patch)
tree20d050d97aab84504d41bc1fa137d2888b99ba9d /blueberry
parente71fc186cd24a1455f2513e849cb6eabfee33b6d (diff)
downloadbt-edb4ee24785b47ec4ea565ed9aae25ebe7f89cdd.tar.gz
Port GD Cert tests to Blueberry/Mobly
Bug: 188852781 Test: Build artifact and run GD Cert tests in venv with Mobly installed, see detail steps in go/port-gd-cert Change-Id: I5e9e69ed281b92e980a32546d107dc922a0add33
Diffstat (limited to 'blueberry')
-rw-r--r--blueberry/tests/gd/cert/cert_self_test.py3
-rw-r--r--blueberry/tests/gd/cert/context.py164
-rw-r--r--blueberry/tests/gd/cert/gd_base_test.py159
-rw-r--r--blueberry/tests/gd/cert/gd_device.py491
-rw-r--r--blueberry/tests/gd/cert/metadata.py79
-rw-r--r--blueberry/tests/gd/cert/pts_base_test.py29
-rw-r--r--blueberry/tests/gd/cert/test_decorators.py267
-rw-r--r--blueberry/tests/gd/cert/tracelogger.py63
-rw-r--r--blueberry/tests/gd/hal/simple_hal_test.py39
-rw-r--r--blueberry/tests/gd/hci/acl_manager_test.py50
-rw-r--r--blueberry/tests/gd/hci/controller_test.py35
-rw-r--r--blueberry/tests/gd/hci/direct_hci_test.py37
-rw-r--r--blueberry/tests/gd/hci/le_acl_manager_test.py37
-rw-r--r--blueberry/tests/gd/hci/le_advertising_manager_test.py37
-rw-r--r--blueberry/tests/gd/hci/le_scanning_manager_test.py29
-rw-r--r--blueberry/tests/gd/hci/le_scanning_with_security_test.py29
-rw-r--r--blueberry/tests/gd/host_config.yaml58
-rw-r--r--blueberry/tests/gd/iso/le_iso_test.py36
-rw-r--r--blueberry/tests/gd/l2cap/classic/l2cap_performance_test.py36
-rw-r--r--blueberry/tests/gd/l2cap/classic/l2cap_test.py36
-rw-r--r--blueberry/tests/gd/l2cap/le/dual_l2cap_test.py36
-rw-r--r--blueberry/tests/gd/l2cap/le/le_l2cap_test.py36
-rw-r--r--blueberry/tests/gd/neighbor/neighbor_test.py37
-rw-r--r--blueberry/tests/gd/security/le_security_test.py36
-rw-r--r--blueberry/tests/gd/security/security_test.py36
-rw-r--r--blueberry/tests/gd/shim/shim_test.py32
-rw-r--r--blueberry/tests/gd/shim/stack_test.py29
27 files changed, 1926 insertions, 30 deletions
diff --git a/blueberry/tests/gd/cert/cert_self_test.py b/blueberry/tests/gd/cert/cert_self_test.py
index edfe10bcf..a014a79a5 100644
--- a/blueberry/tests/gd/cert/cert_self_test.py
+++ b/blueberry/tests/gd/cert/cert_self_test.py
@@ -14,12 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from blueberry.tests.gd.cert.metadata import metadata
+
from mobly import asserts
from mobly import signals
from mobly import test_runner
from mobly import base_test
-from cert.metadata import metadata
from cert.cert_self_test_lib import *
diff --git a/blueberry/tests/gd/cert/context.py b/blueberry/tests/gd/cert/context.py
new file mode 100644
index 000000000..bad984205
--- /dev/null
+++ b/blueberry/tests/gd/cert/context.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import enum
+import logging
+import os
+
+
+class ContextLevel(enum.IntEnum):
+ ROOT = 0
+ TESTCLASS = 1
+ TESTCASE = 2
+
+
+def get_current_context(depth=None):
+ """Get the current test context at the specified depth.
+ Pulls the most recently created context, with a level at or below the given
+ depth, from the _contexts stack.
+
+ Args:
+ depth: The desired context level. For example, the TESTCLASS level would
+ yield the current test class context, even if the test is currently
+ within a test case.
+
+ Returns: An instance of TestContext.
+ """
+ if depth is None:
+ return _contexts[-1]
+ return _contexts[min(depth, len(_contexts) - 1)]
+
+
+class TestContext(object):
+ """An object representing the current context in which a test is executing.
+
+ The context encodes the current state of the test runner with respect to a
+ particular scenario in which code is being executed. For example, if some
+ code is being executed as part of a test case, then the context should
+ encode information about that test case such as its name or enclosing
+ class.
+
+ The subcontext specifies a relative path in which certain outputs,
+ e.g. logcat, should be kept for the given context.
+
+ The full output path is given by
+ <base_output_path>/<context_dir>/<subcontext>.
+
+ Attributes:
+ _base_output_paths: a dictionary mapping a logger's name to its base
+ output path
+ _subcontexts: a dictionary mapping a logger's name to its
+ subcontext-level output directory
+ """
+
+ _base_output_paths = {}
+ _subcontexts = {}
+
+ def get_base_output_path(self, log_name=None):
+ """Gets the base output path for this logger.
+
+ The base output path is interpreted as the reporting root for the
+ entire test runner.
+
+ If a path has been added with add_base_output_path, it is returned.
+ Otherwise, a default is determined by _get_default_base_output_path().
+
+ Args:
+ log_name: The name of the logger.
+
+ Returns:
+ The output path.
+ """
+ if log_name in self._base_output_paths:
+ return self._base_output_paths[log_name]
+ return self._get_default_base_output_path()
+
+ def get_subcontext(self, log_name=None):
+ """Gets the subcontext for this logger.
+
+ The subcontext is interpreted as the directory, relative to the
+ context-level path, where all outputs of the given logger are stored.
+
+ If a path has been added with add_subcontext, it is returned.
+ Otherwise, the empty string is returned.
+
+ Args:
+ log_name: The name of the logger.
+
+ Returns:
+ The output path.
+ """
+ return self._subcontexts.get(log_name, '')
+
+ def get_full_output_path(self, log_name=None):
+ """Gets the full output path for this context.
+
+ The full path represents the absolute path to the output directory,
+ as given by <base_output_path>/<context_dir>/<subcontext>
+
+ Args:
+ log_name: The name of the logger. Used to specify the base output
+ path and the subcontext.
+
+ Returns:
+ The output path.
+ """
+
+ path = os.path.join(
+ self.get_base_output_path(log_name), self._get_default_context_dir(), self.get_subcontext(log_name))
+ os.makedirs(path, exist_ok=True)
+ return path
+
+ def _get_default_base_output_path(self):
+ """Gets the default base output path.
+
+ This will attempt to use logging path set up in the global
+ logger.
+
+ Returns:
+ The logging path.
+
+ Raises:
+ EnvironmentError: If logger has not been initialized.
+ """
+ try:
+ return logging.log_path
+ except AttributeError as e:
+ raise EnvironmentError('The Mobly logger has not been set up and'
+ ' "base_output_path" has not been set.') from e
+
+ def _get_default_context_dir(self):
+ """Gets the default output directory for this context."""
+ raise NotImplementedError()
+
+
+class RootContext(TestContext):
+ """A TestContext that represents a test run."""
+
+ @property
+ def identifier(self):
+ return 'root'
+
+ def _get_default_context_dir(self):
+ """Gets the default output directory for this context.
+
+ Logs at the root level context are placed directly in the base level
+ directory, so no context-level path exists."""
+ return ''
+
+
+# stack for keeping track of the current test context
+_contexts = [RootContext()]
diff --git a/blueberry/tests/gd/cert/gd_base_test.py b/blueberry/tests/gd/cert/gd_base_test.py
new file mode 100644
index 000000000..c8674b31f
--- /dev/null
+++ b/blueberry/tests/gd/cert/gd_base_test.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import importlib
+import logging
+import traceback
+
+from functools import wraps
+from grpc import RpcError
+
+from cert.gd_base_test_lib import setup_class_core
+from cert.gd_base_test_lib import teardown_class_core
+from cert.gd_base_test_lib import setup_test_core
+from cert.gd_base_test_lib import teardown_test_core
+from cert.gd_base_test_lib import dump_crashes_core
+
+from blueberry.tests.gd.cert.context import get_current_context
+from blueberry.tests.gd.cert.gd_device import MOBLY_CONTROLLER_CONFIG_NAME as CONTROLLER_CONFIG_NAME
+from blueberry.tests.gd.cert.tracelogger import TraceLogger
+
+from mobly import asserts, signals
+from mobly import base_test
+
+
+class GdBaseTestClass(base_test.BaseTestClass):
+
+ SUBPROCESS_WAIT_TIMEOUT_SECONDS = 10
+
+ def setup_class(self, dut_module, cert_module):
+ self.log = TraceLogger(logging.getLogger())
+ self.log_path_base = get_current_context().get_full_output_path()
+ self.verbose_mode = bool(self.user_params.get('verbose_mode', False))
+ for config in self.controller_configs[CONTROLLER_CONFIG_NAME]:
+ config['verbose_mode'] = self.verbose_mode
+
+ self.info = setup_class_core(
+ dut_module=dut_module,
+ cert_module=cert_module,
+ verbose_mode=self.verbose_mode,
+ log_path_base=self.log_path_base,
+ controller_configs=self.controller_configs)
+ self.dut_module = self.info['dut_module']
+ self.cert_module = self.info['cert_module']
+ self.rootcanal_running = self.info['rootcanal_running']
+ self.rootcanal_logpath = self.info['rootcanal_logpath']
+ self.rootcanal_process = self.info['rootcanal_process']
+ self.rootcanal_logger = self.info['rootcanal_logger']
+
+ if 'rootcanal' in self.controller_configs:
+ asserts.assert_true(self.info['rootcanal_exist'],
+ "Root canal does not exist at %s" % self.info['rootcanal'])
+ asserts.assert_true(self.info['make_rootcanal_ports_available'],
+ "Failed to make root canal ports available")
+
+ self.log.debug("Running %s" % " ".join(self.info['rootcanal_cmd']))
+ asserts.assert_true(
+ self.info['is_rootcanal_process_started'],
+ msg="Cannot start root-canal at " + str(self.info['rootcanal']))
+ asserts.assert_true(self.info['is_subprocess_alive'], msg="root-canal stopped immediately after running")
+
+ self.controller_configs = self.info['controller_configs']
+
+ # Parse and construct GD device objects
+ self.register_controller(importlib.import_module('blueberry.tests.gd.cert.gd_device'), builtin=True)
+ self.dut = self.gd_device[1]
+ self.cert = self.gd_device[0]
+
+ def teardown_class(self):
+ teardown_class_core(
+ rootcanal_running=self.rootcanal_running,
+ rootcanal_process=self.rootcanal_process,
+ rootcanal_logger=self.rootcanal_logger,
+ subprocess_wait_timeout_seconds=self.SUBPROCESS_WAIT_TIMEOUT_SECONDS)
+
+ def setup_test(self):
+ setup_test_core(dut=self.dut, cert=self.cert, dut_module=self.dut_module, cert_module=self.cert_module)
+
+ def teardown_test(self):
+ teardown_test_core(cert=self.cert, dut=self.dut)
+
+ @staticmethod
+ def get_module_reference_name(a_module):
+ """Returns the module's module's submodule name as reference name.
+
+ Args:
+ a_module: Any module. Ideally, a controller module.
+ Returns:
+ A string corresponding to the module's name.
+ """
+ return a_module.__name__.split('.')[-1]
+
+ def register_controller(self, controller_module, required=True, builtin=False):
+ """Registers an controller module for a test class. Invokes Mobly's
+ implementation of register_controller.
+ """
+ module_ref_name = self.get_module_reference_name(controller_module)
+ module_config_name = controller_module.MOBLY_CONTROLLER_CONFIG_NAME
+
+ # Get controller objects from Mobly's register_controller
+ controllers = self._controller_manager.register_controller(controller_module, required=required)
+ if not controllers:
+ return None
+
+ # Log controller information
+ # Implementation of "get_info" is optional for a controller module.
+ if hasattr(controller_module, "get_info"):
+ controller_info = controller_module.get_info(controllers)
+ self.log.info("Controller %s: %s", module_config_name, controller_info)
+
+ if builtin:
+ setattr(self, module_ref_name, controllers)
+ return controllers
+
+ def __getattribute__(self, name):
+ attr = super().__getattribute__(name)
+ if not callable(attr) or not GdBaseTestClass.__is_entry_function(name):
+ return attr
+
+ @wraps(attr)
+ def __wrapped(*args, **kwargs):
+ try:
+ return attr(*args, **kwargs)
+ except RpcError as e:
+ exception_info = "".join(traceback.format_exception(e.__class__, e, e.__traceback__))
+ raise signals.TestFailure(
+ "RpcError during test\n\nRpcError:\n\n%s\n%s" % (exception_info, self.__dump_crashes()))
+
+ return __wrapped
+
+ __ENTRY_METHODS = {"setup_class", "teardown_class", "setup_test", "teardown_test"}
+
+ @staticmethod
+ def __is_entry_function(name):
+ return name.startswith("test_") or name in GdBaseTestClass.__ENTRY_METHODS
+
+ def __dump_crashes(self):
+ """
+ return: formatted stack traces if found, or last few lines of log
+ """
+ crash_detail = dump_crashes_core(
+ dut=self.dut,
+ cert=self.cert,
+ rootcanal_running=self.rootcanal_running,
+ rootcanal_process=self.rootcanal_process,
+ rootcanal_logpath=self.rootcanal_logpath)
+ return crash_detail
diff --git a/blueberry/tests/gd/cert/gd_device.py b/blueberry/tests/gd/cert/gd_device.py
new file mode 100644
index 000000000..185229040
--- /dev/null
+++ b/blueberry/tests/gd/cert/gd_device.py
@@ -0,0 +1,491 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from abc import ABC
+from datetime import datetime
+import inspect
+import logging
+import os
+import pathlib
+import shutil
+import signal
+import socket
+import subprocess
+import time
+from typing import List
+
+import grpc
+
+from cert.async_subprocess_logger import AsyncSubprocessLogger
+from cert.gd_device_lib import create_core
+from cert.gd_device_lib import destroy_core
+from cert.gd_device_lib import get_info
+from cert.gd_device_lib import replace_vars
+from cert.gd_device_lib import GdDeviceBaseCore
+from cert.gd_device_lib import GdHostOnlyDeviceCore
+from cert.gd_device_lib import MOBLY_CONTROLLER_CONFIG_NAME
+from cert.logging_client_interceptor import LoggingClientInterceptor
+from cert.os_utils import get_gd_root
+from cert.os_utils import read_crash_snippet_and_log_tail
+from cert.os_utils import is_subprocess_alive
+from cert.os_utils import make_ports_available
+from cert.os_utils import TerminalColor
+
+from blueberry.tests.gd.cert.context import get_current_context
+
+from mobly import asserts
+from mobly import utils
+from mobly.controllers.android_device_lib.adb import AdbProxy
+from mobly.controllers.android_device_lib.adb import AdbError
+
+
+def create(configs):
+ create_core(configs)
+ return get_instances_with_configs(configs)
+
+
+def destroy(devices):
+ destroy_core(devices)
+
+
+def get_instances_with_configs(configs):
+ print(configs)
+ devices = []
+ for config in configs:
+ resolved_cmd = []
+ for arg in config["cmd"]:
+ logging.debug(arg)
+ resolved_cmd.append(replace_vars(arg, config))
+ verbose_mode = bool(config.get('verbose_mode', False))
+ if config.get("serial_number"):
+ device = GdAndroidDevice(config["grpc_port"], config["grpc_root_server_port"], config["signal_port"],
+ resolved_cmd, config["label"], MOBLY_CONTROLLER_CONFIG_NAME, config["name"],
+ config["serial_number"], verbose_mode)
+ else:
+ device = GdHostOnlyDevice(config["grpc_port"], config["grpc_root_server_port"], config["signal_port"],
+ resolved_cmd, config["label"], MOBLY_CONTROLLER_CONFIG_NAME, config["name"],
+ verbose_mode)
+ device.setup()
+ devices.append(device)
+ return devices
+
+
+class GdDeviceBase(GdDeviceBaseCore):
+ """
+ Base GD device class that covers common traits which assumes that the
+ device must be driven by a driver-like backing process that takes following
+ command line arguments:
+ --grpc-port: main entry port for facade services
+ --root-server-port: management port for starting and stopping services
+ --btsnoop: path to btsnoop HCI log
+ --signal-port: signaling port to indicate that backing process is started
+ --rootcanal-port: root-canal HCI port, optional
+ """
+
+ def __init__(self, grpc_port: str, grpc_root_server_port: str, signal_port: str, cmd: List[str], label: str,
+ type_identifier: str, name: str, verbose_mode: bool):
+ """Verify arguments and log path, initialize Base GD device, common traits
+ for both device based and host only GD cert tests
+ :param grpc_port: main gRPC service port
+ :param grpc_root_server_port: gRPC root server port
+ :param signal_port: signaling port for backing process start up
+ :param cmd: list of arguments to run in backing process
+ :param label: device label used in logs
+ :param type_identifier: device type identifier used in logs
+ :param name: name of device used in logs
+ """
+ # Must be at the first line of __init__ method
+ values = locals()
+ arguments = [values[arg] for arg in inspect.getfullargspec(GdDeviceBase.__init__).args if arg != "verbose_mode"]
+ asserts.assert_true(all(arguments), "All arguments to GdDeviceBase must not be None nor empty")
+ asserts.assert_true(all(cmd), "cmd list should not have None nor empty component")
+
+ self.log_path_base = get_current_context().get_full_output_path()
+ self.test_runner_base_path = \
+ get_current_context().get_base_output_path()
+
+ GdDeviceBaseCore.__init__(self, grpc_port, grpc_root_server_port, signal_port, cmd, label, type_identifier,
+ name, verbose_mode, self.log_path_base, self.test_runner_base_path)
+
+ def setup(self):
+ """Inherited setup method from base class to set up this device for test,
+ ensure signal port is available and backing process is started and alive,
+ must run before using this device
+ - After calling this, teardown() must be called when test finishes
+ - Should be executed after children classes' setup() methods
+ :return:
+ """
+ GdDeviceBaseCore.setup(self)
+ # Ensure signal port is available
+ # signal port is the only port that always listen on the host machine
+ asserts.assert_true(self.signal_port_available, "[%s] Failed to make signal port available" % self.label)
+
+ # Ensure backing process is started and alive
+ asserts.assert_true(self.backing_process, msg="Cannot start backing_process at " + " ".join(self.cmd))
+ asserts.assert_true(
+ self.is_backing_process_alive,
+ msg="backing_process stopped immediately after running " + " ".join(self.cmd))
+
+ def get_crash_snippet_and_log_tail(self):
+ GdDeviceBaseCore.get_crash_snippet_and_log_tail(self)
+
+ def teardown(self):
+ """Inherited teardown method from base class to tear down this device
+ and clean up any resources.
+ - Must be called after setup()
+ - Should be executed before children classes' teardown()
+ :return:
+ """
+ GdDeviceBaseCore.teardown(self)
+
+ def wait_channel_ready(self):
+ try:
+ GdDeviceBaseCore.wait_channel_ready(self)
+ except grpc.FutureTimeoutError:
+ asserts.fail("[%s] wait channel ready timeout" % self.label)
+
+
+class GdHostOnlyDevice(GdDeviceBase):
+ """
+ Host only device where the backing process is running on the host machine
+ """
+
+ def __init__(self, grpc_port: str, grpc_root_server_port: str, signal_port: str, cmd: List[str], label: str,
+ type_identifier: str, name: str, verbose_mode: bool):
+ super().__init__(grpc_port, grpc_root_server_port, signal_port, cmd, label, MOBLY_CONTROLLER_CONFIG_NAME, name,
+ verbose_mode)
+ # Enable LLVM code coverage output for host only tests
+ self.backing_process_profraw_path = pathlib.Path(self.log_path_base).joinpath(
+ "%s_%s_backing_coverage.profraw" % (self.type_identifier, self.label))
+ self.environment["LLVM_PROFILE_FILE"] = str(self.backing_process_profraw_path)
+ llvm_binutils = pathlib.Path(get_gd_root()).joinpath("llvm_binutils").joinpath("bin")
+ llvm_symbolizer = llvm_binutils.joinpath("llvm-symbolizer")
+ if llvm_symbolizer.is_file():
+ self.environment["ASAN_SYMBOLIZER_PATH"] = llvm_symbolizer
+ else:
+ logging.warning("[%s] Cannot find LLVM symbolizer at %s" % (self.label, str(llvm_symbolizer)))
+
+ def teardown(self):
+ super().teardown()
+ self.generate_coverage_report()
+
+ def generate_coverage_report(self):
+ GdHostOnlyDeviceCore.generate_coverage_report(self, self.backing_process_profraw_path, self.label,
+ self.test_runner_base_path, self.type_identifier, self.cmd)
+
+ def setup(self):
+ # Ensure ports are available
+ # Only check on host only test, for Android devices, these ports will
+ # be opened on Android device and host machine ports will be occupied
+ # by sshd or adb forwarding
+ asserts.assert_true(
+ make_ports_available((self.grpc_port, self.grpc_root_server_port)),
+ "[%s] Failed to make backing process ports available" % self.label)
+ super().setup()
+
+
+class GdAndroidDevice(GdDeviceBase):
+ """Real Android device where the backing process is running on it
+ """
+
+ WAIT_FOR_DEVICE_TIMEOUT_SECONDS = 180
+
+ def __init__(self, grpc_port: str, grpc_root_server_port: str, signal_port: str, cmd: List[str], label: str,
+ type_identifier: str, name: str, serial_number: str, verbose_mode: bool):
+ super().__init__(grpc_port, grpc_root_server_port, signal_port, cmd, label, type_identifier, name, verbose_mode)
+ asserts.assert_true(serial_number, "serial_number must not be None nor empty")
+ self.serial_number = serial_number
+ self.adb = AdbProxy(serial_number)
+
+ def setup(self):
+ logging.info("Setting up device %s %s" % (self.label, self.serial_number))
+ asserts.assert_true(self.adb.ensure_root(), "device %s cannot run as root", self.serial_number)
+
+ # Try freeing ports and ignore results
+ self.cleanup_port_forwarding()
+ self.sync_device_time()
+
+ # Set up port forwarding or reverse or die
+ self.tcp_forward_or_die(self.grpc_port, self.grpc_port)
+ self.tcp_forward_or_die(self.grpc_root_server_port, self.grpc_root_server_port)
+ self.tcp_reverse_or_die(self.signal_port, self.signal_port)
+
+ # Push test binaries
+ self.ensure_verity_disabled()
+ self.push_or_die(os.path.join(get_gd_root(), "target", "bluetooth_stack_with_facade"), "system/bin")
+ self.push_or_die(
+ os.path.join(get_gd_root(), "target", "android.system.suspend.control-V1-ndk.so"), "system/lib64")
+ self.push_or_die(os.path.join(get_gd_root(), "target", "libbluetooth_gd.so"), "system/lib64")
+ self.push_or_die(os.path.join(get_gd_root(), "target", "libgrpc++_unsecure.so"), "system/lib64")
+ self.push_or_die(os.path.join(get_gd_root(), "target", "libgrpc++.so"), "system/lib64")
+ self.push_or_die(os.path.join(get_gd_root(), "target", "libgrpc_wrap.so"), "system/lib64")
+ self.push_or_die(os.path.join(get_gd_root(), "target", "libstatslog.so"), "system/lib64")
+
+ try:
+ self.adb.shell("rm /data/misc/bluetooth/logs/btsnoop_hci.log")
+ except AdbError as error:
+ logging.error("Error during setup: " + str(error))
+
+ try:
+ self.adb.shell("rm /data/misc/bluetooth/logs/btsnooz_hci.log")
+ except AdbError as error:
+ logging.error("Error during setup: " + str(error))
+
+ try:
+ self.adb.shell("rm /data/misc/bluedroid/bt_config.conf")
+ except AdbError as error:
+ logging.error("Error during setup: " + str(error))
+
+ try:
+ self.adb.shell("rm /data/misc/bluedroid/bt_config.bak")
+ except AdbError as error:
+ logging.error("Error during setup: " + str(error))
+
+ self.ensure_no_output(self.adb.shell("svc bluetooth disable"))
+
+ # Start logcat logging
+ self.logcat_output_path = os.path.join(
+ self.log_path_base, '%s_%s_%s_logcat_logs.txt' % (self.type_identifier, self.label, self.serial_number))
+ self.logcat_cmd = ["adb", "-s", self.serial_number, "logcat", "-T", "1", "-v", "year", "-v", "uid"]
+ logging.debug("Running %s", " ".join(self.logcat_cmd))
+ self.logcat_process = subprocess.Popen(
+ self.logcat_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
+ asserts.assert_true(self.logcat_process, msg="Cannot start logcat_process at " + " ".join(self.logcat_cmd))
+ asserts.assert_true(
+ is_subprocess_alive(self.logcat_process),
+ msg="logcat_process stopped immediately after running " + " ".join(self.logcat_cmd))
+ self.logcat_logger = AsyncSubprocessLogger(
+ self.logcat_process, [self.logcat_output_path],
+ log_to_stdout=self.verbose_mode,
+ tag="%s_%s" % (self.label, self.serial_number),
+ color=self.terminal_color)
+
+ # Done run parent setup
+ logging.info("Done preparation for %s, starting backing process" % self.serial_number)
+ super().setup()
+
+ def teardown(self):
+ super().teardown()
+ stop_signal = signal.SIGINT
+ self.logcat_process.send_signal(stop_signal)
+ try:
+ return_code = self.logcat_process.wait(timeout=self.WAIT_CHANNEL_READY_TIMEOUT_SECONDS)
+ except subprocess.TimeoutExpired:
+ logging.error("[%s_%s] Failed to interrupt logcat process via SIGINT, sending SIGKILL" %
+ (self.label, self.serial_number))
+ stop_signal = signal.SIGKILL
+ self.logcat_process.kill()
+ try:
+ return_code = self.logcat_process.wait(timeout=self.WAIT_CHANNEL_READY_TIMEOUT_SECONDS)
+ except subprocess.TimeoutExpired:
+ logging.error("Failed to kill logcat_process %s %s" % (self.label, self.serial_number))
+ return_code = -65536
+ if return_code not in [-stop_signal, 0]:
+ logging.error("logcat_process %s_%s stopped with code: %d" % (self.label, self.serial_number, return_code))
+ self.logcat_logger.stop()
+ self.cleanup_port_forwarding()
+ self.adb.pull("/data/misc/bluetooth/logs/btsnoop_hci.log %s" % os.path.join(self.log_path_base,
+ "%s_btsnoop_hci.log" % self.label))
+ self.adb.pull("/data/misc/bluedroid/bt_config.conf %s" % os.path.join(self.log_path_base,
+ "%s_bt_config.conf" % self.label))
+ self.adb.pull(
+ "/data/misc/bluedroid/bt_config.bak %s" % os.path.join(self.log_path_base, "%s_bt_config.bak" % self.label))
+
+ def cleanup_port_forwarding(self):
+ try:
+ self.adb.remove_tcp_forward(self.grpc_port)
+ except AdbError as error:
+ logging.error("Error during port forwarding cleanup: " + str(error))
+
+ try:
+ self.adb.remove_tcp_forward(self.grpc_root_server_port)
+ except AdbError as error:
+ logging.error("Error during port forwarding cleanup: " + str(error))
+
+ try:
+ self.adb.reverse("--remove tcp:%d" % self.signal_port)
+ except AdbError as error:
+ logging.error("Error during port forwarding cleanup: " + str(error))
+
+ @staticmethod
+ def ensure_no_output(result):
+ """
+ Ensure a command has not output
+ """
+ asserts.assert_true(
+ result is None or len(result) == 0, msg="command returned something when it shouldn't: %s" % result)
+
+ def sync_device_time(self):
+ self.adb.shell("settings put global auto_time 0")
+ self.adb.shell("settings put global auto_time_zone 0")
+ device_tz = self.adb.shell("date +%z")
+ asserts.assert_true(device_tz, "date +%z must return device timezone, "
+ "but returned {} instead".format(device_tz))
+ host_tz = time.strftime("%z")
+ if device_tz != host_tz:
+ target_timezone = utils.get_timezone_olson_id()
+ logging.debug("Device timezone %s does not match host timezone %s, "
+ "syncing them by setting timezone to %s" % (device_tz, host_tz, target_timezone))
+ self.adb.shell("setprop persist.sys.timezone %s" % target_timezone)
+ self.reboot()
+ device_tz = self.adb.shell("date +%z")
+ asserts.assert_equal(
+ host_tz, device_tz, "Device timezone %s still does not match host "
+ "timezone %s after reset" % (device_tz, host_tz))
+ self.adb.shell("date %s" % time.strftime("%m%d%H%M%Y.%S"))
+ datetime_format = "%Y-%m-%dT%H:%M:%S%z"
+ try:
+ device_time = datetime.strptime(self.adb.shell("date +'%s'" % datetime_format), datetime_format)
+ except ValueError:
+ asserts.fail("Failed to get time after sync")
+ return
+ # Include ADB delay that might be longer in SSH environment
+ max_delta_seconds = 3
+ host_time = datetime.now(tz=device_time.tzinfo)
+ asserts.assert_almost_equal(
+ (device_time - host_time).total_seconds(),
+ 0,
+ msg="Device time %s and host time %s off by >%dms after sync" %
+ (device_time.isoformat(), host_time.isoformat(), int(max_delta_seconds * 1000)),
+ delta=max_delta_seconds)
+
+ def push_or_die(self, src_file_path, dst_file_path, push_timeout=300):
+ """Pushes a file to the Android device
+
+ Args:
+ src_file_path: The path to the file to install.
+ dst_file_path: The destination of the file.
+ push_timeout: How long to wait for the push to finish in seconds
+ """
+ out = self.adb.push('%s %s' % (src_file_path, dst_file_path), timeout=push_timeout)
+ if 'error' in out:
+ asserts.fail('Unable to push file %s to %s due to %s' % (src_file_path, dst_file_path, out))
+
+ def tcp_forward_or_die(self, host_port, device_port, num_retry=1):
+ """
+ Forward a TCP port from host to device or fail
+ :param host_port: host port, int, 0 for adb to assign one
+ :param device_port: device port, int
+ :param num_retry: number of times to reboot and retry this before dying
+ :return: host port int
+ """
+ error_or_port = self.adb.tcp_forward(host_port, device_port)
+ if not error_or_port:
+ logging.debug("host port %d was already forwarded" % host_port)
+ return host_port
+ if not isinstance(error_or_port, int):
+ if num_retry > 0:
+ # If requested, reboot an retry
+ num_retry -= 1
+ logging.warning(
+ "[%s] Failed to TCP forward host port %d to "
+ "device port %d, num_retries left is %d" % (self.label, host_port, device_port, num_retry))
+ self.reboot()
+ return self.tcp_forward_or_die(host_port, device_port, num_retry=num_retry)
+ asserts.fail(
+ 'Unable to forward host port %d to device port %d, error %s' % (host_port, device_port, error_or_port))
+ return error_or_port
+
+ def tcp_reverse_or_die(self, device_port, host_port, num_retry=1):
+ """
+ Forward a TCP port from device to host or fail
+ :param device_port: device port, int, 0 for adb to assign one
+ :param host_port: host port, int
+ :param num_retry: number of times to reboot and retry this before dying
+ :return: device port int
+ """
+ error_or_port = self.adb.reverse("tcp:%d tcp:%d" % (device_port, host_port))
+ if not error_or_port:
+ logging.debug("device port %d was already reversed" % device_port)
+ return device_port
+ try:
+ error_or_port = int(error_or_port)
+ except ValueError:
+ if num_retry > 0:
+ # If requested, reboot an retry
+ num_retry -= 1
+ logging.warning(
+ "[%s] Failed to TCP reverse device port %d to "
+ "host port %d, num_retries left is %d" % (self.label, device_port, host_port, num_retry))
+ self.reboot()
+ return self.tcp_reverse_or_die(device_port, host_port, num_retry=num_retry)
+ asserts.fail(
+ 'Unable to reverse device port %d to host port %d, error %s' % (device_port, host_port, error_or_port))
+ return error_or_port
+
+ def ensure_verity_disabled(self):
+ """Ensures that verity is enabled.
+
+ If verity is not enabled, this call will reboot the phone. Note that
+ this only works on debuggable builds.
+ """
+ logging.debug("Disabling verity and remount for %s", self.serial_number)
+ # The below properties will only exist if verity has been enabled.
+ system_verity = self.adb.getprop('partition.system.verified')
+ vendor_verity = self.adb.getprop('partition.vendor.verified')
+ if system_verity or vendor_verity:
+ self.adb.disable_verity()
+ self.reboot()
+ self.adb.remount()
+ self.adb.wait_for_device(timeout=self.WAIT_FOR_DEVICE_TIMEOUT_SECONDS)
+
+ def reboot(self, timeout_minutes=15.0):
+ """Reboots the device.
+
+ Reboot the device, wait for device to complete booting.
+ """
+ logging.debug("Rebooting %s", self.serial_number)
+ self.adb.reboot()
+
+ timeout_start = time.time()
+ timeout = timeout_minutes * 60
+ # Android sometimes return early after `adb reboot` is called. This
+ # means subsequent calls may make it to the device before the reboot
+ # goes through, return false positives for getprops such as
+ # sys.boot_completed.
+ while time.time() < timeout_start + timeout:
+ try:
+ self.adb.get_state()
+ time.sleep(.1)
+ except AdbError:
+ # get_state will raise an error if the device is not found. We
+ # want the device to be missing to prove the device has kicked
+ # off the reboot.
+ break
+ minutes_left = timeout_minutes - (time.time() - timeout_start) / 60.0
+ self.wait_for_boot_completion(timeout_minutes=minutes_left)
+ asserts.assert_true(self.adb.ensure_root(), "device %s cannot run as root after reboot", self.serial_number)
+
+ def wait_for_boot_completion(self, timeout_minutes=15.0):
+ """
+ Waits for Android framework to broadcast ACTION_BOOT_COMPLETED.
+ :param timeout_minutes: number of minutes to wait
+ """
+ timeout_start = time.time()
+ timeout = timeout_minutes * 60
+
+ self.adb.wait_for_device(timeout=self.WAIT_FOR_DEVICE_TIMEOUT_SECONDS)
+ while time.time() < timeout_start + timeout:
+ try:
+ completed = self.adb.getprop("sys.boot_completed")
+ if completed == '1':
+ return
+ except AdbError:
+ # adb shell calls may fail during certain period of booting
+ # process, which is normal. Ignoring these errors.
+ pass
+ time.sleep(5)
+ asserts.fail(msg='Device %s booting process timed out.' % self.serial_number)
diff --git a/blueberry/tests/gd/cert/metadata.py b/blueberry/tests/gd/cert/metadata.py
new file mode 100644
index 000000000..aa1bc9a8b
--- /dev/null
+++ b/blueberry/tests/gd/cert/metadata.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import functools
+import inspect
+
+from mobly import asserts
+
+from blueberry.tests.gd.cert.test_decorators import test_info
+
+
+def _fail_decorator(msg):
+
+ def fail_decorator(func):
+
+ @functools.wraps(func)
+ def fail(*args, **kwargs):
+ asserts.fail(msg)
+
+ return fail
+
+ return fail_decorator
+
+
+def metadata(_do_not_use=None, pts_test_id=None, pts_test_name=None):
+ """
+ Record a piece of test metadata in the Extra section of the test Record in
+ the test summary file. The metadata will come with a timestamp, but there
+ is no guarantee on the order of when the metadata will be written
+
+ Note:
+ - Metadata is recorded per test case as key-value pairs.
+ - Metadata is only guaranteed to be written when the test result is PASS,
+ FAIL or SKIPPED. When there are test infrastructural errors, metadata
+ might not be written successfully
+ :param _do_not_use: a positional argument with default value. This argument
+ is to ensure that @metadata(key=value) is used in a
+ functional form instead of @metadata or @metadata(a)
+ :param pts_test_id: A fully qualified PTS test ID such as
+ L2CAP/COS/IEX/BV-01-C
+ :param pts_test_name: A human readable test name such as
+ "Request Connection" for the above example
+ :return: decorated test case function object
+ """
+ if _do_not_use is not None:
+
+ def fail(*args, **kwargs):
+ asserts.fail("@metadata must be used in functional form such " "as @metadta(key=value)")
+
+ return fail
+
+ # Create a dictionary of optional parameters
+ values = locals()
+ args = {arg: values[arg] for arg in inspect.getfullargspec(metadata).args}
+ del args["_do_not_use"]
+
+ # Check if at least one optional parameter is valid
+ if not any(args.values()):
+ return _fail_decorator("at least one optional argument should be valid")
+
+ # Validate pts_test_id and pts_test_name
+ if any((pts_test_id, pts_test_name)) and \
+ not all((pts_test_id, pts_test_name)):
+ return _fail_decorator("pts_test_id and pts_test_name must both " "be valid if one of them is valid")
+
+ return test_info(**args)
diff --git a/blueberry/tests/gd/cert/pts_base_test.py b/blueberry/tests/gd/cert/pts_base_test.py
new file mode 100644
index 000000000..0e8b99a71
--- /dev/null
+++ b/blueberry/tests/gd/cert/pts_base_test.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from mobly import base_test
+
+import importlib
+
+
+class PTSBaseTestClass(base_test.BaseTestClass):
+
+ def __init__(self, configs):
+ BaseTestClass.__init__(self, configs)
+
+ gd_devices = self.controller_configs.get("GdDevice")
+
+ self.register_controller(importlib.import_module('cert.gd_device'), builtin=True)
diff --git a/blueberry/tests/gd/cert/test_decorators.py b/blueberry/tests/gd/cert/test_decorators.py
new file mode 100644
index 000000000..453062e26
--- /dev/null
+++ b/blueberry/tests/gd/cert/test_decorators.py
@@ -0,0 +1,267 @@
+#!/usr/bin/env python3
+#
+# Copyright 2017 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from mobly import signals
+
+
+def test_info(predicate=None, **keyed_info):
+ """Adds info about test.
+
+ Extra info to include about the test. This info will be available in the
+ test output. Note that if a key is given multiple times it will be added
+ as a list of all values. If multiples of these are stacked their results
+ will be merged.
+
+ Example:
+ # This test will have a variable my_var
+ @test_info(my_var='THIS IS MY TEST')
+ def my_test(self):
+ return False
+
+ Args:
+ predicate: A func to call that if false will skip adding this test
+ info. Function signature is bool(test_obj, args, kwargs)
+ **keyed_info: The key, value info to include in the extras for this
+ test.
+ """
+
+ def test_info_decorator(func):
+ return _TestInfoDecoratorFunc(func, predicate, keyed_info)
+
+ return test_info_decorator
+
+
+def __select_last(test_signals, _):
+ return test_signals[-1]
+
+
+def repeated_test(num_passes, acceptable_failures=0, result_selector=__select_last):
+ """A decorator that runs a test case multiple times.
+
+ This decorator can be used to run a test multiple times and aggregate the
+ data into a single test result. By setting `result_selector`, the user can
+ access the returned result of each run, allowing them to average results,
+ return the median, or gather and return standard deviation values.
+
+ This decorator should be used on test cases, and should not be used on
+ static or class methods. The test case must take in an additional argument,
+ `attempt_number`, which returns the current attempt number, starting from
+ 1.
+
+ Note that any TestSignal intended to abort or skip the test will take
+ abort or skip immediately.
+
+ Args:
+ num_passes: The number of times the test needs to pass to report the
+ test case as passing.
+ acceptable_failures: The number of failures accepted. If the failures
+ exceeds this number, the test will stop repeating. The maximum
+ number of runs is `num_passes + acceptable_failures`. If the test
+ does fail, result_selector will still be called.
+ result_selector: A lambda that takes in the list of TestSignals and
+ returns the test signal to report the test case as. Note that the
+ list also contains any uncaught exceptions from the test execution.
+ """
+
+ def decorator(func):
+ if not func.__name__.startswith('test_'):
+ raise ValueError('Tests must start with "test_".')
+
+ def test_wrapper(self):
+ num_failures = 0
+ num_seen_passes = 0
+ test_signals_received = []
+ for i in range(num_passes + acceptable_failures):
+ try:
+ func(self, i + 1)
+ except (signals.TestFailure, signals.TestError, AssertionError) as signal:
+ test_signals_received.append(signal)
+ num_failures += 1
+ except signals.TestPass as signal:
+ test_signals_received.append(signal)
+ num_seen_passes += 1
+ except (signals.TestSignal, KeyboardInterrupt):
+ raise
+ except Exception as signal:
+ test_signals_received.append(signal)
+ num_failures += 1
+ else:
+ num_seen_passes += 1
+ test_signals_received.append(
+ signals.TestPass('Test iteration %s of %s passed without details.' % (i, func.__name__)))
+
+ if num_failures > acceptable_failures:
+ break
+ elif num_seen_passes == num_passes:
+ break
+ else:
+ self.teardown_test()
+ self.setup_test()
+
+ raise result_selector(test_signals_received, self)
+
+ return test_wrapper
+
+ return decorator
+
+
+def test_tracker_info(uuid, extra_environment_info=None, predicate=None):
+ """Decorator for adding test tracker info to tests results.
+
+ Will add test tracker info inside of Extras/test_tracker_info.
+
+ Example:
+ # This test will be linked to test tracker uuid abcd
+ @test_tracker_info(uuid='abcd')
+ def my_test(self):
+ return False
+
+ Args:
+ uuid: The uuid of the test case in test tracker.
+ extra_environment_info: Extra info about the test tracker environment.
+ predicate: A func that if false when called will ignore this info.
+ """
+ return test_info(test_tracker_uuid=uuid, test_tracker_environment_info=extra_environment_info, predicate=predicate)
+
+
+class _TestInfoDecoratorFunc(object):
+ """Object that acts as a function decorator test info."""
+
+ def __init__(self, func, predicate, keyed_info):
+ self.func = func
+ self.predicate = predicate
+ self.keyed_info = keyed_info
+ self.__name__ = func.__name__
+ self.__doc__ = func.__doc__
+ self.__module__ = func.__module__
+
+ def __get__(self, instance, owner):
+ """Called by Python to create a binding for an instance closure.
+
+ When called by Python this object will create a special binding for
+ that instance. That binding will know how to interact with this
+ specific decorator.
+ """
+ return _TestInfoBinding(self, instance)
+
+ def __call__(self, *args, **kwargs):
+ """
+ When called runs the underlying func and then attaches test info
+ to a signal.
+ """
+ cause = None
+ try:
+ result = self.func(*args, **kwargs)
+
+ if result or result is None:
+ new_signal = signals.TestPass('')
+ else:
+ new_signal = signals.TestFailure('')
+ except signals.TestSignal as signal:
+ new_signal = signal
+ except Exception as ex:
+ cause = ex
+ new_signal = signals.TestError(cause)
+
+ if new_signal.extras is None:
+ new_signal.extras = {}
+ if not isinstance(new_signal.extras, dict):
+ raise ValueError('test_info can only append to signal data that has a dict as the extra value.')
+
+ gathered_extras = self._gather_local_info(None, *args, **kwargs)
+ for k, v in gathered_extras.items():
+ if k not in new_signal.extras:
+ new_signal.extras[k] = v
+ else:
+ if not isinstance(new_signal.extras[k], list):
+ new_signal.extras[k] = [new_signal.extras[k]]
+
+ new_signal.extras[k].insert(0, v)
+
+ raise new_signal from cause
+
+ def gather(self, *args, **kwargs):
+ """
+ Gathers the info from this decorator without invoking the underlying
+ function. This will also gather all child info if the underlying func
+ has that ability.
+
+ Returns: A dictionary of info.
+ """
+ if hasattr(self.func, 'gather'):
+ extras = self.func.gather(*args, **kwargs)
+ else:
+ extras = {}
+
+ self._gather_local_info(extras, *args, **kwargs)
+
+ return extras
+
+ def _gather_local_info(self, gather_into, *args, **kwargs):
+ """Gathers info from this decorator and ignores children.
+
+ Args:
+ gather_into: Gathers into a dictionary that already exists.
+
+ Returns: The dictionary with gathered info in it.
+ """
+ if gather_into is None:
+ extras = {}
+ else:
+ extras = gather_into
+ if not self.predicate or self.predicate(args, kwargs):
+ for k, v in self.keyed_info.items():
+ if v and k not in extras:
+ extras[k] = v
+ elif v and k in extras:
+ if not isinstance(extras[k], list):
+ extras[k] = [extras[k]]
+ extras[k].insert(0, v)
+
+ return extras
+
+
+class _TestInfoBinding(object):
+ """
+ When Python creates an instance of an object it creates a binding object
+ for each closure that contains what the instance variable should be when
+ called. This object is a similar binding for _TestInfoDecoratorFunc.
+ When Python tries to create a binding of a _TestInfoDecoratorFunc it
+ will return one of these objects to hold the instance for that closure.
+ """
+
+ def __init__(self, target, instance):
+ """
+ Args:
+ target: The target for creating a binding to.
+ instance: The instance to bind the target with.
+ """
+ self.target = target
+ self.instance = instance
+ self.__name__ = target.__name__
+
+ def __call__(self, *args, **kwargs):
+ """
+ When this object is called it will call the target with the bound
+ instance.
+ """
+ return self.target(self.instance, *args, **kwargs)
+
+ def gather(self, *args, **kwargs):
+ """
+ Will gather the target with the bound instance.
+ """
+ return self.target.gather(self.instance, *args, **kwargs)
diff --git a/blueberry/tests/gd/cert/tracelogger.py b/blueberry/tests/gd/cert/tracelogger.py
new file mode 100644
index 000000000..a4e5a2203
--- /dev/null
+++ b/blueberry/tests/gd/cert/tracelogger.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright 2016 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import inspect
+import os
+
+
+class TraceLogger(object):
+
+ def __init__(self, logger):
+ self._logger = logger
+
+ @staticmethod
+ def _get_trace_info(level=1, offset=2):
+ # We want the stack frame above this and above the error/warning/info
+ inspect_stack = inspect.stack()
+ trace_info = ''
+ for i in range(level):
+ try:
+ stack_frames = inspect_stack[offset + i]
+ info = inspect.getframeinfo(stack_frames[0])
+ trace_info = '%s[%s:%s:%s]' % (trace_info, os.path.basename(info.filename), info.function, info.lineno)
+ except IndexError:
+ break
+ return trace_info
+
+ def _log_with(self, logging_lambda, trace_level, msg, *args, **kwargs):
+ trace_info = TraceLogger._get_trace_info(level=trace_level, offset=3)
+ logging_lambda('%s %s' % (msg, trace_info), *args, **kwargs)
+
+ def exception(self, msg, *args, **kwargs):
+ self._log_with(self._logger.exception, 5, msg, *args, **kwargs)
+
+ def debug(self, msg, *args, **kwargs):
+ self._log_with(self._logger.debug, 3, msg, *args, **kwargs)
+
+ def error(self, msg, *args, **kwargs):
+ self._log_with(self._logger.error, 3, msg, *args, **kwargs)
+
+ def warn(self, msg, *args, **kwargs):
+ self._log_with(self._logger.warn, 3, msg, *args, **kwargs)
+
+ def warning(self, msg, *args, **kwargs):
+ self._log_with(self._logger.warning, 3, msg, *args, **kwargs)
+
+ def info(self, msg, *args, **kwargs):
+ self._log_with(self._logger.info, 1, msg, *args, **kwargs)
+
+ def __getattr__(self, name):
+ return getattr(self._logger, name)
diff --git a/blueberry/tests/gd/hal/simple_hal_test.py b/blueberry/tests/gd/hal/simple_hal_test.py
new file mode 100644
index 000000000..f7bc1cb41
--- /dev/null
+++ b/blueberry/tests/gd/hal/simple_hal_test.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from hal.cert.simple_hal_test_lib import SimpleHalTestBase
+from mobly import test_runner
+
+_GRPC_TIMEOUT = 10
+
+
+class SimpleHalTest(gd_base_test.GdBaseTestClass, SimpleHalTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='HAL', cert_module='HAL')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ SimpleHalTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ SimpleHalTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/hci/acl_manager_test.py b/blueberry/tests/gd/hci/acl_manager_test.py
new file mode 100644
index 000000000..fd921c33b
--- /dev/null
+++ b/blueberry/tests/gd/hci/acl_manager_test.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from hci.cert.acl_manager_test_lib import AclManagerTestBase
+from mobly import test_runner
+
+
+class AclManagerTest(gd_base_test.GdBaseTestClass, AclManagerTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='HCI_INTERFACES', cert_module='HCI')
+
+ # todo: move into GdBaseTestClass, based on modules inited
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ AclManagerTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ AclManagerTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+ def test_dut_connects(self):
+ AclManagerTestBase.test_dut_connects(self)
+
+ def test_cert_connects(self):
+ AclManagerTestBase.test_cert_connects(self)
+
+ def test_cert_connects_disconnects(self):
+ AclManagerTestBase.test_cert_connects_disconnects(self)
+
+ def test_recombination_l2cap_packet(self):
+ AclManagerTestBase.test_recombination_l2cap_packet(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/hci/controller_test.py b/blueberry/tests/gd/hci/controller_test.py
new file mode 100644
index 000000000..d225d2579
--- /dev/null
+++ b/blueberry/tests/gd/hci/controller_test.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from hci.cert.controller_test_lib import ControllerTestBase
+from mobly import test_runner
+
+
+class ControllerTest(gd_base_test.GdBaseTestClass, ControllerTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='HCI_INTERFACES', cert_module='HCI_INTERFACES')
+
+ def test_get_addresses(self):
+ ControllerTestBase.test_get_addresses(self, self.dut, self.cert)
+
+ def test_write_local_name(self):
+ ControllerTestBase.test_write_local_name(self, self.dut, self.cert)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/hci/direct_hci_test.py b/blueberry/tests/gd/hci/direct_hci_test.py
new file mode 100644
index 000000000..e02a83a53
--- /dev/null
+++ b/blueberry/tests/gd/hci/direct_hci_test.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from hci.cert.direct_hci_test_lib import DirectHciTestBase
+from mobly import test_runner
+
+
+class DirectHciTest(gd_base_test.GdBaseTestClass, DirectHciTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='HCI', cert_module='HAL')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ DirectHciTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ DirectHciTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/hci/le_acl_manager_test.py b/blueberry/tests/gd/hci/le_acl_manager_test.py
new file mode 100644
index 000000000..e65a972f7
--- /dev/null
+++ b/blueberry/tests/gd/hci/le_acl_manager_test.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from hci.cert.le_acl_manager_test_lib import LeAclManagerTestBase
+from mobly import test_runner
+
+
+class LeAclManagerTest(gd_base_test.GdBaseTestClass, LeAclManagerTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='HCI_INTERFACES', cert_module='HCI')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ LeAclManagerTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ LeAclManagerTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/hci/le_advertising_manager_test.py b/blueberry/tests/gd/hci/le_advertising_manager_test.py
new file mode 100644
index 000000000..814645df5
--- /dev/null
+++ b/blueberry/tests/gd/hci/le_advertising_manager_test.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from hci.cert.le_advertising_manager_test_lib import LeAdvertisingManagerTestBase
+from mobly import test_runner
+
+
+class LeAdvertisingManagerTest(gd_base_test.GdBaseTestClass, LeAdvertisingManagerTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='HCI_INTERFACES', cert_module='HCI')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ LeAdvertisingManagerTestBase.setup_test(self, self.cert)
+
+ def teardown_test(self):
+ LeAdvertisingManagerTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/hci/le_scanning_manager_test.py b/blueberry/tests/gd/hci/le_scanning_manager_test.py
new file mode 100644
index 000000000..08227471e
--- /dev/null
+++ b/blueberry/tests/gd/hci/le_scanning_manager_test.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from hci.cert.le_scanning_manager_test_lib import LeScanningManagerTestBase
+from mobly import test_runner
+
+
+class LeScanningManagerTest(gd_base_test.GdBaseTestClass, LeScanningManagerTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='HCI_INTERFACES', cert_module='HCI_INTERFACES')
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/hci/le_scanning_with_security_test.py b/blueberry/tests/gd/hci/le_scanning_with_security_test.py
new file mode 100644
index 000000000..207b2dadf
--- /dev/null
+++ b/blueberry/tests/gd/hci/le_scanning_with_security_test.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from hci.cert.le_scanning_with_security_test_lib import LeScanningWithSecurityTestBase
+from mobly import test_runner
+
+
+class LeScanningWithSecurityTest(gd_base_test.GdBaseTestClass, LeScanningWithSecurityTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='SECURITY', cert_module='HCI_INTERFACES')
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/host_config.yaml b/blueberry/tests/gd/host_config.yaml
index 77498377d..15155db8d 100644
--- a/blueberry/tests/gd/host_config.yaml
+++ b/blueberry/tests/gd/host_config.yaml
@@ -1,32 +1,32 @@
_description: Bluetooth cert testing
TestBeds:
- - _description: Host only cert testbed
- Name: HostOnlyCert
- rootcanal:
- test_port: '6401'
- hci_port: '6402'
- link_layer_port: '6403'
- GdDevice:
- - grpc_port: '8998'
- grpc_root_server_port: '8996'
- signal_port: '8994'
- label: cert
- Name: Cert Device
- cmd:
- - "$GD_ROOT/bluetooth_stack_with_facade"
- - "--grpc-port=$(grpc_port)"
- - "--root-server-port=$(grpc_root_server_port)"
- - "--rootcanal-port=$(rootcanal_port)"
- - "--signal-port=$(signal_port)"
- - grpc_port: '8999'
- grpc_root_server_port: '8997'
- signal_port: '8995'
- label: dut
- Name: DUT Device
- cmd:
- - "$GD_ROOT/bluetooth_stack_with_facade"
- - "--grpc-port=$(grpc_port)"
- - "--root-server-port=$(grpc_root_server_port)"
- - "--rootcanal-port=$(rootcanal_port)"
- - "--signal-port=$(signal_port)"
+ - Name: HostOnlyCert
+ Controllers:
+ rootcanal:
+ test_port: '6401'
+ hci_port: '6402'
+ link_layer_port: '6403'
+ GdDevice:
+ - grpc_port: '8998'
+ grpc_root_server_port: '8996'
+ signal_port: '8994'
+ label: cert
+ name: Cert Device
+ cmd:
+ - "$GD_ROOT/bluetooth_stack_with_facade"
+ - "--grpc-port=$(grpc_port)"
+ - "--root-server-port=$(grpc_root_server_port)"
+ - "--rootcanal-port=$(rootcanal_port)"
+ - "--signal-port=$(signal_port)"
+ - grpc_port: '8999'
+ grpc_root_server_port: '8997'
+ signal_port: '8995'
+ label: dut
+ name: DUT Device
+ cmd:
+ - "$GD_ROOT/bluetooth_stack_with_facade"
+ - "--grpc-port=$(grpc_port)"
+ - "--root-server-port=$(grpc_root_server_port)"
+ - "--rootcanal-port=$(rootcanal_port)"
+ - "--signal-port=$(signal_port)"
logpath: "/tmp/logs"
diff --git a/blueberry/tests/gd/iso/le_iso_test.py b/blueberry/tests/gd/iso/le_iso_test.py
new file mode 100644
index 000000000..e0b509012
--- /dev/null
+++ b/blueberry/tests/gd/iso/le_iso_test.py
@@ -0,0 +1,36 @@
+#
+# Copyright 2021 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from iso.cert.le_iso_test_lib import LeIsoTestBase
+from mobly import test_runner
+
+
+class LeIsoTest(gd_base_test.GdBaseTestClass, LeIsoTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='L2CAP', cert_module='HCI_INTERFACES')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ LeIsoTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ LeIsoTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/l2cap/classic/l2cap_performance_test.py b/blueberry/tests/gd/l2cap/classic/l2cap_performance_test.py
new file mode 100644
index 000000000..9a0010c2c
--- /dev/null
+++ b/blueberry/tests/gd/l2cap/classic/l2cap_performance_test.py
@@ -0,0 +1,36 @@
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from l2cap.classic.cert.l2cap_performance_test_lib import L2capPerformanceTestBase
+from mobly import test_runner
+
+
+class L2capPerformanceTest(gd_base_test.GdBaseTestClass, L2capPerformanceTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='L2CAP', cert_module='HCI_INTERFACES')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ L2capPerformanceTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ L2capPerformanceTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/l2cap/classic/l2cap_test.py b/blueberry/tests/gd/l2cap/classic/l2cap_test.py
new file mode 100644
index 000000000..71dcc5b2b
--- /dev/null
+++ b/blueberry/tests/gd/l2cap/classic/l2cap_test.py
@@ -0,0 +1,36 @@
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from l2cap.classic.cert.l2cap_test_lib import L2capTestBase
+from mobly import test_runner
+
+
+class L2capTest(gd_base_test.GdBaseTestClass, L2capTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='L2CAP', cert_module='HCI_INTERFACES')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ L2capTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ L2capTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/l2cap/le/dual_l2cap_test.py b/blueberry/tests/gd/l2cap/le/dual_l2cap_test.py
new file mode 100644
index 000000000..814da6b2d
--- /dev/null
+++ b/blueberry/tests/gd/l2cap/le/dual_l2cap_test.py
@@ -0,0 +1,36 @@
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from l2cap.le.cert.dual_l2cap_test_lib import DualL2capTestBase
+from mobly import test_runner
+
+
+class DualL2capTest(gd_base_test.GdBaseTestClass, DualL2capTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='L2CAP', cert_module='HCI_INTERFACES')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ DualL2capTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ DualL2capTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/l2cap/le/le_l2cap_test.py b/blueberry/tests/gd/l2cap/le/le_l2cap_test.py
new file mode 100644
index 000000000..ed3f38350
--- /dev/null
+++ b/blueberry/tests/gd/l2cap/le/le_l2cap_test.py
@@ -0,0 +1,36 @@
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from l2cap.le.cert.le_l2cap_test_lib import LeL2capTestBase
+from mobly import test_runner
+
+
+class LeL2capTest(gd_base_test.GdBaseTestClass, LeL2capTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='L2CAP', cert_module='HCI_INTERFACES')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ LeL2capTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ LeL2capTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/neighbor/neighbor_test.py b/blueberry/tests/gd/neighbor/neighbor_test.py
new file mode 100644
index 000000000..ceb9ea2f2
--- /dev/null
+++ b/blueberry/tests/gd/neighbor/neighbor_test.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from neighbor.cert.neighbor_test_lib import NeighborTestBase
+from mobly import test_runner
+
+
+class NeighborTest(gd_base_test.GdBaseTestClass, NeighborTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='HCI_INTERFACES', cert_module='HCI')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ NeighborTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ NeighborTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/security/le_security_test.py b/blueberry/tests/gd/security/le_security_test.py
new file mode 100644
index 000000000..1ba1ca558
--- /dev/null
+++ b/blueberry/tests/gd/security/le_security_test.py
@@ -0,0 +1,36 @@
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from security.cert.le_security_test_lib import LeSecurityTestBase
+from mobly import test_runner
+
+
+class LeSecurityTest(gd_base_test.GdBaseTestClass, LeSecurityTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='SECURITY', cert_module='SECURITY')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ LeSecurityTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ LeSecurityTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/security/security_test.py b/blueberry/tests/gd/security/security_test.py
new file mode 100644
index 000000000..d6bb324ee
--- /dev/null
+++ b/blueberry/tests/gd/security/security_test.py
@@ -0,0 +1,36 @@
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from security.cert.security_test_lib import SecurityTestBase
+from mobly import test_runner
+
+
+class SecurityTest(gd_base_test.GdBaseTestClass, SecurityTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='SECURITY', cert_module='L2CAP')
+
+ def setup_test(self):
+ gd_base_test.GdBaseTestClass.setup_test(self)
+ SecurityTestBase.setup_test(self, self.dut, self.cert)
+
+ def teardown_test(self):
+ SecurityTestBase.teardown_test(self)
+ gd_base_test.GdBaseTestClass.teardown_test(self)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/shim/shim_test.py b/blueberry/tests/gd/shim/shim_test.py
new file mode 100644
index 000000000..65f9564d8
--- /dev/null
+++ b/blueberry/tests/gd/shim/shim_test.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from shim.cert.shim_test_lib import ShimTestBase
+from mobly import test_runner
+
+
+class ShimTest(gd_base_test.GdBaseTestClass, ShimTestBase):
+
+ def setup_class(self):
+ gd_base_test.GdBaseTestClass.setup_class(self, dut_module='SHIM', cert_module='SHIM')
+
+ def test_dumpsys(self):
+ ShimTestBase.test_dumpsys(self, self.dut, self.cert)
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/blueberry/tests/gd/shim/stack_test.py b/blueberry/tests/gd/shim/stack_test.py
new file mode 100644
index 000000000..8a1373aa7
--- /dev/null
+++ b/blueberry/tests/gd/shim/stack_test.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from blueberry.tests.gd.cert import gd_base_test
+from shim.cert.stack_test_lib import StackTestBase
+from mobly import test_runner
+
+
+class StackTest(gd_base_test.GdBaseTestClass, StackTestBase):
+
+ def setup_class(self):
+ super().setup_class(dut_module='SHIM', cert_module='SHIM')
+
+
+if __name__ == '__main__':
+ test_runner.main()