blob: 2c862e7458db6fcd97a2094d7314dcc7c3c176c7 [file] [log] [blame]
# Copyright (C) 2024 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.
"""Mobly base test class for Neaby Connections.
Override the NCBaseTestClass#_get_country_code method if the test requires
a special country code, the 'US' is used by default.
"""
import logging
import os
import time
from mobly import asserts
from mobly import base_test
from mobly import records
from mobly import utils
from mobly.controllers import android_device
from mobly.controllers.android_device_lib import errors
import yaml
from betocq import android_wifi_utils
from betocq import nc_constants
from betocq import setup_utils
from betocq import version
NEARBY_SNIPPET_PACKAGE_NAME = 'com.google.android.nearby.mobly.snippet'
NEARBY_SNIPPET_2_PACKAGE_NAME = 'com.google.android.nearby.mobly.snippet.second'
# TODO(b/330803934): Need to design external path for OEM.
_CONFIG_EXTERNAL_PATH = 'TBD'
class NCBaseTestClass(base_test.BaseTestClass):
"""The Base of Nearby Connection E2E tests."""
_run_identifier_is_set = False
def __init__(self, configs):
super().__init__(configs)
self.ads: list[android_device.AndroidDevice] = []
self.advertiser: android_device.AndroidDevice = None
self.discoverer: android_device.AndroidDevice = None
self.test_parameters: nc_constants.TestParameters = (
nc_constants.TestParameters.from_user_params(self.user_params)
)
self._nearby_snippet_apk_path: str = None
self._nearby_snippet_2_apk_path: str = None
self.performance_test_iterations: int = 1
self.num_bug_reports: int = 0
self._requires_2_snippet_apks = False
self.__loaded_2_nearby_snippets = False
self.__skipped_test_class = False
def _get_skipped_test_class_reason(self) -> str | None:
return None
def setup_class(self) -> None:
self._set_run_identifier()
self._setup_openwrt_wifi()
self.ads = self.register_controller(android_device, min_number=2)
try:
self.discoverer = android_device.get_device(
self.ads, role='source_device'
)
self.advertiser = android_device.get_device(
self.ads, role='target_device'
)
except errors.Error:
logging.warning(
'The source,target devices are not specified in testbed;'
'The result may not be expected.'
)
self.advertiser, self.discoverer = self.ads
utils.concurrent_exec(
self._setup_android_hw_capability,
param_list=[[ad] for ad in self.ads],
raise_on_exception=True,
)
skipped_test_class_reason = self._get_skipped_test_class_reason()
if skipped_test_class_reason:
self.__skipped_test_class = True
asserts.abort_class(skipped_test_class_reason)
file_tag = 'files' if 'files' in self.user_params else 'mh_files'
self._nearby_snippet_apk_path = self.user_params.get(file_tag, {}).get(
'nearby_snippet', ['']
)[0]
if self.test_parameters.requires_bt_multiplex:
self._requires_2_snippet_apks = True
self._nearby_snippet_2_apk_path = self.user_params.get(file_tag, {}).get(
'nearby_snippet_2', ['']
)[0]
# disconnect from all wifi automatically
utils.concurrent_exec(
android_wifi_utils.forget_all_wifi,
param_list=[[ad] for ad in self.ads],
raise_on_exception=True,
)
utils.concurrent_exec(
self._setup_android_device,
param_list=[[ad] for ad in self.ads],
raise_on_exception=True,
)
def _set_run_identifier(self) -> None:
"""Set a run_identifier property describing the test run context.
This property is only set once, even if multiple test classes are run as
part of a test suite.
"""
if NCBaseTestClass._run_identifier_is_set:
return
run_identifier = {}
run_identifier['test_version'] = version.TEST_SCRIPT_VERSION
run_identifier['target_cuj'] = self.test_parameters.target_cuj_name
run_identifier_str = ', '.join(
[f'{key}:{value}' for key, value in run_identifier.items()]
)
run_identifier_str = f'{{{run_identifier_str}}}'
self.record_data({'properties': {'run_identifier': run_identifier_str}})
NCBaseTestClass._run_identifier_is_set = True
def _setup_openwrt_wifi(self):
"""Sets up the wifi connection with OpenWRT."""
if not self.user_params.get('use_auto_controlled_wifi_ap', False):
return
self.openwrt = self.register_controller(openwrt_device)[0]
if 'wifi_channel' in self.user_params:
wifi_channel = self.user_params['wifi_channel']
self.wifi_info = self.openwrt.start_wifi(
config=wifi_configs.WiFiConfig(channel=wifi_channel)
)
else:
wifi_channel = None
self.wifi_info = self.openwrt.start_wifi(config=wifi_configs.WiFiConfig())
if wifi_channel is None:
self.test_parameters.wifi_ssid = self.wifi_info.ssid
self.test_parameters.wifi_password = self.wifi_info.password
elif wifi_channel == 6:
self.test_parameters.wifi_2g_ssid = self.wifi_info.ssid
self.test_parameters.wifi_2g_password = self.wifi_info.password
elif wifi_channel == 36:
self.test_parameters.wifi_5g_ssid = self.wifi_info.ssid
self.test_parameters.wifi_5g_password = self.wifi_info.password
elif wifi_channel == 52:
self.test_parameters.wifi_dfs_5g_ssid = self.wifi_info.ssid
self.test_parameters.wifi_dfs_5g_password = self.wifi_info.password
else:
raise ValueError('Unknown Wi-Fi channel: %s' % wifi_channel)
def _setup_android_hw_capability(
self, ad: android_device.AndroidDevice
) -> None:
ad.android_version = int(ad.adb.getprop('ro.build.version.release'))
# TODO(b/330803934): Need to design external path for OEM.
if not os.path.isfile(_CONFIG_EXTERNAL_PATH):
return
config_path = _CONFIG_EXTERNAL_PATH
with open(config_path, 'r') as f:
rule = yaml.safe_load(f).get(ad.model, None)
asserts.assert_is_not_none(
rule, f'{ad} Model {ad.model} is not supported in config file'
)
for key, value in rule.items():
ad.log.debug('Setting capability %s to %s', repr(key), repr(value))
setattr(ad, key, value)
def _get_country_code(self) -> str:
return 'US'
def _setup_android_device(self, ad: android_device.AndroidDevice) -> None:
ad.debug_tag = ad.serial + '(' + ad.adb.getprop('ro.product.model') + ')'
if not ad.is_adb_root:
if self.test_parameters.allow_unrooted_device:
ad.log.info('Unrooted device is detected. Test coverage is limited')
else:
asserts.abort_all('The test only can run on rooted device.')
setup_utils.disable_gms_auto_updates(ad)
ad.debug_tag = ad.serial + '(' + ad.adb.getprop('ro.product.model') + ')'
ad.log.info('try to install nearby_snippet_apk')
if self._nearby_snippet_apk_path:
setup_utils.install_apk(ad, self._nearby_snippet_apk_path)
else:
ad.log.warning(
'nearby_snippet apk is not specified, '
'make sure it is installed in the device'
)
ad.log.info('grant manage external storage permission')
setup_utils.grant_manage_external_storage_permission(
ad, NEARBY_SNIPPET_PACKAGE_NAME
)
ad.load_snippet('nearby', NEARBY_SNIPPET_PACKAGE_NAME)
if self._requires_2_snippet_apks:
ad.log.info('try to install nearby_snippet_2_apk')
if self._nearby_snippet_2_apk_path:
setup_utils.install_apk(ad, self._nearby_snippet_2_apk_path)
else:
ad.log.warning(
'nearby_snippet_2 apk is not specified, '
'make sure it is installed in the device'
)
setup_utils.grant_manage_external_storage_permission(
ad, NEARBY_SNIPPET_2_PACKAGE_NAME
)
setup_utils.enable_bluetooth_multiplex(ad)
ad.load_snippet('nearby2', NEARBY_SNIPPET_2_PACKAGE_NAME)
self.__loaded_2_nearby_snippets = True
if not ad.nearby.wifiIsEnabled():
ad.nearby.wifiEnable()
setup_utils.disconnect_from_wifi(ad)
setup_utils.enable_logs(ad)
setup_utils.disable_redaction(ad)
setup_utils.enable_wifi_aware(ad)
setup_utils.enable_dfs_scc(ad)
setup_utils.set_country_code(ad, self._get_country_code())
def setup_test(self):
self.record_data({
'Test Name': self.current_test_info.name,
'sponge_properties': {
'beto_team': 'Nearby Connections',
'beto_feature': 'Nearby Connections',
},
})
self._reset_nearby_connection()
def _reset_wifi_connection(self) -> None:
"""Resets wifi connections on both devices."""
self.discoverer.nearby.wifiClearConfiguredNetworks()
self.advertiser.nearby.wifiClearConfiguredNetworks()
time.sleep(nc_constants.WIFI_DISCONNECTION_DELAY.total_seconds())
def _reset_nearby_connection(self) -> None:
"""Resets nearby connection."""
self.discoverer.nearby.stopDiscovery()
self.discoverer.nearby.stopAllEndpoints()
self.advertiser.nearby.stopAdvertising()
self.advertiser.nearby.stopAllEndpoints()
if self.__loaded_2_nearby_snippets:
self.discoverer.nearby2.stopDiscovery()
self.discoverer.nearby2.stopAllEndpoints()
self.advertiser.nearby2.stopAdvertising()
self.advertiser.nearby2.stopAllEndpoints()
time.sleep(nc_constants.NEARBY_RESET_WAIT_TIME.total_seconds())
def _teardown_device(self, ad: android_device.AndroidDevice) -> None:
ad.nearby.transferFilesCleanup()
setup_utils.enable_gms_auto_updates(ad)
if self.test_parameters.disconnect_wifi_after_test:
setup_utils.disconnect_from_wifi(ad)
ad.unload_snippet('nearby')
if self.__loaded_2_nearby_snippets:
ad.unload_snippet('nearby2')
def teardown_test(self) -> None:
utils.concurrent_exec(
lambda d: d.services.create_output_excerpts_all(self.current_test_info),
param_list=[[ad] for ad in self.ads],
raise_on_exception=True,
)
def teardown_class(self) -> None:
if self.__skipped_test_class:
logging.info('Skipping teardown class.')
return
# handle summary results
self._summary_test_results()
utils.concurrent_exec(
self._teardown_device,
param_list=[[ad] for ad in self.ads],
raise_on_exception=True,
)
if hasattr(self, 'openwrt') and hasattr(self, 'wifi_info'):
self.openwrt.stop_wifi(self.wifi_info)
def _summary_test_results(self) -> None:
pass
def on_fail(self, record: records.TestResultRecord) -> None:
if self.__skipped_test_class:
logging.info('Skipping on_fail.')
return
self.num_bug_reports = self.num_bug_reports + 1
if self.num_bug_reports <= nc_constants.MAX_NUM_BUG_REPORT:
logging.info('take bug report for failure')
android_device.take_bug_reports(
self.ads,
destination=self.current_test_info.output_path,
)