| """Command-line test runner script for running Bluetooth tests. |
| |
| This module allows users to initiate Bluetooth test targets and run them against |
| specified DUTs (devices-under-test) using a simple command line interface. |
| """ |
| |
| from __future__ import absolute_import |
| from __future__ import division |
| |
| from __future__ import print_function |
| |
| import base64 |
| |
| from absl import app |
| from absl import flags |
| from absl import logging |
| |
| # Internal import |
| # Internal import |
| # Internal import |
| # Internal import |
| # Internal import |
| |
| FLAGS = flags.FLAGS |
| |
| flags.DEFINE_multi_string('bt_test', None, 'Bluetooth test to run.') |
| flags.DEFINE_multi_string('bt_dut', None, |
| 'Bluetooth device to allocate for tests.') |
| |
| |
| # Valid config keys for the --bt_test command line flag. |
| BT_TEST_CONFIG_KEYS = {'target'} |
| |
| # Valid config keys for the --bt_dut (device-under-test) command line flag. |
| BT_DUT_CONFIG_KEYS = {'hardware'} |
| |
| TEST_FLAGS = ('--notest_loasd --test_output=streamed ' |
| '--test_arg=--param_dut_config=%s') |
| |
| |
| class Error(Exception): |
| """Base class for module exceptions.""" |
| pass |
| |
| |
| class TestConfigError(Error): |
| """Raised when --bt_test config flags are specified incorrectly.""" |
| pass |
| |
| |
| class DutConfigError(Error): |
| """Raised when --bt_dut config flags are specified incorrectly.""" |
| pass |
| |
| |
| def validate_bt_test_flags(flag_value): |
| """Validates the format of specified --bt_test flags. |
| |
| Args: |
| flag_value: string, the config flag value for a given --bt_test flag. |
| |
| Returns: |
| bool, True if --bt_test flags have been specified correctly. |
| """ |
| if not flag_value: |
| logging.error('No tests specified! Please specify at least one ' |
| 'test using the --bt_test flag.') |
| return False |
| for test in flag_value: |
| config_args = test.split(',') |
| for config_arg in config_args: |
| if config_arg.split('=')[0] not in BT_TEST_CONFIG_KEYS: |
| logging.error('--bt_test config key "%s" is invalid!', |
| config_arg.split('=')[0]) |
| return False |
| return True |
| |
| |
| def validate_bt_dut_flags(flag_value): |
| """Validates the format of specified --bt_dut flags. |
| |
| Args: |
| flag_value: string, the config flag value for a given --bt_dut flag. |
| |
| Returns: |
| bool, True if --bt_dut flags have been specified correctly. |
| """ |
| if not flag_value: |
| logging.error('No DUTs specified! Please specify at least one ' |
| 'DUT using the --bt_dut flag.') |
| return False |
| for dut in flag_value: |
| config_args = dut.split(',') |
| for config_arg in config_args: |
| if config_arg.split('=')[0] not in BT_DUT_CONFIG_KEYS: |
| logging.error('--bt_dut config key "%s" is invalid!', |
| config_arg.split('=')[0]) |
| return False |
| return True |
| |
| flags.register_validator( |
| 'bt_test', validate_bt_test_flags, |
| ('Invalid --bt_test configuration specified!' |
| ' Valid configuration fields include: %s') |
| % BT_TEST_CONFIG_KEYS) |
| |
| |
| flags.register_validator( |
| 'bt_dut', validate_bt_dut_flags, |
| ('Invalid --bt_dut configuration specified!' |
| ' Valid configuration fields include: %s') |
| % BT_DUT_CONFIG_KEYS) |
| |
| |
| def parse_flag_value(flag_value): |
| """Parses a config flag value string into a dict. |
| |
| Example input: 'target=//tests:bluetooth_pairing_test' |
| Example output: {'target': '//tests:bluetooth_pairing_test'} |
| |
| Args: |
| flag_value: string, the config flag value for a given flag. |
| |
| Returns: |
| dict, A dict object representation of a config flag value. |
| """ |
| config_dict = {} |
| config_args = flag_value.split(',') |
| for config_arg in config_args: |
| config_dict[config_arg.split('=')[0]] = config_arg.split('=')[1] |
| return config_dict |
| |
| |
| def get_device_type(gateway_stub, dut_config_dict): |
| """Determines a device type based on a device query. |
| |
| Args: |
| gateway_stub: An RPC2 stub object. |
| dut_config_dict: dict, A dict of device config args. |
| |
| Returns: |
| string, The MobileHarness device type. |
| |
| Raises: |
| DutConfigError: If --bt_dut flag(s) are incorrectly specified. |
| """ |
| device_query_filter = device_query_pb2.DeviceQueryFilter() |
| device_query_filter.type_regex.append('AndroidRealDevice') |
| for dut_config_key in dut_config_dict: |
| dimension_filter = device_query_filter.dimension_filter.add() |
| dimension_filter.name = dut_config_key |
| dimension_filter.value_regex = dut_config_dict[dut_config_key] |
| request = gateway_service_pb2.QueryDeviceRequest( |
| device_query_filter=device_query_filter) |
| response = gateway_stub.QueryDevice(request) |
| if response.device_query_result.device_info: |
| return 'AndroidRealDevice' |
| |
| device_query_filter.ClearField('type_regex') |
| device_query_filter.type_regex.append('TestbedDevice') |
| request = gateway_service_pb2.QueryDeviceRequest( |
| device_query_filter=device_query_filter) |
| response = gateway_stub.QueryDevice(request) |
| if response.device_query_result.device_info: |
| return 'TestbedDevice' |
| |
| raise DutConfigError('Invalid --bt_dut config specified: %s' % |
| dut_config_dict) |
| |
| |
| def generate_dut_configs(gateway_stub): |
| """Generates a unicode string specifying the desired DUT configurations. |
| |
| Args: |
| gateway_stub: An RPC2 stub object. |
| |
| Returns: |
| string, Unicode string specifying DUT configurations. |
| |
| Raises: |
| DutConfigError: If --bt_dut flag(s) are incorrectly specified. |
| """ |
| dut_list = job_config_pb2.JobConfig().DeviceList() |
| dut_config_dict_list = [parse_flag_value(value) for value in FLAGS.bt_dut] |
| |
| for dut_config_dict in dut_config_dict_list: |
| dut_config_dict['pool'] = 'bluetooth-iop' |
| dut = job_config_pb2.JobConfig().SubDeviceSpec() |
| if 'hardware' not in dut_config_dict: |
| raise DutConfigError('Must specify hardware name for bt_dut: %s' % |
| dut_config_dict) |
| dut.type = get_device_type(gateway_stub, dut_config_dict) |
| for config_key in dut_config_dict: |
| dut.dimensions.content[config_key] = dut_config_dict[config_key] |
| dut_list.sub_device_spec.append(dut) |
| logging.info(base64.b64encode(dut_list.SerializeToString()).decode('utf-8')) |
| return base64.b64encode(dut_list.SerializeToString()).decode('utf-8') |
| |
| |
| def generate_blaze_targets(session_config, gateway_stub): |
| """Generates and appends blaze test targets to a MobileHarness session. |
| |
| Args: |
| session_config: The SessionConfig object to append blaze test targets to. |
| gateway_stub: An RPC2 stub object. |
| |
| Raises: |
| TestConfigError: If --bt_test flag(s) are incorrectly specified. |
| """ |
| test_config_dict_list = [parse_flag_value(value) for value in FLAGS.bt_test] |
| |
| for test_config_dict in test_config_dict_list: |
| target = setting_pb2.BlazeTarget() |
| if 'target' not in test_config_dict: |
| raise TestConfigError('Must specify a target for bt_test: %s' % |
| test_config_dict) |
| target.target_name = test_config_dict['target'] |
| target.test_flags = TEST_FLAGS % generate_dut_configs(gateway_stub) |
| session_config.blaze_target.append(target) |
| |
| |
| def run_session(): |
| """Runs a configured test session. |
| |
| Returns: |
| A RunSessionResponse object. |
| """ |
| session_config = setting_pb2.SessionConfig() |
| channel = rpcutil.GetNewChannel('blade:mobileharness-gateway') |
| gateway_stub = gateway_service_pb2.GatewayService.NewRPC2Stub(channel=channel) |
| generate_blaze_targets(session_config, gateway_stub) |
| request = gateway_service_pb2.RunSessionRequest() |
| request.session_config.CopyFrom(session_config) |
| response = gateway_stub.RunSession(request) |
| logging.info('Sponge link: %s', response.sponge) |
| logging.info('Session ID: %s', response.session_id) |
| return response |
| |
| |
| def main(argv): |
| logging.use_python_logging() |
| del argv |
| run_session() |
| |
| if __name__ == '__main__': |
| flags.mark_flag_as_required('bt_test') |
| flags.mark_flag_as_required('bt_dut') |
| app.run(main) |