Merge Android 14
Bug: 298295554
Merged-In: I5739c9d3b1ae22e06d76007bf51bddd7c71be429
Change-Id: Ia0cf62dad6a04cce046c15f8223a2a599f542e86
diff --git a/acts/framework/acts/controllers/bits.py b/acts/framework/acts/controllers/bits.py
index d89a9b3..caadeb7 100644
--- a/acts/framework/acts/controllers/bits.py
+++ b/acts/framework/acts/controllers/bits.py
@@ -1,5 +1,7 @@
"""Module managing the required definitions for using the bits power monitor"""
+import csv
+import json
import logging
import os
import time
@@ -424,8 +426,14 @@
In the case where there is not enough information to retrieve a
monsoon-like file, this function will do nothing.
"""
- available_channels = self._client.list_channels(
- self._active_collection.name)
+ metrics = self._client.get_metrics(self._active_collection.name)
+
+ try:
+ self._save_rails_csv(metrics)
+ except Exception as e:
+ logging.warning(
+ 'Could not save rails data to csv format with error {}'.format(e))
+ available_channels = [channel['name'] for channel in metrics['data']]
milli_amps_channel = None
for channel in available_channels:
@@ -447,6 +455,70 @@
self._active_collection.name,
milli_amps_channel)
+ def _save_rails_csv(self, metrics):
+ # Creates csv path for rails data
+ monsoon_path = self._active_collection.monsoon_output_path
+ dir_path = os.path.dirname(monsoon_path)
+ if dir_path.endswith('Monsoon'):
+ dir_path = os.path.join(os.path.dirname(dir_path), 'Kibble')
+ os.makedirs(dir_path, exist_ok=True)
+ rails_basename = os.path.basename(monsoon_path)
+ if rails_basename.endswith('.txt'):
+ rails_basename = os.path.splitext(rails_basename)[0]
+ json_basename = 'kibble_rails_' + rails_basename + '.json'
+ rails_basename = 'kibble_rails_' + rails_basename + '.csv'
+ root_rail_results_basename = '{}_results.csv'.format(
+ self._root_rail.split(':')[0])
+ rails_csv_path = os.path.join(dir_path, rails_basename)
+ rails_json_path = os.path.join(dir_path, json_basename)
+ root_rail_results_path = os.path.join(dir_path, root_rail_results_basename)
+
+ logging.info('dump metric to json format: {}'.format(rails_json_path))
+ with open(rails_json_path, 'w') as f:
+ json.dump(metrics['data'], f, sort_keys=True, indent=2)
+
+ # Gets all channels
+ channels = {
+ channel['name'].split('.')[-1].split(':')[0]
+ for channel in metrics['data']
+ }
+ channels = list(channels)
+ list.sort(channels)
+
+ rail_dict = {
+ channel['name'].split('.')[-1] : channel['avg']
+ for channel in metrics['data']
+ }
+
+ root_rail_key = self._root_rail.split(':')[0] + ':mW'
+ root_rail_power = 0
+ if root_rail_key in rail_dict:
+ root_rail_power = rail_dict[root_rail_key]
+ logging.info('root rail {} power is: {}'.format(root_rail_key, root_rail_power))
+
+ path_existed = os.path.exists(root_rail_results_path)
+ with open(root_rail_results_path, 'a') as f:
+ if not path_existed:
+ f.write('{},{}'.format(root_rail_key, 'power(mW)'))
+ f.write('\n{},{}'.format(self._active_collection.name, root_rail_power))
+
+ header = ['CHANNEL', 'VALUE', 'UNIT', 'VALUE', 'UNIT', 'VALUE', 'UNIT']
+ with open(rails_csv_path, 'w') as f:
+ csvwriter = csv.writer(f)
+ csvwriter.writerow(header)
+ for key in channels:
+ if not key.startswith('C') and not key.startswith('M'):
+ continue
+ try:
+ row = [key, '0', 'mA', '0', 'mV', '0', 'mW']
+ row[1] = str(rail_dict[key + ':mA'])
+ row[3] = str(rail_dict[key + ':mV'])
+ row[5] = str(rail_dict[key + ':mW'])
+ csvwriter.writerow(row)
+ logging.debug('channel {}: {}'.format(key, row))
+ except Exception as e:
+ logging.info('channel {} fail'.format(key))
+
def get_waveform(self, file_path=None):
"""Parses a file generated in release_resources.
@@ -462,6 +534,26 @@
return list(power_metrics.import_raw_data(file_path))
+ def get_bits_root_rail_csv_export(self, file_path=None, collection_name=None):
+ """Export raw data samples for root rail in csv format.
+
+ Args:
+ file_path: Path to save the export file.
+ collection_name: Name of collection to be exported on client.
+ """
+ if file_path is None:
+ raise ValueError('file_path cannot be None')
+ if collection_name is None:
+ raise ValueError('collection_name cannot be None')
+ try:
+ key = self._root_rail.split(':')[0] + ':mW'
+ file_name = 'raw_data_' + collection_name + '.csv'
+ raw_bits_data_path = os.path.join(file_path, file_name)
+ self._client.export_as_csv([key], collection_name,
+ raw_bits_data_path)
+ except Exception as e:
+ logging.warning('Failed to save raw data due to : {}'.format(e))
+
def teardown(self):
if self._service is None:
return
diff --git a/acts/framework/acts/controllers/bits_lib/bits_service_config.py b/acts/framework/acts/controllers/bits_lib/bits_service_config.py
index cb2d219..a5fb2f9 100644
--- a/acts/framework/acts/controllers/bits_lib/bits_service_config.py
+++ b/acts/framework/acts/controllers/bits_lib/bits_service_config.py
@@ -144,7 +144,11 @@
if 'serial' not in kibble:
raise ValueError('An individual kibble config must have a '
'serial')
-
+ if 'subkibble_params' in kibble:
+ user_defined_kibble_config = kibble['subkibble_params']
+ kibble_config = copy.deepcopy(user_defined_kibble_config)
+ else:
+ kibble_config = copy.deepcopy(DEFAULT_KIBBLE_CONFIG)
board = kibble['board']
connector = kibble['connector']
serial = kibble['serial']
@@ -154,7 +158,6 @@
self.boards_configs[board][
'board_file'] = kibble_board_file
self.boards_configs[board]['kibble_py'] = kibble_bin
- kibble_config = copy.deepcopy(DEFAULT_KIBBLE_CONFIG)
kibble_config['connector'] = connector
self.boards_configs[board]['attached_kibbles'][
serial] = kibble_config
diff --git a/acts/framework/acts/controllers/buds_lib/dev_utils/proto/gen/apollo_qa_pb2.py b/acts/framework/acts/controllers/buds_lib/dev_utils/proto/gen/apollo_qa_pb2.py
index fefcfe4..1290491 100644
--- a/acts/framework/acts/controllers/buds_lib/dev_utils/proto/gen/apollo_qa_pb2.py
+++ b/acts/framework/acts/controllers/buds_lib/dev_utils/proto/gen/apollo_qa_pb2.py
@@ -11,7 +11,7 @@
_sym_db = _symbol_database.Default()
-import nanopb_pb2 as nanopb__pb2
+from . import nanopb_pb2 as nanopb__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x61pollo_qa.proto\x12$apollo.lib.apollo_dev_util_lib.proto\x1a\x0cnanopb.proto\"t\n\rApolloQATrace\x12\x11\n\ttimestamp\x18\x01 \x02(\r\x12\x39\n\x02id\x18\x02 \x02(\x0e\x32-.apollo.lib.apollo_dev_util_lib.proto.TraceId\x12\x15\n\x04\x64\x61ta\x18\x03 \x03(\rB\x07\x10\x01\x92?\x02\x10\x05\"\xcd\x02\n\x16\x41polloQAGetVerResponse\x12\x11\n\ttimestamp\x18\x01 \x02(\r\x12\x16\n\x0e\x63sr_fw_version\x18\x02 \x02(\r\x12\x1a\n\x12\x63sr_fw_debug_build\x18\x03 \x02(\x08\x12\x17\n\x0fvm_build_number\x18\x04 \x02(\r\x12\x16\n\x0evm_debug_build\x18\x05 \x02(\x08\x12\x14\n\x0cpsoc_version\x18\x06 \x02(\r\x12\x1a\n\x0b\x62uild_label\x18\x07 \x02(\tB\x05\x92?\x02\x08 \x12Q\n\x0flast_ota_status\x18\x08 \x01(\x0e\x32\x38.apollo.lib.apollo_dev_util_lib.proto.PreviousBootStatus\x12\x17\n\x0f\x63harger_version\x18\t \x02(\r\x12\x1d\n\x15\x65xpected_psoc_version\x18\n \x01(\r\"u\n\x18\x41polloQAGetCodecResponse\x12\x11\n\ttimestamp\x18\x01 \x02(\r\x12\x46\n\x05\x63odec\x18\x02 \x01(\x0e\x32\x37.apollo.lib.apollo_dev_util_lib.proto.ApolloQAA2dpCodec\"\xa6\x01\n\x1c\x41polloQAGetDspStatusResponse\x12\x11\n\ttimestamp\x18\x01 \x02(\r\x12\x15\n\ris_dsp_loaded\x18\x02 \x02(\x08\x12\x43\n\nsink_state\x18\x03 \x02(\x0e\x32/.apollo.lib.apollo_dev_util_lib.proto.SinkState\x12\x17\n\x0f\x66\x65\x61tures_active\x18\x04 \x02(\r\"\xb9\x01\n\x18\x41polloQAFactoryPlaySound\x12Y\n\x06prompt\x18\x01 \x02(\x0e\x32I.apollo.lib.apollo_dev_util_lib.proto.ApolloQAFactoryPlaySound.PromptType\"B\n\nPromptType\x12\x1c\n\x18PROMPT_TYPE_BT_CONNECTED\x10\x01\x12\x16\n\x12PROMPT_TYPE_IN_EAR\x10\x02\"\x1c\n\x1a\x41polloQAFactoryInfoRequest\"\xb6\x01\n\x1b\x41polloQAFactoryInfoResponse\x12\x11\n\ttimestamp\x18\x01 \x02(\r\x12\x1b\n\x0c\x63rystal_trim\x18\x02 \x01(\x05\x42\x05\x92?\x02\x38\x10\x12\x19\n\x11\x63rash_dump_exists\x18\x03 \x01(\x08\x12!\n\x19is_developer_mode_enabled\x18\x04 \x01(\x08\x12\x1b\n\x13is_always_connected\x18\x05 \x01(\x08\x12\x0c\n\x04hwid\x18\x06 \x01(\r*\xb8\x01\n\x13\x41polloQAMessageType\x12\t\n\x05TRACE\x10\x01\x12\x14\n\x10GET_VER_RESPONSE\x10\x02\x12\x16\n\x12GET_CODEC_RESPONSE\x10\x03\x12\x1b\n\x17GET_DSP_STATUS_RESPONSE\x10\x04\x12\x16\n\x12\x46\x41\x43TORY_PLAY_SOUND\x10\x05\x12\x18\n\x14\x46\x41\x43TORY_INFO_REQUEST\x10\x06\x12\x19\n\x15\x46\x41\x43TORY_INFO_RESPONSE\x10\x07*\xfc\x02\n\x07TraceId\x12\x17\n\x13OTA_ERASE_PARTITION\x10\x01\x12\x1d\n\x19OTA_START_PARTITION_WRITE\x10\x02\x12 \n\x1cOTA_FINISHED_PARTITION_WRITE\x10\x03\x12\x17\n\x13OTA_SIGNATURE_START\x10\x04\x12\x19\n\x15OTA_SIGNATURE_FAILURE\x10\x05\x12\x19\n\x15OTA_TRIGGERING_LOADER\x10\x06\x12\x1c\n\x18OTA_LOADER_VERIFY_FAILED\x10\x07\x12\x10\n\x0cOTA_PROGRESS\x10\x08\x12\x0f\n\x0bOTA_ABORTED\x10\t\x12\x1c\n\x18\x41VRCP_PLAY_STATUS_CHANGE\x10\n\x12\x11\n\rVOLUME_CHANGE\x10\x0b\x12\x1a\n\x16\x43OMMANDER_RECV_COMMAND\x10\x0c\x12\x1c\n\x18\x43OMMANDER_FINISH_COMMAND\x10\r\x12\x1c\n\x18\x43OMMANDER_REJECT_COMMAND\x10\x0e*m\n\x0f\x41vrcpPlayStatus\x12\x0b\n\x07STOPPED\x10\x00\x12\x0b\n\x07PLAYING\x10\x01\x12\n\n\x06PAUSED\x10\x02\x12\x0c\n\x08\x46WD_SEEK\x10\x08\x12\x0c\n\x08REV_SEEK\x10\x10\x12\t\n\x05\x45RROR\x10\x05\x12\r\n\tSEEK_MASK\x10\x18*4\n\x12PreviousBootStatus\x12\x0f\n\x0bOTA_SUCCESS\x10\x01\x12\r\n\tOTA_ERROR\x10\x02*%\n\x11\x41polloQAA2dpCodec\x12\x07\n\x03\x41\x41\x43\x10\x01\x12\x07\n\x03SBC\x10\x02*\xd8\x02\n\tSinkState\x12\t\n\x05LIMBO\x10\x00\x12\x0f\n\x0b\x43ONNECTABLE\x10\x01\x12\x10\n\x0c\x44ISCOVERABLE\x10\x02\x12\r\n\tCONNECTED\x10\x03\x12\x1c\n\x18OUTGOING_CALLS_ESTABLISH\x10\x04\x12\x1c\n\x18INCOMING_CALLS_ESTABLISH\x10\x05\x12\x13\n\x0f\x41\x43TIVE_CALL_SCO\x10\x06\x12\r\n\tTEST_MODE\x10\x07\x12\x1a\n\x16THREE_WAY_CALL_WAITING\x10\x08\x12\x1a\n\x16THREE_WAY_CALL_ON_HOLD\x10\t\x12\x17\n\x13THREE_WAY_MULTICALL\x10\n\x12\x19\n\x15INCOMING_CALL_ON_HOLD\x10\x0b\x12\x16\n\x12\x41\x43TIVE_CALL_NO_SCO\x10\x0c\x12\x12\n\x0e\x41\x32\x44P_STREAMING\x10\r\x12\x16\n\x12\x44\x45VICE_LOW_BATTERY\x10\x0e\x42)\n\x1d\x63om.google.android.bisto.nanoB\x08\x41polloQA')
diff --git a/acts/framework/acts/controllers/cellular_lib/PresetSimulation.py b/acts/framework/acts/controllers/cellular_lib/PresetSimulation.py
index b3c2e5a..d536b09 100644
--- a/acts/framework/acts/controllers/cellular_lib/PresetSimulation.py
+++ b/acts/framework/acts/controllers/cellular_lib/PresetSimulation.py
@@ -27,6 +27,12 @@
KEY_CELL_INFO = "cell_info"
KEY_SCPI_FILE_NAME = "scpi_file"
+ NETWORK_BIT_MASK = {
+ 'nr_lte': '11000001000000000000'
+ }
+ ADB_CMD_LOCK_NETWORK = 'cmd phone set-allowed-network-types-for-users -s 0 {network_bit_mask}'
+ NR_LTE_BIT_MASK_KEY = 'nr_lte'
+
def __init__(self,
simulator,
log,
@@ -47,6 +53,8 @@
super().__init__(simulator, log, dut, test_config, calibration_table,
nr_mode)
+ # require param for idle test case
+ self.rrc_sc_timer = 0
# Set to KeySight APN
log.info('Configuring APN.')
@@ -56,15 +64,6 @@
# Enable roaming on the phone
self.dut.toggle_data_roaming(True)
- # Force device to LTE only so that it connects faster
- try:
- self.dut.set_preferred_network_type(
- BaseCellularDut.PreferredNetworkType.NR_LTE)
- except Exception as e:
- # If this fails the test should be able to run anyways, even if it
- # takes longer to find the cell.
- self.log.warning('Setting preferred RAT failed: ' + str(e))
-
def setup_simulator(self):
"""Do initial configuration in the simulator. """
self.log.info('This simulation does not require initial setup.')
@@ -99,11 +98,7 @@
RuntimeError: simulation fail to start
due to unable to connect dut and cells.
"""
-
- try:
- self.attach()
- except Exception as exc:
- raise RuntimeError('Simulation fail to start.') from exc
+ self.attach()
def attach(self):
"""Attach UE to the callbox.
diff --git a/acts/framework/acts/controllers/spirent_lib/gss7000.py b/acts/framework/acts/controllers/spirent_lib/gss7000.py
index 961d4e8..3f463fc 100644
--- a/acts/framework/acts/controllers/spirent_lib/gss7000.py
+++ b/acts/framework/acts/controllers/spirent_lib/gss7000.py
@@ -162,7 +162,7 @@
Type, list.
"""
root = ET.fromstring(xml)
- capability_ls = list()
+ capability_ls = []
sig_cap_list = root.find('data').find('Signal_capabilities').findall(
'Signal')
for signal in sig_cap_list:
@@ -203,15 +203,13 @@
if scenario == '':
errmsg = ('Missing scenario file')
raise GSS7000Error(error=errmsg, command='load_scenario')
- else:
- self._logger.debug('Stopped the original scenario')
- self._query('-,EN,1')
- cmd = 'SC,' + scenario
- self._logger.debug('Loading scenario')
- self._query(cmd)
- self._logger.debug('Scenario is loaded')
- return True
- return False
+ self._logger.debug('Stopped the original scenario')
+ self._query('-,EN,1')
+ cmd = 'SC,' + scenario
+ self._logger.debug('Loading scenario')
+ self._query(cmd)
+ self._logger.debug('Scenario is loaded')
+ return True
def start_scenario(self, scenario=''):
"""Load and Start the running scenario.
@@ -223,6 +221,8 @@
if scenario:
if self.load_scenario(scenario):
self._query('RU')
+ # TODO: Need to refactor the logic design to solve the comment in ag/19222896
+ # Track the issue in b/241200605
else:
infmsg = 'No scenario is loaded. Stop running scenario'
self._logger.debug(infmsg)
@@ -230,7 +230,7 @@
pass
if scenario:
- infmsg = 'Started running scenario {}'.format(scenario)
+ infmsg = f'Started running scenario {scenario}'
else:
infmsg = 'Started running current scenario'
@@ -279,12 +279,12 @@
GSS7000Error: raise when power offset level is not in [-170, -115] range.
"""
if not -170 <= ref_dBm <= -115:
- errmsg = ('"power_offset" must be within [-170, -115], '
- 'current input is {}').format(str(ref_dBm))
+ errmsg = (f'"power_offset" must be within [-170, -115], '
+ f'current input is {ref_dBm}')
raise GSS7000Error(error=errmsg, command='set_ref_power')
- cmd = 'REF_DBM,{}'.format(str(round(ref_dBm, 1)))
+ cmd = f'REF_DBM,{ref_dBm:.1f}'
self._query(cmd)
- infmsg = 'Set reference power level: {}'.format(str(round(ref_dBm, 1)))
+ infmsg = f'Set reference power level: {ref_dBm:.1f}'
self._logger.debug(infmsg)
def get_status(self, return_txt=False):
@@ -309,8 +309,7 @@
'Waiting for further commands.'
}
return status_dict.get(status)
- else:
- return int(status)
+ return int(status)
def set_power(self, power_level=-130):
"""Set Power Level of GSS7000 Tx
@@ -331,8 +330,7 @@
self.set_power_offset(1, power_offset)
self.set_power_offset(2, power_offset)
- infmsg = 'Set GSS7000 transmit power to "{}"'.format(
- round(power_level, 1))
+ infmsg = f'Set GSS7000 transmit power to "{power_level:.1f}"'
self._logger.debug(infmsg)
def power_lev_offset_cal(self, power_level=-130, sat='GPS', band='L1'):
@@ -418,8 +416,8 @@
f'Satellite system and band ({sat_band}) are not supported.'
f'The GSS7000 support list: {self.capability}')
raise GSS7000Error(error=errmsg, command='set_scenario_power')
- else:
- sat_band_tp = tuple(sat_band.split('_'))
+
+ sat_band_tp = tuple(sat_band.split('_'))
return sat_band_tp
@@ -436,14 +434,14 @@
Default. -130
sat_id: set power level for specific satellite identifiers
Type, int.
- sat_system: to set power level for all Satellites
+ sat_system: to set power level for specific system
Type, str
Option 'GPS/GLO/GAL/BDS'
Type, str
Default, '', assumed to be GPS.
freq_band: Frequency band to set the power level
Type, str
- Option 'L1/L5/B1I/B1C/B2A/F1/E5/ALL'
+ Option 'L1/L5/B1I/B1C/B2A/F1/E5'
Default, '', assumed to be L1.
Raises:
GSS7000Error: raise when power offset is not in [-49, -15] range.
@@ -455,8 +453,7 @@
'B1I': 1,
'B1C': 1,
'F1': 1,
- 'E5': 2,
- 'ALL': 3
+ 'E5': 2
}
# Convert and check satellite system and band
@@ -464,12 +461,13 @@
# Get freq band setting
band_cmd = band_dict.get(band, 1)
+ # When set sat_id --> control specific SV power.
+ # When set is not set --> control all SVs of specific system power.
if not sat_id:
- sat_id = 0
+ sat_id = 1
all_tx_type = 1
else:
all_tx_type = 0
-
# Convert absolute power level to absolute power offset.
power_offset = self.power_lev_offset_cal(power_level, sat, band)
@@ -478,13 +476,10 @@
f'current input is {power_offset}')
raise GSS7000Error(error=errmsg, command='set_power_offset')
+ # If no specific sat_system is set, the default is GPS L1.
if band_cmd == 1:
cmd = f'-,POW_LEV,v1_a1,{power_offset},{sat},{sat_id},0,0,0,1,1,{all_tx_type}'
self._query(cmd)
elif band_cmd == 2:
cmd = f'-,POW_LEV,v1_a2,{power_offset},{sat},{sat_id},0,0,0,1,1,{all_tx_type}'
self._query(cmd)
- elif band_cmd == 3:
- cmd = f'-,POW_LEV,v1_a1,{power_offset},{sat},{sat_id},0,0,0,1,1,{all_tx_type}'
- self._query(cmd)
- cmd = f'-,POW_LEV,v1_a2,{power_offset},{sat},{sat_id},0,0,0,1,1,{all_tx_type}'
diff --git a/acts/framework/acts/controllers/uxm_lib/uxm_cellular_simulator.py b/acts/framework/acts/controllers/uxm_lib/uxm_cellular_simulator.py
index ffb5e16..5ef7eac 100644
--- a/acts/framework/acts/controllers/uxm_lib/uxm_cellular_simulator.py
+++ b/acts/framework/acts/controllers/uxm_lib/uxm_cellular_simulator.py
@@ -11,14 +11,81 @@
# 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 re
+import logging
import os
+import paramiko
import socket
import time
-import paramiko
-import re
from acts.controllers.cellular_simulator import AbstractCellularSimulator
+class SocketWrapper():
+ """A wrapper for socket communicate with test equipment.
+
+ Attributes:
+ _socket: a socket object.
+ _ip: a string value for ip address
+ which we want to connect.
+ _port: an integer for port
+ which we want to connect.
+ _connecting_timeout: an integer for socket connecting timeout.
+ _encode_format: a string specify encoding format.
+ _cmd_terminator: a character indicates the end of command/data
+ which need to be sent.
+ """
+
+ def __init__(self, ip, port,
+ connecting_timeout=120,
+ cmd_terminator='\n',
+ encode_format='utf-8',
+ buff_size=1024):
+ self._socket = None
+ self._ip = ip
+ self._port = port
+ self._connecting_timeout = connecting_timeout
+ self._cmd_terminator = cmd_terminator
+ self._encode_format = encode_format
+ self._buff_size = buff_size
+ self._logger = logging.getLogger(__name__)
+
+ def _connect(self):
+ self._socket = socket.create_connection(
+ (self._ip, self._port), timeout=self._connecting_timeout
+ )
+
+ def send_command(self, cmd: str):
+ if not self._socket:
+ self._connect()
+ if cmd and cmd[-1] != self._cmd_terminator:
+ cmd = cmd + self._cmd_terminator
+ self._socket.sendall(cmd.encode(self._encode_format))
+
+ def send_command_recv(self, cmd: str) -> str:
+ """Send data and wait for response
+
+ Args:
+ cmd: a string command to be sent.
+
+ Returns:
+ a string response.
+ """
+ self.send_command(cmd)
+ response = ''
+ try:
+ response = self._socket.recv(self._buff_size).decode(
+ self._encode_format
+ )
+ except socket.timeout as e:
+ self._logger.info('Socket timeout while receiving response.')
+ self.close()
+ raise
+
+ return response
+
+ def close(self):
+ self._socket.close()
+ self._socket = None
class UXMCellularSimulator(AbstractCellularSimulator):
"""A cellular simulator for UXM callbox."""
@@ -28,20 +95,26 @@
KEY_CELL_TYPE = "cell_type"
# UXM socket port
- UXM_PORT = 5125
+ UXM_SOCKET_PORT = 5125
# UXM SCPI COMMAND
SCPI_IMPORT_STATUS_QUERY_CMD = 'SYSTem:SCPI:IMPort:STATus?'
SCPI_SYSTEM_ERROR_CHECK_CMD = 'SYST:ERR?\n'
+ SCPI_CHECK_CONNECTION_CMD = '*IDN?\n'
+ SCPI_DEREGISTER_UE_IMS = 'SYSTem:IMS:SERVer:UE:DERegister'
# require: path to SCPI file
SCPI_IMPORT_SCPI_FILE_CMD = 'SYSTem:SCPI:IMPort "{}"\n'
# require: 1. cell type (E.g. NR5G), 2. cell number (E.g CELL1)
SCPI_CELL_ON_CMD = 'BSE:CONFig:{}:{}:ACTive 1'
- # require: 1. cell type (E.g. NR5G), 2. cell number (E.g CELL1)
SCPI_CELL_OFF_CMD = 'BSE:CONFig:{}:{}:ACTive 0'
- # require: 1. cell type (E.g. NR5G), 2. cell number (E.g CELL1)
SCPI_GET_CELL_STATUS = 'BSE:STATus:{}:{}?'
- SCPI_CHECK_CONNECTION_CMD = '*IDN?\n'
+ SCPI_RRC_RELEASE_LTE_CMD = 'BSE:FUNCtion:{}:{}:RELease:SEND'
+ SCPI_RRC_RELEASE_NR_CMD = 'BSE:CONFig:{}:{}:RCONtrol:RRC:STARt RRELease'
+ # require cell number
+ SCPI_CREATE_DEDICATED_BEARER = 'BSE:FUNCtion:LTE:{}:NAS:EBID10:DEDicated:CREate'
+ SCPI_CHANGE_SIM_NR_CMD = 'BSE:CONFig:NR5G:CELL1:SECurity:AUTHenticate:KEY:TYPE {}'
+ SCPI_CHANGE_SIM_LTE_CMD = 'BSE:CONFig:LTE:SECurity:AUTHenticate:KEY {}'
+ SCPI_SETTINGS_PRESET_CMD = 'SYSTem:PRESet:FULL'
# UXM's Test Application recovery
TA_BOOT_TIME = 100
@@ -49,11 +122,28 @@
# shh command
SSH_START_GUI_APP_CMD_FORMAT = 'psexec -s -d -i 1 "{exe_path}"'
SSH_CHECK_APP_RUNNING_CMD_FORMAT = 'tasklist | findstr /R {regex_app_name}'
+ SSH_KILL_PROCESS_BY_NAME = 'taskkill /IM {process_name} /F'
+ UXM_TEST_APP_NAME = 'TestApp.exe'
# start process success regex
PSEXEC_PROC_STARTED_REGEX_FORMAT = 'started on * with process ID {proc_id}'
- def __init__(self, ip_address, custom_files, uxm_user,
+ # HCCU default value
+ HCCU_SOCKET_PORT = 4882
+ # number of digit of the length of setup name
+ HCCU_SCPI_CHANGE_SETUP_CMD = ':SYSTem:SETup:CONFig #{number_of_digit}{setup_name_len}{setup_name}'
+ HCCU_SCPI_CHANGE_SCENARIO_CMD = ':SETup:SCENe "((NE_1, {scenario_name}))"'
+ HCCU_STATUS_CHECK_CMD = ':SETup:INSTrument:STATus? 0\n'
+ HCCU_FR2_SETUP_NAME = '{Name:"TSPC_1UXM5G_HF_2RRH_M1740A"}'
+ HCCU_FR1_SETUP_NAME = '{Name:"TSPC_1UXM5G_LF"}'
+ HCCU_GET_INSTRUMENT_COUNT_CMD = ':SETup:INSTrument:COUNt?'
+ HCCU_FR2_INSTRUMENT_COUNT = 5
+ HCCU_FR1_INSTRUMENT_COUNT = 2
+ HCCU_FR2_SCENARIO = 'NR_4DL2x2_2UL2x2_LTE_4CC'
+ HCCU_FR1_SCENARIO = 'NR_1DL4x4_1UL2x2_LTE_4CC'
+
+
+ def __init__(self, ip_address, custom_files,uxm_user,
ssh_private_key_to_uxm, ta_exe_path, ta_exe_name):
"""Initializes the cellular simulator.
@@ -73,10 +163,11 @@
self.cells = []
self.uxm_ip = ip_address
self.uxm_user = uxm_user
- self.ssh_private_key_to_uxm = ssh_private_key_to_uxm
+ self.ssh_private_key_to_uxm = os.path.expanduser(
+ ssh_private_key_to_uxm)
self.ta_exe_path = ta_exe_path
self.ta_exe_name = ta_exe_name
- self.ssh_client = self._create_ssh_client()
+ self.ssh_client = self.create_ssh_client()
# get roclbottom file
for file in self.custom_files:
@@ -85,11 +176,124 @@
# connect to Keysight Test Application via socket
self.recovery_ta()
- self.socket = self._socket_connect(self.uxm_ip, self.UXM_PORT)
+ self.socket = self._socket_connect(self.uxm_ip, self.UXM_SOCKET_PORT)
self.check_socket_connection()
self.timeout = 120
- def _create_ssh_client(self):
+ # hccu socket
+ self.hccu_socket_port = self.HCCU_SOCKET_PORT
+ self.hccu_socket = SocketWrapper(self.uxm_ip, self.hccu_socket_port)
+
+ def socket_connect(self):
+ self.socket = self._socket_connect(self.uxm_ip, self.UXM_SOCKET_PORT)
+
+ def switch_HCCU_scenario(self, scenario_name: str):
+ cmd = self.HCCU_SCPI_CHANGE_SCENARIO_CMD.format(
+ scenario_name=scenario_name)
+ self.hccu_socket.send_command(cmd)
+ self.log.debug(f'Sent command: {cmd}')
+ # this is require for the command to take effect
+ # because hccu's port need to be free.
+ self.hccu_socket.close()
+
+ def switch_HCCU_setup(self, setup_name: str):
+ """Change HHCU system setup.
+
+ Args:
+ setup_name: a string name
+ of the system setup will be changed to.
+ """
+ setup_name_len = str(len(setup_name))
+ number_of_digit = str(len(setup_name_len))
+ cmd = self.HCCU_SCPI_CHANGE_SETUP_CMD.format(
+ number_of_digit=number_of_digit,
+ setup_name_len=setup_name_len,
+ setup_name=setup_name
+ )
+ self.hccu_socket.send_command(cmd)
+ self.log.debug(f'Sent command: {cmd}')
+ # this is require for the command to take effect
+ # because hccu's port need to be free.
+ self.hccu_socket.close()
+
+ def wait_until_hccu_operational(self, timeout=1200):
+ """ Wait for hccu is ready to operate for a specified timeout.
+
+ Args:
+ timeout: time we are waiting for
+ hccu in opertional status.
+
+ Returns:
+ True if HCCU status is operational within timeout.
+ False otherwise.
+ """
+ # check status
+ self.log.info('Waiting for HCCU to ready to operate.')
+ cmd = self.HCCU_STATUS_CHECK_CMD
+ t = 0
+ interval = 10
+ while t < timeout:
+ response = self.hccu_socket.send_command_recv(cmd)
+ if response == 'OPER\n':
+ return True
+ time.sleep(interval)
+ t += interval
+ return False
+
+ def switch_HCCU_settings(self, is_fr2: bool):
+ """Set HCCU setup configuration.
+
+ HCCU stands for Hardware Configuration Control Utility,
+ an interface allows us to control Keysight Test Equipment.
+
+ Args:
+ is_fr2: a bool value.
+ """
+ # change HCCU configration
+ data = ''
+ scenario_name = ''
+ instrument_count_res = self.hccu_socket.send_command_recv(
+ self.HCCU_GET_INSTRUMENT_COUNT_CMD)
+ instrument_count = int(instrument_count_res)
+ # if hccu setup is correct, no need to change.
+ if is_fr2 and instrument_count == self.HCCU_FR2_INSTRUMENT_COUNT:
+ self.log.info('UXM has correct HCCU setup.')
+ return
+ if not is_fr2 and instrument_count == self.HCCU_FR1_INSTRUMENT_COUNT:
+ self.log.info('UXM has correct HCCU setup.')
+ return
+
+ self.log.info('UXM has incorrect HCCU setup, start changing setup.')
+ # terminate TA and close socket
+ self.log.info('Terminate TA before switch HCCU settings.')
+ self.terminate_process(self.UXM_TEST_APP_NAME)
+ self.socket.close()
+
+ # change hccu setup
+ if is_fr2:
+ data = self.HCCU_FR2_SETUP_NAME
+ scenario_name = self.HCCU_FR2_SCENARIO
+ else:
+ data = self.HCCU_FR1_SETUP_NAME
+ scenario_name = self.HCCU_FR1_SCENARIO
+ self.log.info('Switch HCCU setup.')
+ self.switch_HCCU_setup(data)
+ time.sleep(10)
+ if not self.wait_until_hccu_operational():
+ raise RuntimeError('Fail to switch HCCU setup.')
+
+ # change scenario
+ self.log.info('Ativate HCCU scenario.')
+ self.switch_HCCU_scenario(scenario_name)
+ time.sleep(40)
+ if not self.wait_until_hccu_operational():
+ raise RuntimeError('Fail to switch HCCU scenario.')
+
+ # start TA and reconnect socket.
+ self.recovery_ta()
+ self.socket = self._socket_connect(self.uxm_ip, self.UXM_SOCKET_PORT)
+
+ def create_ssh_client(self):
"""Create a ssh client to host."""
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
@@ -99,6 +303,18 @@
self.log.info('SSH client to %s is connected' % self.uxm_ip)
return ssh
+ def terminate_process(self, process_name):
+ cmd = self.SSH_KILL_PROCESS_BY_NAME.format(
+ process_name=process_name
+ )
+ stdin, stdout, stderr = self.ssh_client.exec_command(cmd)
+ stdin.close()
+ err = ''.join(stderr.readlines())
+ out = ''.join(stdout.readlines())
+ final_output = str(out) + str(err)
+ self.log.info(final_output)
+ return out
+
def is_ta_running(self):
is_running_cmd = self.SSH_CHECK_APP_RUNNING_CMD_FORMAT.format(
regex_app_name=self.ta_exe_name)
@@ -141,7 +357,7 @@
retries = 12
for _ in range(retries):
try:
- s = self._socket_connect(self.uxm_ip, self.UXM_PORT)
+ s = self._socket_connect(self.uxm_ip, self.UXM_SOCKET_PORT)
s.close()
return
except ConnectionRefusedError as cre:
@@ -171,6 +387,22 @@
raise ValueError('Missing cell info from configurations file')
self.cells = cell_info
+ def deregister_ue_ims(self):
+ """Remove UE IMS profile from UXM."""
+ self._socket_send_SCPI_command(
+ self.SCPI_DEREGISTER_UE_IMS)
+
+ def create_dedicated_bearer(self):
+ """Create a dedicated bearer setup for ims call.
+
+ After UE connected and register on UXM IMS tab.
+ It is required to create a dedicated bearer setup
+ with EPS bearer ID 10.
+ """
+ cell_number = self.cells[0][self.KEY_CELL_NUMBER]
+ self._socket_send_SCPI_command(
+ self.SCPI_CREATE_DEDICATED_BEARER.format(cell_number))
+
def turn_cell_on(self, cell_type, cell_number):
"""Turn UXM's cell on.
@@ -237,7 +469,7 @@
host: IP address of desktop where Keysight Test Application resides.
port: port that Keysight Test Application is listening for socket
communication.
- Return:
+ Returns:
s: socket object.
"""
self.log.info('Establishing connection to callbox via socket')
@@ -279,7 +511,7 @@
def check_system_error(self):
"""Query system error from Keysight Test Application.
- Return:
+ Returns:
status: a message indicate the number of errors
and detail of errors if any.
a string `0,"No error"` indicates no error.
@@ -296,6 +528,9 @@
path: path to SCPI file.
"""
self._socket_send_SCPI_command(
+ self.SCPI_SETTINGS_PRESET_CMD)
+ time.sleep(10)
+ self._socket_send_SCPI_command(
self.SCPI_IMPORT_SCPI_FILE_CMD.format(path))
time.sleep(45)
@@ -328,13 +563,23 @@
# Restart SL4A
dut.ad.start_services()
+ def set_sim_type(self, is_3gpp_sim):
+ sim_type = 'KEYSight'
+ if is_3gpp_sim:
+ sim_type = 'TEST3GPP'
+ self._socket_send_SCPI_command(
+ self.SCPI_CHANGE_SIM_NR_CMD.format(sim_type))
+ time.sleep(2)
+ self._socket_send_SCPI_command(
+ self.SCPI_CHANGE_SIM_LTE_CMD.format(sim_type))
+ time.sleep(2)
+
def wait_until_attached_one_cell(self,
cell_type,
cell_number,
dut,
wait_for_camp_interval,
- attach_retries,
- change_dut_setting_allow=True):
+ attach_retries):
"""Wait until connect to given UXM cell.
After turn off airplane mode, sleep for
@@ -346,54 +591,74 @@
which we are trying to connect to.
cell_number: ordinal number of a cell
which we are trying to connect to.
- dut: a CellularAndroid controller.
+ dut: a AndroidCellular controller.
wait_for_camp_interval: sleep interval,
wait for device to camp.
attach_retries: number of retry
to wait for device
to connect to 1 basestation.
- change_dut_setting_allow: turn on/off APM
- or reboot device helps with device camp time.
- However, if we are trying to connect to second cell
- changing APM status or reboot is not allowed.
Raise:
- AbstractCellularSimulator.CellularSimulatorError:
- device unable to connect to cell.
+ RuntimeError: device unable to connect to cell.
"""
- # airplane mode off
- # dut.ad.adb.shell('settings put secure adaptive_connectivity_enabled 0')
- dut.toggle_airplane_mode(False)
+ # airplane mode on
+ dut.toggle_airplane_mode(True)
time.sleep(5)
+
# turn cell on
self.turn_cell_on(cell_type, cell_number)
time.sleep(5)
- # waits for connect
+ interval = 10
+ # waits for device to camp
for index in range(1, attach_retries):
- # airplane mode on
- time.sleep(wait_for_camp_interval)
- cell_state = self.get_cell_status(cell_type, cell_number)
- self.log.info(f'cell state: {cell_state}')
- if cell_state == 'CONN\n':
- return True
- if cell_state == 'OFF\n':
+ count = 0
+ # airplane mode off
+ dut.toggle_airplane_mode(False)
+ time.sleep(5)
+ # check connection in small interval
+ while count < wait_for_camp_interval:
+ time.sleep(interval)
+ cell_state = self.get_cell_status(cell_type, cell_number)
+ self.log.info(f'cell state: {cell_state}')
+ if cell_state == 'CONN\n':
+ # wait for connection stable
+ time.sleep(15)
+ # check connection status again
+ cell_state = self.get_cell_status(cell_type, cell_number)
+ self.log.info(f'cell state: {cell_state}')
+ if cell_state == 'CONN\n':
+ return True
+ if cell_state == 'OFF\n':
+ self.turn_cell_on(cell_type, cell_number)
+ time.sleep(5)
+ count += interval
+
+ # reboot device
+ if (index % 2) == 0:
+ dut.ad.reboot()
+ if self.rockbottom_script:
+ self.dut_rockbottom(dut)
+ else:
+ self.log.warning(
+ f'Rockbottom script was not executed after reboot.'
+ )
+ # toggle APM and cell on/off
+ elif (index % 1) == 0:
+ # Toggle APM on
+ dut.toggle_airplane_mode(True)
+ time.sleep(5)
+
+ # Toggle simulator cell
+ self.turn_cell_off(cell_type, cell_number)
+ time.sleep(5)
self.turn_cell_on(cell_type, cell_number)
time.sleep(5)
- if change_dut_setting_allow:
- if (index % 4) == 0:
- dut.ad.reboot()
- if self.rockbottom_script:
- self.dut_rockbottom(dut)
- else:
- self.log.warning(
- f'Rockbottom script {self} was not executed after reboot'
- )
- else:
- # airplane mode on
- dut.toggle_airplane_mode(True)
- time.sleep(5)
- # airplane mode off
- dut.toggle_airplane_mode(False)
+
+ # Toggle APM off
+ dut.toggle_airplane_mode(False)
+ time.sleep(5)
+ # increase length of small waiting interval
+ interval += 5
# Phone cannot connected to basestation of callbox
raise RuntimeError(
@@ -418,37 +683,44 @@
second_cell_number = self.cells[1][self.KEY_CELL_NUMBER]
# connect to 1st cell
- try:
- self.wait_until_attached_one_cell(first_cell_type,
- first_cell_number, dut, timeout,
- attach_retries)
- except Exception as exc:
- raise RuntimeError(f'Cannot connect to first cell') from exc
+ self.wait_until_attached_one_cell(first_cell_type,
+ first_cell_number, dut, timeout,
+ attach_retries)
- # connect to 2nd cell
+ # aggregation to NR
if len(self.cells) == 2:
self.turn_cell_on(
second_cell_type,
second_cell_number,
)
- self._socket_send_SCPI_command(
- 'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:DL None')
- self._socket_send_SCPI_command(
- 'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:UL None')
- self._socket_send_SCPI_command(
- 'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:DL CELL1')
- self._socket_send_SCPI_command(
- 'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:DL CELL1')
- time.sleep(1)
- self._socket_send_SCPI_command(
- "BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:APPly")
- try:
- self.wait_until_attached_one_cell(second_cell_type,
- second_cell_number, dut,
- timeout, attach_retries,
- False)
- except Exception as exc:
- raise RuntimeError(f'Cannot connect to second cell') from exc
+
+ for _ in range(1, attach_retries):
+ self.log.info('Try to aggregate to NR.')
+ self._socket_send_SCPI_command(
+ 'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:DL None')
+ self._socket_send_SCPI_command(
+ 'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:UL None')
+ self._socket_send_SCPI_command(
+ 'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:DL CELL1')
+ self._socket_send_SCPI_command(
+ 'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:DL CELL1')
+ time.sleep(1)
+ self._socket_send_SCPI_command(
+ "BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:APPly")
+ # wait for status stable
+ time.sleep(10)
+ cell_state = self.get_cell_status(second_cell_type, second_cell_number)
+ self.log.info(f'cell state: {cell_state}')
+ if cell_state == 'CONN\n':
+ return
+ else:
+ self.turn_cell_off(second_cell_type, second_cell_number)
+ # wait for LTE cell to connect again
+ self.wait_until_attached_one_cell(first_cell_type,
+ first_cell_number, dut, 120,
+ 2)
+
+ raise RuntimeError(f'Fail to aggregate to NR from LTE.')
def set_lte_rrc_state_change_timer(self, enabled, time=10):
"""Configures the LTE RRC state change timer.
@@ -677,16 +949,42 @@
timeout: after this amount of time the method will raise a
CellularSimulatorError exception. Default is 120 seconds.
"""
- raise NotImplementedError(
- 'This UXM callbox simulator does not support this feature.')
+ # turn on RRC release
+ cell_type = self.cells[0][self.KEY_CELL_TYPE]
+ cell_number = self.cells[0][self.KEY_CELL_NUMBER]
+
+ # choose cmd base on cell type
+ cmd = None
+ if cell_type == 'LTE':
+ cmd = self.SCPI_RRC_RELEASE_LTE_CMD
+ else:
+ cmd = self.SCPI_RRC_RELEASE_NR_CMD
+
+ if not cmd:
+ raise RuntimeError(f'Cell type [{cell_type}] is not supporting IDLE.')
+
+ # checking status
+ self.log.info('Wait for IDLE state.')
+ for _ in range(5):
+ cell_state = self.get_cell_status(cell_type, cell_number)
+ self.log.info(f'cell state: {cell_state}')
+ if cell_state == 'CONN\n':
+ # RRC release
+ self._socket_send_SCPI_command(cmd.format(cell_type, cell_number))
+ # wait for status stable
+ time.sleep(60)
+ elif cell_state == 'IDLE\n':
+ return
+
+ raise RuntimeError('RRC release fail.')
def detach(self):
""" Turns off all the base stations so the DUT loose connection."""
for cell in self.cells:
cell_type = cell[self.KEY_CELL_TYPE]
cell_number = cell[self.KEY_CELL_NUMBER]
- self._socket_send_SCPI_command(
- self.SCPI_CELL_OFF_CMD.format(cell_type, cell_number))
+ self.turn_cell_off(cell_type, cell_number)
+ time.sleep(5)
def stop(self):
"""Stops current simulation.
diff --git a/acts_tests/acts_contrib/test_utils/bt/BtMultiprofileBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/BtMultiprofileBaseTest.py
new file mode 100644
index 0000000..f2246df
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/BtMultiprofileBaseTest.py
@@ -0,0 +1,197 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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 time
+from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
+import acts_contrib.test_utils.bt.BleBaseTest as BleBT
+from acts_contrib.test_utils.power.IperfHelper import IperfHelper
+from acts_contrib.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
+from concurrent.futures import ThreadPoolExecutor
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils
+
+
+class BtMultiprofileBaseTest(A2dpBaseTest, BleBT.BleBaseTest):
+ """Base class for BT mutiprofile related tests.
+
+ Inherited from the A2DP Base class, Ble Base class
+ """
+ # Iperf waiting time (margin)
+ IPERF_MARGIN = 10
+
+ def mutiprofile_test(self,
+ codec_config=None,
+ mode=None,
+ victim=None,
+ aggressor=None,
+ metric=None):
+
+ if victim == 'A2DP' and aggressor == 'Ble_Scan' and metric == 'range':
+ scan_callback = self.start_ble_scan(self.dut, mode)
+ self.run_a2dp_to_max_range(codec_config)
+ self.dut.droid.bleStopBleScan(scan_callback)
+ self.log.info("BLE Scan stopped successfully")
+ return True
+
+ if victim == 'Ble_Scan' and aggressor == 'A2DP' and metric == 'scan_accuracy':
+ scan_callback = self.start_ble_scan(self.dut, mode)
+ recorded_file = self.play_and_record_audio(
+ self.audio_params['duration'])
+ self.dut.droid.bleStopBleScan(scan_callback)
+ self.media.stop()
+ self.log.info("BLE Scan & A2DP streaming stopped successfully")
+ return True
+
+ if victim == 'RFCOMM' and aggressor == 'Ble_Scan' and metric == 'throughput':
+ self.remote_device = self.android_devices[2]
+ scan_callback = self.start_ble_scan(self.dut, mode)
+ if not orchestrate_rfcomm_connection(self.dut, self.remote_device):
+ return False
+ self.log.info("RFCOMM Connection established")
+ self.measure_rfcomm_throughput(100)
+ self.dut.droid.bleStopBleScan(scan_callback)
+ self.log.info("BLE Scan stopped successfully")
+
+ if victim == 'A2DP' and aggressor == 'Ble_Adv' and metric == 'range':
+ advertise_callback = self.start_ble_adv(self.dut, mode, 2)
+ self.run_a2dp_to_max_range(codec_config)
+ self.dut.droid.bleStopBleAdvertising(advertise_callback)
+ self.log.info("Advertisement stopped Successfully")
+ return True
+
+ if victim == 'A2DP' and aggressor == 'Ble_conn' and metric == 'range':
+ self.start_ble_connection(self.dut, self.android_devices[2], mode)
+ self.run_a2dp_to_max_range(codec_config)
+ return True
+
+ if victim == 'A2DP' and aggressor == 'wifi' and metric == 'range':
+ self.setup_hotspot_and_connect_client()
+ self.setup_iperf_and_run_throughput()
+ self.run_a2dp_to_max_range(codec_config)
+ self.process_iperf_results()
+ return True
+
+ if victim == 'Ble_Scan' and aggressor == 'wifi' and metric == 'scan_accuracy':
+ scan_callback = self.start_ble_scan(self.dut, mode)
+ self.setup_hotspot_and_connect_client()
+ self.setup_iperf_and_run_throughput()
+ time.sleep(self.audio_params['duration'] + self.IPERF_MARGIN + 2)
+ self.log.info("BLE Scan & iPerf started successfully")
+ self.process_iperf_results()
+ self.dut.droid.bleStopBleScan(scan_callback)
+ self.log.info("BLE Scan stopped successfully")
+ return True
+
+ if victim == 'Ble_Adv' and aggressor == 'wifi' and metric == 'periodic_adv':
+ advertise_callback = self.start_ble_adv(self.dut, mode, 2)
+ self.setup_hotspot_and_connect_client()
+ self.setup_iperf_and_run_throughput()
+ time.sleep(self.audio_params['duration'] + self.IPERF_MARGIN + 2)
+ self.log.info("BLE Advertisement & iPerf started successfully")
+ self.process_iperf_results()
+ self.dut.droid.bleStopBleAdvertising(advertise_callback)
+ self.log.info("Advertisement stopped Successfully")
+ return True
+
+ if victim == 'RFCOMM' and aggressor == 'wifi' and metric == 'throughput':
+ self.remote_device = self.android_devices[2]
+ if not orchestrate_rfcomm_connection(self.dut, self.remote_device):
+ return False
+ self.log.info("RFCOMM Connection established")
+ self.setup_hotspot_and_connect_client()
+ executor = ThreadPoolExecutor(2)
+ throughput = executor.submit(self.measure_rfcomm_throughput, 100)
+ executor.submit(self.setup_iperf_and_run_throughput, )
+ time.sleep(self.audio_params['duration'] + self.IPERF_MARGIN + 10)
+ self.process_iperf_results()
+ return True
+
+ def measure_rfcomm_throughput(self, iteration):
+ """Measures the throughput of a data transfer.
+
+ Sends data over RFCOMM from the client device that is read by the server device.
+ Calculates the throughput for the transfer.
+
+ Args:
+ iteration : An integer value that respesents number of RFCOMM data trasfer iteration
+
+ Returns:
+ The throughput of the transfer in bits per second.
+ """
+ #An integer value designating the number of buffers to be sent.
+ num_of_buffers = 1
+ #An integer value designating the size of each buffer, in bytes.
+ buffer_size = 22000
+ throughput_list = []
+ for transfer in range(iteration):
+ (self.dut.droid.bluetoothConnectionThroughputSend(
+ num_of_buffers, buffer_size))
+
+ throughput = (
+ self.remote_device.droid.bluetoothConnectionThroughputRead(
+ num_of_buffers, buffer_size))
+ throughput = throughput * 8
+ throughput_list.append(throughput)
+ self.log.info(
+ ("RFCOMM Throughput is :{} bits/sec".format(throughput)))
+ throughput = statistics.mean(throughput_list)
+ return throughput
+
+ def setup_hotspot_and_connect_client(self):
+ """
+ Setup hotspot on the remote device and client connects to hotspot
+
+ """
+ self.network = {
+ wutils.WifiEnums.SSID_KEY: 'Pixel_2G',
+ wutils.WifiEnums.PWD_KEY: '1234567890'
+ }
+ # Setup tethering on dut
+ wutils.start_wifi_tethering(self.android_devices[1],
+ self.network[wutils.WifiEnums.SSID_KEY],
+ self.network[wutils.WifiEnums.PWD_KEY],
+ WIFI_CONFIG_APBAND_2G)
+
+ # Connect client device to Hotspot
+ wutils.wifi_connect(self.dut, self.network, check_connectivity=False)
+
+ def setup_iperf_and_run_throughput(self):
+ self.iperf_server_address = self.android_devices[
+ 1].droid.connectivityGetIPv4Addresses('wlan2')[0]
+ # Create the iperf config
+ iperf_config = {
+ 'traffic_type': 'TCP',
+ 'duration': self.audio_params['duration'] + self.IPERF_MARGIN,
+ 'server_idx': 0,
+ 'traffic_direction': 'UL',
+ 'port': self.iperf_servers[0].port,
+ 'start_meas_time': 4,
+ }
+ # Start iperf traffic (dut is the client)
+ self.client_iperf_helper = IperfHelper(iperf_config)
+ self.iperf_servers[0].start()
+ wputils.run_iperf_client_nonblocking(
+ self.dut, self.iperf_server_address,
+ self.client_iperf_helper.iperf_args)
+
+ def process_iperf_results(self):
+ time.sleep(self.IPERF_MARGIN + 2)
+ self.client_iperf_helper.process_iperf_results(self.dut, self.log,
+ self.iperf_servers,
+ self.test_name)
+ self.iperf_servers[0].stop()
+ return True
diff --git a/acts_tests/acts_contrib/test_utils/cellular/__init__.py b/acts_tests/acts_contrib/test_utils/cellular/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/cellular/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/cellular/cellular_base_test.py b/acts_tests/acts_contrib/test_utils/cellular/cellular_base_test.py
index ea7615c..27a66e0 100644
--- a/acts_tests/acts_contrib/test_utils/cellular/cellular_base_test.py
+++ b/acts_tests/acts_contrib/test_utils/cellular/cellular_base_test.py
@@ -201,7 +201,7 @@
if getattr(self, param) is None:
raise RuntimeError('The uxm cellular simulator '
'requires %s to be set in the '
- 'config file.' % key)
+ 'config file.' % param)
return uxm.UXMCellularSimulator(self.uxm_ip, self.custom_files,
self.uxm_user,
self.ssh_private_key_to_uxm,
diff --git a/acts_tests/acts_contrib/test_utils/cellular/keysight_5g_testapp.py b/acts_tests/acts_contrib/test_utils/cellular/keysight_5g_testapp.py
new file mode 100644
index 0000000..8e4d10a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/cellular/keysight_5g_testapp.py
@@ -0,0 +1,864 @@
+#!/usr/bin/env python3.4
+#
+# 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.
+
+import collections
+import pyvisa
+import time
+from acts import logger
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+
+SHORT_SLEEP = 1
+VERY_SHORT_SLEEP = 0.1
+SUBFRAME_DURATION = 0.001
+VISA_QUERY_DELAY = 0.01
+
+
+class Keysight5GTestApp(object):
+ """Controller for the Keysight 5G NR Test Application.
+
+ This controller enables interacting with a Keysight Test Application
+ running on a remote test PC and implements many of the configuration
+ parameters supported in test app.
+ """
+
+ VISA_LOCATION = '/opt/keysight/iolibs/libktvisa32.so'
+
+ def __init__(self, config):
+ self.config = config
+ self.log = logger.create_tagged_trace_logger("{}{}".format(
+ self.config['brand'], self.config['model']))
+ self.resource_manager = pyvisa.ResourceManager(self.VISA_LOCATION)
+ self.test_app = self.resource_manager.open_resource(
+ 'TCPIP0::{}::{}::INSTR'.format(self.config['ip_address'],
+ self.config['hislip_interface']))
+ self.test_app.timeout = 200000
+ self.test_app.write_termination = '\n'
+ self.test_app.read_termination = '\n'
+ self.test_app.query_delay = VISA_QUERY_DELAY
+ self.last_loaded_scpi = None
+
+ inst_id = self.send_cmd('*IDN?', 1)
+ if 'Keysight' not in inst_id[0]:
+ self.log.error(
+ 'Failed to connect to Keysight Test App: {}'.format(inst_id))
+ else:
+ self.log.info("Test App ID: {}".format(inst_id))
+
+ def destroy(self):
+ self.test_app.close()
+
+ ### Programming Utilities
+ @staticmethod
+ def _format_cells(cells):
+ "Helper function to format list of cells."
+ if isinstance(cells, int):
+ return 'CELL{}'.format(cells)
+ elif isinstance(cells, str):
+ return cells
+ elif isinstance(cells, list):
+ cell_list = [
+ Keysight5GTestApp._format_cells(cell) for cell in cells
+ ]
+ cell_list = ','.join(cell_list)
+ return cell_list
+
+ @staticmethod
+ def _format_response(response):
+ "Helper function to format test app response."
+
+ def _format_response_entry(entry):
+ try:
+ formatted_entry = float(entry)
+ except:
+ formatted_entry = entry
+ return formatted_entry
+
+ if ',' not in response:
+ return _format_response_entry(response)
+ response = response.split(',')
+ formatted_response = [
+ _format_response_entry(entry) for entry in response
+ ]
+ return formatted_response
+
+ def send_cmd(self, command, read_response=0, check_errors=1):
+ "Helper function to write to or query test app."
+ if read_response:
+ try:
+ response = Keysight5GTestApp._format_response(
+ self.test_app.query(command))
+ time.sleep(VISA_QUERY_DELAY)
+ if check_errors:
+ error = self.test_app.query('SYSTem:ERRor?')
+ time.sleep(VISA_QUERY_DELAY)
+ if 'No error' not in error:
+ self.log.warning("Command: {}. Error: {}".format(
+ command, error))
+ return response
+ except:
+ raise RuntimeError('Lost connection to test app.')
+ else:
+ try:
+ self.test_app.write(command)
+ time.sleep(VISA_QUERY_DELAY)
+ if check_errors:
+ error = self.test_app.query('SYSTem:ERRor?')
+ if 'No error' not in error:
+ self.log.warning("Command: {}. Error: {}".format(
+ command, error))
+ self.send_cmd('*OPC?', 1)
+ time.sleep(VISA_QUERY_DELAY)
+ except:
+ raise RuntimeError('Lost connection to test app.')
+ return None
+
+ def import_scpi_file(self, file_name, check_last_loaded=0):
+ """Function to import SCPI file specified in file_name.
+
+ Args:
+ file_name: name of SCPI file to run
+ check_last_loaded: flag to check last loaded scpi and
+ only load if different.
+ """
+ if file_name == self.last_loaded_scpi and check_last_loaded:
+ self.log.info('Skipping SCPI import.')
+ self.send_cmd("SYSTem:SCPI:IMPort '{}'".format(file_name))
+ while int(self.send_cmd('SYSTem:SCPI:IMPort:STATus?', 1)):
+ self.send_cmd('*OPC?', 1)
+ self.log.info('Done with SCPI import')
+
+ ### Configure Cells
+ def assert_cell_off_decorator(func):
+ "Decorator function that ensures cells or off when configuring them"
+
+ def inner(self, *args, **kwargs):
+ if "nr" in func.__name__:
+ cell_type = 'NR5G'
+ else:
+ cell_type = kwargs.get('cell_type', args[0])
+ cell = kwargs.get('cell', args[1])
+ cell_state = self.get_cell_state(cell_type, cell)
+ if cell_state:
+ self.log.error('Cell must be off when calling {}'.format(
+ func.__name__))
+ return (func(self, *args, **kwargs))
+
+ return inner
+
+ def assert_cell_off(self, cell_type, cell):
+ cell_state = self.get_cell_state(cell_type, cell)
+ if cell_state:
+ self.log.error('Cell must be off')
+
+ def select_cell(self, cell_type, cell):
+ """Function to select active cell.
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ """
+ self.send_cmd('BSE:SELected:CELL {},{}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell)))
+
+ def select_display_tab(self, cell_type, cell, tab, subtab):
+ """Function to select display tab.
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ tab: tab to display for the selected cell
+ """
+ supported_tabs = {
+ 'PHY': [
+ 'BWP', 'HARQ', 'PDSCH', 'PDCCH', 'PRACH', 'PUSCH', 'PUCCH',
+ 'SRSC'
+ ],
+ 'BTHR': ['SUMMARY', 'OTAGRAPH', 'ULOTA', 'DLOTA'],
+ 'CSI': []
+ }
+ if (tab not in supported_tabs) or (subtab not in supported_tabs[tab]):
+ return
+ self.select_cell(cell_type, cell)
+ self.send_cmd('DISPlay:{} {},{}'.format(cell_type, tab, subtab))
+
+ def get_cell_state(self, cell_type, cell):
+ """Function to get cell on/off state.
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ Returns:
+ cell_state: boolean. True if cell on
+ """
+ cell_state = int(
+ self.send_cmd(
+ 'BSE:CONFig:{}:{}:ACTive:STATe?'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell)), 1))
+ return cell_state
+
+ def wait_for_cell_status(self,
+ cell_type,
+ cell,
+ states,
+ timeout,
+ polling_interval=SHORT_SLEEP):
+ """Function to wait for a specific cell status
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ states: list of acceptable states (ON, CONN, AGG, ACT, etc)
+ timeout: amount of time to wait for requested status
+ Returns:
+ True if one of the listed states is achieved
+ False if timed out waiting for acceptable state.
+ """
+ states = [states] if isinstance(states, str) else states
+ for i in range(int(timeout / polling_interval)):
+ current_state = self.send_cmd(
+ 'BSE:STATus:{}:{}?'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell)), 1)
+ if current_state in states:
+ return True
+ time.sleep(polling_interval)
+ self.log.warning('Timeout waiting for {} {} {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), states))
+ return False
+
+ def set_cell_state(self, cell_type, cell, state):
+ """Function to set cell state
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ state: requested state
+ """
+ self.send_cmd('BSE:CONFig:{}:{}:ACTive:STATe {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), state))
+
+ def set_cell_type(self, cell_type, cell, sa_or_nsa):
+ """Function to set cell duplex mode
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ sa_or_nsa: SA or NSA
+ """
+ self.assert_cell_off(cell_type, cell)
+ self.send_cmd('BSE: CONFig:NR5G:{}:TYPE {}'.format(
+ Keysight5GTestApp._format_cells(cell), sa_or_nsa))
+
+ def set_cell_duplex_mode(self, cell_type, cell, duplex_mode):
+ """Function to set cell duplex mode
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ duplex_mode: TDD or FDD
+ """
+ self.assert_cell_off(cell_type, cell)
+ self.send_cmd('BSE:CONFig:{}:{}:DUPLEX:MODe {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), duplex_mode))
+
+ def set_cell_band(self, cell_type, cell, band):
+ """Function to set cell band
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ band: LTE or NR band (e.g. 1,3,N260, N77)
+ """
+ self.assert_cell_off(cell_type, cell)
+ self.send_cmd('BSE:CONFig:{}:{}:BAND {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), band))
+
+ def set_cell_channel(self, cell_type, cell, channel, arfcn=1):
+ """Function to set cell frequency/channel
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ channel: requested channel (ARFCN) or frequency in MHz
+ """
+ self.assert_cell_off(cell_type, cell)
+ if cell_type == 'NR5G' and isinstance(
+ channel, str) and channel.lower() in ['low', 'mid', 'high']:
+ self.send_cmd('BSE:CONFig:{}:{}:TESTChanLoc {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), channel.upper()))
+ elif arfcn == 1:
+ self.send_cmd('BSE:CONFig:{}:{}:DL:CHANnel {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), channel))
+ else:
+ self.send_cmd('BSE:CONFig:{}:{}:DL:FREQuency:MAIN {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell),
+ channel * 1e6))
+
+ def toggle_contiguous_nr_channels(self, force_contiguous):
+ self.assert_cell_off('NR5G', 1)
+ self.log.warning('Forcing contiguous NR channels overrides channel config.')
+ self.send_cmd('BSE:CONFig:NR5G:PHY:OPTimize:CONTiguous:STATe 0')
+ if force_contiguous:
+ self.send_cmd('BSE:CONFig:NR5G:PHY:OPTimize:CONTiguous:STATe 1')
+
+ def configure_contiguous_nr_channels(self, cell, band, channel):
+ """Function to set cell frequency/channel
+
+ Args:
+ cell: cell/carrier number
+ band: band to set channel in (only required for preset)
+ channel_preset: frequency in MHz or preset in [low, mid, or high]
+ """
+ self.assert_cell_off('NR5G', cell)
+ self.send_cmd('BSE:CONFig:NR5G:PHY:OPTimize:CONTiguous:STATe 0')
+ if channel.lower() in ['low', 'mid', 'high']:
+ pcc_arfcn = cputils.PCC_PRESET_MAPPING[band][channel]
+ self.set_cell_channel('NR5G', cell, pcc_arfcn, 1)
+ else:
+ self.set_cell_channel('NR5G', cell, channel, 0)
+ self.send_cmd('BSE:CONFig:NR5G:PHY:OPTimize:CONTiguous:STATe 1')
+
+ def configure_noncontiguous_nr_channels(self, cells, band, channels):
+ """Function to set cell frequency/channel
+
+ Args:
+ cell: cell/carrier number
+ band: band number
+ channel: frequency in MHz
+ """
+ for cell in cells:
+ self.assert_cell_off('NR5G', cell)
+ self.send_cmd('BSE:CONFig:NR5G:PHY:OPTimize:CONTiguous:STATe 0')
+ for cell, channel in zip(cells, channels):
+ self.set_cell_channel('NR5G', cell, channel, arfcn=0)
+
+ def set_cell_bandwidth(self, cell_type, cell, bandwidth):
+ """Function to set cell bandwidth
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ bandwidth: requested bandwidth
+ """
+ self.assert_cell_off(cell_type, cell)
+ self.send_cmd('BSE:CONFig:{}:{}:DL:BW {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), bandwidth))
+
+ def set_nr_subcarrier_spacing(self, cell, subcarrier_spacing):
+ """Function to set cell bandwidth
+
+ Args:
+ cell: cell/carrier number
+ subcarrier_spacing: requested SCS
+ """
+ self.assert_cell_off('NR5G', cell)
+ self.send_cmd('BSE:CONFig:NR5G:{}:SUBCarrier:SPACing:COMMon {}'.format(
+ Keysight5GTestApp._format_cells(cell), subcarrier_spacing))
+
+ def set_cell_mimo_config(self, cell_type, cell, link, mimo_config):
+ """Function to set cell mimo config.
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ link: uplink or downlink
+ mimo_config: requested mimo configuration (refer to SCPI
+ documentation for allowed range of values)
+ """
+ self.assert_cell_off(cell_type, cell)
+ if cell_type == 'NR5G':
+ self.send_cmd('BSE:CONFig:{}:{}:{}:MIMO:CONFig {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), link,
+ mimo_config))
+ else:
+ self.send_cmd('BSE:CONFig:{}:{}:PHY:DL:ANTenna:CONFig {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), mimo_config))
+
+ def set_lte_cell_transmission_mode(self, cell, transmission_mode):
+ """Function to set LTE cell transmission mode.
+
+ Args:
+ cell: cell/carrier number
+ transmission_mode: one of TM1, TM2, TM3, TM4 ...
+ """
+ self.assert_cell_off('LTE', cell)
+ self.send_cmd('BSE:CONFig:LTE:{}:RRC:TMODe {}'.format(
+ Keysight5GTestApp._format_cells(cell), transmission_mode))
+
+ def set_cell_dl_power(self, cell_type, cell, power, full_bw):
+ """Function to set cell power
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ power: requested power
+ full_bw: boolean controlling if requested power is per channel
+ or subcarrier
+ """
+ if full_bw:
+ self.send_cmd('BSE:CONFIG:{}:{}:DL:POWer:CHANnel {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), power))
+ else:
+ self.send_cmd('BSE:CONFIG:{}:{}:DL:POWer:EPRE {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), power))
+ self.send_cmd('BSE:CONFig:{}:APPLY'.format(cell_type))
+
+ def set_cell_duplex_mode(self, cell_type, cell, duplex_mode):
+ """Function to set cell power
+
+ Args:
+ cell_type: LTE or NR5G cell
+ cell: cell/carrier number
+ duplex mode: TDD or FDD
+ """
+ self.assert_cell_off(cell_type, cell)
+ self.send_cmd('BSE:CONFig:{}:{}:DUPLEX:MODe {}'.format(
+ cell_type, Keysight5GTestApp._format_cells(cell), duplex_mode))
+
+ def set_dl_carriers(self, cells):
+ """Function to set aggregated DL NR5G carriers
+
+ Args:
+ cells: list of DL cells/carriers to aggregate with LTE (e.g. [1,2])
+ """
+ self.send_cmd('BSE:CONFig:NR5G:CELL1:CAGGregation:NRCC:DL {}'.format(
+ Keysight5GTestApp._format_cells(cells)))
+
+ def set_ul_carriers(self, cells):
+ """Function to set aggregated UL NR5G carriers
+
+ Args:
+ cells: list of DL cells/carriers to aggregate with LTE (e.g. [1,2])
+ """
+ self.send_cmd('BSE:CONFig:NR5G:CELL1:CAGGregation:NRCC:UL {}'.format(
+ Keysight5GTestApp._format_cells(cells)))
+
+ def set_nr_cell_schedule_scenario(self, cell, scenario):
+ """Function to set NR schedule to one of predefince quick configs.
+
+ Args:
+ cell: cell number to address. schedule will apply to all cells
+ scenario: one of the predefined test app schedlue quick configs
+ (e.g. FULL_TPUT, BASIC).
+ """
+ self.assert_cell_off('NR5G', cell)
+ self.send_cmd(
+ 'BSE:CONFig:NR5G:{}:SCHeduling:QCONFig:SCENario {}'.format(
+ Keysight5GTestApp._format_cells(cell), scenario))
+ self.send_cmd('BSE:CONFig:NR5G:SCHeduling:QCONFig:APPLy:ALL')
+
+ def set_nr_cell_mcs(self, cell, dl_mcs, ul_mcs):
+ """Function to set NR cell DL & UL MCS
+
+ Args:
+ cell: cell number to address. MCS will apply to all cells
+ dl_mcs: mcs index to use on DL
+ ul_mcs: mcs index to use on UL
+ """
+ self.assert_cell_off('NR5G', cell)
+ self.send_cmd(
+ 'BSE:CONFig:NR5G:SCHeduling:SETParameter "CELLALL:BWPALL:FCALL:SCALL", "DL:IMCS", "{}"'
+ .format(dl_mcs))
+ self.send_cmd(
+ 'BSE:CONFig:NR5G:SCHeduling:SETParameter "CELLALL:BWPALL:FCALL:SCALL", "UL:IMCS", "{}"'
+ .format(ul_mcs))
+
+ def set_lte_cell_mcs(
+ self,
+ cell,
+ dl_mcs_table,
+ dl_mcs,
+ ul_mcs_table,
+ ul_mcs,
+ ):
+ """Function to set NR cell DL & UL MCS
+
+ Args:
+ cell: cell number to address. MCS will apply to all cells
+ dl_mcs: mcs index to use on DL
+ ul_mcs: mcs index to use on UL
+ """
+ if dl_mcs_table == 'QAM256':
+ dl_mcs_table_formatted = 'ASUBframe'
+ elif dl_mcs_table == 'QAM1024':
+ dl_mcs_table_formatted = 'ASUB1024'
+ elif dl_mcs_table == 'QAM64':
+ dl_mcs_table_formatted = 'DISabled'
+ self.assert_cell_off('LTE', cell)
+ self.send_cmd(
+ 'BSE:CONFig:LTE:SCHeduling:SETParameter "CELLALL", "DL:MCS:TABle", "{}"'
+ .format(dl_mcs_table_formatted))
+ self.send_cmd(
+ 'BSE:CONFig:LTE:SCHeduling:SETParameter "CELLALL:SFALL:CWALL", "DL:IMCS", "{}"'
+ .format(dl_mcs))
+ self.send_cmd(
+ 'BSE:CONFig:LTE:SCHeduling:SETParameter "CELLALL", "UL:MCS:TABle", "{}"'
+ .format(ul_mcs_table))
+ self.send_cmd(
+ 'BSE:CONFig:LTE:SCHeduling:SETParameter "CELLALL:SFALL", "UL:IMCS", "{}"'
+ .format(ul_mcs))
+
+ def set_lte_control_region_size(self, cell, num_symbols):
+ self.assert_cell_off('LTE', cell)
+ self.send_cmd('BSE:CONFig:LTE:{}:PHY:PCFich:CFI {}'.format(
+ Keysight5GTestApp._format_cells(cell), num_symbols))
+
+ def set_lte_ul_mac_padding(self, mac_padding):
+ self.assert_cell_off('LTE', 'CELL1')
+ padding_str = 'TRUE' if mac_padding else 'FALSE'
+ self.send_cmd(
+ 'BSE:CONFig:LTE:SCHeduling:SETParameter "CELLALL", "UL:MAC:PADDING", "{}"'
+ .format(padding_str))
+
+ def set_nr_ul_dft_precoding(self, cell, precoding):
+ """Function to configure DFT-precoding on uplink.
+
+ Args:
+ cell: cell number to address. MCS will apply to all cells
+ precoding: 0/1 to disable/enable precoding
+ """
+ self.assert_cell_off('NR5G', cell)
+ precoding_str = "ENABled" if precoding else "DISabled"
+ self.send_cmd(
+ 'BSE:CONFig:NR5G:{}:SCHeduling:QCONFig:UL:TRANsform:PRECoding {}'.
+ format(Keysight5GTestApp._format_cells(cell), precoding_str))
+ precoding_str = "True" if precoding else "False"
+ self.send_cmd(
+ 'BSE:CONFig:NR5G:SCHeduling:SETParameter "CELLALL:BWPALL", "UL:TPEnabled", "{}"'
+ .format(precoding_str))
+
+ def configure_ul_clpc(self, channel, mode, target):
+ """Function to configure UL power control on all cells/carriers
+
+ Args:
+ channel: physical channel must be PUSCh or PUCCh
+ mode: mode supported by test app (all up/down bits, target, etc)
+ target: target power if mode is set to target
+ """
+ self.send_cmd('BSE:CONFig:NR5G:UL:{}:CLPControl:MODE:ALL {}'.format(
+ channel, mode))
+ if "tar" in mode.lower():
+ self.send_cmd(
+ 'BSE:CONFig:NR5G:UL:{}:CLPControl:TARGet:POWer:ALL {}'.format(
+ channel, target))
+
+ def apply_lte_carrier_agg(self, cells):
+ """Function to start LTE carrier aggregation on already configured cells"""
+ if self.wait_for_cell_status('LTE', 'CELL1', 'CONN', 60):
+ self.send_cmd(
+ 'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:SCC {}'.format(
+ Keysight5GTestApp._format_cells(cells)))
+ self.send_cmd(
+ 'BSE:CONFig:LTE:CELL1:CAGGregation:ACTivate:SCC {}'.format(
+ Keysight5GTestApp._format_cells(cells)))
+
+ def apply_carrier_agg(self):
+ """Function to start carrier aggregation on already configured cells"""
+ if self.wait_for_cell_status('LTE', 'CELL1', 'CONN', 60):
+ self.send_cmd(
+ 'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:APPly')
+ else:
+ raise RuntimeError('LTE must be connected to start aggregation.')
+
+ def get_ip_throughput(self, cell_type):
+ """Function to query IP layer throughput on LTE or NR
+
+ Args:
+ cell_type: LTE or NR5G
+ Returns:
+ dict containing DL and UL IP-layer throughput
+ """
+ #Tester reply format
+ #{ report-count, total-bytes, current transfer-rate, average transfer-rate, peak transfer-rate }
+ dl_tput = self.send_cmd(
+ 'BSE:MEASure:{}:BTHRoughput:DL:THRoughput:IP?'.format(cell_type),
+ 1)
+ ul_tput = self.send_cmd(
+ 'BSE:MEASure:{}:BTHRoughput:UL:THRoughput:IP?'.format(cell_type),
+ 1)
+ return {'dl_tput': dl_tput, 'ul_tput': ul_tput}
+
+ def _get_throughput(self, cell_type, link, cell):
+ """Helper function to get PHY layer throughput on single cell"""
+ if cell_type == 'LTE':
+ tput_response = self.send_cmd(
+ 'BSE:MEASure:LTE:{}:BTHRoughput:{}:THRoughput:OTA:{}?'.format(
+ Keysight5GTestApp._format_cells(cell), link,
+ Keysight5GTestApp._format_cells(cell)), 1)
+ elif cell_type == 'NR5G':
+ # Tester reply format
+ #progress-count, ack-count, ack-ratio, nack-count, nack-ratio, statdtx-count, statdtx-ratio, pdschBlerCount, pdschBlerRatio, pdschTputRatio.
+ tput_response = self.send_cmd(
+ 'BSE:MEASure:NR5G:BTHRoughput:{}:THRoughput:OTA:{}?'.format(
+ link, Keysight5GTestApp._format_cells(cell)), 1)
+ tput_result = {
+ 'frame_count': tput_response[0] / 1e6,
+ 'current_tput': tput_response[1] / 1e6,
+ 'min_tput': tput_response[2] / 1e6,
+ 'max_tput': tput_response[3] / 1e6,
+ 'average_tput': tput_response[4] / 1e6,
+ 'theoretical_tput': tput_response[5] / 1e6,
+ }
+ return tput_result
+
+ def get_throughput(self, cell_type, cells):
+ """Function to get PHY layer throughput on on or more cells
+
+ This function returns the throughput data on the requested cells
+ during the last BLER test run, i.e., throughpt data must be fetch at
+ the end/after a BLE test run on the Keysight Test App.
+
+ Args:
+ cell_type: LTE or NR5G
+ cells: list of cells to query for throughput data
+ Returns:
+ tput_result: dict containing all throughput statistics in Mbps
+ """
+ if not isinstance(cells, list):
+ cells = [cells]
+ tput_result = collections.OrderedDict()
+ for cell in cells:
+ tput_result[cell] = {
+ 'DL': self._get_throughput(cell_type, 'DL', cell),
+ 'UL': self._get_throughput(cell_type, 'UL', cell)
+ }
+ frame_count = tput_result[cell]['DL']['frame_count']
+ agg_tput = {
+ 'DL': {
+ 'frame_count': frame_count,
+ 'current_tput': 0,
+ 'min_tput': 0,
+ 'max_tput': 0,
+ 'average_tput': 0,
+ 'theoretical_tput': 0
+ },
+ 'UL': {
+ 'frame_count': frame_count,
+ 'current_tput': 0,
+ 'min_tput': 0,
+ 'max_tput': 0,
+ 'average_tput': 0,
+ 'theoretical_tput': 0
+ }
+ }
+ for cell, cell_tput in tput_result.items():
+ for link, link_tput in cell_tput.items():
+ for key, value in link_tput.items():
+ if 'tput' in key:
+ agg_tput[link][key] = agg_tput[link][key] + value
+ tput_result['total'] = agg_tput
+ return tput_result
+
+ def _clear_bler_measurement(self, cell_type):
+ """Helper function to clear BLER results."""
+ if cell_type == 'LTE':
+ self.send_cmd('BSE:MEASure:LTE:CELL1:BTHRoughput:CLEar')
+ elif cell_type == 'NR5G':
+ self.send_cmd('BSE:MEASure:NR5G:BTHRoughput:CLEar')
+
+ def _configure_bler_measurement(self, cell_type, continuous, length):
+ """Helper function to configure BLER results."""
+ if continuous:
+ if cell_type == 'LTE':
+ self.send_cmd('BSE:MEASure:LTE:CELL1:BTHRoughput:CONTinuous 1')
+ elif cell_type == 'NR5G':
+ self.send_cmd('BSE:MEASure:NR5G:BTHRoughput:CONTinuous 1')
+ elif length > 1:
+ if cell_type == 'LTE':
+ self.send_cmd(
+ 'BSE:MEASure:LTE:CELL1:BTHRoughput:LENGth {}'.format(
+ length))
+ self.send_cmd('BSE:MEASure:LTE:CELL1:BTHRoughput:CONTinuous 0')
+ elif cell_type == 'NR5G':
+ self.send_cmd(
+ 'BSE:MEASure:NR5G:BTHRoughput:LENGth {}'.format(length))
+ self.send_cmd('BSE:MEASure:NR5G:BTHRoughput:CONTinuous 0')
+
+ def _set_bler_measurement_state(self, cell_type, state):
+ """Helper function to start or stop BLER measurement."""
+ if cell_type == 'LTE':
+ self.send_cmd(
+ 'BSE:MEASure:LTE:CELL1:BTHRoughput:STATe {}'.format(state))
+ elif cell_type == 'NR5G':
+ self.send_cmd(
+ 'BSE:MEASure:NR5G:BTHRoughput:STATe {}'.format(state))
+
+ def start_bler_measurement(self, cell_type, cells, length):
+ """Function to kick off a BLER measurement
+
+ Args:
+ cell_type: LTE or NR5G
+ length: integer length of BLER measurements in subframes
+ """
+ self._clear_bler_measurement(cell_type)
+ self._set_bler_measurement_state(cell_type, 0)
+ self._configure_bler_measurement(cell_type, 0, length)
+ self._set_bler_measurement_state(cell_type, 1)
+ time.sleep(0.1)
+ bler_check = self.get_bler_result(cell_type, cells, length, 0)
+ if bler_check['total']['DL']['frame_count'] == 0:
+ self.log.warning('BLER measurement did not start. Retrying')
+ self.start_bler_measurement(cell_type, cells, length)
+
+ def _get_bler(self, cell_type, link, cell):
+ """Helper function to get single-cell BLER measurement results."""
+ if cell_type == 'LTE':
+ bler_response = self.send_cmd(
+ 'BSE:MEASure:LTE:CELL1:BTHRoughput:{}:BLER:CELL1?'.format(
+ link), 1)
+ elif cell_type == 'NR5G':
+ bler_response = self.send_cmd(
+ 'BSE:MEASure:NR5G:BTHRoughput:{}:BLER:{}?'.format(
+ link, Keysight5GTestApp._format_cells(cell)), 1)
+ bler_result = {
+ 'frame_count': bler_response[0],
+ 'ack_count': bler_response[1],
+ 'ack_ratio': bler_response[2],
+ 'nack_count': bler_response[3],
+ 'nack_ratio': bler_response[4]
+ }
+ return bler_result
+
+ def get_bler_result(self,
+ cell_type,
+ cells,
+ length,
+ wait_for_length=1,
+ polling_interval=SHORT_SLEEP):
+ """Function to get BLER results.
+
+ This function gets the BLER measurements results on one or more
+ requested cells. The function can either return BLER statistics
+ immediately or wait until a certain number of subframes have been
+ counted (e.g. if the BLER measurement is done)
+
+ Args:
+ cell_type: LTE or NR5G
+ cells: list of cells for which to get BLER
+ length: number of subframes to wait for (typically set to the
+ configured length of the BLER measurements)
+ wait_for_length: boolean to block/wait till length subframes have
+ been counted.
+ Returns:
+ bler_result: dict containing per-cell and aggregate BLER results
+ """
+
+ if not isinstance(cells, list):
+ cells = [cells]
+ while wait_for_length:
+ dl_bler = self._get_bler(cell_type, 'DL', cells[0])
+ if dl_bler['frame_count'] < length:
+ time.sleep(polling_interval)
+ else:
+ break
+
+ bler_result = collections.OrderedDict()
+ for cell in cells:
+ bler_result[cell] = {
+ 'DL': self._get_bler(cell_type, 'DL', cell),
+ 'UL': self._get_bler(cell_type, 'UL', cell)
+ }
+ agg_bler = {
+ 'DL': {
+ 'frame_count': length,
+ 'ack_count': 0,
+ 'ack_ratio': 0,
+ 'nack_count': 0,
+ 'nack_ratio': 0
+ },
+ 'UL': {
+ 'frame_count': length,
+ 'ack_count': 0,
+ 'ack_ratio': 0,
+ 'nack_count': 0,
+ 'nack_ratio': 0
+ }
+ }
+ for cell, cell_bler in bler_result.items():
+ for link, link_bler in cell_bler.items():
+ for key, value in link_bler.items():
+ if 'ack_count' in key:
+ agg_bler[link][key] = agg_bler[link][key] + value
+ dl_ack_nack = agg_bler['DL']['ack_count'] + agg_bler['DL']['nack_count']
+ ul_ack_nack = agg_bler['UL']['ack_count'] + agg_bler['UL']['nack_count']
+ try:
+ agg_bler['DL'][
+ 'ack_ratio'] = agg_bler['DL']['ack_count'] / dl_ack_nack
+ agg_bler['DL'][
+ 'nack_ratio'] = agg_bler['DL']['nack_count'] / dl_ack_nack
+ agg_bler['UL'][
+ 'ack_ratio'] = agg_bler['UL']['ack_count'] / ul_ack_nack
+ agg_bler['UL'][
+ 'nack_ratio'] = agg_bler['UL']['nack_count'] / ul_ack_nack
+ except:
+ self.log.debug(bler_result)
+ agg_bler['DL']['ack_ratio'] = 0
+ agg_bler['DL']['nack_ratio'] = 1
+ agg_bler['UL']['ack_ratio'] = 0
+ agg_bler['UL']['nack_ratio'] = 1
+ bler_result['total'] = agg_bler
+ return bler_result
+
+ def measure_bler(self, cell_type, cells, length):
+ """Function to start and wait for BLER results.
+
+ This function starts a BLER test on a number of cells and waits for the
+ test to complete before returning the BLER measurements.
+
+ Args:
+ cell_type: LTE or NR5G
+ cells: list of cells for which to get BLER
+ length: number of subframes to wait for (typically set to the
+ configured length of the BLER measurements)
+ Returns:
+ bler_result: dict containing per-cell and aggregate BLER results
+ """
+ self.start_bler_measurement(cell_type, cells, length)
+ time.sleep(length * SUBFRAME_DURATION)
+ bler_result = self.get_bler_result(cell_type, cells, length, 1)
+ return bler_result
+
+ def start_nr_rsrp_measurement(self, cells, length):
+ """Function to start 5G NR RSRP measurement.
+
+ Args:
+ cells: list of NR cells to get RSRP on
+ length: length of RSRP measurement in milliseconds
+ Returns:
+ rsrp_result: dict containing per-cell and aggregate BLER results
+ """
+ for cell in cells:
+ self.send_cmd('BSE:MEASure:NR5G:{}:L1:RSRPower:STOP'.format(
+ Keysight5GTestApp._format_cells(cell)))
+ for cell in cells:
+ self.send_cmd('BSE:MEASure:NR5G:{}:L1:RSRPower:LENGth {}'.format(
+ Keysight5GTestApp._format_cells(cell), length))
+ for cell in cells:
+ self.send_cmd('BSE:MEASure:NR5G:{}:L1:RSRPower:STARt'.format(
+ Keysight5GTestApp._format_cells(cell)))
+
+ def get_nr_rsrp_measurement_state(self, cells):
+ for cell in cells:
+ self.log.info(
+ self.send_cmd(
+ 'BSE:MEASure:NR5G:{}:L1:RSRPower:STATe?'.format(
+ Keysight5GTestApp._format_cells(cell)), 1))
+
+ def get_nr_rsrp_measurement_results(self, cells):
+ for cell in cells:
+ self.log.info(
+ self.send_cmd(
+ 'BSE:MEASure:NR5G:{}:L1:RSRPower:REPorts:JSON?'.format(
+ Keysight5GTestApp._format_cells(cell)), 1))
diff --git a/acts_tests/acts_contrib/test_utils/cellular/keysight_catr_chamber.py b/acts_tests/acts_contrib/test_utils/cellular/keysight_catr_chamber.py
new file mode 100644
index 0000000..bd7540d
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/cellular/keysight_catr_chamber.py
@@ -0,0 +1,181 @@
+# Copyright 2020 Google Inc. All Rights Reserved.
+# Author: oelayach@google.com
+
+import pyvisa
+import time
+from acts import logger
+from ota_chamber import Chamber
+
+
+class Chamber(Chamber):
+ """Base class implementation for signal generators.
+
+ Base class provides functions whose implementation is shared by all
+ chambers.
+ """
+ CHAMBER_SLEEP = 10
+
+ def __init__(self, config):
+ self.config = config
+ self.log = logger.create_tagged_trace_logger("{}{}".format(
+ self.config['brand'], self.config['model']))
+ self.chamber_resource = pyvisa.ResourceManager()
+ self.chamber_inst = self.chamber_resource.open_resource(
+ '{}::{}::{}::INSTR'.format(self.config['network_id'],
+ self.config['ip_address'],
+ self.config['hislip_interface']))
+ self.chamber_inst.timeout = 200000
+ self.chamber_inst.write_termination = '\n'
+ self.chamber_inst.read_termination = '\n'
+
+ self.id_check(self.config)
+ self.current_azim = 0
+ self.current_roll = 0
+ if self.config.get('reset_home', True):
+ self.find_chamber_home()
+ self.move_theta_phi_abs(self.config['chamber_home']['theta'],
+ self.config['chamber_home']['phi'])
+ self.set_new_home_position()
+ else:
+ self.config['chamber_home'] = {'phi': 0, 'theta': 0}
+ self.log.warning(
+ 'Reset home set to false. Assumed [0,0]. Chamber angles may not be as expected.'
+ )
+
+ def id_check(self, config):
+ """ Checks Chamber ID."""
+ self.log.info("ID Check Successful.")
+ self.log.info(self.chamber_inst.query("*IDN?"))
+
+ def reset(self):
+ self.reset_phi_theta()
+
+ def disconnect(self):
+ if self.config.get('reset_home', True):
+ self.reset_phi_theta()
+ self.chamber_inst.close()
+ self.chamber_resource.close()
+
+ def find_chamber_home(self):
+ self.chamber_inst.write(f"POS:BOR:INIT")
+ self.set_new_home_position()
+ self.wait_for_move_end()
+
+ def set_new_home_position(self):
+ self.chamber_inst.write(f"POS:ZERO:RES")
+
+ def get_phi(self):
+ return self.current_azim
+
+ def get_theta(self):
+ return self.current_roll
+
+ def get_pattern_sweep_limits(self):
+ return {
+ "pattern_phi_start": -self.config['chamber_home']['phi'],
+ "pattern_phi_stop": 165 - self.config['chamber_home']['phi'],
+ "pattern_theta_start": -self.config['chamber_home']['theta'],
+ "pattern_theta_stop": 360 - self.config['chamber_home']['theta'],
+ }
+
+ def move_phi_abs(self, phi):
+ self.log.info("Moving to Phi={}".format(phi))
+ self.move_to_azim_roll(phi, self.current_roll)
+
+ def move_theta_abs(self, theta):
+ self.log.info("Moving to Theta={}".format(theta))
+ self.move_to_azim_roll(self.current_azim, theta)
+
+ def move_theta_phi_abs(self, theta, phi):
+ self.log.info("Moving chamber to [{}, {}]".format(theta, phi))
+ self.move_to_azim_roll(phi, theta)
+
+ def move_phi_rel(self, phi):
+ self.log.info("Moving Phi by {} degrees".format(phi))
+ self.move_to_azim_roll(self.current_azim + phi, self.current_roll)
+
+ def move_theta_rel(self, theta):
+ self.log.info("Moving Theta by {} degrees".format(theta))
+ self.move_to_azim_roll(self.current_azim, self.current_roll + theta)
+
+ def move_feed_roll(self, roll):
+ self.log.info("Moving feed roll to {} degrees".format(roll))
+ self.chamber_inst.write(f"POS:MOVE:ROLL:FEED {roll}")
+ self.chamber_inst.write("POS:MOVE:INIT")
+ self.wait_for_move_end()
+ self.current_feed_roll = self.chamber_inst.query("POS:MOVE:ROLL:FEED?")
+
+ def reset_phi(self):
+ self.log.info("Resetting Phi.")
+ self.move_to_azim_roll(0, self.current_roll)
+ self.phi = 0
+
+ def reset_theta(self):
+ self.log.info("Resetting Theta.")
+ self.move_to_azim_roll(self.current_azim, 0)
+ self.theta = 0
+
+ def reset_phi_theta(self):
+ """ Resets signal generator."""
+ self.log.info("Resetting to home.")
+ self.chamber_inst.write(f"POS:ZERO:GOTO")
+ self.wait_for_move_end()
+
+ # Keysight-provided functions
+ def wait_for_move_end(self):
+ moving_bitmask = 4
+ while True:
+ stat = int(self.chamber_inst.query("STAT:OPER:COND?"))
+ if (stat & moving_bitmask) == 0:
+ return
+ time.sleep(0.25)
+
+ def wait_for_sweep_end(self):
+ sweeping_bitmask = 16
+ while True:
+ stat = int(self.chamber_inst.query("STAT:OPER:COND?"))
+ if (stat & sweeping_bitmask) == 0:
+ return
+ time.sleep(0.25)
+
+ def move_to_azim_roll(self, azim, roll):
+ self.chamber_inst.write(f"POS:MOVE:AZIM {azim};ROLL {roll}")
+ self.chamber_inst.write("POS:MOVE:INIT")
+ self.wait_for_move_end()
+ curr_motor = self.chamber_inst.query("POS:CURR:MOT?")
+ curr_azim, curr_roll = map(float, (curr_motor.split(',')))
+ self.current_azim = curr_azim
+ self.current_roll = curr_roll
+ return curr_azim, curr_roll
+
+ def sweep_setup(self, azim_sss: tuple, roll_sss: tuple, sweep_type: str):
+ self.chamber_inst.write(
+ f"POS:SWE:AZIM:STAR {azim_sss[0]};STOP {azim_sss[1]};STEP {azim_sss[2]}"
+ )
+ self.chamber_inst.write(
+ f"POS:SWE:ROLL:STAR {roll_sss[0]};STOP {roll_sss[1]};STEP {roll_sss[2]}"
+ )
+ self.chamber_inst.write(f"POS:SWE:TYPE {sweep_type}")
+ self.chamber_inst.write("POS:SWE:CONT 1")
+
+ def sweep_init(self):
+
+ def query_float_list(inst, scpi):
+ resp = inst.query(scpi)
+ return list(map(float, resp.split(',')))
+
+ self.chamber_inst.write("POS:SWE:INIT")
+ self.wait_for_sweep_end()
+ azims = query_float_list(self.chamber_inst, "FETC:AZIM?")
+ rolls = query_float_list(self.chamber_inst, "FETC:ROLL?")
+ phis = query_float_list(self.chamber_inst, "FETC:DUT:PHI?")
+ thetas = query_float_list(self.chamber_inst, "FETC:DUT:THET?")
+ return zip(azims, rolls, phis, thetas)
+
+ def configure_positioner(self, pos_name, pos_visa_addr):
+ select = "True"
+ simulate = "False"
+ options = ""
+ data = f"'{pos_name}~{select}~{simulate}~{pos_visa_addr}~{options}'"
+ self.chamber_inst.write(f"EQU:CONF {data}")
+ self.chamber_inst.write("EQU:UPD")
diff --git a/acts_tests/acts_contrib/test_utils/cellular/mock_chamber.py b/acts_tests/acts_contrib/test_utils/cellular/mock_chamber.py
new file mode 100644
index 0000000..36d74c5
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/cellular/mock_chamber.py
@@ -0,0 +1,65 @@
+# Copyright 2020 Google Inc. All Rights Reserved.
+# Author: oelayach@google.com
+from acts import logger
+from ota_chamber import Chamber
+
+
+class MockChamber(Chamber):
+ """Base class implementation for signal generators.
+
+ Base class provides functions whose implementation is shared by all
+ chambers.
+ """
+
+ def __init__(self, config):
+ self.config = config
+ self.log = logger.create_tagged_trace_logger("{}{}".format(
+ self.config['brand'], self.config['model']))
+ self.id_check(self.config)
+ self.reset()
+
+ def id_check(self, config):
+ """ Check Chamber."""
+ self.log.info("ID Check Successful.")
+
+ def reset(self):
+ """ Resets Chamber."""
+ self.log.info("Resetting instrument.")
+
+ def disconnect(self):
+ """ Disconnects Chamber."""
+ self.log.info("Disconnecting instrument.")
+
+ def get_phi(self):
+ return self.phi
+
+ def get_theta(self):
+ return self.phi
+
+ def move_phi_abs(self, phi):
+ self.log.info("Moving to Phi={}".format(phi))
+ self.phi = phi
+
+ def move_theta_abs(self, theta):
+ self.log.info("Moving to Theta={}".format(theta))
+ self.theta = theta
+
+ def move_phi_rel(self, phi):
+ self.log.info("Moving Phi by {} degrees".format(phi))
+ self.phi = self.phi + phi
+
+ def move_theta_rel(self, theta):
+ self.log.info("Moving Theta by {} degrees".format(theta))
+ self.theta = self.theta + theta
+
+ def reset_phi(self):
+ self.log.info("Resetting Phi.")
+ self.phi = 0
+
+ def reset_theta(self):
+ self.log.info("Resetting Theta.")
+ self.theta = 0
+
+ def reset_phi_theta(self):
+ """ Resets signal generator."""
+ self.log.info("Resetting to home.")
diff --git a/acts_tests/acts_contrib/test_utils/cellular/ota_chamber.py b/acts_tests/acts_contrib/test_utils/cellular/ota_chamber.py
new file mode 100644
index 0000000..923f22b
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/cellular/ota_chamber.py
@@ -0,0 +1,94 @@
+# Copyright 2020 Google Inc. All Rights Reserved.
+# Author: oelayach@google.com
+
+import importlib
+from acts import logger
+
+ACTS_CONTROLLER_CONFIG_NAME = 'Chamber'
+
+
+def create(configs):
+ """Factory method for Signal Generators.
+
+ Args:
+ configs: list of dicts with signal generator settings
+ """
+ SUPPORTED_MODELS = {
+ ("Keysight", "CATR"): "lassen.controllers.chamber.KeysightCATRChamber",
+ ("Mock", "Chamber"): "lassen.controllers.chamber.MockChamber"
+ }
+ objs = []
+ for config in configs:
+ if (config["brand"], config["model"]) not in SUPPORTED_MODELS:
+ raise ValueError("Not a valid chamber model.")
+ module = importlib.import_module(SUPPORTED_MODELS[(config["brand"],
+ config["model"])])
+ chamber = module.Chamber(config)
+ objs.append(chamber)
+ return objs
+
+
+def destroy(devices):
+ for device in devices:
+ device.reset()
+
+
+class Chamber():
+ """Base class implementation for signal generators.
+
+ Base class provides functions whose implementation is shared by all
+ chambers.
+ """
+
+ def __init__(self, config):
+ self.config = config
+ self.log = logger.create_tagged_trace_logger("{}{}".format(
+ self.config['brand'], self.config['model']))
+ self.id_check(self.config)
+ self.reset()
+
+ def id_check(self, config):
+ """ Resets signal generator."""
+ self.log.info("ID Check Successful.")
+
+ def reset(self):
+ """ Resets signal generator."""
+ self.log.info("Resetting instrument.")
+
+ def disconnect(self):
+ """ Disconnects Chamber."""
+ self.log.info("Disconnecting instrument.")
+
+ def get_phi(self):
+ return self.phi
+
+ def get_theta(self):
+ return self.phi
+
+ def move_phi_abs(self, phi):
+ self.log.info("Moving to Phi={}".format(phi))
+ self.phi = phi
+
+ def move_theta_abs(self, theta):
+ self.log.info("Moving to Theta={}".format(theta))
+ self.theta = theta
+
+ def move_phi_rel(self, phi):
+ self.log.info("Moving Phi by {} degrees".format(phi))
+ self.phi = self.phi + phi
+
+ def move_theta_rel(self, theta):
+ self.log.info("Moving Theta by {} degrees".format(theta))
+ self.theta = self.theta + theta
+
+ def reset_phi(self):
+ self.log.info("Resetting Phi.")
+ self.phi = 0
+
+ def reset_theta(self):
+ self.log.info("Resetting Theta.")
+ self.theta = 0
+
+ def reset_phi_theta(self):
+ """ Resets signal generator."""
+ self.log.info("Resetting to home.")
diff --git a/acts_tests/acts_contrib/test_utils/cellular/performance/CellularThroughputBaseTest.py b/acts_tests/acts_contrib/test_utils/cellular/performance/CellularThroughputBaseTest.py
new file mode 100644
index 0000000..57e66ff
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/cellular/performance/CellularThroughputBaseTest.py
@@ -0,0 +1,481 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 collections
+import csv
+import itertools
+import json
+import re
+
+import numpy
+import os
+import time
+from acts import asserts
+from acts import context
+from acts import base_test
+from acts import utils
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.controllers.utils_lib import ssh
+from acts.controllers import iperf_server as ipf
+from acts_contrib.test_utils.cellular.keysight_5g_testapp import Keysight5GTestApp
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from functools import partial
+
+LONG_SLEEP = 10
+MEDIUM_SLEEP = 2
+IPERF_TIMEOUT = 10
+SHORT_SLEEP = 1
+SUBFRAME_LENGTH = 0.001
+STOP_COUNTER_LIMIT = 3
+
+
+class CellularThroughputBaseTest(base_test.BaseTestClass):
+ """Base class for Cellular Throughput Testing
+
+ This base class enables cellular throughput tests on a lab/callbox setup
+ with PHY layer or iperf traffic.
+ """
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+ self.testclass_params = None
+
+ def setup_class(self):
+ """Initializes common test hardware and parameters.
+
+ This function initializes hardwares and compiles parameters that are
+ common to all tests in this class.
+ """
+ # Setup controllers
+ self.dut = self.android_devices[-1]
+ self.keysight_test_app = Keysight5GTestApp(
+ self.user_params['Keysight5GTestApp'])
+ self.iperf_server = self.iperf_servers[0]
+ self.iperf_client = self.iperf_clients[0]
+ self.remote_server = ssh.connection.SshConnection(
+ ssh.settings.from_config(
+ self.user_params['RemoteServer']['ssh_config']))
+
+ # Configure Tester
+ if self.testclass_params.get('reload_scpi', 1):
+ self.keysight_test_app.import_scpi_file(
+ self.testclass_params['scpi_file'])
+
+ # Declare testclass variables
+ self.testclass_results = collections.OrderedDict()
+
+ # Configure test retries
+ self.user_params['retry_tests'] = [self.__class__.__name__]
+
+ # Turn Airplane mode on
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+
+ def teardown_class(self):
+ self.log.info('Turning airplane mode on')
+ try:
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ except:
+ self.log.warning('Cannot perform teardown operations on DUT.')
+ try:
+ self.keysight_test_app.set_cell_state('LTE', 1, 0)
+ self.keysight_test_app.destroy()
+ except:
+ self.log.warning('Cannot perform teardown operations on tester.')
+ self.process_testclass_results()
+
+ def setup_test(self):
+ self.retry_flag = False
+ if self.testclass_params['enable_pixel_logs']:
+ cputils.start_pixel_logger(self.dut)
+
+ def teardown_test(self):
+ self.retry_flag = False
+ self.log.info('Turing airplane mode on')
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ if self.keysight_test_app.get_cell_state('LTE', 'CELL1'):
+ self.log.info('Turning LTE off.')
+ self.keysight_test_app.set_cell_state('LTE', 'CELL1', 0)
+ log_path = os.path.join(
+ context.get_current_context().get_full_output_path(), 'pixel_logs')
+ os.makedirs(self.log_path, exist_ok=True)
+ if self.testclass_params['enable_pixel_logs']:
+ cputils.stop_pixel_logger(self.dut, log_path)
+ self.process_testcase_results()
+ self.pass_fail_check()
+
+ def on_retry(self):
+ """Function to control test logic on retried tests.
+
+ This function is automatically executed on tests that are being
+ retried. In this case the function resets wifi, toggles it off and on
+ and sets a retry_flag to enable further tweaking the test logic on
+ second attempts.
+ """
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ if self.keysight_test_app.get_cell_state('LTE', 'CELL1'):
+ self.log.info('Turning LTE off.')
+ self.keysight_test_app.set_cell_state('LTE', 'CELL1', 0)
+ self.retry_flag = True
+
+ def pass_fail_check(self):
+ pass
+
+ def process_testcase_results(self):
+ pass
+
+ def process_testclass_results(self):
+ pass
+
+ def get_per_cell_power_sweeps(self, testcase_params):
+ raise NotImplementedError(
+ 'get_per_cell_power_sweeps must be implemented.')
+
+ def compile_test_params(self, testcase_params):
+ """Function that completes all test params based on the test name.
+
+ Args:
+ testcase_params: dict containing test-specific parameters
+ """
+ # Measurement Duration
+ testcase_params['bler_measurement_length'] = int(
+ self.testclass_params['traffic_duration'] / SUBFRAME_LENGTH)
+ # Cell power sweep
+ # TODO: Make this a function to support single power and sweep modes for each cell
+ testcase_params['cell_power_sweep'] = self.get_per_cell_power_sweeps(
+ testcase_params)
+ # Traffic & iperf params
+ if self.testclass_params['traffic_type'] == 'PHY':
+ return testcase_params
+ if self.testclass_params['traffic_type'] == 'TCP':
+ testcase_params['iperf_socket_size'] = self.testclass_params.get(
+ 'tcp_socket_size', None)
+ testcase_params['iperf_processes'] = self.testclass_params.get(
+ 'tcp_processes', 1)
+ elif self.testclass_params['traffic_type'] == 'UDP':
+ testcase_params['iperf_socket_size'] = self.testclass_params.get(
+ 'udp_socket_size', None)
+ testcase_params['iperf_processes'] = self.testclass_params.get(
+ 'udp_processes', 1)
+ adb_iperf_server = isinstance(self.iperf_server,
+ ipf.IPerfServerOverAdb)
+ if testcase_params['traffic_direction'] == 'DL':
+ reverse_direction = 0 if adb_iperf_server else 1
+ testcase_params[
+ 'use_client_output'] = False if adb_iperf_server else True
+ elif testcase_params['traffic_direction'] == 'UL':
+ reverse_direction = 1 if adb_iperf_server else 0
+ testcase_params[
+ 'use_client_output'] = True if adb_iperf_server else False
+ testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+ duration=self.testclass_params['traffic_duration'],
+ reverse_direction=reverse_direction,
+ traffic_type=self.testclass_params['traffic_type'],
+ socket_size=testcase_params['iperf_socket_size'],
+ num_processes=testcase_params['iperf_processes'],
+ udp_throughput=self.testclass_params['UDP_rates'].get(
+ testcase_params['num_dl_cells'],
+ self.testclass_params['UDP_rates']["default"]),
+ udp_length=1440)
+ return testcase_params
+
+ def run_iperf_traffic(self, testcase_params):
+ self.iperf_server.start(tag=0)
+ dut_ip = self.dut.droid.connectivityGetIPv4Addresses('rmnet0')[0]
+ if 'iperf_server_address' in self.testclass_params:
+ iperf_server_address = self.testclass_params[
+ 'iperf_server_address']
+ elif isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+ iperf_server_address = dut_ip
+ else:
+ iperf_server_address = wputils.get_server_address(
+ self.remote_server, dut_ip, '255.255.255.0')
+ client_output_path = self.iperf_client.start(
+ iperf_server_address, testcase_params['iperf_args'], 0,
+ self.testclass_params['traffic_duration'] + IPERF_TIMEOUT)
+ server_output_path = self.iperf_server.stop()
+ # Parse and log result
+ if testcase_params['use_client_output']:
+ iperf_file = client_output_path
+ else:
+ iperf_file = server_output_path
+ try:
+ iperf_result = ipf.IPerfResult(iperf_file)
+ current_throughput = numpy.mean(iperf_result.instantaneous_rates[
+ self.testclass_params['iperf_ignored_interval']:-1]) * 8 * (
+ 1.024**2)
+ except:
+ self.log.warning(
+ 'ValueError: Cannot get iperf result. Setting to 0')
+ current_throughput = 0
+ return current_throughput
+
+ def run_single_throughput_measurement(self, testcase_params):
+ result = collections.OrderedDict()
+ self.log.info('Starting BLER & throughput tests.')
+ if testcase_params['endc_combo_config']['nr_cell_count']:
+ self.keysight_test_app.start_bler_measurement(
+ 'NR5G', testcase_params['endc_combo_config']['nr_dl_carriers'],
+ testcase_params['bler_measurement_length'])
+ if testcase_params['endc_combo_config']['lte_cell_count']:
+ self.keysight_test_app.start_bler_measurement(
+ 'LTE', testcase_params['endc_combo_config']['lte_carriers'][0],
+ testcase_params['bler_measurement_length'])
+
+ if self.testclass_params['traffic_type'] != 'PHY':
+ result['iperf_throughput'] = self.run_iperf_traffic(
+ testcase_params)
+
+ if testcase_params['endc_combo_config']['nr_cell_count']:
+ result['nr_bler_result'] = self.keysight_test_app.get_bler_result(
+ 'NR5G', testcase_params['endc_combo_config']['nr_dl_carriers'],
+ testcase_params['bler_measurement_length'])
+ result['nr_tput_result'] = self.keysight_test_app.get_throughput(
+ 'NR5G', testcase_params['endc_combo_config']['nr_dl_carriers'])
+ if testcase_params['endc_combo_config']['lte_cell_count']:
+ result['lte_bler_result'] = self.keysight_test_app.get_bler_result(
+ 'LTE', testcase_params['endc_combo_config']['lte_carriers'],
+ testcase_params['bler_measurement_length'])
+ result['lte_tput_result'] = self.keysight_test_app.get_throughput(
+ 'LTE', testcase_params['endc_combo_config']['lte_carriers'])
+ return result
+
+ def print_throughput_result(self, result):
+ # Print Test Summary
+ if 'nr_tput_result' in result:
+ self.log.info(
+ "----NR5G STATS-------NR5G STATS-------NR5G STATS---")
+ self.log.info(
+ "DL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
+ .format(
+ result['nr_tput_result']['total']['DL']['min_tput'],
+ result['nr_tput_result']['total']['DL']['average_tput'],
+ result['nr_tput_result']['total']['DL']['max_tput'],
+ result['nr_tput_result']['total']['DL']
+ ['theoretical_tput']))
+ self.log.info(
+ "UL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
+ .format(
+ result['nr_tput_result']['total']['UL']['min_tput'],
+ result['nr_tput_result']['total']['UL']['average_tput'],
+ result['nr_tput_result']['total']['UL']['max_tput'],
+ result['nr_tput_result']['total']['UL']
+ ['theoretical_tput']))
+ self.log.info("DL BLER: {:.2f}%\tUL BLER: {:.2f}%".format(
+ result['nr_bler_result']['total']['DL']['nack_ratio'] * 100,
+ result['nr_bler_result']['total']['UL']['nack_ratio'] * 100))
+ if 'lte_tput_result' in result:
+ self.log.info("----LTE STATS-------LTE STATS-------LTE STATS---")
+ self.log.info(
+ "DL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
+ .format(
+ result['lte_tput_result']['total']['DL']['min_tput'],
+ result['lte_tput_result']['total']['DL']['average_tput'],
+ result['lte_tput_result']['total']['DL']['max_tput'],
+ result['lte_tput_result']['total']['DL']
+ ['theoretical_tput']))
+ if self.testclass_params['lte_ul_mac_padding']:
+ self.log.info(
+ "UL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
+ .format(
+ result['lte_tput_result']['total']['UL']['min_tput'],
+ result['lte_tput_result']['total']['UL']
+ ['average_tput'],
+ result['lte_tput_result']['total']['UL']['max_tput'],
+ result['lte_tput_result']['total']['UL']
+ ['theoretical_tput']))
+ self.log.info("DL BLER: {:.2f}%\tUL BLER: {:.2f}%".format(
+ result['lte_bler_result']['total']['DL']['nack_ratio'] * 100,
+ result['lte_bler_result']['total']['UL']['nack_ratio'] * 100))
+ if self.testclass_params['traffic_type'] != 'PHY':
+ self.log.info("{} Tput: {:.2f} Mbps".format(
+ self.testclass_params['traffic_type'],
+ result['iperf_throughput']))
+
+ def setup_tester(self, testcase_params):
+ # Configure all cells
+ for cell_idx, cell in enumerate(
+ testcase_params['endc_combo_config']['cell_list']):
+ self.keysight_test_app.set_cell_duplex_mode(
+ cell['cell_type'], cell['cell_number'], cell['duplex_mode'])
+ self.keysight_test_app.set_cell_band(cell['cell_type'],
+ cell['cell_number'],
+ cell['band'])
+ self.keysight_test_app.set_cell_dl_power(
+ cell['cell_type'], cell['cell_number'],
+ testcase_params['cell_power_sweep'][cell_idx][0], 1)
+ if cell['cell_type'] == 'NR5G':
+ self.keysight_test_app.set_nr_subcarrier_spacing(
+ cell['cell_number'], cell['subcarrier_spacing'])
+ if 'channel' in cell:
+ self.keysight_test_app.set_cell_channel(
+ cell['cell_type'], cell['cell_number'], cell['channel'])
+ self.keysight_test_app.set_cell_bandwidth(cell['cell_type'],
+ cell['cell_number'],
+ cell['dl_bandwidth'])
+ self.keysight_test_app.set_cell_mimo_config(
+ cell['cell_type'], cell['cell_number'], 'DL',
+ cell['dl_mimo_config'])
+ if cell['cell_type'] == 'LTE':
+ self.keysight_test_app.set_lte_cell_transmission_mode(
+ cell['cell_number'], cell['transmission_mode'])
+ self.keysight_test_app.set_lte_control_region_size(
+ cell['cell_number'], 1)
+ if cell['ul_enabled'] and cell['cell_type'] == 'NR5G':
+ self.keysight_test_app.set_cell_mimo_config(
+ cell['cell_type'], cell['cell_number'], 'UL',
+ cell['ul_mimo_config'])
+
+ if testcase_params.get('force_contiguous_nr_channel', False):
+ self.keysight_test_app.toggle_contiguous_nr_channels(1)
+
+ if testcase_params['endc_combo_config']['lte_cell_count']:
+ self.keysight_test_app.set_lte_cell_mcs(
+ 'CELL1', testcase_params['lte_dl_mcs_table'],
+ testcase_params['lte_dl_mcs'],
+ testcase_params['lte_ul_mcs_table'],
+ testcase_params['lte_ul_mcs'])
+ self.keysight_test_app.set_lte_ul_mac_padding(
+ self.testclass_params['lte_ul_mac_padding'])
+
+ if testcase_params['endc_combo_config']['nr_cell_count']:
+ if 'schedule_scenario' in testcase_params:
+ self.keysight_test_app.set_nr_cell_schedule_scenario(
+ 'CELL1',
+ testcase_params['schedule_scenario'])
+ self.keysight_test_app.set_nr_ul_dft_precoding(
+ 'CELL1', testcase_params['transform_precoding'])
+ self.keysight_test_app.set_nr_cell_mcs(
+ 'CELL1', testcase_params['nr_dl_mcs'],
+ testcase_params['nr_ul_mcs'])
+ self.keysight_test_app.set_dl_carriers(
+ testcase_params['endc_combo_config']['nr_dl_carriers'])
+ self.keysight_test_app.set_ul_carriers(
+ testcase_params['endc_combo_config']['nr_ul_carriers'])
+
+ # Turn on LTE cells
+ for cell in testcase_params['endc_combo_config']['cell_list']:
+ if cell['cell_type'] == 'LTE' and not self.keysight_test_app.get_cell_state(
+ cell['cell_type'], cell['cell_number']):
+ self.log.info('Turning LTE Cell {} on.'.format(
+ cell['cell_number']))
+ self.keysight_test_app.set_cell_state(cell['cell_type'],
+ cell['cell_number'], 1)
+
+ # Activate LTE aggregation
+ if testcase_params['endc_combo_config']['lte_scc_list']:
+ self.keysight_test_app.apply_lte_carrier_agg(
+ testcase_params['endc_combo_config']['lte_scc_list'])
+
+ self.log.info('Waiting for LTE connections')
+ # Turn airplane mode off
+ num_apm_toggles = 5
+ for idx in range(num_apm_toggles):
+ self.log.info('Turning off airplane mode')
+ asserts.assert_true(utils.force_airplane_mode(self.dut, False),
+ 'Can not turn off airplane mode.')
+ if self.keysight_test_app.wait_for_cell_status(
+ 'LTE', 'CELL1', 'CONN', 180):
+ break
+ elif idx < num_apm_toggles - 1:
+ self.log.info('Turning on airplane mode')
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ time.sleep(MEDIUM_SLEEP)
+ else:
+ asserts.fail('DUT did not connect to LTE.')
+
+ if testcase_params['endc_combo_config']['nr_cell_count']:
+ self.keysight_test_app.apply_carrier_agg()
+ self.log.info('Waiting for 5G connection')
+ connected = self.keysight_test_app.wait_for_cell_status(
+ 'NR5G', testcase_params['endc_combo_config']['nr_cell_count'],
+ ['ACT', 'CONN'], 60)
+ if not connected:
+ asserts.fail('DUT did not connect to NR.')
+ time.sleep(SHORT_SLEEP)
+
+ def _test_throughput_bler(self, testcase_params):
+ """Test function to run cellular throughput and BLER measurements.
+
+ The function runs BLER/throughput measurement after configuring the
+ callbox and DUT. The test supports running PHY or TCP/UDP layer traffic
+ in a variety of band/carrier/mcs/etc configurations.
+
+ Args:
+ testcase_params: dict containing test-specific parameters
+ Returns:
+ result: dict containing throughput results and meta data
+ """
+ # Prepare results dicts
+ testcase_params = self.compile_test_params(testcase_params)
+ testcase_results = collections.OrderedDict()
+ testcase_results['testcase_params'] = testcase_params
+ testcase_results['results'] = []
+
+ # Setup tester and wait for DUT to connect
+ self.setup_tester(testcase_params)
+
+ # Run throughput test loop
+ stop_counter = 0
+ if testcase_params['endc_combo_config']['nr_cell_count']:
+ self.keysight_test_app.select_display_tab('NR5G', 1, 'BTHR',
+ 'OTAGRAPH')
+ else:
+ self.keysight_test_app.select_display_tab('LTE', 1, 'BTHR',
+ 'OTAGRAPH')
+ for power_idx in range(len(testcase_params['cell_power_sweep'][0])):
+ result = collections.OrderedDict()
+ # Set DL cell power
+ result['cell_power'] = []
+ for cell_idx, cell in enumerate(
+ testcase_params['endc_combo_config']['cell_list']):
+ current_cell_power = testcase_params['cell_power_sweep'][
+ cell_idx][power_idx]
+ result['cell_power'].append(current_cell_power)
+ self.keysight_test_app.set_cell_dl_power(
+ cell['cell_type'], cell['cell_number'], current_cell_power,
+ 1)
+
+ # Start BLER and throughput measurements
+ result = self.run_single_throughput_measurement(testcase_params)
+ testcase_results['results'].append(result)
+
+ self.print_throughput_result(result)
+
+ if (('lte_bler_result' in result
+ and result['lte_bler_result']['total']['DL']['nack_ratio'] *
+ 100 > 99) or
+ ('nr_bler_result' in result
+ and result['nr_bler_result']['total']['DL']['nack_ratio'] *
+ 100 > 99)):
+ stop_counter = stop_counter + 1
+ else:
+ stop_counter = 0
+ if stop_counter == STOP_COUNTER_LIMIT:
+ break
+
+ # Save results
+ self.testclass_results[self.current_test_name] = testcase_results
diff --git a/acts_tests/acts_contrib/test_utils/cellular/performance/__init__.py b/acts_tests/acts_contrib/test_utils/cellular/performance/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/cellular/performance/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/cellular/performance/cellular_performance_test_utils.py b/acts_tests/acts_contrib/test_utils/cellular/performance/cellular_performance_test_utils.py
new file mode 100644
index 0000000..4aaff91
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/cellular/performance/cellular_performance_test_utils.py
@@ -0,0 +1,230 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 collections
+import logging
+import os
+import time
+
+PCC_PRESET_MAPPING = {
+ 'N257': {
+ 'low': 2054999,
+ 'mid': 2079165,
+ 'high': 2090832
+ },
+ 'N258': {
+ 'low': 2017499,
+ 'mid': 2043749,
+ 'high': 2057499
+ },
+ 'N260': {
+ 'low': 2229999,
+ 'mid': 2254165,
+ 'high': 2265832
+ },
+ 'N261': {
+ 'low': 2071667
+ }
+}
+
+DUPLEX_MODE_TO_BAND_MAPPING = {
+ 'LTE': {
+ 'FDD': [
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 65, 66, 67, 68, 69, 70,
+ 71, 72, 73, 74, 75, 76, 85, 252, 255
+ ],
+ 'TDD': [
+ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 45, 46, 47, 48,
+ 50, 51, 53
+ ]
+ },
+ 'NR5G': {
+ 'FDD': [
+ 'N1', 'N2', 'N3', 'N5', 'N7', 'N8', 'N12', 'N13', 'N14', 'N18',
+ 'N20', 'N25', 'N26', 'N28', 'N30', 'N65', 'N66', 'N70', 'N71',
+ 'N74'
+ ],
+ 'TDD': [
+ 'N34', 'N38', 'N39', 'N40', 'N41', 'N48', 'N50', 'N51', 'N53',
+ 'N77', 'N78', 'N79', 'N90', 'N257', 'N258', 'N259', 'N260', 'N261'
+ ]
+ },
+}
+
+
+def extract_test_id(testcase_params, id_fields):
+ test_id = collections.OrderedDict(
+ (param, testcase_params[param]) for param in id_fields)
+ return test_id
+
+
+def start_pixel_logger(ad):
+ """Function to start pixel logger with default log mask.
+
+ Args:
+ ad: android device on which to start logger
+ """
+
+ try:
+ ad.adb.shell(
+ 'rm -R /storage/emulated/0/Android/data/com.android.pixellogger/files/logs/logs/'
+ )
+ except:
+ pass
+ ad.adb.shell(
+ 'am startservice -a com.android.pixellogger.service.logging.LoggingService.ACTION_START_LOGGING'
+ )
+
+
+def stop_pixel_logger(ad, log_path, tag=None):
+ """Function to stop pixel logger and retrieve logs
+
+ Args:
+ ad: android device on which to start logger
+ log_path: location of saved logs
+ """
+ ad.adb.shell(
+ 'am startservice -a com.android.pixellogger.service.logging.LoggingService.ACTION_STOP_LOGGING'
+ )
+ logging.info('Waiting for Pixel log file')
+ file_name = None
+ file_size = 0
+ previous_file_size = 0
+ for idx in range(600):
+ try:
+ file = ad.adb.shell(
+ 'ls -l /storage/emulated/0/Android/data/com.android.pixellogger/files/logs/logs/'
+ ).split(' ')
+ file_name = file[-1]
+ file_size = file[-4]
+ except:
+ file_name = None
+ file_size = 0
+ if file_name and file_size == previous_file_size:
+ logging.info('Log file found after {}s.'.format(idx))
+ break
+ else:
+ previous_file_size = file_size
+ time.sleep(1)
+ try:
+ local_file_name = '{}_{}'.format(file_name, tag) if tag else file_name
+ local_path = os.path.join(log_path, local_file_name)
+ ad.pull_files(
+ '/storage/emulated/0/Android/data/com.android.pixellogger/files/logs/logs/{}'
+ .format(file_name), log_path)
+ return local_path
+ except:
+ logging.error('Could not pull pixel logs.')
+
+
+def log_system_power_metrics(ad, verbose=1):
+ # Log temperature sensors
+ if verbose:
+ temp_sensors = ad.adb.shell(
+ 'ls -1 /dev/thermal/tz-by-name/').splitlines()
+ else:
+ temp_sensors = ['BIG', 'battery', 'quiet_therm', 'usb_pwr_therm']
+ temp_measurements = collections.OrderedDict()
+ for sensor in temp_sensors:
+ try:
+ temp_measurements[sensor] = ad.adb.shell(
+ 'cat /dev/thermal/tz-by-name/{}/temp'.format(sensor))
+ except:
+ temp_measurements[sensor] = float('nan')
+ logging.debug('Temperature sensor readings: {}'.format(temp_measurements))
+
+ # Log mitigation items
+ if verbose:
+ mitigation_points = [
+ "batoilo",
+ "ocp_cpu1",
+ "ocp_cpu2",
+ "ocp_gpu",
+ "ocp_tpu",
+ "smpl_warn",
+ "soft_ocp_cpu1",
+ "soft_ocp_cpu2",
+ "soft_ocp_gpu",
+ "soft_ocp_tpu",
+ "vdroop1",
+ "vdroop2",
+ ]
+ else:
+ mitigation_points = [
+ "batoilo",
+ "smpl_warn",
+ "vdroop1",
+ "vdroop2",
+ ]
+
+ parameters_f = ['count', 'capacity', 'timestamp', 'voltage']
+ parameters_v = ['count', 'cap', 'time', 'volt']
+ mitigation_measurements = collections.OrderedDict()
+ for mp in mitigation_points:
+ mitigation_measurements[mp] = collections.OrderedDict()
+ for par_f, par_v in zip(parameters_f, parameters_v):
+ mitigation_measurements[mp][par_v] = ad.adb.shell(
+ 'cat /sys/devices/virtual/pmic/mitigation/last_triggered_{}/{}_{}'
+ .format(par_f, mp, par_v))
+ logging.debug('Mitigation readings: {}'.format(mitigation_measurements))
+
+ # Log power meter items
+ power_meter_measurements = collections.OrderedDict()
+ for device in ['device0', 'device1']:
+ power_str = ad.adb.shell(
+ 'cat /sys/bus/iio/devices/iio:{}/lpf_power'.format(
+ device)).splitlines()
+ power_meter_measurements[device] = collections.OrderedDict()
+ for line in power_str:
+ if line.startswith('CH'):
+ try:
+ line_split = line.split(', ')
+ power_meter_measurements[device][line_split[0]] = int(
+ line_split[1])
+ except (IndexError, ValueError):
+ continue
+ elif line.startswith('t='):
+ try:
+ power_meter_measurements[device]['t_pmeter'] = int(
+ line[2:])
+ except (IndexError, ValueError):
+ continue
+ else:
+ continue
+ logging.debug(
+ 'Power Meter readings: {}'.format(power_meter_measurements))
+
+ # Log battery items
+ if verbose:
+ battery_parameters = [
+ "act_impedance", "capacity", "charge_counter", "charge_full",
+ "charge_full_design", "current_avg", "current_now",
+ "cycle_count", "health", "offmode_charger", "present",
+ "rc_switch_enable", "resistance", "status", "temp",
+ "voltage_avg", "voltage_now", "voltage_ocv"
+ ]
+ else:
+ battery_parameters = [
+ "capacity", "current_avg", "current_now", "voltage_avg",
+ "voltage_now", "voltage_ocv"
+ ]
+
+ battery_meaurements = collections.OrderedDict()
+ for par in battery_parameters:
+ battery_meaurements['bat_{}'.format(par)] = ad.adb.shell(
+ 'cat /sys/class/power_supply/maxfg/{}'.format(par))
+ logging.debug('Battery readings: {}'.format(battery_meaurements))
diff --git a/acts_tests/acts_contrib/test_utils/cellular/performance/shannon_log_parser.py b/acts_tests/acts_contrib/test_utils/cellular/performance/shannon_log_parser.py
new file mode 100644
index 0000000..89c2adf
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/cellular/performance/shannon_log_parser.py
@@ -0,0 +1,808 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 datetime
+import gzip
+import itertools
+import logging
+import numpy
+import os
+import re
+import shutil
+import subprocess
+import zipfile
+from acts import context
+from pathlib import Path
+
+_DIGITS_REGEX = re.compile(r'-?\d+')
+_TX_PWR_MAX = 100
+_TX_PWR_MIN = -100
+
+
+class LastNrPower:
+ last_time = 0
+ last_pwr = 0
+
+
+class LastLteValue:
+ last_time = 0
+ last_pwr = 0
+
+
+def _string_to_float(input_string):
+ """Convert string to float value."""
+ try:
+ tmp = float(input_string)
+ except ValueError:
+ print(input_string)
+ tmp = float('nan')
+ return tmp
+
+
+def to_time(time_str):
+ """Convert time string to data time."""
+ return datetime.datetime.strptime(time_str, '%H:%M:%S.%f')
+
+
+def to_time_sec(time_str, start_time):
+ """"Converts time string to seconds elapsed since start time."""
+ return (to_time(time_str) - start_time).total_seconds()
+
+
+class LogParser(object):
+ """Base class to parse log csv files."""
+
+ def __init__(self):
+ self.timestamp_col = -1
+ self.message_col = -1
+ self.start_time = None
+
+ # Dictionary of {log_item_header: log_time_parser} elements
+ self.PARSER_INFO = {
+ r'###[AS] RSRP[': self._parse_lte_rsrp,
+ r'|CC0| PCell RSRP ': self._parse_lte_rsrp2,
+ r'|CC0 Mx0 Sc0| PCell RSRP ': self._parse_lte_rsrp2,
+ r'LT12 PUSCH_Power': self._parse_lte_power,
+ r'UL_PWR(PUSCH)=>pwr_val:': self._parse_lte_power,
+ r'[BM]SSB_RSRP(1)': self._parse_nr_rsrp,
+ r'[BM]SSB_RSRP(0)': self._parse_nr_rsrp,
+ r'###[AS] CurAnt': self._parse_nr_rsrp2,
+ r'[PHY] RF module(': self._parse_fr2_rsrp,
+ #r'[RF] PD : CC0 (monitoring) target_pwr': _parse_fr2_power,
+ #r'[NrPhyTxScheduler][PuschCalcPower] Po_nominal_pusch': _parse_nr_power,
+ r'[NrPhyTxPuschScheduler][PuschCalcPower] Po_nominal_pusch':
+ self._parse_fr2_power,
+ r'[RF NR SUB6] PD : CC0 (monitoring) target_pwr':
+ self._parse_nr_power2,
+ r'[RF NR SUB6] PD : CC1 (monitoring) target_pwr':
+ self._parse_nr_power2,
+ r'[AS] RFAPI_ChangeAntennaSwitch': self._parse_lte_ant_sel,
+ r'[AS] Ant switching': self._parse_lte_ant_sel2,
+ r'###[AS] Select Antenna': self._parse_nr_ant_sel,
+ r'###[AS] CurAnt(': self._parse_nr_ant_sel2,
+ r'[SAR][RESTORE]': self._parse_sar_mode,
+ r'[SAR][NORMAL]': self._parse_sar_mode,
+ r'[SAR][LIMITED-TAPC]': self._parse_tapc_sar_mode,
+ r'###[TAS] [0] ProcessRestoreStage:: [RESTORE]':
+ self._parse_nr_sar_mode,
+ r'###[TAS] [0] ProcessNormalStage:: [NORMAL]':
+ self._parse_nr_sar_mode,
+ r'|CC0| UL Power : PRACH ': self._parse_lte_avg_power,
+ r'activeStackId=0\, [Monitor] txPower ': self._parse_wcdma_power,
+ r'[SAR][DYNAMIC] EN-DC(2) UsedAvgSarLTE': self._parse_sar_values,
+ #r'[SAR][DYNAMIC] UsedAvgSarLTE_100s': self._parse_sar_values2,
+ r'[SAR][DYNAMIC] EN-DC(0) UsedAvgSarLTE':
+ self._parse_lte_sar_values,
+ r'###[TAS] [0] CalcGain:: TotalUsedSar': self._parse_nr_sar_values,
+ r'[SAR][DYNAMIC] IsLTEOn(1) IsNROn(0) ':
+ self._parse_lte_sar_values,
+ r'[SAR][DYNAMIC] IsLTEOn(1) IsNROn(1) ': self._parse_sar_values,
+ r'[MAIN][VolteStatusInd] Volte status ': self._parse_volte_status,
+ r'[PHY] CC0 SLP : dlGrantRatio(3)/ulGrantRatio(3)/RbRatio(3)':
+ self._parse_df_value,
+ r'CC0 AVG: CELLGROUP(0) DCI(D/U):': self._parse_df_value,
+ r'[OL-AIT] band': self._parse_ul_mimo,
+ }
+
+ self.SAR_MODES = [
+ 'none', 'MIN', 'SAV_1', 'SAV_2', 'MAIN', 'PRE_SAV', 'LIMITED-TAPC',
+ 'MAX', 'none'
+ ]
+ self.SAR_MODES_DESC = [
+ '', 'Minimum', 'Slope Saving1', 'Slope Saving2', 'Maintenance',
+ 'Pre-Save', 'Limited TAPC', 'Maximum', ''
+ ]
+
+ def parse_header(self, header_line):
+ header = header_line.split(',')
+ try:
+ self.timestamp_col = header.index('PC Time')
+ self.message_col = header.index('Message')
+ except ValueError:
+ print('Error: PC Time and Message fields are not present')
+ try:
+ self.core_id_col = header.index('Core ID')
+ except:
+ self.core_id_col = self.timestamp_col
+
+ def parse_log(self, log_file, gap_options=0):
+ """Extract required data from the exported CSV file."""
+
+ log_data = LogData()
+ log_data.gap_options = gap_options
+ # Fr-1 as default
+ fr_id = 0
+
+ with open(log_file, 'r') as file:
+ # Read header line
+ header = file.readline()
+ try:
+ self.parse_header(header)
+ except:
+ print('Could not parse header')
+ return log_data
+
+ # Use first message for start time
+ line = file.readline()
+ print(line)
+ line_data = line[1:-2].split('","')
+ if len(line_data) < self.message_col:
+ print('Error: Empty exported file')
+ return log_data
+
+ start_time = to_time(line_data[self.timestamp_col])
+
+ print('Parsing log file ... ', end='', flush=True)
+ for line in file:
+ line_data = line[1:-2].split('","')
+ if len(line_data) < self.message_col + 1:
+ continue
+
+ message = line_data[self.message_col]
+ if "frIdx 1 " in message:
+ fr_id = 1
+ elif "frIdx 0 " in message:
+ fr_id = 0
+ for line_prefix, line_parser in self.PARSER_INFO.items():
+ if message.startswith(line_prefix):
+ timestamp = to_time_sec(line_data[self.timestamp_col],
+ start_time)
+ if self.core_id_col == self.timestamp_col:
+ line_parser(timestamp, message[len(line_prefix):],
+ 'L1', log_data, fr_id)
+ else:
+ if " CC1 " in message:
+ line_parser(timestamp,
+ message[len(line_prefix):], 'L2',
+ log_data, fr_id)
+ else:
+ line_parser(timestamp,
+ message[len(line_prefix):],
+ line_data[self.core_id_col],
+ log_data, fr_id)
+ break
+
+ if log_data.nr.tx_pwr_time:
+ if log_data.nr.tx_pwr_time[1] > log_data.nr.tx_pwr_time[0] + 50:
+ log_data.nr.tx_pwr_time = log_data.nr.tx_pwr_time[1:]
+ log_data.nr.tx_pwr = log_data.nr.tx_pwr[1:]
+
+ self._find_cur_ant(log_data.lte)
+ self._find_cur_ant(log_data.nr)
+ return log_data
+
+ def get_file_start_time(self, log_file):
+ # Fr-1 as default
+
+ with open(log_file, 'r') as file:
+ # Read header line
+ header = file.readline()
+ try:
+ self.parse_header(header)
+ except:
+ print('Could not parse header')
+ return None
+
+ # Use first message for start time
+ line = file.readline()
+ line_data = line[1:-2].split('","')
+ if len(line_data) < self.message_col:
+ print('Error: Empty exported file')
+ return None
+
+ start_time = to_time(line_data[self.timestamp_col])
+ return start_time
+
+ def set_start_time(self, line):
+ """Set start time of logs to the time in the line."""
+ if len(line) == 0:
+ print("Empty Line")
+ return
+ line_data = line[1:-2].split('","')
+ self.start_time = to_time(line_data[self.timestamp_col])
+
+ def get_message(self, line):
+ """Returns message and timestamp for the line."""
+ line_data = line[1:-2].split('","')
+ if len(line_data) < self.message_col + 1:
+ return None
+
+ self.line_data = line_data
+ return line_data[self.message_col]
+
+ def get_time(self, line):
+ """Convert time string to time in seconds from the start time."""
+ line_data = line[1:-2].split('","')
+ if len(line_data) < self.timestamp_col + 1:
+ return 0
+
+ return to_time_sec(line_data[self.timestamp_col], self.start_time)
+
+ def _feed_nr_power(self, timestamp, tx_pwr, option, lte_nr, window,
+ default, interval):
+ if option < 101 and LastNrPower.last_time > 0 and timestamp - LastNrPower.last_time > interval:
+ #print ('window=',window, ' interval=',interval, ' gap=',timestamp-LastNrPower.last_time)
+ ti = LastNrPower.last_time
+ while ti < timestamp:
+ ti += (timestamp - LastNrPower.last_time) / window
+ lte_nr.tx_pwr_time.append(ti)
+ if option == 0:
+ lte_nr.tx_pwr.append(tx_pwr / default)
+ elif option == 1:
+ lte_nr.tx_pwr.append(LastNrPower.last_pwr)
+ elif option == 2:
+ lte_nr.tx_pwr.append((tx_pwr + LastNrPower.last_pwr) / 2)
+ elif option == 3:
+ lte_nr.tx_pwr.append(0)
+ else:
+ lte_nr.tx_pwr.append(option)
+ else:
+ lte_nr.tx_pwr_time.append(timestamp)
+ lte_nr.tx_pwr.append(tx_pwr)
+ LastNrPower.last_time = timestamp
+ LastNrPower.last_pwr = tx_pwr
+
+ def _feed_lte_power(self, timestamp, tx_pwr, log_data, lte_nr, window,
+ default, interval):
+ if log_data.gap_options <= 100 and LastLteValue.last_time > 0 and timestamp - LastLteValue.last_time > interval:
+ #print ('window=',window, ' interval=',interval, ' gap=',timestamp-LastLteValue.last_time)
+ ti = LastLteValue.last_time
+ while ti < timestamp:
+ ti += (timestamp - LastLteValue.last_time) / window
+ lte_nr.tx_pwr_time.append(ti)
+ if log_data.gap_options == 0:
+ lte_nr.tx_pwr.append(tx_pwr / default)
+ elif log_data.gap_options == 1:
+ lte_nr.tx_pwr.append(LastLteValue.last_pwr)
+ elif log_data.gap_options == 2:
+ lte_nr.tx_pwr.append((tx_pwr + LastLteValue.last_pwr) / 2)
+ elif log_data.gap_options == 3:
+ lte_nr.tx_pwr.append(0)
+ else:
+ lte_nr.tx_pwr.append(log_data.gap_options)
+ else:
+ lte_nr.tx_pwr_time.append(timestamp)
+ lte_nr.tx_pwr.append(tx_pwr)
+ LastLteValue.last_time = timestamp
+ LastLteValue.last_pwr = tx_pwr
+
+ def _parse_lte_power(self, timestamp, message, core_id, log_data, fr_id):
+ match = re.search(r'-?\d+', message)
+ if match:
+ tx_pwr = _string_to_float(match.group())
+ if _TX_PWR_MIN < tx_pwr < _TX_PWR_MAX:
+ self._feed_lte_power(timestamp, tx_pwr, log_data, log_data.lte,
+ 20, 1, 1)
+
+ def _parse_lte_rsrp(self, timestamp, message, core_id, log_data, fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ rsrp0 = _string_to_float(data[0]) / 100.0
+ rsrp1 = _string_to_float(data[1]) / 100.0
+ if rsrp0 != 0.0 and rsrp1 != 0.0:
+ log_data.lte.rsrp_time.append(timestamp)
+ log_data.lte.rsrp_rx0.append(rsrp0)
+ log_data.lte.rsrp_rx1.append(rsrp1)
+
+ def _parse_lte_rsrp2(self, timestamp, message, core_id, log_data, fr_id):
+ m = re.search('^\[ ?-?\d+ \((.*?)\)', message)
+ if not m:
+ return
+ data = _DIGITS_REGEX.findall(m.group(1))
+ if len(data) < 2:
+ return
+ rsrp0 = _string_to_float(data[0])
+ rsrp1 = _string_to_float(data[1])
+ if rsrp0 != 0.0 and rsrp1 != 0.0:
+ log_data.lte.rsrp2_time.append(timestamp)
+ log_data.lte.rsrp2_rx0.append(rsrp0)
+ log_data.lte.rsrp2_rx1.append(rsrp1)
+
+ def _parse_nr_rsrp(self, timestamp, message, core_id, log_data, fr_id):
+ index = message.find('rx0/rx1/rx2/rx3')
+ if index != -1:
+ data = _DIGITS_REGEX.findall(message[index:])
+ log_data.nr.rsrp_time.append(timestamp)
+ log_data.nr.rsrp_rx0.append(_string_to_float(data[4]) / 100)
+ log_data.nr.rsrp_rx1.append(_string_to_float(data[5]) / 100)
+
+ def _parse_nr_rsrp2(self, timestamp, message, core_id, log_data, fr_id):
+ index = message.find('Rsrp')
+ if index != -1:
+ data = _DIGITS_REGEX.findall(message[index:])
+ log_data.nr.rsrp2_time.append(timestamp)
+ log_data.nr.rsrp2_rx0.append(_string_to_float(data[0]) / 100)
+ log_data.nr.rsrp2_rx1.append(_string_to_float(data[1]) / 100)
+
+ def _parse_fr2_rsrp(self, timestamp, message, core_id, log_data, fr_id):
+ index = message.find('rsrp')
+ data = _DIGITS_REGEX.search(message)
+ module_index = _string_to_float(data.group(0))
+ data = _DIGITS_REGEX.findall(message[index:])
+ rsrp = _string_to_float(data[0])
+
+ if rsrp == 0:
+ return
+ if module_index == 0:
+ log_data.fr2.rsrp0_time.append(timestamp)
+ log_data.fr2.rsrp0.append(rsrp if rsrp < 999 else float('nan'))
+ elif module_index == 1:
+ log_data.fr2.rsrp1_time.append(timestamp)
+ log_data.fr2.rsrp1.append(rsrp if rsrp < 999 else float('nan'))
+
+ def _parse_fr2_power(self, timestamp, message, core_id, log_data, fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ tx_pwr = _string_to_float(data[-1]) / 10
+ if _TX_PWR_MIN < tx_pwr < _TX_PWR_MAX:
+ log_data.fr2.tx_pwr_time.append(timestamp)
+ log_data.fr2.tx_pwr.append(tx_pwr)
+
+ def _parse_nr_power(self, timestamp, message, core_id, log_data, fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ tx_pwr = _string_to_float(data[-1]) / 10
+ if _TX_PWR_MIN < tx_pwr < _TX_PWR_MAX:
+ if core_id == 'L2':
+ self._feed_nr_power(timestamp, tx_pwr, log_data.gap_options,
+ log_data.nr2, 5, 1, 1)
+ else:
+ self._feed_nr_power(timestamp, tx_pwr, log_data.gap_options,
+ log_data.nr, 5, 1, 1)
+
+ def _parse_nr_power2(self, timestamp, message, core_id, log_data, fr_id):
+ if fr_id != 0:
+ return
+ data = _DIGITS_REGEX.findall(message)
+ tx_pwr = _string_to_float(data[0]) / 10
+ if _TX_PWR_MIN < tx_pwr < _TX_PWR_MAX:
+ if core_id == 'L2':
+ self._feed_nr_power(timestamp, tx_pwr, log_data.gap_options,
+ log_data.nr2, 5, 1, 1)
+ else:
+ self._feed_nr_power(timestamp, tx_pwr, log_data.gap_options,
+ log_data.nr, 5, 1, 1)
+
+ def _parse_lte_ant_sel(self, timestamp, message, core_id, log_data, fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ new_ant = _string_to_float(data[1])
+ old_ant = _string_to_float(data[2])
+ log_data.lte.ant_sel_time.append(timestamp)
+ log_data.lte.ant_sel_old.append(old_ant)
+ log_data.lte.ant_sel_new.append(new_ant)
+
+ def _parse_lte_ant_sel2(self, timestamp, message, core_id, log_data,
+ fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ if data[0] == '0':
+ log_data.lte.cur_ant_time.append(timestamp)
+ log_data.lte.cur_ant.append(0)
+ elif data[0] == '1':
+ log_data.lte.cur_ant_time.append(timestamp)
+ log_data.lte.cur_ant.append(1)
+ elif data[0] == '10':
+ log_data.lte.cur_ant_time.append(timestamp)
+ log_data.lte.cur_ant.append(1)
+ log_data.lte.cur_ant_time.append(timestamp)
+ log_data.lte.cur_ant.append(0)
+ elif data[0] == '01':
+ log_data.lte.cur_ant_time.append(timestamp)
+ log_data.lte.cur_ant.append(0)
+ log_data.lte.cur_ant_time.append(timestamp)
+ log_data.lte.cur_ant.append(1)
+
+ def _parse_nr_ant_sel(self, timestamp, message, core_id, log_data, fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ log_data.nr.ant_sel_time.append(timestamp)
+ log_data.nr.ant_sel_new.append(_string_to_float(data[1]))
+ log_data.nr.ant_sel_old.append(_string_to_float(data[0]))
+
+ def _parse_nr_ant_sel2(self, timestamp, message, core_id, log_data, fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ log_data.nr.ant_sel_time.append(timestamp)
+ sel_ant = _string_to_float(data[0])
+ log_data.nr.ant_sel_new.append(sel_ant)
+ if log_data.nr.ant_sel_new:
+ log_data.nr.ant_sel_old.append(log_data.nr.ant_sel_new[-1])
+
+ def _parse_sar_mode(self, timestamp, message, core_id, log_data, fr_id):
+ sar_mode = len(self.SAR_MODES) - 1
+ for i, mode in enumerate(self.SAR_MODES):
+ if message.startswith('[' + mode + ']'):
+ sar_mode = i
+ log_data.lte.sar_mode_time.append(timestamp)
+ log_data.lte.sar_mode.append(sar_mode)
+
+ def _parse_tapc_sar_mode(self, timestamp, message, core_id, log_data,
+ fr_id):
+ log_data.lte.sar_mode_time.append(timestamp)
+ log_data.lte.sar_mode.append(self.SAR_MODES.index('LIMITED-TAPC'))
+
+ def _parse_nr_sar_mode(self, timestamp, message, core_id, log_data, fr_id):
+ sar_mode = len(self.SAR_MODES) - 1
+ for i, mode in enumerate(self.SAR_MODES):
+ if message.startswith('[' + mode + ']'):
+ sar_mode = i
+
+ log_data.nr.sar_mode_time.append(timestamp)
+ log_data.nr.sar_mode.append(sar_mode)
+
+ def _parse_lte_avg_power(self, timestamp, message, core_id, log_data,
+ fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ tx_pwr = _string_to_float(data[2])
+ if _TX_PWR_MIN < tx_pwr < _TX_PWR_MAX:
+ log_data.lte.tx_avg_pwr_time.append(timestamp)
+ log_data.lte.tx_avg_pwr.append(tx_pwr)
+
+ def _parse_wcdma_power(self, timestamp, message, core_id, log_data, fr_id):
+ match = re.search(r'-?\d+', message)
+ if match:
+ tx_pwr = _string_to_float(match.group()) / 10
+ if tx_pwr < _TX_PWR_MAX and tx_pwr > _TX_PWR_MIN:
+ log_data.wcdma.tx_pwr_time.append(timestamp)
+ log_data.wcdma.tx_pwr.append(tx_pwr)
+
+ def _parse_sar_values(self, timestamp, message, core_id, log_data, fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ log_data.endc_sar_time.append(timestamp)
+ log_data.endc_sar_lte.append(_string_to_float(data[0]) / 1000.0)
+ log_data.endc_sar_nr.append(_string_to_float(data[1]) / 1000.0)
+
+ def _parse_sar_values2(self, timestamp, message, core_id, log_data, fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ log_data.endc_sar_time.append(timestamp)
+ log_data.endc_sar_lte.append(_string_to_float(data[-3]) / 1000.0)
+ log_data.endc_sar_nr.append(_string_to_float(data[-1]) / 1000.0)
+
+ def _parse_lte_sar_values(self, timestamp, message, core_id, log_data,
+ fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ log_data.lte_sar_time.append(timestamp)
+ log_data.lte_sar.append(_string_to_float(data[0]) / 1000.0)
+
+ def _parse_nr_sar_values(self, timestamp, message, core_id, log_data,
+ fr_id):
+ data = _DIGITS_REGEX.findall(message)
+ log_data.nr_sar_time.append(timestamp)
+ log_data.nr_sar.append(_string_to_float(data[0]) / 1000.0)
+
+ def _parse_df_value(self, timestamp, message, core_id, log_data, fr_id):
+ match = re.search(r' \d+', message)
+ if match:
+ nr_df = _string_to_float(match.group())
+ log_data.nr.df = (nr_df / 1000 % 1000) / 100
+ log_data.nr.duty_cycle_time.append(timestamp)
+ log_data.nr.duty_cycle.append(log_data.nr.df * 100)
+ else:
+ match = re.search(r'\d+\\,\d+', message)
+ if match:
+ lte_df = match.group(0).split(",")
+ log_data.lte.df = _string_to_float(lte_df[1]) / 100
+ log_data.lte.duty_cycle_time.append(timestamp)
+ log_data.lte.duty_cycle.append(log_data.nr.df * 100)
+
+ def _parse_volte_status(self, timestamp, message, core_id, log_data,
+ fr_id):
+ if message.startswith('[0 -> 1]'):
+ log_data.volte_time.append(timestamp)
+ log_data.volte_status.append(1)
+ elif message.startswith('[3 -> 0]'):
+ log_data.volte_time.append(timestamp)
+ log_data.volte_status.append(0)
+
+ def _parse_ul_mimo(self, timestamp, message, core_id, log_data, fr_id):
+ match = re.search(r'UL-MIMO', message)
+ if match:
+ log_data.ul_mimo = 1
+
+ def _find_cur_ant(self, log_data):
+ """Interpolate antenna selection from antenna switching data."""
+ if not log_data.cur_ant_time and log_data.ant_sel_time:
+ if log_data.rsrp_time:
+ start_time = log_data.rsrp_time[0]
+ end_time = log_data.rsrp_time[-1]
+ elif log_data.tx_pwr:
+ start_time = log_data.tx_pwr_time[0]
+ end_time = log_data.tx_pwr_time[-1]
+ else:
+ start_time = log_data.ant_sel_time[0]
+ end_time = log_data.ant_sel_time[-1]
+
+ [sel_time,
+ sel_ant] = self.get_ant_selection(log_data.ant_sel_time,
+ log_data.ant_sel_old,
+ log_data.ant_sel_new,
+ start_time, end_time)
+
+ log_data.cur_ant_time = sel_time
+ log_data.cur_ant = sel_ant
+
+ def get_ant_selection(self, config_time, old_antenna_config,
+ new_antenna_config, start_time, end_time):
+ """Generate antenna selection data from antenna switching information."""
+ sel_time = []
+ sel_ant = []
+ if not config_time:
+ return [sel_time, sel_ant]
+
+ # Add data point for the start time
+ if config_time[0] > start_time:
+ sel_time = [start_time]
+ sel_ant = [old_antenna_config[0]]
+
+ # Add one data point before the switch and one data point after the switch.
+ for i in range(len(config_time)):
+ if not (i > 0
+ and old_antenna_config[i - 1] == old_antenna_config[i]
+ and new_antenna_config[i - 1] == new_antenna_config[i]):
+ sel_time.append(config_time[i])
+ sel_ant.append(old_antenna_config[i])
+ sel_time.append(config_time[i])
+ sel_ant.append(new_antenna_config[i])
+
+ # Add data point for the end time
+ if end_time > config_time[-1]:
+ sel_time.append(end_time)
+ sel_ant.append(new_antenna_config[-1])
+
+ return [sel_time, sel_ant]
+
+
+class RatLogData:
+ """Log data structure for each RAT (LTE/NR)."""
+
+ def __init__(self, label):
+
+ self.label = label
+
+ self.rsrp_time = [] # RSRP time
+ self.rsrp_rx0 = [] # RSRP for receive antenna 0
+ self.rsrp_rx1 = [] # RSRP for receive antenna 1
+
+ # second set of RSRP logs
+ self.rsrp2_time = [] # RSRP time
+ self.rsrp2_rx0 = [] # RSRP for receive antenna 0
+ self.rsrp2_rx1 = [] # RSRP for receive antenna 1
+
+ self.ant_sel_time = [] # Antenna selection/switch time
+ self.ant_sel_old = [] # Previous antenna selection
+ self.ant_sel_new = [] # New antenna selection
+
+ self.cur_ant_time = [] # Antenna selection/switch time
+ self.cur_ant = [] # Previous antenna selection
+
+ self.tx_pwr_time = [] # TX power time
+ self.tx_pwr = [] # TX power
+
+ self.tx_avg_pwr_time = []
+ self.tx_avg_pwr = []
+
+ self.sar_mode = []
+ self.sar_mode_time = []
+
+ self.df = 1.0 # Duty factor for UL transmission
+ self.duty_cycle = [] # Duty factors for UL transmission
+ self.duty_cycle_time = [] # Duty factors for UL transmission
+ self.initial_power = 0
+ self.sar_limit_dbm = None
+ self.avg_window_size = 100
+
+
+class LogData:
+ """Log data structure."""
+
+ def __init__(self):
+ self.lte = RatLogData('LTE')
+ self.lte.avg_window_size = 100
+
+ self.nr = RatLogData('NR CC0')
+ self.nr.avg_window_size = 100
+
+ # NR 2nd CC
+ self.nr2 = RatLogData('NR CC1')
+ self.nr2.avg_window_size = 100
+
+ self.wcdma = RatLogData('WCDMA')
+
+ self.fr2 = RatLogData('FR2')
+ self.fr2.rsrp0_time = []
+ self.fr2.rsrp0 = []
+ self.fr2.rsrp1_time = []
+ self.fr2.rsrp1 = []
+ self.fr2.avg_window_size = 4
+
+ self.lte_sar_time = []
+ self.lte_sar = []
+
+ self.nr_sar_time = []
+ self.nr_sar = []
+
+ self.endc_sar_time = []
+ self.endc_sar_lte = []
+ self.endc_sar_nr = []
+
+ self.volte_time = []
+ self.volte_status = []
+
+ # Options to handle data gaps
+ self.gap_options = 0
+
+ self.ul_mimo = 0 # Is UL_MIMO
+
+
+class ShannonLogger(object):
+
+ def __init__(self, dut=None, modem_bin=None, filter_file_path=None):
+ self.dm_app = shutil.which(r'DMConsole')
+ self.dut = dut
+ if self.dut:
+ self.modem_bin = self.pull_modem_file()
+ elif modem_bin:
+ self.modem_bin = modem_bin
+ else:
+ raise (RuntimeError,
+ 'ShannonLogger requires AndroidDevice or modem binary.')
+ self.filter_file = filter_file_path
+
+ def pull_modem_file(self):
+ local_modem_path = os.path.join(
+ context.get_current_context().get_full_output_path(), 'modem_bin')
+ os.makedirs(local_modem_path, exist_ok=True)
+ try:
+ self.dut.pull_files(
+ '/mnt/vendor/modem_img/images/default/modem.bin',
+ local_modem_path)
+ modem_bin_file = os.path.join(local_modem_path, 'modem.bin')
+ except:
+ self.dut.pull_files(
+ '/mnt/vendor/modem_img/images/default/modem.bin.gz',
+ local_modem_path)
+ modem_zip_file = os.path.join(local_modem_path, 'modem.bin.gz')
+ modem_bin_file = modem_zip_file[:-3]
+ with open(modem_zip_file, 'rb') as in_file:
+ with open(modem_bin_file, 'wb') as out_file:
+ file_content = gzip.decompress(in_file.read())
+ out_file.write(file_content)
+ return modem_bin_file
+
+ def _unzip_log(self, log_zip_file, in_place=1):
+ log_zip_file = Path(log_zip_file)
+ with zipfile.ZipFile(log_zip_file, 'r') as zip_ref:
+ file_names = zip_ref.namelist()
+ if in_place:
+ zip_dir = log_zip_file.parent
+ else:
+ zip_dir = log_zip_file.with_suffix('')
+ zip_ref.extractall(zip_dir)
+ unzipped_files = [
+ os.path.join(zip_dir, file_name) for file_name in file_names
+ ]
+ return unzipped_files
+
+ def unzip_modem_logs(self, log_zip_file):
+ log_files = self._unzip_log(log_zip_file, in_place=0)
+ sdm_files = []
+ for log_file in log_files:
+ if zipfile.is_zipfile(log_file):
+ sdm_files.append(self._unzip_log(log_file, in_place=1)[0])
+ os.remove(log_file)
+ elif Path(
+ log_file
+ ).suffix == '.sdm' and 'sbuff_power_on_log.sdm' not in log_file:
+ sdm_files.append(log_file)
+ return sorted(set(sdm_files))
+
+ def _export_single_log(self, file):
+ temp_file = str(Path(file).with_suffix('.csv'))
+ if self.filter_file:
+ export_cmd = [
+ self.dm_app, 'traceexport', '-c', '-csv', '-f',
+ self.filter_file, '-b', self.modem_bin, '-o', temp_file, file
+ ]
+ else:
+ export_cmd = [
+ self.dm_app, 'traceexport', '-c', '-csv', '-b', self.modem_bin,
+ '-o', temp_file, file
+ ]
+ logging.debug('Executing: {}'.format(export_cmd))
+ subprocess.call(export_cmd)
+ return temp_file
+
+ def _export_logs(self, log_files):
+ csv_files = []
+ for file in log_files:
+ csv_files.append(self._export_single_log(file))
+ return csv_files
+
+ def _filter_log(self, input_filename, output_filename, write_header):
+ """Export log messages from input file to output file."""
+ log_parser = LogParser()
+ with open(input_filename, 'r') as input_file:
+ with open(output_filename, 'a') as output_file:
+
+ header_line = input_file.readline()
+ log_parser.parse_header(header_line)
+ if log_parser.message_col == -1:
+ return
+
+ if write_header:
+ output_file.write(header_line)
+ # Write next line for time reference.
+ output_file.write(input_file.readline())
+
+ for line in input_file:
+ message = log_parser.get_message(line)
+ if message:
+ for filter_str in log_parser.PARSER_INFO:
+ if message.startswith(filter_str):
+ output_file.write(line)
+ break
+
+ def _export_filtered_logs(self, csv_files):
+ start_times = []
+ log_parser = LogParser()
+ reordered_csv_files = []
+ for file in csv_files:
+ start_time = log_parser.get_file_start_time(file)
+ if start_time:
+ start_times.append(start_time)
+ reordered_csv_files.append(file)
+ print(reordered_csv_files)
+ print(start_times)
+ file_order = numpy.argsort(start_times)
+ print(file_order)
+ reordered_csv_files = [reordered_csv_files[i] for i in file_order]
+ print(reordered_csv_files)
+ log_directory = Path(reordered_csv_files[0]).parent
+ exported_file = os.path.join(log_directory, 'modem_log.csv')
+ write_header = True
+ for file in reordered_csv_files:
+ self._filter_log(file, exported_file, write_header)
+ write_header = False
+ return exported_file
+
+ def _parse_log(self, log_file, gap_options=0):
+ """Extract required data from the exported CSV file."""
+ log_parser = LogParser()
+ log_data = log_parser.parse_log(log_file, gap_options=0)
+ return log_data
+
+ def process_log(self, log_zip_file):
+ sdm_log_files = self.unzip_modem_logs(log_zip_file)
+ csv_log_files = self._export_logs(sdm_log_files)
+ exported_log = self._export_filtered_logs(csv_log_files)
+ log_data = self._parse_log(exported_log, 0)
+ for file in itertools.chain(sdm_log_files, csv_log_files):
+ os.remove(file)
+ return log_data
diff --git a/acts_tests/acts_contrib/test_utils/gnss/GnssBlankingBase.py b/acts_tests/acts_contrib/test_utils/gnss/GnssBlankingBase.py
index 9fe9fa4..f6d962f 100644
--- a/acts_tests/acts_contrib/test_utils/gnss/GnssBlankingBase.py
+++ b/acts_tests/acts_contrib/test_utils/gnss/GnssBlankingBase.py
@@ -13,40 +13,39 @@
# 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.
+'''GNSS Base Class for Blanking and Hot Start Sensitivity Search'''
import os
-from glob import glob
+import re
from time import sleep
from collections import namedtuple
+from itertools import product
from numpy import arange
-from pandas import DataFrame
+from pandas import DataFrame, merge
from acts.signals import TestError
from acts.signals import TestFailure
from acts.logger import epoch_to_log_line_timestamp
from acts.context import get_current_context
from acts_contrib.test_utils.gnss import LabTtffTestBase as lttb
+from acts_contrib.test_utils.gnss.LabTtffTestBase import glob_re
from acts_contrib.test_utils.gnss.gnss_test_utils import launch_eecoexer
-from acts_contrib.test_utils.gnss.gnss_test_utils import excute_eecoexer_function
+from acts_contrib.test_utils.gnss.gnss_test_utils import execute_eecoexer_function
from acts_contrib.test_utils.gnss.gnss_test_utils import start_gnss_by_gtw_gpstool
from acts_contrib.test_utils.gnss.gnss_test_utils import get_current_epoch_time
from acts_contrib.test_utils.gnss.gnss_test_utils import check_current_focus_app
from acts_contrib.test_utils.gnss.gnss_test_utils import process_ttff_by_gtw_gpstool
from acts_contrib.test_utils.gnss.gnss_test_utils import check_ttff_data
from acts_contrib.test_utils.gnss.gnss_test_utils import process_gnss_by_gtw_gpstool
-from acts_contrib.test_utils.gnss.gnss_test_utils import start_pixel_logger
-from acts_contrib.test_utils.gnss.gnss_test_utils import stop_pixel_logger
-from acts_contrib.test_utils.gnss.dut_log_test_utils import start_diagmdlog_background
from acts_contrib.test_utils.gnss.dut_log_test_utils import get_gpstool_logs
-from acts_contrib.test_utils.gnss.dut_log_test_utils import stop_background_diagmdlog
-from acts_contrib.test_utils.gnss.dut_log_test_utils import get_pixellogger_bcm_log
from acts_contrib.test_utils.gnss.gnss_testlog_utils import parse_gpstool_ttfflog_to_df
-def range_wi_end(ad, start, stop, step):
+def range_wi_end(dut, start, stop, step):
"""
Generate a list of data from start to stop with the step. The list includes start and stop value
and also supports floating point.
Args:
+ dut: An AndroidDevice object.
start: start value.
Type, int or float.
stop: stop value.
@@ -57,7 +56,7 @@
range_ls: the list of data.
"""
if step == 0:
- ad.log.warn('Step is 0. Return empty list')
+ dut.log.warn('Step is 0. Return empty list')
range_ls = []
else:
if start == stop:
@@ -68,44 +67,50 @@
if (step < 0 and range_ls[-1] > stop) or (step > 0 and
range_ls[-1] < stop):
range_ls.append(stop)
+ dut.log.debug(f'The range list is: {range_ls}')
return range_ls
-def check_ttff_pe(ad, ttff_data, ttff_mode, pe_criteria):
+def check_ttff_pe(dut, ttff_data, ttff_mode, pe_criteria):
"""Verify all TTFF results from ttff_data.
Args:
- ad: An AndroidDevice object.
+ dut: An AndroidDevice object.
ttff_data: TTFF data of secs, position error and signal strength.
ttff_mode: TTFF Test mode for current test item.
pe_criteria: Criteria for current test item.
"""
ret = True
- ad.log.info("%d iterations of TTFF %s tests finished." %
- (len(ttff_data.keys()), ttff_mode))
- ad.log.info("%s PASS criteria is %f meters" % (ttff_mode, pe_criteria))
- ad.log.debug("%s TTFF data: %s" % (ttff_mode, ttff_data))
+ no_iteration = len(ttff_data.keys())
+ dut.log.info(
+ f'{no_iteration} iterations of TTFF {ttff_mode} tests finished.')
+ dut.log.info(f'{ttff_mode} PASS criteria is {pe_criteria} meters')
+ dut.log.debug(f'{ttff_mode} TTFF data: {ttff_data}')
if len(ttff_data.keys()) == 0:
- ad.log.error("GTW_GPSTool didn't process TTFF properly.")
+ dut.log.error("GTW_GPSTool didn't process TTFF properly.")
raise TestFailure("GTW_GPSTool didn't process TTFF properly.")
if any(
float(ttff_data[key].ttff_pe) >= pe_criteria
for key in ttff_data.keys()):
- ad.log.error("One or more TTFF %s are over test criteria %f meters" %
- (ttff_mode, pe_criteria))
+ dut.log.error(
+ f'One or more TTFF {ttff_mode} are over test criteria {pe_criteria} meters'
+ )
ret = False
else:
- ad.log.info("All TTFF %s are within test criteria %f meters." %
- (ttff_mode, pe_criteria))
+ dut.log.info(
+ f'All TTFF {ttff_mode} are within test criteria {pe_criteria} meters.'
+ )
ret = True
return ret
class GnssBlankingBase(lttb.LabTtffTestBase):
""" LAB GNSS Cellular Coex Tx Power Sweep TTFF/FFPE Tests"""
+ GNSS_PWR_SWEEP = 'gnss_pwr_sweep'
+ CELL_PWR_SWEEP = 'cell_pwr_sweep'
def __init__(self, controllers):
""" Initializes class attributes. """
@@ -118,22 +123,28 @@
self.gsm_sweep_params = None
self.lte_tdd_pc3_sweep_params = None
self.lte_tdd_pc2_sweep_params = None
- self.sa_sensitivity = -150
- self.gnss_pwr_lvl_offset = -5
- self.maskfile = None
+ self.coex_stop_cmd = None
+ self.scen_sweep = False
+ self.gnss_pwr_sweep_init_ls = []
+ self.gnss_pwr_sweep_fine_sweep_ls = []
def setup_class(self):
super().setup_class()
- req_params = ['sa_sensitivity', 'gnss_pwr_lvl_offset']
+
+ # Required parameters
+ req_params = [self.GNSS_PWR_SWEEP]
self.unpack_userparams(req_param_names=req_params)
- cell_sweep_params = self.user_params.get('cell_pwr_sweep', [])
- self.gsm_sweep_params = cell_sweep_params.get("GSM", [10, 33, 1])
- self.lte_tdd_pc3_sweep_params = cell_sweep_params.get(
- "LTE_TDD_PC3", [10, 24, 1])
- self.lte_tdd_pc2_sweep_params = cell_sweep_params.get(
- "LTE_TDD_PC2", [10, 26, 1])
- self.sa_sensitivity = self.user_params.get('sa_sensitivity', -150)
- self.gnss_pwr_lvl_offset = self.user_params.get('gnss_pwr_lvl_offset', -5)
+ self.unpack_gnss_pwr_sweep()
+
+ # Optional parameters
+ cell_sweep_params = self.user_params.get(self.CELL_PWR_SWEEP, [])
+
+ if cell_sweep_params:
+ self.gsm_sweep_params = cell_sweep_params.get("GSM", [10, 33, 1])
+ self.lte_tdd_pc3_sweep_params = cell_sweep_params.get(
+ "LTE_TDD_PC3", [10, 24, 1])
+ self.lte_tdd_pc2_sweep_params = cell_sweep_params.get(
+ "LTE_TDD_PC2", [10, 26, 1])
def setup_test(self):
super().setup_test()
@@ -148,11 +159,8 @@
self.gnss_log_path = os.path.join(self.log_path, cur_test_item_dir)
os.makedirs(self.gnss_log_path, exist_ok=True)
- # Start GNSS chip log
- if self.diag_option == "QCOM":
- start_diagmdlog_background(self.dut, maskfile=self.maskfile)
- else:
- start_pixel_logger(self.dut)
+ ## Start GNSS chip log
+ self.start_dut_gnss_log()
def teardown_test(self):
super().teardown_test()
@@ -161,35 +169,69 @@
self.diag_option)
os.makedirs(gnss_vendor_log_path, exist_ok=True)
- # Stop GNSS chip log and pull the logs to local file system.
- if self.diag_option == "QCOM":
- stop_background_diagmdlog(self.dut,
- gnss_vendor_log_path,
- keep_logs=False)
- else:
- stop_pixel_logger(self.dut)
- self.log.info('Getting Pixel BCM Log!')
- get_pixellogger_bcm_log(self.dut,
- gnss_vendor_log_path,
- keep_logs=False)
+ # Stop GNSS chip log and pull the logs to local file system
+ self.stop_and_pull_dut_gnss_log(gnss_vendor_log_path)
# Stop cellular Tx and close GPStool and EEcoexer APPs.
- self.stop_cell_tx()
+ self.stop_coex_tx()
self.log.debug('Close GPStool APP')
self.dut.force_stop_apk("com.android.gpstool")
self.log.debug('Close EEcoexer APP')
self.dut.force_stop_apk("com.google.eecoexer")
- def stop_cell_tx(self):
+ def derive_sweep_list(self, data):
+ """
+ Derive sweep list from config
+ Args:
+ data: GNSS simulator scenario power setting.
+ type, dictionary.
+ """
+ match_tag = r'(?P<sat>[a-z]+)_(?P<band>[a-z]+\d\S*)'
+ sweep_all_ls = []
+ set_all_ls = []
+ regex_match = re.compile(match_tag)
+ method = data.get('method')
+ for key, value in data.items():
+ result = regex_match.search(key)
+ if result:
+ set_all_ls.append(result.groupdict())
+ sweep_all_ls.append(
+ range_wi_end(self.dut, value[0], value[1], value[2]))
+ if method == 'product':
+ swp_result_ls = list(product(*sweep_all_ls))
+ else:
+ swp_result_ls = list(zip(*sweep_all_ls))
+
+ self.log.debug(f'set_all_ls: {set_all_ls}')
+ self.log.debug(f'swp_result_ls: {swp_result_ls}')
+ return set_all_ls, swp_result_ls
+
+ def unpack_gnss_pwr_sweep(self):
+ """ Unpack gnss_pwr_sweep and construct sweep parameters
+ """
+
+ for key, value in self.gnss_pwr_sweep.items():
+ if key == 'init':
+ self.gnss_pwr_sweep_init_ls = []
+ self.log.info(f'Sweep: {value}')
+ result = self.derive_sweep_list(value)
+ self.gnss_pwr_sweep_init_ls.append(result)
+ elif key == 'fine_sweep':
+ self.gnss_pwr_sweep_fine_sweep_ls = []
+ self.log.info(f'Sweep: {value}')
+ result = self.derive_sweep_list(value)
+ self.gnss_pwr_sweep_fine_sweep_ls.append(result)
+ else:
+ self.log.error(f'{key} is a unsupported key in gnss_pwr_sweep.')
+
+ def stop_coex_tx(self):
"""
Stop EEcoexer Tx power.
"""
- # EEcoexer cellular stop Tx command.
- stop_cell_tx_cmd = 'CELLR,19'
-
# Stop cellular Tx by EEcoexer.
- self.log.info('Stop EEcoexer Test Command: {}'.format(stop_cell_tx_cmd))
- excute_eecoexer_function(self.dut, stop_cell_tx_cmd)
+ if self.coex_stop_cmd:
+ self.log.info(f'Stop EEcoexer Test Command: {self.coex_stop_cmd}')
+ execute_eecoexer_function(self.dut, self.coex_stop_cmd)
def analysis_ttff_ffpe(self, ttff_data, json_tag=''):
"""
@@ -208,45 +250,81 @@
get_gpstool_logs(self.dut, gps_log_path, False)
# Parsing the log of GTW GPStool into pandas dataframe.
- target_log_name_regx = os.path.join(gps_log_path, 'GPSLogs', 'files',
- 'GNSS_*')
- self.log.info('Get GPStool logs from: {}'.format(target_log_name_regx))
- gps_api_log_ls = glob(target_log_name_regx)
+ target_dir = os.path.join(gps_log_path, 'GPSLogs', 'files')
+ gps_api_log_ls = glob_re(self.dut, target_dir, r'GNSS_\d+')
latest_gps_api_log = max(gps_api_log_ls, key=os.path.getctime)
- self.log.info(
- 'Get latest GPStool log is: {}'.format(latest_gps_api_log))
+ self.log.info(f'Get latest GPStool log is: {latest_gps_api_log}')
+ df_ttff_ffpe = DataFrame(
+ parse_gpstool_ttfflog_to_df(latest_gps_api_log))
+ # Add test case, TTFF and FFPE data into the dataframe.
+ ttff_dict = {}
+ for i in ttff_data:
+ data = ttff_data[i]._asdict()
+ ttff_dict[i] = dict(data)
+
+ ttff_data_df = DataFrame(ttff_dict).transpose()
+ ttff_data_df = ttff_data_df[[
+ 'ttff_loop', 'ttff_sec', 'ttff_pe', 'ttff_haccu'
+ ]]
try:
- df_ttff_ffpe = DataFrame(
- parse_gpstool_ttfflog_to_df(latest_gps_api_log))
+ df_ttff_ffpe = merge(df_ttff_ffpe,
+ ttff_data_df,
+ left_on='loop',
+ right_on='ttff_loop')
+ except: # pylint: disable=bare-except
+ self.log.warning("Can't merge ttff_data and df.")
+ df_ttff_ffpe['test_case'] = json_tag
- # Add test case, TTFF and FFPE data into the dataframe.
- ttff_dict = {}
- for i in ttff_data:
- data = ttff_data[i]._asdict()
- ttff_dict[i] = dict(data)
- ttff_time = []
- ttff_pe = []
- test_case = []
- for value in ttff_dict.values():
- ttff_time.append(value['ttff_sec'])
- ttff_pe.append(value['ttff_pe'])
- test_case.append(json_tag)
- self.log.info('test_case length {}'.format(str(len(test_case))))
+ json_file = f'gps_log_{json_tag}.json'
+ ttff_data_json_file = f'gps_log_{json_tag}_ttff_data.json'
+ json_path = os.path.join(gps_log_path, json_file)
+ ttff_data_json_path = os.path.join(gps_log_path, ttff_data_json_file)
+ # Save dataframe into json file.
+ df_ttff_ffpe.to_json(json_path, orient='table', index=False)
+ ttff_data_df.to_json(ttff_data_json_path, orient='table', index=False)
- df_ttff_ffpe['test_case'] = test_case
- df_ttff_ffpe['ttff_sec'] = ttff_time
- df_ttff_ffpe['ttff_pe'] = ttff_pe
- json_file = 'gps_log_{}.json'.format(json_tag)
- json_path = os.path.join(gps_log_path, json_file)
- # Save dataframe into json file.
- df_ttff_ffpe.to_json(json_path, orient='table', index=False)
- except ValueError:
- self.log.warning('Can\'t create the parsed the log data in file.')
+ def hot_start_ttff_ffpe_process(self, iteration, wait):
+ """
+ Function to run hot start TTFF/FFPE by GTW GPSTool
+ Args:
+ iteration: TTFF/FFPE test iteration.
+ type, integer.
+ wait: wait time before the hot start TTFF/FFPE test.
+ type, integer.
+ """
+ # Start GTW GPStool.
+ self.dut.log.info("Restart GTW GPSTool")
+ start_gnss_by_gtw_gpstool(self.dut, state=True)
+ if wait > 0:
+ self.log.info(
+ f'Wait for {wait} seconds before TTFF to acquire data.')
+ sleep(wait)
+ # Get current time and convert to human readable format
+ begin_time = get_current_epoch_time()
+ log_begin_time = epoch_to_log_line_timestamp(begin_time)
+ self.dut.log.debug(f'Start time is {log_begin_time}')
+
+ # Run hot start TTFF
+ for i in range(3):
+ self.log.info(f'Start hot start attempt {i + 1}')
+ self.dut.adb.shell(
+ f'am broadcast -a com.android.gpstool.ttff_action '
+ f'--es ttff hs --es cycle {iteration} --ez raninterval False')
+ sleep(1)
+ if self.dut.search_logcat(
+ "act=com.android.gpstool.start_test_action", begin_time):
+ self.dut.log.info("Send TTFF start_test_action successfully.")
+ break
+ else:
+ check_current_focus_app(self.dut)
+ raise TestError("Fail to send TTFF start_test_action.")
+ return begin_time
def gnss_hot_start_ttff_ffpe_test(self,
iteration,
sweep_enable=False,
- json_tag=''):
+ json_tag='',
+ wait=0):
"""
GNSS hot start ttff ffpe tset
@@ -261,40 +339,26 @@
this as a part of file name to save TTFF and FFPE results into json file.
Type, str.
Default, ''.
+ wait: wait time before ttff test.
+ Type, int.
+ Default, 0.
Raise:
TestError: fail to send TTFF start_test_action.
"""
- # Start GTW GPStool.
test_type = namedtuple('Type', ['command', 'criteria'])
- test_type_ttff = test_type('Hot Start', self.hs_ttff_criteria)
+ test_type_ttff = test_type('Hot Start', self.hs_criteria)
test_type_pe = test_type('Hot Start', self.hs_ttff_pecriteria)
- self.dut.log.info("Restart GTW GPSTool")
- start_gnss_by_gtw_gpstool(self.dut, state=True)
-
- # Get current time and convert to human readable format
- begin_time = get_current_epoch_time()
- log_begin_time = epoch_to_log_line_timestamp(begin_time)
- self.dut.log.debug('Start time is {}'.format(log_begin_time))
-
- # Run hot start TTFF
- for i in range(3):
- self.log.info('Start hot start attempt %d' % (i + 1))
- self.dut.adb.shell(
- "am broadcast -a com.android.gpstool.ttff_action "
- "--es ttff hs --es cycle {} --ez raninterval False".format(
- iteration))
- sleep(1)
- if self.dut.search_logcat(
- "act=com.android.gpstool.start_test_action", begin_time):
- self.dut.log.info("Send TTFF start_test_action successfully.")
- break
- else:
- check_current_focus_app(self.dut)
- raise TestError("Fail to send TTFF start_test_action.")
# Verify hot start TTFF results
- ttff_data = process_ttff_by_gtw_gpstool(self.dut, begin_time,
- self.simulator_location)
+ begin_time = self.hot_start_ttff_ffpe_process(iteration, wait)
+ try:
+ ttff_data = process_ttff_by_gtw_gpstool(self.dut, begin_time,
+ self.simulator_location)
+ except: # pylint: disable=bare-except
+ self.log.warning('Fail to acquire TTFF data. Retry again.')
+ begin_time = self.hot_start_ttff_ffpe_process(iteration, wait)
+ ttff_data = process_ttff_by_gtw_gpstool(self.dut, begin_time,
+ self.simulator_location)
# Stop GTW GPSTool
self.dut.log.info("Stop GTW GPSTool")
@@ -320,10 +384,8 @@
return True
def hot_start_gnss_power_sweep(self,
- start_pwr,
- stop_pwr,
- offset,
- wait,
+ sweep_ls,
+ wait=0,
iteration=1,
sweep_enable=False,
title=''):
@@ -331,14 +393,11 @@
GNSS simulator power sweep of hot start test.
Args:
- start_pwr: GNSS simulator power sweep start power level.
- Type, int.
- stop_pwr: GNSS simulator power sweep stop power level.
- Type, int.
- offset: GNSS simulator power sweep offset
- Type, int.
+ sweep_ls: list of sweep parameters.
+ Type, tuple.
wait: Wait time before the power sweep.
Type, int.
+ Default, 0.
iteration: The iteration times of hot start test.
Type, int.
Default, 1.
@@ -349,15 +408,15 @@
title: the target log folder title for GNSS sensitivity search test items.
Type, str.
Default, ''.
+ Return:
+ Bool, gnss_pwr_params.
"""
# Calculate loop range list from gnss_simulator_power_level and sa_sensitivity
- range_ls = range_wi_end(self.dut, start_pwr, stop_pwr, offset)
- sweep_range = ','.join([str(x) for x in range_ls])
self.log.debug(
- 'Start the GNSS simulator power sweep. The sweep range is [{}]'.
- format(sweep_range))
+ f'Start the GNSS simulator power sweep. The sweep tuple is [{sweep_ls}]'
+ )
if sweep_enable:
self.start_gnss_and_wait(wait)
@@ -368,21 +427,41 @@
# Sweep GNSS simulator power level in range_ls.
# Do hot start for every power level.
# Check the TTFF result if it can pass the criteria.
- gnss_pwr_lvl = -130
- for gnss_pwr_lvl in range_ls:
-
- # Set GNSS Simulator power level
- self.log.info('Set GNSS simulator power level to %.1f' %
- gnss_pwr_lvl)
- self.gnss_simulator.set_power(gnss_pwr_lvl)
- json_tag = title + '_gnss_pwr_' + str(gnss_pwr_lvl)
-
+ gnss_pwr_params = None
+ previous_pwr_lvl = None
+ current_pwr_lvl = None
+ return_pwr_lvl = {}
+ for j, gnss_pwr_params in enumerate(sweep_ls[1]):
+ json_tag = f'{title}_'
+ current_pwr_lvl = gnss_pwr_params
+ if j == 0:
+ previous_pwr_lvl = current_pwr_lvl
+ for i, pwr in enumerate(gnss_pwr_params):
+ sat_sys = sweep_ls[0][i].get('sat').upper()
+ band = sweep_ls[0][i].get('band').upper()
+ # Set GNSS Simulator power level
+ self.gnss_simulator.ping_inst()
+ self.gnss_simulator.set_scenario_power(power_level=pwr,
+ sat_system=sat_sys,
+ freq_band=band)
+ self.log.info(f'Set {sat_sys} {band} with power {pwr}')
+ json_tag = json_tag + f'{sat_sys}_{band}_{pwr}'
+ # Wait 30 seconds if major power sweep level is changed.
+ wait = 0
+ if j > 0:
+ if current_pwr_lvl[0] != previous_pwr_lvl[0]:
+ wait = 30
# GNSS hot start test
if not self.gnss_hot_start_ttff_ffpe_test(iteration, sweep_enable,
- json_tag):
- sensitivity = gnss_pwr_lvl - offset
- return False, sensitivity
- return True, gnss_pwr_lvl
+ json_tag, wait):
+ result = False
+ break
+ previous_pwr_lvl = current_pwr_lvl
+ result = True
+ for i, pwr in enumerate(previous_pwr_lvl):
+ key = f'{sweep_ls[0][i].get("sat").upper()}_{sweep_ls[0][i].get("band").upper()}'
+ return_pwr_lvl.setdefault(key, pwr)
+ return result, return_pwr_lvl
def gnss_init_power_setting(self, first_wait=180):
"""
@@ -398,39 +477,22 @@
"""
# Start and set GNSS simulator
- self.start_and_set_gnss_simulator_power()
+ self.start_set_gnss_power()
# Start 1st time cold start to obtain ephemeris
process_gnss_by_gtw_gpstool(self.dut, self.test_types['cs'].criteria)
- self.hot_start_gnss_power_sweep(self.gnss_simulator_power_level,
- self.sa_sensitivity,
- self.gnss_pwr_lvl_offset, first_wait)
+ # Read initial power sweep settings
+ if self.gnss_pwr_sweep_init_ls:
+ for sweep_ls in self.gnss_pwr_sweep_init_ls:
+ ret, gnss_pwr_lvl = self.hot_start_gnss_power_sweep(
+ sweep_ls, first_wait)
+ else:
+ self.log.warning('Skip initial power sweep.')
+ ret = False
+ gnss_pwr_lvl = None
- return True
-
- def start_gnss_and_wait(self, wait=60):
- """
- The process of enable gnss and spend the wait time for GNSS to
- gather enoung information that make sure the stability of testing.
-
- Args:
- wait: wait time between power sweep.
- Type, int.
- Default, 60.
- """
- # Create log path for waiting section logs of GPStool.
- gnss_wait_log_dir = os.path.join(self.gnss_log_path, 'GNSS_wait')
-
- # Enable GNSS to receive satellites' signals for "wait_between_pwr" seconds.
- self.log.info('Enable GNSS for searching satellites')
- start_gnss_by_gtw_gpstool(self.dut, state=True)
- self.log.info('Wait for {} seconds'.format(str(wait)))
- sleep(wait)
-
- # Stop GNSS and pull the logs.
- start_gnss_by_gtw_gpstool(self.dut, state=False)
- get_gpstool_logs(self.dut, gnss_wait_log_dir, False)
+ return ret, gnss_pwr_lvl
def cell_power_sweep(self):
"""
@@ -449,9 +511,6 @@
power_search_ls = range_wi_end(self.dut, self.start_pwr, self.stop_pwr,
self.offset)
- # Set GNSS simulator power level.
- self.gnss_simulator.set_power(self.sa_sensitivity)
-
# Create gnss log folders for init and cellular sweep
gnss_init_log_dir = os.path.join(self.gnss_log_path, 'GNSS_init')
@@ -461,8 +520,8 @@
if power_search_ls:
# Run the cellular and GNSS coexistence test item.
for i, pwr_lvl in enumerate(power_search_ls):
- self.log.info('Cellular power sweep loop: {}'.format(int(i)))
- self.log.info('Cellular target power: {}'.format(int(pwr_lvl)))
+ self.log.info(f'Cellular power sweep loop: {i}')
+ self.log.info(f'Cellular target power: {pwr_lvl}')
# Enable GNSS to receive satellites' signals for "wait_between_pwr" seconds.
# Wait more time before 1st power level
@@ -473,9 +532,9 @@
self.start_gnss_and_wait(wait)
# Set cellular Tx power level.
- eecoex_cmd = self.eecoex_func.format(str(pwr_lvl))
+ eecoex_cmd = self.eecoex_func.format(pwr_lvl)
eecoex_cmd_file_str = eecoex_cmd.replace(',', '_')
- excute_eecoexer_function(self.dut, eecoex_cmd)
+ execute_eecoexer_function(self.dut, eecoex_cmd)
# Get the last power level that can pass hots start ttff/ffpe spec.
if self.gnss_hot_start_ttff_ffpe_test(ttft_iteration, True,
@@ -489,7 +548,7 @@
power_th = power_search_ls[i - 1]
# Stop cellular Tx after a test cycle.
- self.stop_cell_tx()
+ self.stop_coex_tx()
else:
# Run the stand alone test item.
@@ -499,7 +558,6 @@
self.gnss_hot_start_ttff_ffpe_test(ttft_iteration, True,
eecoex_cmd_file_str)
- self.log.info('The GNSS WWAN coex celluar Tx power is {}'.format(
- str(power_th)))
+ self.log.info(f'The GNSS WWAN coex celluar Tx power is {power_th}')
return power_th
diff --git a/acts_tests/acts_contrib/test_utils/gnss/LabTtffTestBase.py b/acts_tests/acts_contrib/test_utils/gnss/LabTtffTestBase.py
index 6a6bd5d..77ebcf6 100644
--- a/acts_tests/acts_contrib/test_utils/gnss/LabTtffTestBase.py
+++ b/acts_tests/acts_contrib/test_utils/gnss/LabTtffTestBase.py
@@ -13,35 +13,51 @@
# 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.
+'''GNSS Base Class for Lab TTFF/FFPE'''
import os
import time
-import glob
import errno
+import re
from collections import namedtuple
-from pandas import DataFrame
-from acts import utils
-from acts import signals
-from acts.base_test import BaseTestClass
-from acts.controllers.gnss_lib import GnssSimulator
-from acts.context import get_current_context
-from acts_contrib.test_utils.gnss import dut_log_test_utils as diaglog
-from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
-from acts_contrib.test_utils.gnss import gnss_testlog_utils as glogutils
+from pandas import DataFrame, merge
from acts_contrib.test_utils.gnss.gnss_defines import DEVICE_GPSLOG_FOLDER
from acts_contrib.test_utils.gnss.gnss_defines import GPS_PKG_NAME
from acts_contrib.test_utils.gnss.gnss_defines import BCM_GPS_XML_PATH
+from acts_contrib.test_utils.gnss import dut_log_test_utils as diaglog
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.gnss import gnss_testlog_utils as glogutils
+from acts import utils
+from acts import signals
+from acts.controllers.gnss_lib import GnssSimulator
+from acts.context import get_current_context
+from acts.base_test import BaseTestClass
+
+
+def glob_re(dut, directory, regex_tag):
+ """glob with regular expression method.
+ Args:
+ dut: An AndroidDevice object.
+ directory: Target directory path.
+ Type, str
+ regex_tag: regular expression format string.
+ Type, str
+ Return:
+ result_ls: list of glob result
+ """
+ all_files_in_dir = os.listdir(directory)
+ dut.log.debug(f'glob_re dir: {all_files_in_dir}')
+ target_log_name_regx = re.compile(regex_tag)
+ tmp_ls = list(filter(target_log_name_regx.match, all_files_in_dir))
+ result_ls = [os.path.join(directory, file) for file in tmp_ls]
+ dut.log.debug(f'glob_re list: {result_ls}')
+ return result_ls
class LabTtffTestBase(BaseTestClass):
""" LAB TTFF Tests Base Class"""
GTW_GPSTOOL_APP = 'gtw_gpstool_apk'
- GNSS_SIMULATOR_KEY = 'gnss_simulator'
- GNSS_SIMULATOR_IP_KEY = 'gnss_simulator_ip'
- GNSS_SIMULATOR_PORT_KEY = 'gnss_simulator_port'
- GNSS_SIMULATOR_PORT_CTRL_KEY = 'gnss_simulator_port_ctrl'
- GNSS_SIMULATOR_SCENARIO_KEY = 'gnss_simulator_scenario'
- GNSS_SIMULATOR_POWER_LEVEL_KEY = 'gnss_simulator_power_level'
+ GNSS_SIMULATOR_KEY = 'gnss_sim_params'
CUSTOM_FILES_KEY = 'custom_files'
CSTTFF_CRITERIA = 'cs_criteria'
HSTTFF_CRITERIA = 'hs_criteria'
@@ -52,76 +68,106 @@
TTFF_ITERATION = 'ttff_iteration'
SIMULATOR_LOCATION = 'simulator_location'
DIAG_OPTION = 'diag_option'
+ SCENARIO_POWER = 'scenario_power'
+ MDSAPP = 'mdsapp'
+ MASKFILE = 'maskfile'
+ MODEMPARFILE = 'modemparfile'
+ NV_DICT = 'nv_dict'
+ TTFF_TIMEOUT = 'ttff_timeout'
def __init__(self, controllers):
""" Initializes class attributes. """
super().__init__(controllers)
-
self.dut = None
self.gnss_simulator = None
self.rockbottom_script = None
self.gnss_log_path = self.log_path
self.gps_xml_bk_path = BCM_GPS_XML_PATH + '.bk'
+ self.gpstool_ver = ''
+ self.test_params = None
+ self.custom_files = None
+ self.maskfile = None
+ self.mdsapp = None
+ self.modemparfile = None
+ self.nv_dict = None
+ self.scenario_power = None
+ self.ttff_timeout = None
+ self.test_types = None
+ self.simulator_location = None
+ self.gnss_simulator_scenario = None
+ self.gnss_simulator_power_level = None
def setup_class(self):
super().setup_class()
- req_params = [
- self.GNSS_SIMULATOR_KEY, self.GNSS_SIMULATOR_IP_KEY,
- self.GNSS_SIMULATOR_PORT_KEY, self.GNSS_SIMULATOR_SCENARIO_KEY,
- self.GNSS_SIMULATOR_POWER_LEVEL_KEY, self.CSTTFF_CRITERIA,
- self.HSTTFF_CRITERIA, self.WSTTFF_CRITERIA, self.TTFF_ITERATION,
- self.SIMULATOR_LOCATION, self.DIAG_OPTION
- ]
+ # Update parameters by test case configurations.
+ test_param = self.TAG + '_params'
+ self.test_params = self.user_params.get(test_param, {})
+ if not self.test_params:
+ self.log.warning(test_param + ' was not found in the user '
+ 'parameters defined in the config file.')
+ # Override user_param values with test parameters
+ self.user_params.update(self.test_params)
+
+ # Unpack user_params with default values. All the usages of user_params
+ # as self attributes need to be included either as a required parameter
+ # or as a parameter with a default value.
+
+ # Required parameters
+ req_params = [
+ self.CSTTFF_PECRITERIA, self.WSTTFF_PECRITERIA, self.HSTTFF_PECRITERIA,
+ self.CSTTFF_CRITERIA, self.HSTTFF_CRITERIA, self.WSTTFF_CRITERIA,
+ self.TTFF_ITERATION, self.GNSS_SIMULATOR_KEY, self.DIAG_OPTION,
+ self.GTW_GPSTOOL_APP
+ ]
self.unpack_userparams(req_param_names=req_params)
+
+ # Optional parameters
+ self.custom_files = self.user_params.get(self.CUSTOM_FILES_KEY,[])
+ self.maskfile = self.user_params.get(self.MASKFILE,'')
+ self.mdsapp = self.user_params.get(self.MDSAPP,'')
+ self.modemparfile = self.user_params.get(self.MODEMPARFILE,'')
+ self.nv_dict = self.user_params.get(self.NV_DICT,{})
+ self.scenario_power = self.user_params.get(self.SCENARIO_POWER, [])
+ self.ttff_timeout = self.user_params.get(self.TTFF_TIMEOUT, 60)
+
+ # Set TTFF Spec.
+ test_type = namedtuple('Type', ['command', 'criteria'])
+ self.test_types = {
+ 'cs': test_type('Cold Start', self.cs_criteria),
+ 'ws': test_type('Warm Start', self.ws_criteria),
+ 'hs': test_type('Hot Start', self.hs_criteria)
+ }
+
self.dut = self.android_devices[0]
- self.gnss_simulator_scenario = self.user_params[
- self.GNSS_SIMULATOR_SCENARIO_KEY]
- self.gnss_simulator_power_level = self.user_params[
- self.GNSS_SIMULATOR_POWER_LEVEL_KEY]
- self.gtw_gpstool_app = self.user_params[self.GTW_GPSTOOL_APP]
- custom_files = self.user_params.get(self.CUSTOM_FILES_KEY, [])
- self.cs_ttff_criteria = self.user_params.get(self.CSTTFF_CRITERIA, [])
- self.hs_ttff_criteria = self.user_params.get(self.HSTTFF_CRITERIA, [])
- self.ws_ttff_criteria = self.user_params.get(self.WSTTFF_CRITERIA, [])
- self.cs_ttff_pecriteria = self.user_params.get(self.CSTTFF_PECRITERIA,
- [])
- self.hs_ttff_pecriteria = self.user_params.get(self.HSTTFF_PECRITERIA,
- [])
- self.ws_ttff_pecriteria = self.user_params.get(self.WSTTFF_PECRITERIA,
- [])
- self.ttff_iteration = self.user_params.get(self.TTFF_ITERATION, [])
- self.simulator_location = self.user_params.get(self.SIMULATOR_LOCATION,
- [])
- self.diag_option = self.user_params.get(self.DIAG_OPTION, [])
+
+ # GNSS Simulator Setup
+ self.simulator_location = self.gnss_sim_params.get(
+ self.SIMULATOR_LOCATION, [])
+ self.gnss_simulator_scenario = self.gnss_sim_params.get('scenario')
+ self.gnss_simulator_power_level = self.gnss_sim_params.get(
+ 'power_level')
# Create gnss_simulator instance
- gnss_simulator_key = self.user_params[self.GNSS_SIMULATOR_KEY]
- gnss_simulator_ip = self.user_params[self.GNSS_SIMULATOR_IP_KEY]
- gnss_simulator_port = self.user_params[self.GNSS_SIMULATOR_PORT_KEY]
+ gnss_simulator_key = self.gnss_sim_params.get('type')
+ gnss_simulator_ip = self.gnss_sim_params.get('ip')
+ gnss_simulator_port = self.gnss_sim_params.get('port')
if gnss_simulator_key == 'gss7000':
- gnss_simulator_port_ctrl = self.user_params[
- self.GNSS_SIMULATOR_PORT_CTRL_KEY]
+ gnss_simulator_port_ctrl = self.gnss_sim_params.get('port_ctrl')
else:
gnss_simulator_port_ctrl = None
self.gnss_simulator = GnssSimulator.AbstractGnssSimulator(
gnss_simulator_key, gnss_simulator_ip, gnss_simulator_port,
gnss_simulator_port_ctrl)
- test_type = namedtuple('Type', ['command', 'criteria'])
- self.test_types = {
- 'cs': test_type('Cold Start', self.cs_ttff_criteria),
- 'ws': test_type('Warm Start', self.ws_ttff_criteria),
- 'hs': test_type('Hot Start', self.hs_ttff_criteria)
- }
-
# Unpack the rockbottom script file if its available.
- for file in custom_files:
- if 'rockbottom_' + self.dut.model in file:
- self.rockbottom_script = file
- break
+ if self.custom_files:
+ for file in self.custom_files:
+ if 'rockbottom_' + self.dut.model in file:
+ self.rockbottom_script = file
+ break
def setup_test(self):
@@ -129,25 +175,43 @@
self.gnss_simulator.stop_scenario()
self.gnss_simulator.close()
if self.rockbottom_script:
- self.log.info('Running rockbottom script for this device ' +
- self.dut.model)
+ self.log.info(
+ f'Running rockbottom script for this device {self.dut.model}')
self.dut_rockbottom()
else:
- self.log.info('Not running rockbottom for this device ' +
- self.dut.model)
+ self.log.info(
+ f'Not running rockbottom for this device {self.dut.model}')
utils.set_location_service(self.dut, True)
gutils.reinstall_package_apk(self.dut, GPS_PKG_NAME,
- self.gtw_gpstool_app)
+ self.gtw_gpstool_apk)
+ gpstool_ver_cmd = f'dumpsys package {GPS_PKG_NAME} | grep versionName'
+ self.gpstool_ver = self.dut.adb.shell(gpstool_ver_cmd).split('=')[1]
+ self.log.info(f'GTW GPSTool version: {self.gpstool_ver}')
# For BCM DUTs, delete gldata.sto and set IgnoreRomAlm="true" based on b/196936791#comment20
if self.diag_option == "BCM":
gutils.remount_device(self.dut)
# Backup gps.xml
- copy_cmd = "cp {} {}".format(BCM_GPS_XML_PATH, self.gps_xml_bk_path)
+ if self.dut.file_exists(BCM_GPS_XML_PATH):
+ copy_cmd = f'cp {BCM_GPS_XML_PATH} {self.gps_xml_bk_path}'
+ elif self.dut.file_exists(self.gps_xml_bk_path):
+ self.log.debug(f'{BCM_GPS_XML_PATH} is missing')
+ self.log.debug(
+ f'Copy {self.gps_xml_bk_path} and rename to {BCM_GPS_XML_PATH}'
+ )
+ copy_cmd = f'cp {self.gps_xml_bk_path} {BCM_GPS_XML_PATH}'
+ else:
+ self.log.error(
+ f'Missing both {BCM_GPS_XML_PATH} and {self.gps_xml_bk_path} in DUT'
+ )
+ raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
+ self.gps_xml_bk_path)
self.dut.adb.shell(copy_cmd)
gutils.delete_bcm_nvmem_sto_file(self.dut)
gutils.bcm_gps_ignore_rom_alm(self.dut)
+ if self.current_test_name == "test_tracking_power_sweep":
+ gutils.bcm_gps_ignore_warmstandby(self.dut)
# Reboot DUT to apply the setting
gutils.reboot(self.dut)
self.gnss_simulator.connect()
@@ -160,9 +224,9 @@
# The rockbottom script might include a device reboot, so it is
# necessary to stop SL4A during its execution.
self.dut.stop_services()
- self.log.info('Executing rockbottom script for ' + self.dut.model)
+ self.log.info(f'Executing rockbottom script for {self.dut.model}')
os.chmod(self.rockbottom_script, 0o777)
- os.system('{} {}'.format(self.rockbottom_script, self.dut.serial))
+ os.system(f'{self.rockbottom_script} {self.dut.serial}')
# Make sure the DUT is in root mode after coming back
self.dut.root_adb()
# Restart SL4A
@@ -174,9 +238,9 @@
# Restore the gps.xml everytime after the test.
if self.diag_option == "BCM":
# Restore gps.xml
- rm_cmd = "rm -rf {}".format(BCM_GPS_XML_PATH)
- restore_cmd = "mv {} {}".format(self.gps_xml_bk_path,
- BCM_GPS_XML_PATH)
+ gutils.remount_device(self.dut)
+ rm_cmd = f'rm -rf {BCM_GPS_XML_PATH}'
+ restore_cmd = f'cp {self.gps_xml_bk_path} {BCM_GPS_XML_PATH}'
self.dut.adb.shell(rm_cmd)
self.dut.adb.shell(restore_cmd)
@@ -187,7 +251,7 @@
self.gnss_simulator.stop_scenario()
self.gnss_simulator.close()
- def start_and_set_gnss_simulator_power(self):
+ def start_set_gnss_power(self):
"""
Start GNSS simulator secnario and set power level.
@@ -195,7 +259,24 @@
self.gnss_simulator.start_scenario(self.gnss_simulator_scenario)
time.sleep(25)
- self.gnss_simulator.set_power(self.gnss_simulator_power_level)
+ if self.scenario_power:
+ self.log.info(
+ 'Set GNSS simulator power with power_level by scenario_power')
+ for setting in self.scenario_power:
+ power_level = setting.get('power_level', -130)
+ sat_system = setting.get('sat_system', '')
+ freq_band = setting.get('freq_band', 'ALL')
+ sat_id = setting.get('sat_id', '')
+ self.log.debug(f'sat: {sat_system}; freq_band: {freq_band}, '
+ f'power_level: {power_level}, sat_id: {sat_id}')
+ self.gnss_simulator.set_scenario_power(power_level,
+ sat_id,
+ sat_system,
+ freq_band)
+ else:
+ self.log.debug('Set GNSS simulator power '
+ f'with power_level: {self.gnss_simulator_power_level}')
+ self.gnss_simulator.set_power(self.gnss_simulator_power_level)
def get_and_verify_ttff(self, mode):
"""Retrieve ttff with designate mode.
@@ -204,14 +285,9 @@
mode: A string for identify gnss test mode.
"""
if mode not in self.test_types:
- raise signals.TestError('Unrecognized mode %s' % mode)
+ raise signals.TestError(f'Unrecognized mode {mode}')
test_type = self.test_types.get(mode)
- if mode != 'cs':
- wait_time = 900
- else:
- wait_time = 300
-
gutils.process_gnss_by_gtw_gpstool(self.dut,
self.test_types['cs'].criteria)
begin_time = gutils.get_current_epoch_time()
@@ -219,12 +295,12 @@
ttff_mode=mode,
iteration=self.ttff_iteration,
raninterval=True,
- hot_warm_sleep=wait_time)
+ hot_warm_sleep=3,
+ timeout=self.ttff_timeout)
# Since Wear takes little longer to update the TTFF info.
# Workround to solve the wearable timing issue
if gutils.is_device_wearable(self.dut):
time.sleep(20)
-
ttff_data = gutils.process_ttff_by_gtw_gpstool(self.dut, begin_time,
self.simulator_location)
@@ -232,41 +308,43 @@
gps_log_path = os.path.join(self.gnss_log_path, 'GPSLogs')
os.makedirs(gps_log_path, exist_ok=True)
- self.dut.adb.pull("{} {}".format(DEVICE_GPSLOG_FOLDER, gps_log_path))
-
- gps_api_log = glob.glob(gps_log_path + '/*/GNSS_*.txt')
- ttff_loop_log = glob.glob(gps_log_path +
- '/*/GPS_{}_*.txt'.format(mode.upper()))
+ self.dut.adb.pull(f'{DEVICE_GPSLOG_FOLDER} {gps_log_path}')
+ local_log_dir = os.path.join(gps_log_path, 'files')
+ gps_api_log = glob_re(self.dut, local_log_dir, r'GNSS_\d+')
+ ttff_loop_log = glob_re(self.dut, local_log_dir,
+ fr'\w+_{mode.upper()}_\d+')
if not gps_api_log and ttff_loop_log:
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
gps_log_path)
- df = DataFrame(glogutils.parse_gpstool_ttfflog_to_df(gps_api_log[0]))
+ df_ttff_ffpe = DataFrame(glogutils.parse_gpstool_ttfflog_to_df(gps_api_log[0]))
ttff_dict = {}
for i in ttff_data:
- d = ttff_data[i]._asdict()
- ttff_dict[i] = dict(d)
+ data = ttff_data[i]._asdict()
+ ttff_dict[i] = dict(data)
- ttff_time = []
- ttff_pe = []
- ttff_haccu = []
- for i in ttff_dict.keys():
- ttff_time.append(ttff_dict[i]['ttff_sec'])
- ttff_pe.append(ttff_dict[i]['ttff_pe'])
- ttff_haccu.append(ttff_dict[i]['ttff_haccu'])
- df['ttff_sec'] = ttff_time
- df['ttff_pe'] = ttff_pe
- df['ttff_haccu'] = ttff_haccu
- df.to_json(gps_log_path + '/gps_log.json', orient='table')
+ ttff_data_df = DataFrame(ttff_dict).transpose()
+ ttff_data_df = ttff_data_df[[
+ 'ttff_loop', 'ttff_sec', 'ttff_pe', 'ttff_haccu'
+ ]]
+ try:
+ df_ttff_ffpe = merge(df_ttff_ffpe, ttff_data_df, left_on='loop', right_on='ttff_loop')
+ except: # pylint: disable=bare-except
+ self.log.warning("Can't merge ttff_data and df.")
+ ttff_data_df.to_json(gps_log_path + '/gps_log_ttff_data.json',
+ orient='table',
+ index=False)
+ df_ttff_ffpe.to_json(gps_log_path + '/gps_log.json', orient='table', index=False)
result = gutils.check_ttff_data(self.dut,
ttff_data,
ttff_mode=test_type.command,
criteria=test_type.criteria)
if not result:
- raise signals.TestFailure('%s TTFF fails to reach '
- 'designated criteria' % test_type.command)
+ raise signals.TestFailure(
+ f'{test_type.command} TTFF fails to reach '
+ 'designated criteria')
return ttff_data
def verify_pe(self, mode):
@@ -285,7 +363,7 @@
}
if mode not in ffpe_types:
- raise signals.TestError('Unrecognized mode %s' % mode)
+ raise signals.TestError(f'Unrecognized mode {mode}')
test_type = ffpe_types.get(mode)
ttff_data = self.get_and_verify_ttff(mode)
@@ -294,8 +372,9 @@
ttff_mode=test_type.command,
pe_criteria=test_type.pecriteria)
if not result:
- raise signals.TestFailure('%s TTFF fails to reach '
- 'designated criteria' % test_type.command)
+ raise signals.TestFailure(
+ f'{test_type.command} TTFF fails to reach '
+ 'designated criteria')
return ttff_data
def clear_gps_log(self):
@@ -303,9 +382,77 @@
Delete the existing GPS GTW Log from DUT.
"""
- self.dut.adb.shell("rm -rf {}".format(DEVICE_GPSLOG_FOLDER))
+ self.dut.adb.shell(f'rm -rf {DEVICE_GPSLOG_FOLDER}')
- def gnss_ttff_ffpe(self, mode, sub_context_path=''):
+ def start_dut_gnss_log(self):
+ """Start GNSS chip log according to different diag_option"""
+ # Start GNSS chip log
+ if self.diag_option == "QCOM":
+ diaglog.start_diagmdlog_background(self.dut, maskfile=self.maskfile)
+ else:
+ gutils.start_pixel_logger(self.dut)
+
+ def stop_and_pull_dut_gnss_log(self, gnss_vendor_log_path=None):
+ """
+ Stop DUT GNSS logger and pull log into local PC dir
+ Arg:
+ gnss_vendor_log_path: gnss log path directory.
+ Type, str.
+ Default, None
+ """
+ if not gnss_vendor_log_path:
+ gnss_vendor_log_path = self.gnss_log_path
+ if self.diag_option == "QCOM":
+ diaglog.stop_background_diagmdlog(self.dut,
+ gnss_vendor_log_path,
+ keep_logs=False)
+ else:
+ gutils.stop_pixel_logger(self.dut)
+ self.log.info('Getting Pixel BCM Log!')
+ diaglog.get_pixellogger_bcm_log(self.dut,
+ gnss_vendor_log_path,
+ keep_logs=False)
+
+ def start_gnss_and_wait(self, wait=60):
+ """
+ The process of enable gnss and spend the wait time for GNSS to
+ gather enoung information that make sure the stability of testing.
+
+ Args:
+ wait: wait time between power sweep.
+ Type, int.
+ Default, 60.
+ """
+ # Create log path for waiting section logs of GPStool.
+ gnss_wait_log_dir = os.path.join(self.gnss_log_path, 'GNSS_wait')
+
+ # Enable GNSS to receive satellites' signals for "wait_between_pwr" seconds.
+ self.log.info('Enable GNSS for searching satellites')
+ gutils.start_gnss_by_gtw_gpstool(self.dut, state=True)
+ self.log.info(f'Wait for {wait} seconds')
+ time.sleep(wait)
+
+ # Stop GNSS and pull the logs.
+ gutils.start_gnss_by_gtw_gpstool(self.dut, state=False)
+ diaglog.get_gpstool_logs(self.dut, gnss_wait_log_dir, False)
+
+ def exe_eecoexer_loop_cmd(self, cmd_list=None):
+ """
+ Function for execute EECoexer command list
+ Args:
+ cmd_list: a list of EECoexer function command.
+ Type, list.
+ """
+ if cmd_list:
+ for cmd in cmd_list:
+ self.log.info('Execute EEcoexer Command: {}'.format(cmd))
+ gutils.execute_eecoexer_function(self.dut, cmd)
+
+ def gnss_ttff_ffpe(self,
+ mode,
+ sub_context_path='',
+ coex_cmd='',
+ stop_coex_cmd=''):
"""
Base ttff and ffpe function
Args:
@@ -317,16 +464,24 @@
full_output_path = get_current_context().get_full_output_path()
self.gnss_log_path = os.path.join(full_output_path, sub_context_path)
os.makedirs(self.gnss_log_path, exist_ok=True)
- self.log.debug('Create log path: {}'.format(self.gnss_log_path))
+ self.log.debug(f'Create log path: {self.gnss_log_path}')
# Start and set GNSS simulator
- self.start_and_set_gnss_simulator_power()
+ self.start_set_gnss_power()
# Start GNSS chip log
- if self.diag_option == "QCOM":
- diaglog.start_diagmdlog_background(self.dut, maskfile=self.maskfile)
+ self.start_dut_gnss_log()
+
+ # Wait for acquiring almanac
+ if mode != 'cs':
+ wait_time = 900
else:
- gutils.start_pixel_logger(self.dut)
+ wait_time = 3
+ self.start_gnss_and_wait(wait=wait_time)
+
+ # Start Coex if available
+ if coex_cmd and stop_coex_cmd:
+ self.exe_eecoexer_loop_cmd(coex_cmd)
# Start verifying TTFF and FFPE
self.verify_pe(mode)
@@ -337,13 +492,8 @@
os.makedirs(gnss_vendor_log_path, exist_ok=True)
# Stop GNSS chip log and pull the logs to local file system
- if self.diag_option == "QCOM":
- diaglog.stop_background_diagmdlog(self.dut,
- gnss_vendor_log_path,
- keep_logs=False)
- else:
- gutils.stop_pixel_logger(self.dut)
- self.log.info('Getting Pixel BCM Log!')
- diaglog.get_pixellogger_bcm_log(self.dut,
- gnss_vendor_log_path,
- keep_logs=False)
+ self.stop_and_pull_dut_gnss_log(gnss_vendor_log_path)
+
+ # Stop Coex if available
+ if coex_cmd and stop_coex_cmd:
+ self.exe_eecoexer_loop_cmd(stop_coex_cmd)
diff --git a/acts_tests/acts_contrib/test_utils/gnss/gnss_constant.py b/acts_tests/acts_contrib/test_utils/gnss/gnss_constant.py
new file mode 100644
index 0000000..c7835ac
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/gnss_constant.py
@@ -0,0 +1 @@
+TTFF_MODE = {"cs": "Cold Start", "ws": "Warm Start", "hs": "Hot Start", "csa": "CSWith Assist"}
diff --git a/acts_tests/acts_contrib/test_utils/gnss/gnss_measurement.py b/acts_tests/acts_contrib/test_utils/gnss/gnss_measurement.py
new file mode 100644
index 0000000..a9f9579
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/gnss_measurement.py
@@ -0,0 +1,208 @@
+import os
+import pathlib
+import re
+import shutil
+import tempfile
+from collections import defaultdict
+
+
+class AdrInfo:
+ """Represent one ADR value
+ An ADR value is a decimal number range from 0 - 31
+
+ How to parse the ADR value:
+ First, transform the decimal number to binary then we will get a 5 bit number
+ The meaning of each bit is as follow:
+ 0 0 0 0 0
+ HalfCycleReported HalfCycleResolved CycleSlip Reset Valid
+ Special rule:
+ For an ADR value in binary fits the pattern: * * 0 0 1, we call it a usable ADR
+ More insight of ADR value:
+ go/adrstates
+
+ Attributes:
+ is_valid: (bool)
+ is_reset: (bool)
+ is_cycle_slip: (bool)
+ is_half_cycle_resolved: (bool)
+ is_half_cycle_reported: (bool)
+ is_usable: (bool)
+ """
+ def __init__(self, adr_value: int, count: int):
+ src = bin(int(adr_value))
+ self._valid = int(src[-1])
+ self._reset = int(src[-2])
+ self._cycle_slip = int(src[-3])
+ self._half_cycle_resolved = int(src[-4])
+ self._half_cycle_reported = int(src[-5])
+ self.count = count
+
+ @property
+ def is_usable(self):
+ return self.is_valid and not self.is_reset and not self.is_cycle_slip
+
+ @property
+ def is_valid(self):
+ return bool(self._valid)
+
+ @property
+ def is_reset(self):
+ return bool(self._reset)
+
+ @property
+ def is_cycle_slip(self):
+ return bool(self._cycle_slip)
+
+ @property
+ def is_half_cycle_resolved(self):
+ return bool(self._half_cycle_resolved)
+
+ @property
+ def is_half_cycle_reported(self):
+ return bool(self._half_cycle_reported)
+
+
+class AdrStatistic:
+ """Represent the ADR statistic
+
+ Attributes:
+ usable_count: (int)
+ valid_count: (int)
+ reset_count: (int)
+ cycle_slip_count: (int)
+ half_cycle_resolved_count: (int)
+ half_cycle_reported_count: (int)
+ total_count: (int)
+ usable_rate: (float)
+ usable_count / total_count
+ valid_rate: (float)
+ valid_count / total_count
+ """
+ def __init__(self):
+ self.usable_count = 0
+ self.valid_count = 0
+ self.reset_count = 0
+ self.cycle_slip_count = 0
+ self.half_cycle_resolved_count = 0
+ self.half_cycle_reported_count = 0
+ self.total_count = 0
+
+ @property
+ def usable_rate(self):
+ denominator = max(1, self.total_count)
+ result = self.usable_count / denominator
+ return round(result, 3)
+
+ @property
+ def valid_rate(self):
+ denominator = max(1, self.total_count)
+ result = self.valid_count / denominator
+ return round(result, 3)
+
+ def add_adr_info(self, adr_info: AdrInfo):
+ """Add ADR info record to increase the statistic
+
+ Args:
+ adr_info: AdrInfo object
+ """
+ if adr_info.is_valid:
+ self.valid_count += adr_info.count
+ if adr_info.is_reset:
+ self.reset_count += adr_info.count
+ if adr_info.is_cycle_slip:
+ self.cycle_slip_count += adr_info.count
+ if adr_info.is_half_cycle_resolved:
+ self.half_cycle_resolved_count += adr_info.count
+ if adr_info.is_half_cycle_reported:
+ self.half_cycle_reported_count += adr_info.count
+ if adr_info.is_usable:
+ self.usable_count += adr_info.count
+ self.total_count += adr_info.count
+
+
+
+class GnssMeasurement:
+ """Represent the content of measurement file generated by gps tool"""
+
+ FILE_PATTERN = "/storage/emulated/0/Android/data/com.android.gpstool/files/MEAS*.txt"
+
+ def __init__(self, ad):
+ self.ad = ad
+
+ def _generate_local_temp_path(self, file_name="file.txt"):
+ """Generate a file path for temporarily usage
+
+ Returns:
+ string: local file path
+ """
+ folder = tempfile.mkdtemp()
+ file_path = os.path.join(folder, file_name)
+ return file_path
+
+ def _get_latest_measurement_file_path(self):
+ """Get the latest measurement file path on device
+
+ Returns:
+ string: file path on device
+ """
+ command = f"ls -tr {self.FILE_PATTERN} | tail -1"
+ result = self.ad.adb.shell(command)
+ return result
+
+ def get_latest_measurement_file(self):
+ """Pull the latest measurement file from device to local
+
+ Returns:
+ string: local file path to the measurement file
+
+ Raise:
+ FileNotFoundError: can't get measurement file from device
+ """
+ self.ad.log.info("Get measurement file from device")
+ dest = self._generate_local_temp_path(file_name="measurement.txt")
+ src = self._get_latest_measurement_file_path()
+ if not src:
+ raise FileNotFoundError(f"Can not find measurement file: pattern {self.FILE_PATTERN}")
+ self.ad.pull_files(src, dest)
+ return dest
+
+ def _get_adr_src_value(self):
+ """Get ADR value from measurement file
+
+ Returns:
+ dict: {<ADR_value>: count, <ADR_value>: count...}
+ """
+ try:
+ file_path = self.get_latest_measurement_file()
+ adr_src = defaultdict(int)
+ adr_src_regex = re.compile("=\s(\d*)")
+ with open(file_path) as f:
+ for line in f:
+ if "AccumulatedDeltaRangeState" in line:
+ value = re.search(adr_src_regex, line)
+ if not value:
+ self.ad.log.warn("Can't get ADR value %s" % line)
+ continue
+ key = value.group(1)
+ adr_src[key] += 1
+ return adr_src
+ finally:
+ folder = pathlib.PurePosixPath(file_path).parent
+ shutil.rmtree(folder, ignore_errors=True)
+
+ def get_adr_static(self):
+ """Get ADR statistic
+
+ Summarize ADR value from measurement file
+
+ Returns:
+ AdrStatistic object
+ """
+ self.ad.log.info("Get ADR statistic")
+ adr_src = self._get_adr_src_value()
+ adr_static = AdrStatistic()
+ for key, value in adr_src.items():
+ self.ad.log.debug("ADR value: %s - count: %s" % (key, value))
+ adr_info = AdrInfo(key, value)
+ adr_static.add_adr_info(adr_info)
+ return adr_static
diff --git a/acts_tests/acts_contrib/test_utils/gnss/gnss_test_utils.py b/acts_tests/acts_contrib/test_utils/gnss/gnss_test_utils.py
index 5efa817..ec9ba61 100644
--- a/acts_tests/acts_contrib/test_utils/gnss/gnss_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/gnss/gnss_test_utils.py
@@ -13,31 +13,39 @@
# 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 time
import re
import os
+import pathlib
import math
import shutil
import fnmatch
import posixpath
+import subprocess
import tempfile
-import zipfile
+from retry import retry
from collections import namedtuple
from datetime import datetime
from xml.etree import ElementTree
+from contextlib import contextmanager
+from statistics import median
from acts import utils
from acts import asserts
from acts import signals
from acts.libs.proc import job
+from acts.controllers.adb_lib.error import AdbCommandError
from acts.controllers.android_device import list_adb_devices
from acts.controllers.android_device import list_fastboot_devices
from acts.controllers.android_device import DEFAULT_QXDM_LOG_PATH
from acts.controllers.android_device import SL4A_APK_NAME
+from acts_contrib.test_utils.gnss.gnss_measurement import GnssMeasurement
from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.tel import tel_logging_utils as tlutils
from acts_contrib.test_utils.tel import tel_test_utils as tutils
+from acts_contrib.test_utils.gnss import gnssstatus_utils
+from acts_contrib.test_utils.gnss import gnss_constant
+from acts_contrib.test_utils.gnss import supl
from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import InstrumentationCommandBuilder
from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import InstrumentationTestCommandBuilder
from acts.utils import get_current_epoch_time
@@ -46,6 +54,8 @@
from acts_contrib.test_utils.gnss.gnss_defines import BCM_NVME_STO_PATH
WifiEnums = wutils.WifiEnums
+FIRST_FIXED_MAX_WAITING_TIME = 60
+UPLOAD_TO_SPONGE_PREFIX = "TestResult "
PULL_TIMEOUT = 300
GNSSSTATUS_LOG_PATH = (
"/storage/emulated/0/Android/data/com.android.gpstool/files/")
@@ -54,7 +64,7 @@
"TTFF_REPORT", "utc_time ttff_loop ttff_sec ttff_pe ttff_ant_cn "
"ttff_base_cn ttff_haccu")
TRACK_REPORT = namedtuple(
- "TRACK_REPORT", "l5flag pe ant_top4cn ant_cn base_top4cn base_cn")
+ "TRACK_REPORT", "l5flag pe ant_top4cn ant_cn base_top4cn base_cn device_time report_time")
LOCAL_PROP_FILE_CONTENTS = """\
log.tag.LocationManagerService=VERBOSE
log.tag.GnssLocationProvider=VERBOSE
@@ -70,6 +80,11 @@
log.tag.GnssPsdsDownloader=VERBOSE
log.tag.Gnss=VERBOSE
log.tag.GnssConfiguration=VERBOSE"""
+LOCAL_PROP_FILE_CONTENTS_FOR_WEARABLE = """\
+log.tag.ImsPhone=VERBOSE
+log.tag.GsmCdmaPhone=VERBOSE
+log.tag.Phone=VERBOSE
+log.tag.GCoreFlp=VERBOSE"""
TEST_PACKAGE_NAME = "com.google.android.apps.maps"
LOCATION_PERMISSIONS = [
"android.permission.ACCESS_FINE_LOCATION",
@@ -99,6 +114,7 @@
XTRA_SERVER_2="http://"
XTRA_SERVER_3="http://"
"""
+_BRCM_DUTY_CYCLE_PATTERN = re.compile(r".*PGLOR,\d+,STA.*")
class GnssTestUtilsError(Exception):
@@ -129,7 +145,8 @@
ad: An AndroidDevice object.
"""
ad.log.info("Reboot device to make changes take effect.")
- ad.reboot()
+ # TODO(diegowchung): remove the timeout setting after p23 back to normal
+ ad.reboot(timeout=600)
ad.unlock_screen(password=None)
if not is_mobile_data_on(ad):
set_mobile_data(ad, True)
@@ -149,7 +166,11 @@
else:
ad.adb.shell("echo LogEnabled=true >> /data/vendor/gps/libgps.conf")
ad.adb.shell("chown gps.system /data/vendor/gps/libgps.conf")
- ad.adb.shell("echo %r >> /data/local.prop" % LOCAL_PROP_FILE_CONTENTS)
+ if is_device_wearable(ad):
+ PROP_CONTENTS = LOCAL_PROP_FILE_CONTENTS + LOCAL_PROP_FILE_CONTENTS_FOR_WEARABLE
+ else:
+ PROP_CONTENTS = LOCAL_PROP_FILE_CONTENTS
+ ad.adb.shell("echo %r >> /data/local.prop" % PROP_CONTENTS)
ad.adb.shell("chmod 644 /data/local.prop")
ad.adb.shell("setprop persist.logd.logpersistd.size 20000")
ad.adb.shell("setprop persist.logd.size 16777216")
@@ -221,12 +242,6 @@
remount_device(ad)
ad.log.info("Enable SUPL mode.")
ad.adb.shell("echo -e '\nSUPL_MODE=1' >> /etc/gps_debug.conf")
- if is_device_wearable(ad):
- lto_mode_wearable(ad, True)
- elif not check_chipset_vendor_by_qualcomm(ad):
- lto_mode(ad, True)
- else:
- reboot(ad)
def disable_supl_mode(ad):
@@ -238,16 +253,33 @@
remount_device(ad)
ad.log.info("Disable SUPL mode.")
ad.adb.shell("echo -e '\nSUPL_MODE=0' >> /etc/gps_debug.conf")
+ if not check_chipset_vendor_by_qualcomm(ad):
+ supl.set_supl_over_wifi_state(ad, False)
+
+
+def enable_vendor_orbit_assistance_data(ad):
+ """Enable vendor assistance features.
+ For Qualcomm: Enable XTRA
+ For Broadcom: Enable LTO
+
+ Args:
+ ad: An AndroidDevice object.
+ """
+ ad.root_adb()
if is_device_wearable(ad):
lto_mode_wearable(ad, True)
- elif not check_chipset_vendor_by_qualcomm(ad):
- lto_mode(ad, True)
- else:
+ elif check_chipset_vendor_by_qualcomm(ad):
+ disable_xtra_throttle(ad)
reboot(ad)
+ else:
+ lto_mode(ad, True)
-def kill_xtra_daemon(ad):
- """Kill XTRA daemon to test SUPL only test item.
+def disable_vendor_orbit_assistance_data(ad):
+ """Disable vendor assistance features.
+
+ For Qualcomm: disable XTRA
+ For Broadcom: disable LTO
Args:
ad: An AndroidDevice object.
@@ -256,11 +288,41 @@
if is_device_wearable(ad):
lto_mode_wearable(ad, False)
elif check_chipset_vendor_by_qualcomm(ad):
- ad.log.info("Disable XTRA-daemon until next reboot.")
- ad.adb.shell("killall xtra-daemon", ignore_status=True)
+ disable_qualcomm_orbit_assistance_data(ad)
else:
lto_mode(ad, False)
+def gla_mode(ad, state: bool):
+ """Enable or disable Google Location Accuracy feature.
+
+ Args:
+ ad: An AndroidDevice object.
+ state: True to enable GLA, False to disable GLA.
+ """
+ ad.root_adb()
+ if state:
+ ad.adb.shell('settings put global assisted_gps_enabled 1')
+ ad.log.info("Modify current GLA Mode to MS_BASED mode")
+ else:
+ ad.adb.shell('settings put global assisted_gps_enabled 0')
+ ad.log.info("Modify current GLA Mode to standalone mode")
+
+ out = int(ad.adb.shell("settings get global assisted_gps_enabled"))
+ if out == 1:
+ ad.log.info("GLA is enabled, MS_BASED mode")
+ else:
+ ad.log.info("GLA is disabled, standalone mode")
+
+
+def disable_qualcomm_orbit_assistance_data(ad):
+ """Disable assiatance features for Qualcomm project.
+
+ Args:
+ ad: An AndroidDevice object.
+ """
+ ad.log.info("Disable XTRA-daemon until next reboot.")
+ ad.adb.shell("killall xtra-daemon", ignore_status=True)
+
def disable_private_dns_mode(ad):
"""Due to b/118365122, it's better to disable private DNS mode while
@@ -282,26 +344,17 @@
Args:
ad: An AndroidDevice object.
"""
+ check_location_service(ad)
enable_gnss_verbose_logging(ad)
- enable_compact_and_particle_fusion_log(ad)
prepare_gps_overlay(ad)
- if check_chipset_vendor_by_qualcomm(ad):
- disable_xtra_throttle(ad)
- enable_supl_mode(ad)
- if is_device_wearable(ad):
- ad.adb.shell("settings put global stay_on_while_plugged_in 7")
- else:
- ad.adb.shell("settings put system screen_off_timeout 1800000")
- wutils.wifi_toggle_state(ad, False)
+ set_screen_always_on(ad)
ad.log.info("Setting Bluetooth state to False")
ad.droid.bluetoothToggleState(False)
- check_location_service(ad)
set_wifi_and_bt_scanning(ad, True)
disable_private_dns_mode(ad)
- reboot(ad)
init_gtw_gpstool(ad)
- if not is_mobile_data_on(ad):
- set_mobile_data(ad, True)
+ if is_device_wearable(ad):
+ disable_battery_defend(ad)
def prepare_gps_overlay(ad):
@@ -368,8 +421,14 @@
ad.ed.clear_all_events()
wutils.reset_wifi(ad)
wutils.start_wifi_connection_scan_and_ensure_network_found(ad, SSID)
- wutils.wifi_connect(ad, network, num_of_tries=5)
-
+ for i in range(5):
+ wutils.wifi_connect(ad, network, check_connectivity=False)
+ # Validates wifi connection with ping_gateway=False to avoid issue like
+ # b/254913994.
+ if wutils.validate_connection(ad, ping_gateway=False):
+ ad.log.info("WiFi connection is validated")
+ return
+ raise signals.TestError("Failed to connect WiFi")
def set_wifi_and_bt_scanning(ad, state=True):
"""Set Wi-Fi and Bluetooth scanning on/off in Settings -> Location
@@ -406,6 +465,22 @@
raise signals.TestError("Failed to turn Location on")
+def delete_device_folder(ad, folder):
+ ad.log.info("Folder to be deleted: %s" % folder)
+ folder_contents = ad.adb.shell(f"ls {folder}", ignore_status=True)
+ ad.log.debug("Contents to be deleted: %s" % folder_contents)
+ ad.adb.shell("rm -rf %s" % folder, ignore_status=True)
+
+
+def remove_pixel_logger_folder(ad):
+ if check_chipset_vendor_by_qualcomm(ad):
+ folder = "/sdcard/Android/data/com.android.pixellogger/files/logs/diag_logs"
+ else:
+ folder = "/sdcard/Android/data/com.android.pixellogger/files/logs/gps/"
+
+ delete_device_folder(ad, folder)
+
+
def clear_logd_gnss_qxdm_log(ad):
"""Clear /data/misc/logd,
/storage/emulated/0/Android/data/com.android.gpstool/files and
@@ -416,20 +491,21 @@
"""
remount_device(ad)
ad.log.info("Clear Logd, GNSS and PixelLogger Log from previous test item.")
- ad.adb.shell("rm -rf /data/misc/logd", ignore_status=True)
+ folders_should_be_removed = ["/data/misc/logd"]
ad.adb.shell(
'find %s -name "*.txt" -type f -delete' % GNSSSTATUS_LOG_PATH,
ignore_status=True)
if check_chipset_vendor_by_qualcomm(ad):
- diag_logs = (
- "/sdcard/Android/data/com.android.pixellogger/files/logs/diag_logs")
- ad.adb.shell("rm -rf %s" % diag_logs, ignore_status=True)
output_path = posixpath.join(DEFAULT_QXDM_LOG_PATH, "logs")
+ folders_should_be_removed += [output_path]
else:
- output_path = ("/sdcard/Android/data/com.android.pixellogger/files"
- "/logs/gps/")
- ad.adb.shell("rm -rf %s" % output_path, ignore_status=True)
- reboot(ad)
+ always_on_logger_log_path = ("/data/vendor/gps/logs")
+ folders_should_be_removed += [always_on_logger_log_path]
+ for folder in folders_should_be_removed:
+ delete_device_folder(ad, folder)
+ remove_pixel_logger_folder(ad)
+ if not is_device_wearable(ad):
+ reboot(ad)
def get_gnss_qxdm_log(ad, qdb_path=None):
@@ -589,14 +665,13 @@
ad.log.info("XTRA downloaded and injected successfully.")
return True
ad.log.error("XTRA downloaded FAIL.")
- elif is_device_wearable(ad):
- lto_results = ad.adb.shell("ls -al /data/vendor/gps/lto*")
- if "lto2.dat" in lto_results:
- ad.log.info("LTO downloaded and injected successfully.")
- return True
else:
- lto_results = ad.search_logcat("GnssPsdsAidl: injectPsdsData: "
- "psdsType: 1", begin_time)
+ if is_device_wearable(ad):
+ lto_results = ad.search_logcat("GnssLocationProvider: "
+ "calling native_inject_psds_data", begin_time)
+ else:
+ lto_results = ad.search_logcat("GnssPsdsAidl: injectPsdsData: "
+ "psdsType: 1", begin_time)
if lto_results:
ad.log.debug("%s" % lto_results[-1]["log_message"])
ad.log.info("LTO downloaded and injected successfully.")
@@ -776,7 +851,7 @@
def start_gnss_by_gtw_gpstool(ad,
state,
- type="gnss",
+ api_type="gnss",
bgdisplay=False,
freq=0,
lowpower=False,
@@ -786,7 +861,7 @@
Args:
ad: An AndroidDevice object.
state: True to start GNSS. False to Stop GNSS.
- type: Different API for location fix. Use gnss/flp/nmea
+ api_type: Different API for location fix. Use gnss/flp/nmea
bgdisplay: true to run GTW when Display off. false to not run GTW when
Display off.
freq: An integer to set location update frequency.
@@ -795,36 +870,42 @@
"""
cmd = "am start -S -n com.android.gpstool/.GPSTool --es mode gps"
if not state:
- ad.log.info("Stop %s on GTW_GPSTool." % type)
+ ad.log.info("Stop %s on GTW_GPSTool." % api_type)
cmd = "am broadcast -a com.android.gpstool.stop_gps_action"
else:
options = ("--es type {} --ei freq {} --ez BG {} --ez meas {} --ez "
- "lowpower {}").format(type, freq, bgdisplay, meas, lowpower)
+ "lowpower {}").format(api_type, freq, bgdisplay, meas, lowpower)
cmd = cmd + " " + options
- ad.adb.shell(cmd)
+ ad.adb.shell(cmd, ignore_status=True, timeout = 300)
time.sleep(3)
def process_gnss_by_gtw_gpstool(ad,
criteria,
- type="gnss",
+ api_type="gnss",
clear_data=True,
- meas_flag=False):
+ meas_flag=False,
+ freq=0,
+ bg_display=False):
"""Launch GTW GPSTool and Clear all GNSS aiding data
Start GNSS tracking on GTW_GPSTool.
Args:
ad: An AndroidDevice object.
criteria: Criteria for current test item.
- type: Different API for location fix. Use gnss/flp/nmea
+ api_type: Different API for location fix. Use gnss/flp/nmea
clear_data: True to clear GNSS aiding data. False is not to. Default
set to True.
meas_flag: True to enable GnssMeasurement. False is not to. Default
set to False.
+ freq: An integer to set location update frequency. Default set to 0.
+ bg_display: To enable GPS tool bg display or not
Returns:
- True: First fix TTFF are within criteria.
- False: First fix TTFF exceed criteria.
+ First fix datetime obj
+
+ Raises:
+ signals.TestFailure: when first fixed is over criteria or not even get first fixed
"""
retries = 3
for i in range(retries):
@@ -836,27 +917,28 @@
begin_time = get_current_epoch_time()
if clear_data:
clear_aiding_data_by_gtw_gpstool(ad)
- ad.log.info("Start %s on GTW_GPSTool - attempt %d" % (type.upper(),
+ ad.log.info("Start %s on GTW_GPSTool - attempt %d" % (api_type.upper(),
i+1))
- start_gnss_by_gtw_gpstool(ad, state=True, type=type, meas=meas_flag)
+ start_gnss_by_gtw_gpstool(ad, state=True, api_type=api_type, meas=meas_flag, freq=freq,
+ bgdisplay=bg_display)
for _ in range(10 + criteria):
logcat_results = ad.search_logcat("First fixed", begin_time)
if logcat_results:
ad.log.debug(logcat_results[-1]["log_message"])
first_fixed = int(logcat_results[-1]["log_message"].split()[-1])
ad.log.info("%s First fixed = %.3f seconds" %
- (type.upper(), first_fixed/1000))
+ (api_type.upper(), first_fixed/1000))
if (first_fixed/1000) <= criteria:
- return True
- start_gnss_by_gtw_gpstool(ad, state=False, type=type)
+ return logcat_results[-1]["datetime_obj"]
+ start_gnss_by_gtw_gpstool(ad, state=False, api_type=api_type)
raise signals.TestFailure("Fail to get %s location fixed "
"within %d seconds criteria."
- % (type.upper(), criteria))
+ % (api_type.upper(), criteria))
time.sleep(1)
check_current_focus_app(ad)
- start_gnss_by_gtw_gpstool(ad, state=False, type=type)
+ start_gnss_by_gtw_gpstool(ad, state=False, api_type=api_type)
raise signals.TestFailure("Fail to get %s location fixed within %d "
- "attempts." % (type.upper(), retries))
+ "attempts." % (api_type.upper(), retries))
def start_ttff_by_gtw_gpstool(ad,
@@ -866,7 +948,8 @@
raninterval=False,
mininterval=10,
maxinterval=40,
- hot_warm_sleep=300):
+ hot_warm_sleep=300,
+ timeout=60):
"""Identify which TTFF mode for different test items.
Args:
@@ -878,8 +961,12 @@
mininterval: Minimum value of random interval pool. The unit is second.
maxinterval: Maximum value of random interval pool. The unit is second.
hot_warm_sleep: Wait time for acquiring Almanac.
+ timeout: TTFF time out. The unit is second.
+ Returns:
+ latest_start_time: (Datetime) the start time of latest successful TTFF
"""
begin_time = get_current_epoch_time()
+ ad.log.debug("[start_ttff] Search logcat start time: %s" % begin_time)
if (ttff_mode == "hs" or ttff_mode == "ws") and not aid_data:
ad.log.info("Wait {} seconds to start TTFF {}...".format(
hot_warm_sleep, ttff_mode.upper()))
@@ -891,16 +978,32 @@
ad.log.info("Start TTFF CSWith Assist...")
time.sleep(3)
for i in range(1, 4):
- ad.adb.shell("am broadcast -a com.android.gpstool.ttff_action "
- "--es ttff {} --es cycle {} --ez raninterval {} "
- "--ei mininterval {} --ei maxinterval {}".format(
+ try:
+ ad.log.info(f"Before sending TTFF gms version is {get_gms_version(ad)}")
+ ad.adb.shell("am broadcast -a com.android.gpstool.ttff_action "
+ "--es ttff {} --es cycle {} --ez raninterval {} "
+ "--ei mininterval {} --ei maxinterval {}".format(
ttff_mode, iteration, raninterval, mininterval,
maxinterval))
+ except job.TimeoutError:
+ # If this is the last retry and we still get timeout error, raises the timeoutError.
+ if i == 3:
+ raise
+ # Currently we encounter lots of timeout issue in Qualcomm devices. But so far we don't
+ # know the root cause yet. In order to continue the test, we ignore the timeout for
+ # retry.
+ ad.log.warn("Send TTFF command timeout.")
+ ad.log.info(f"Current gms version is {get_gms_version(ad)}")
+ # Wait 2 second to retry
+ time.sleep(2)
+ continue
time.sleep(1)
- if ad.search_logcat("act=com.android.gpstool.start_test_action",
- begin_time):
+ result = ad.search_logcat("act=com.android.gpstool.start_test_action", begin_time)
+ if result:
+ ad.log.debug("TTFF start log %s" % result)
+ latest_start_time = max(list(map(lambda x: x['datetime_obj'], result)))
ad.log.info("Send TTFF start_test_action successfully.")
- break
+ return latest_start_time
else:
check_current_focus_app(ad)
raise signals.TestError("Fail to send TTFF start_test_action.")
@@ -908,31 +1011,73 @@
def gnss_tracking_via_gtw_gpstool(ad,
criteria,
- type="gnss",
+ api_type="gnss",
testtime=60,
- meas_flag=False):
+ meas_flag=False,
+ freq=0,
+ is_screen_off=False):
"""Start GNSS/FLP tracking tests for input testtime on GTW_GPSTool.
Args:
ad: An AndroidDevice object.
criteria: Criteria for current TTFF.
- type: Different API for location fix. Use gnss/flp/nmea
+ api_type: Different API for location fix. Use gnss/flp/nmea
testtime: Tracking test time for minutes. Default set to 60 minutes.
meas_flag: True to enable GnssMeasurement. False is not to. Default
set to False.
+ freq: An integer to set location update frequency. Default set to 0.
+ is_screen_off: whether to turn off during tracking
"""
process_gnss_by_gtw_gpstool(
- ad, criteria=criteria, type=type, meas_flag=meas_flag)
- ad.log.info("Start %s tracking test for %d minutes" % (type.upper(),
+ ad, criteria=criteria, api_type=api_type, meas_flag=meas_flag, freq=freq,
+ bg_display=is_screen_off)
+ ad.log.info("Start %s tracking test for %d minutes" % (api_type.upper(),
testtime))
begin_time = get_current_epoch_time()
+ with set_screen_status(ad, off=is_screen_off):
+ wait_n_mins_for_gnss_tracking(ad, begin_time, testtime, api_type)
+ ad.log.info("Successfully tested for %d minutes" % testtime)
+ start_gnss_by_gtw_gpstool(ad, state=False, api_type=api_type)
+
+
+def wait_n_mins_for_gnss_tracking(ad, begin_time, testtime, api_type="gnss",
+ ignore_hal_crash=False):
+ """Waits for GNSS tracking to finish and detect GNSS crash during the waiting time.
+
+ Args:
+ ad: An AndroidDevice object.
+ begin_time: The start time of tracking.
+ api_type: Different API for location fix. Use gnss/flp/nmea
+ testtime: Tracking test time for minutes.
+ ignore_hal_crash: To ignore HAL crash error no not.
+ """
while get_current_epoch_time() - begin_time < testtime * 60 * 1000:
- detect_crash_during_tracking(ad, begin_time, type)
- ad.log.info("Successfully tested for %d minutes" % testtime)
- start_gnss_by_gtw_gpstool(ad, state=False, type=type)
+ detect_crash_during_tracking(ad, begin_time, api_type, ignore_hal_crash)
+ # add sleep here to avoid too many request and cause device not responding
+ time.sleep(1)
+def run_ttff_via_gtw_gpstool(ad, mode, criteria, test_cycle, true_location):
+ """Run GNSS TTFF test with selected mode and parse the results.
-def parse_gtw_gpstool_log(ad, true_position, type="gnss"):
+ Args:
+ mode: "cs", "ws" or "hs"
+ criteria: Criteria for the TTFF.
+
+ Returns:
+ ttff_data: A dict of all TTFF data.
+ """
+ # Before running TTFF, we will run tracking and try to get first fixed.
+ # But the TTFF before TTFF doesn't apply to any criteria, so we set a maximum value.
+ process_gnss_by_gtw_gpstool(ad, criteria=FIRST_FIXED_MAX_WAITING_TIME)
+ ttff_start_time = start_ttff_by_gtw_gpstool(ad, mode, test_cycle)
+ ttff_data = process_ttff_by_gtw_gpstool(ad, ttff_start_time, true_location)
+ result = check_ttff_data(ad, ttff_data, gnss_constant.TTFF_MODE.get(mode), criteria)
+ asserts.assert_true(
+ result, "TTFF %s fails to reach designated criteria: %d "
+ "seconds." % (gnss_constant.TTFF_MODE.get(mode), criteria))
+ return ttff_data
+
+def parse_gtw_gpstool_log(ad, true_position, api_type="gnss", validate_gnssstatus=False):
"""Process GNSS/FLP API logs from GTW GPSTool and output track_data to
test_run_info for ACTS plugin to parse and display on MobileHarness as
Property.
@@ -941,8 +1086,14 @@
ad: An AndroidDevice object.
true_position: Coordinate as [latitude, longitude] to calculate
position error.
- type: Different API for location fix. Use gnss/flp/nmea
+ api_type: Different API for location fix. Use gnss/flp/nmea
+ validate_gnssstatus: Validate gnssstatus or not
+
+ Returns:
+ A dict of location reported from GPSTool
+ {<utc_time>: TRACK_REPORT, ...}
"""
+ gnssstatus_count = 0
test_logfile = {}
track_data = {}
ant_top4_cn = 0
@@ -952,11 +1103,13 @@
track_lat = 0
track_long = 0
l5flag = "false"
+ gps_datetime_pattern = re.compile("(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{0,5})")
+ gps_datetime_format = "%Y/%m/%d %H:%M:%S.%f"
file_count = int(ad.adb.shell("find %s -type f -iname *.txt | wc -l"
% GNSSSTATUS_LOG_PATH))
if file_count != 1:
- ad.log.error("%d API logs exist." % file_count)
- dir_file = ad.adb.shell("ls %s" % GNSSSTATUS_LOG_PATH).split()
+ ad.log.warn("%d API logs exist." % file_count)
+ dir_file = ad.adb.shell("ls -tr %s" % GNSSSTATUS_LOG_PATH).split()
for path_key in dir_file:
if fnmatch.fnmatch(path_key, "*.txt"):
logpath = posixpath.join(GNSSSTATUS_LOG_PATH, path_key)
@@ -970,7 +1123,20 @@
if not test_logfile:
raise signals.TestError("Failed to get test log file in device.")
lines = ad.adb.shell("cat %s" % test_logfile).split("\n")
+ gnss_svid_container = gnssstatus_utils.GnssSvidContainer()
for line in lines:
+ if line.startswith('Fix'):
+ try:
+ gnss_status = gnssstatus_utils.GnssStatus(line)
+ gnssstatus_count += 1
+ except gnssstatus_utils.RegexParseException as e:
+ ad.log.warn(e)
+ continue
+
+ gnss_svid_container.add_satellite(gnss_status)
+ if validate_gnssstatus:
+ gnss_status.validate_gnssstatus()
+
if "Antenna_History Avg Top4" in line:
ant_top4_cn = float(line.split(":")[-1].strip())
elif "Antenna_History Avg" in line:
@@ -985,8 +1151,13 @@
track_lat = float(line.split(":")[-1].strip())
elif "Longitude" in line:
track_long = float(line.split(":")[-1].strip())
+ elif "Read:" in line:
+ target = re.search(gps_datetime_pattern, line)
+ device_time = datetime.strptime(target.group(1), gps_datetime_format)
elif "Time" in line:
- track_utc = line.split("Time:")[-1].strip()
+ target = re.search(gps_datetime_pattern, line)
+ track_utc = target.group(1)
+ report_time = datetime.strptime(track_utc, gps_datetime_format)
if track_utc in track_data.keys():
continue
pe = calculate_position_error(track_lat, track_long, true_position)
@@ -995,9 +1166,13 @@
ant_top4cn=ant_top4_cn,
ant_cn=ant_cn,
base_top4cn=base_top4_cn,
- base_cn=base_cn)
+ base_cn=base_cn,
+ device_time=device_time,
+ report_time=report_time,
+ )
+ ad.log.info("Total %d gnssstatus samples verified" %gnssstatus_count)
ad.log.debug(track_data)
- prop_basename = "TestResult %s_tracking_" % type.upper()
+ prop_basename = UPLOAD_TO_SPONGE_PREFIX + f"{api_type.upper()}_tracking_"
time_list = sorted(track_data.keys())
l5flag_list = [track_data[key].l5flag for key in time_list]
pe_list = [float(track_data[key].pe) for key in time_list]
@@ -1016,9 +1191,84 @@
ad.log.info(prop_basename+"Ant_AvgSignal %.1f" % ant_cn_list[-1])
ad.log.info(prop_basename+"Base_AvgTop4Signal %.1f" % base_top4cn_list[-1])
ad.log.info(prop_basename+"Base_AvgSignal %.1f" % base_cn_list[-1])
+ _log_svid_info(gnss_svid_container, prop_basename, ad)
+ return track_data
-def process_ttff_by_gtw_gpstool(ad, begin_time, true_position, type="gnss"):
+def verify_gps_time_should_be_close_to_device_time(ad, tracking_result):
+ """Check the time gap between GPS time and device time.
+
+ In normal cases, the GPS time should be close to device time. But if GPS week rollover happens,
+ the GPS time may goes back to 20 years ago. In order to capture this issue, we assert the time
+ diff between the GPS time and device time.
+
+ Args:
+ ad: The device under test.
+ tracking_result: The result we get from GNSS tracking.
+ """
+ ad.log.info("Validating GPS/Device time difference")
+ max_time_diff_in_seconds = 2.0
+ exceed_report = []
+ for report in tracking_result.values():
+ time_diff_in_seconds = abs((report.report_time - report.device_time).total_seconds())
+ if time_diff_in_seconds > max_time_diff_in_seconds:
+ message = (f"GPS time: {report.report_time} Device time: {report.device_time} "
+ f"diff: {time_diff_in_seconds}")
+ exceed_report.append(message)
+ fail_message = (f"The following items exceed {max_time_diff_in_seconds}s\n" +
+ "\n".join(exceed_report))
+ asserts.assert_false(exceed_report, msg=fail_message)
+
+
+def validate_location_fix_rate(ad, location_reported, run_time, fix_rate_criteria):
+ """Check location reported count
+
+ The formula is "total_fix_points / (run_time * 60)"
+ When the result is lower than fix_rate_criteria, fail the test case
+
+ Args:
+ ad: AndroidDevice object
+ location_reported: (Enumerate) Contains the reported location
+ run_time: (int) How many minutes do we need to verify
+ fix_rate_criteria: The threshold of the pass criteria
+ if we expect fix rate to be 99%, then fix_rate_criteria should be 0.99
+ """
+ ad.log.info("Validating fix rate")
+ pass_criteria = run_time * 60 * fix_rate_criteria
+ actual_location_count = len(location_reported)
+
+ # The fix rate may exceed 100% occasionally, to standardlize the result
+ # set maximum fix rate to 100%
+ actual_fix_rate = min(1, (actual_location_count / (run_time * 60)))
+ actual_fix_rate_percentage = f"{actual_fix_rate:.0%}"
+
+ log_prefix = UPLOAD_TO_SPONGE_PREFIX + f"FIX_RATE_"
+ ad.log.info("%sresult %s" % (log_prefix, actual_fix_rate_percentage))
+ ad.log.debug("Actual location count %s" % actual_location_count)
+
+ fail_message = (f"Fail to meet criteria. Expect to have at least {pass_criteria} location count"
+ f" Actual: {actual_location_count}")
+ asserts.assert_true(pass_criteria <= actual_location_count, msg=fail_message)
+
+
+def _log_svid_info(container, log_prefix, ad):
+ """Write GnssSvidContainer svid information into logger
+ Args:
+ container: A GnssSvidContainer object
+ log_prefix:
+ A prefix used to specify the log will be upload to dashboard
+ ad: An AndroidDevice object
+ """
+ for sv_type, svids in container.used_in_fix.items():
+ message = f"{log_prefix}{sv_type} {len(svids)}"
+ ad.log.info(message)
+ ad.log.debug("Satellite used in fix %s ids are: %s", sv_type, svids)
+
+ for sv_type, svids in container.not_used_in_fix.items():
+ ad.log.debug("Satellite not used in fix %s ids are: %s", sv_type, svids)
+
+
+def process_ttff_by_gtw_gpstool(ad, begin_time, true_position, api_type="gnss"):
"""Process TTFF and record results in ttff_data.
Args:
@@ -1026,7 +1276,7 @@
begin_time: test begin time.
true_position: Coordinate as [latitude, longitude] to calculate
position error.
- type: Different API for location fix. Use gnss/flp/nmea
+ api_type: Different API for location fix. Use gnss/flp/nmea
Returns:
ttff_data: A dict of all TTFF data.
@@ -1051,7 +1301,7 @@
if ttff_sec != 0.0:
ttff_ant_cn = float(ttff_log[18].strip("]"))
ttff_base_cn = float(ttff_log[25].strip("]"))
- if type == "gnss":
+ if api_type == "gnss":
gnss_results = ad.search_logcat("GPSService: Check item",
begin_time)
if gnss_results:
@@ -1067,7 +1317,7 @@
utc_time = epoch_to_human_time(loc_time)
ttff_haccu = float(
gnss_location_log[11].split("=")[-1].strip(","))
- elif type == "flp":
+ elif api_type == "flp":
flp_results = ad.search_logcat("GPSService: FLP Location",
begin_time)
if flp_results:
@@ -1139,11 +1389,13 @@
% (len(ttff_data.keys()), ttff_mode))
ad.log.info("%s PASS criteria is %d seconds" % (ttff_mode, criteria))
ad.log.debug("%s TTFF data: %s" % (ttff_mode, ttff_data))
- ttff_property_key_and_value(ad, ttff_data, ttff_mode)
if len(ttff_data.keys()) == 0:
ad.log.error("GTW_GPSTool didn't process TTFF properly.")
- return False
- elif any(float(ttff_data[key].ttff_sec) == 0.0 for key in ttff_data.keys()):
+ raise ValueError("No ttff loop is done")
+
+ ttff_property_key_and_value(ad, ttff_data, ttff_mode)
+
+ if any(float(ttff_data[key].ttff_sec) == 0.0 for key in ttff_data.keys()):
ad.log.error("One or more TTFF %s Timeout" % ttff_mode)
return False
elif any(float(ttff_data[key].ttff_sec) >= criteria for key in
@@ -1165,6 +1417,7 @@
ttff_data: TTFF data of secs, position error and signal strength.
ttff_mode: TTFF Test mode for current test item.
"""
+ timeout_ttff = 61
prop_basename = "TestResult "+ttff_mode.replace(" ", "_")+"_TTFF_"
sec_list = [float(ttff_data[key].ttff_sec) for key in ttff_data.keys()]
pe_list = [float(ttff_data[key].ttff_pe) for key in ttff_data.keys()]
@@ -1175,12 +1428,14 @@
haccu_list = [float(ttff_data[key].ttff_haccu) for key in
ttff_data.keys()]
timeoutcount = sec_list.count(0.0)
+ sec_list = sorted(sec_list)
if len(sec_list) == timeoutcount:
- avgttff = 9527
+ median_ttff = avgttff = timeout_ttff
else:
avgttff = sum(sec_list)/(len(sec_list) - timeoutcount)
+ median_ttff = median(sec_list)
if timeoutcount != 0:
- maxttff = 9527
+ maxttff = timeout_ttff
else:
maxttff = max(sec_list)
avgdis = sum(pe_list)/len(pe_list)
@@ -1189,6 +1444,7 @@
base_avgcn = sum(base_cn_list)/len(base_cn_list)
avg_haccu = sum(haccu_list)/len(haccu_list)
ad.log.info(prop_basename+"AvgTime %.1f" % avgttff)
+ ad.log.info(prop_basename+"MedianTime %.1f" % median_ttff)
ad.log.info(prop_basename+"MaxTime %.1f" % maxttff)
ad.log.info(prop_basename+"TimeoutCount %d" % timeoutcount)
ad.log.info(prop_basename+"AvgDis %.1f" % avgdis)
@@ -1249,11 +1505,14 @@
Args:
ad: An AndroidDevice object.
+ Returns:
+ string: the current focused window / app
"""
time.sleep(1)
current = ad.adb.shell(
"dumpsys window | grep -E 'mCurrentFocus|mFocusedApp'")
ad.log.debug("\n"+current)
+ return current
def check_location_api(ad, retries):
@@ -1300,7 +1559,8 @@
criteria = criteria * 1000
search_pattern = ("GPSTool : networkLocationType = %s" % location_type)
for i in range(retries):
- begin_time = get_current_epoch_time()
+ # Capture the begin time 1 seconds before due to time gap.
+ begin_time = get_current_epoch_time() - 1000
ad.log.info("Try to get NLP status - attempt %d" % (i+1))
ad.adb.shell(
"am start -S -n com.android.gpstool/.GPSTool --es mode nlp")
@@ -1404,6 +1664,11 @@
"but audio is not in MUSIC state")
+def get_gms_version(ad):
+ cmd = "dumpsys package com.google.android.gms | grep versionName"
+ return ad.adb.shell(cmd).split("\n")[0].split("=")[1]
+
+
def get_baseband_and_gms_version(ad, extra_msg=""):
"""Get current radio baseband and GMSCore version of AndroidDevice object.
@@ -1417,9 +1682,7 @@
try:
build_version = ad.adb.getprop("ro.build.id")
baseband_version = ad.adb.getprop("gsm.version.baseband")
- gms_version = ad.adb.shell(
- "dumpsys package com.google.android.gms | grep versionName"
- ).split("\n")[0].split("=")[1]
+ gms_version = get_gms_version(ad)
if check_chipset_vendor_by_qualcomm(ad):
mpss_version = ad.adb.shell(
"cat /sys/devices/soc0/images | grep MPSS | cut -d ':' -f 3")
@@ -1947,7 +2210,7 @@
raise signals.TestError("Failed to launch EEcoexer.")
-def excute_eecoexer_function(ad, eecoexer_args):
+def execute_eecoexer_function(ad, eecoexer_args):
"""Execute EEcoexer commands.
Args:
@@ -1970,6 +2233,21 @@
ad.adb.shell(wait_for_cmd)
+def get_process_pid(ad, process_name):
+ """Gets the process PID
+
+ Args:
+ ad: The device under test
+ process_name: The name of the process
+
+ Returns:
+ The PID of the process
+ """
+ command = f"ps -A | grep {process_name} | awk '{{print $2}}'"
+ pid = ad.adb.shell(command)
+ return pid
+
+
def restart_gps_daemons(ad):
"""Restart GPS daemons by killing services of gpsd, lhd and scd.
@@ -1979,22 +2257,22 @@
gps_daemons_list = ["gpsd", "lhd", "scd"]
ad.root_adb()
for service in gps_daemons_list:
- begin_time = get_current_epoch_time()
time.sleep(3)
ad.log.info("Kill GPS daemon \"%s\"" % service)
- ad.adb.shell("killall %s" % service)
+ service_pid = get_process_pid(ad, service)
+ ad.log.debug("%s PID: %s" % (service, service_pid))
+ ad.adb.shell(f"kill -9 {service_pid}")
# Wait 3 seconds for daemons and services to start.
time.sleep(3)
- restart_services = ad.search_logcat("starting service", begin_time)
- for restart_service in restart_services:
- if service in restart_service["log_message"]:
- ad.log.info(restart_service["log_message"])
- ad.log.info(
- "GPS daemon \"%s\" restarts successfully." % service)
- break
- else:
+
+ new_pid = get_process_pid(ad, service)
+ ad.log.debug("%s new PID: %s" % (service, new_pid))
+ if not new_pid or service_pid == new_pid:
raise signals.TestError("Unable to restart \"%s\"" % service)
+ ad.log.info("GPS daemon \"%s\" restarts successfully. PID from %s to %s" % (
+ service, service_pid, new_pid))
+
def is_device_wearable(ad):
"""Check device is wearable project or not.
@@ -2044,6 +2322,21 @@
return None
+def _get_dpo_info_from_logcat(ad, begin_time):
+ """Gets the DPO info from logcat.
+
+ Args:
+ ad: The device under test.
+ begin_time: The start time of the log.
+ """
+ dpo_results = ad.search_logcat("HardwareClockDiscontinuityCount",
+ begin_time)
+ if not dpo_results:
+ raise signals.TestError(
+ "No \"HardwareClockDiscontinuityCount\" is found in logs.")
+ return dpo_results
+
+
def check_dpo_rate_via_gnss_meas(ad, begin_time, dpo_threshold):
"""Check DPO engage rate through "HardwareClockDiscontinuityCount" in
GnssMeasurement callback.
@@ -2054,11 +2347,7 @@
dpo_threshold: The value to set threshold. (Ex: dpo_threshold = 60)
"""
time_regex = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3})'
- dpo_results = ad.search_logcat("HardwareClockDiscontinuityCount",
- begin_time)
- if not dpo_results:
- raise signals.TestError(
- "No \"HardwareClockDiscontinuityCount\" is found in logs.")
+ dpo_results = _get_dpo_info_from_logcat(ad, begin_time)
ad.log.info(dpo_results[0]["log_message"])
ad.log.info(dpo_results[-1]["log_message"])
start_time = re.compile(
@@ -2083,13 +2372,14 @@
threshold))
-def parse_brcm_nmea_log(ad, nmea_pattern, brcm_error_log_allowlist):
+def parse_brcm_nmea_log(ad, nmea_pattern, brcm_error_log_allowlist, stop_logger=True):
"""Parse specific NMEA pattern out of BRCM NMEA log.
Args:
ad: An AndroidDevice object.
nmea_pattern: Specific NMEA pattern to parse.
brcm_error_log_allowlist: Benign error logs to exclude.
+ stop_logger: To stop pixel logger or not.
Returns:
brcm_log_list: A list of specific NMEA pattern logs.
@@ -2097,58 +2387,68 @@
brcm_log_list = []
brcm_log_error_pattern = ["lhd: FS: Start Failsafe dump", "E slog"]
brcm_error_log_list = []
- stop_pixel_logger(ad)
pixellogger_path = (
"/sdcard/Android/data/com.android.pixellogger/files/logs/gps/.")
- tmp_log_path = tempfile.mkdtemp()
- ad.pull_files(pixellogger_path, tmp_log_path)
- for path_key in os.listdir(tmp_log_path):
- zip_path = posixpath.join(tmp_log_path, path_key)
- if path_key.endswith(".zip"):
- ad.log.info("Processing zip file: {}".format(zip_path))
- with zipfile.ZipFile(zip_path, "r") as zip_file:
- zip_file.extractall(tmp_log_path)
- gl_logs = zip_file.namelist()
- # b/214145973 check if hidden exists in pixel logger zip file
- tmp_file = [name for name in gl_logs if 'tmp' in name]
- if tmp_file:
- ad.log.warn(f"Hidden file {tmp_file} exists in pixel logger zip file")
- break
- elif os.path.isdir(zip_path):
- ad.log.info("BRCM logs didn't zip properly. Log path is directory.")
- tmp_log_path = zip_path
- gl_logs = os.listdir(tmp_log_path)
- ad.log.info("Processing BRCM log files: {}".format(gl_logs))
- break
- else:
- raise signals.TestError(
- "No BRCM logs found in {}".format(os.listdir(tmp_log_path)))
- gl_logs = [log for log in gl_logs
- if log.startswith("gl") and log.endswith(".log")]
- for file in gl_logs:
- nmea_log_path = posixpath.join(tmp_log_path, file)
- ad.log.info("Parsing log pattern of \"%s\" in %s" % (nmea_pattern,
- nmea_log_path))
- brcm_log = open(nmea_log_path, "r", encoding="UTF-8", errors="ignore")
- lines = brcm_log.readlines()
- for line in lines:
- if nmea_pattern in line:
- brcm_log_list.append(line)
- for attr in brcm_log_error_pattern:
- if attr in line:
- benign_log = False
- for allow_log in brcm_error_log_allowlist:
- if allow_log in line:
- benign_log = True
- ad.log.info("\"%s\" is in allow-list and removed "
- "from error." % allow_log)
- if not benign_log:
- brcm_error_log_list.append(line)
+ if not isinstance(nmea_pattern, re.Pattern):
+ nmea_pattern = re.compile(nmea_pattern)
+
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ try:
+ ad.pull_files(pixellogger_path, tmp_dir)
+ except AdbCommandError:
+ raise FileNotFoundError("No pixel logger folders found")
+
+ # Although we don't rely on the zip file, stop pixel logger here to avoid
+ # wasting resources.
+ if stop_logger:
+ stop_pixel_logger(ad)
+
+ tmp_path = pathlib.Path(tmp_dir)
+ log_folders = sorted([x for x in tmp_path.iterdir() if x.is_dir()])
+ if not log_folders:
+ raise FileNotFoundError("No BRCM logs found.")
+ # The folder name is a string of datetime, the latest one will be in the last index.
+ gl_logs = log_folders[-1].glob("**/gl*.log")
+
+ for nmea_log_path in gl_logs:
+ ad.log.info("Parsing log pattern of \"%s\" in %s" % (nmea_pattern,
+ nmea_log_path))
+ with open(nmea_log_path, "r", encoding="UTF-8", errors="ignore") as lines:
+ for line in lines:
+ line = line.strip()
+ if nmea_pattern.fullmatch(line):
+ brcm_log_list.append(line)
+ for attr in brcm_log_error_pattern:
+ if attr in line:
+ benign_log = False
+ for regex_pattern in brcm_error_log_allowlist:
+ if re.search(regex_pattern, line):
+ benign_log = True
+ ad.log.debug("\"%s\" is in allow-list and removed "
+ "from error." % line)
+ if not benign_log:
+ brcm_error_log_list.append(line)
+
brcm_error_log = "".join(brcm_error_log_list)
- shutil.rmtree(tmp_log_path, ignore_errors=True)
return brcm_log_list, brcm_error_log
+def _get_power_mode_log_from_pixel_logger(ad, brcm_error_log_allowlist, stop_pixel_logger=True):
+ """Gets the power log from pixel logger.
+
+ Args:
+ ad: The device under test.
+ brcm_error_log_allow_list: The allow list to ignore certain error in pixel logger.
+ stop_pixel_logger: To disable pixel logger when getting the log.
+ """
+ pglor_list, brcm_error_log = parse_brcm_nmea_log(
+ ad, _BRCM_DUTY_CYCLE_PATTERN, brcm_error_log_allowlist, stop_pixel_logger)
+ if not pglor_list:
+ raise signals.TestFailure("Fail to get DPO logs from pixel logger")
+
+ return pglor_list, brcm_error_log
+
+
def check_dpo_rate_via_brcm_log(ad, dpo_threshold, brcm_error_log_allowlist):
"""Check DPO engage rate through "$PGLOR,11,STA" in BRCM Log.
D - Disabled, Always full power.
@@ -2164,10 +2464,7 @@
always_full_power_count = 0
full_power_count = 0
power_save_count = 0
- pglor_list, brcm_error_log = parse_brcm_nmea_log(
- ad, "$PGLOR,11,STA", brcm_error_log_allowlist)
- if not pglor_list:
- raise signals.TestFailure("Fail to get DPO logs from pixel logger")
+ pglor_list, brcm_error_log = _get_power_mode_log_from_pixel_logger(ad, brcm_error_log_allowlist)
for pglor in pglor_list:
power_res = re.compile(r',P,(\w),').search(pglor).group(1)
@@ -2199,74 +2496,77 @@
brcm_error_log))
-def pair_to_wearable(ad, ad1):
- """Pair phone to watch via Bluetooth.
+def process_pair(watch, phone):
+ """Pair phone to watch via Bluetooth in OOBE.
Args:
- ad: A pixel phone.
- ad1: A wearable project.
+ watch: A wearable project.
+ phone: A pixel phone.
"""
- check_location_service(ad1)
- utils.sync_device_time(ad1)
- bt_model_name = ad.adb.getprop("ro.product.model")
- bt_sn_name = ad.adb.getprop("ro.serialno")
+ check_location_service(phone)
+ utils.sync_device_time(phone)
+ bt_model_name = watch.adb.getprop("ro.product.model")
+ bt_sn_name = watch.adb.getprop("ro.serialno")
bluetooth_name = bt_model_name +" " + bt_sn_name[10:]
- fastboot_factory_reset(ad, False)
- ad.log.info("Wait 1 min for wearable system busy time.")
+ fastboot_factory_reset(watch, False)
+ # TODO (chenstanley)Need to re-structure for better code and test flow instead of simply waiting
+ watch.log.info("Wait 1 min for wearable system busy time.")
time.sleep(60)
- ad.adb.shell("input keyevent 4")
+ watch.adb.shell("input keyevent 4")
# Clear Denali paired data in phone.
- ad1.adb.shell("pm clear com.google.android.gms")
- ad1.adb.shell("pm clear com.google.android.apps.wear.companion")
- ad1.adb.shell("am start -S -n com.google.android.apps.wear.companion/"
+ phone.adb.shell("pm clear com.google.android.gms")
+ phone.adb.shell("pm clear com.google.android.apps.wear.companion")
+ phone.adb.shell("am start -S -n com.google.android.apps.wear.companion/"
"com.google.android.apps.wear.companion.application.RootActivity")
- uia_click(ad1, "Next")
- uia_click(ad1, "I agree")
- uia_click(ad1, bluetooth_name)
- uia_click(ad1, "Pair")
- uia_click(ad1, "Skip")
- uia_click(ad1, "Skip")
- uia_click(ad1, "Finish")
- ad.log.info("Wait 3 mins for complete pairing process.")
+ uia_click(phone, "Continue")
+ uia_click(phone, "More")
+ uia_click(phone, "I agree")
+ uia_click(phone, "I accept")
+ uia_click(phone, bluetooth_name)
+ uia_click(phone, "Pair")
+ uia_click(phone, "Skip")
+ uia_click(phone, "Next")
+ uia_click(phone, "Skip")
+ uia_click(phone, "Done")
+ # TODO (chenstanley)Need to re-structure for better code and test flow instead of simply waiting
+ watch.log.info("Wait 3 mins for complete pairing process.")
time.sleep(180)
- ad.adb.shell("settings put global stay_on_while_plugged_in 7")
- check_location_service(ad)
- enable_gnss_verbose_logging(ad)
- if is_bluetooth_connected(ad, ad1):
- ad.log.info("Pairing successfully.")
- else:
- raise signals.TestFailure("Fail to pair watch and phone successfully.")
+ set_screen_always_on(watch)
+ check_location_service(watch)
+ enable_gnss_verbose_logging(watch)
-def is_bluetooth_connected(ad, ad1):
+def is_bluetooth_connected(watch, phone):
"""Check if device's Bluetooth status is connected or not.
Args:
- ad: A wearable project
- ad1: A pixel phone.
+ watch: A wearable project
+ phone: A pixel phone.
"""
- return ad.droid.bluetoothIsDeviceConnected(ad1.droid.bluetoothGetLocalAddress())
+ return watch.droid.bluetoothIsDeviceConnected(phone.droid.bluetoothGetLocalAddress())
-def detect_crash_during_tracking(ad, begin_time, type):
+def detect_crash_during_tracking(ad, begin_time, api_type, ignore_hal_crash=False):
"""Check if GNSS or GPSTool crash happened druing GNSS Tracking.
Args:
ad: An AndroidDevice object.
begin_time: Start Time to check if crash happened in logs.
- type: Using GNSS or FLP reading method in GNSS tracking.
+ api_type: Using GNSS or FLP reading method in GNSS tracking.
+ ignore_hal_crash: In BRCM devices, once the HAL is being killed, it will write error/fatal logs.
+ Ignore this error if the error logs are expected.
"""
gnss_crash_list = [".*Fatal signal.*gnss",
- ".*Fatal signal.*xtra",
- ".*F DEBUG.*gnss",
- ".*Fatal signal.*gpsd"]
+ ".*Fatal signal.*xtra"]
+ if not ignore_hal_crash:
+ gnss_crash_list += [".*Fatal signal.*gpsd", ".*F DEBUG.*gnss"]
if not ad.is_adb_logcat_on:
ad.start_adb_logcat()
for attr in gnss_crash_list:
gnss_crash_result = ad.adb.shell(
- "logcat -d | grep -E -i '%s'" % attr)
+ "logcat -d | grep -E -i '%s'" % attr, ignore_status=True, timeout = 300)
if gnss_crash_result:
- start_gnss_by_gtw_gpstool(ad, state=False, type=type)
+ start_gnss_by_gtw_gpstool(ad, state=False, api_type=api_type)
raise signals.TestFailure(
"Test failed due to GNSS HAL crashed. \n%s" %
gnss_crash_result)
@@ -2381,57 +2681,103 @@
ad.log.info("Delete BCM's NVMEM ephemeris files.\n%s" % status)
-def bcm_gps_xml_add_option(ad,
+def bcm_gps_xml_update_option(ad,
+ option,
search_line=None,
append_txt=None,
+ update_txt=None,
+ delete_txt=None,
gps_xml_path=BCM_GPS_XML_PATH):
"""Append parameter setting in gps.xml for BCM solution
Args:
+ option: A str to identify the operation (add/update/delete).
ad: An AndroidDevice object.
search_line: Pattern matching of target
line for appending new line data.
- append_txt: New line that will be appended after the search_line.
+ append_txt: New lines that will be appended after the search_line.
+ update_txt: New line to update the original file.
+ delete_txt: lines to delete from the original file.
gps_xml_path: gps.xml file location of DUT
"""
remount_device(ad)
#Update gps.xml
- if not search_line or not append_txt:
- ad.log.info("Nothing for update.")
- else:
- tmp_log_path = tempfile.mkdtemp()
- ad.pull_files(gps_xml_path, tmp_log_path)
- gps_xml_tmp_path = os.path.join(tmp_log_path, "gps.xml")
- gps_xml_file = open(gps_xml_tmp_path, "r")
- lines = gps_xml_file.readlines()
- gps_xml_file.close()
- fout = open(gps_xml_tmp_path, "w")
- append_txt_tag = append_txt.strip()
+ tmp_log_path = tempfile.mkdtemp()
+ ad.pull_files(gps_xml_path, tmp_log_path)
+ gps_xml_tmp_path = os.path.join(tmp_log_path, "gps.xml")
+ gps_xml_file = open(gps_xml_tmp_path, "r")
+ lines = gps_xml_file.readlines()
+ gps_xml_file.close()
+ fout = open(gps_xml_tmp_path, "w")
+ if option == "add":
for line in lines:
- if append_txt_tag in line:
- ad.log.info('{} is already in the file. Skip'.format(append_txt))
+ if line.strip() in append_txt:
+ ad.log.info("{} is already in the file. Skip".format(append_txt))
continue
fout.write(line)
if search_line in line:
- fout.write(append_txt)
- ad.log.info("Update new line: '{}' in gps.xml.".format(append_txt))
- fout.close()
+ for add_txt in append_txt:
+ fout.write(add_txt)
+ ad.log.info("Add new line: '{}' in gps.xml.".format(add_txt))
+ elif option == "update":
+ for line in lines:
+ if search_line in line:
+ ad.log.info(line)
+ fout.write(update_txt)
+ ad.log.info("Update line: '{}' in gps.xml.".format(update_txt))
+ continue
+ fout.write(line)
+ elif option == "delete":
+ for line in lines:
+ if delete_txt in line:
+ ad.log.info("Delete line: '{}' in gps.xml.".format(line.strip()))
+ continue
+ fout.write(line)
+ fout.close()
- # Update gps.xml with gps_new.xml
- ad.push_system_file(gps_xml_tmp_path, gps_xml_path)
+ # Update gps.xml with gps_new.xml
+ ad.push_system_file(gps_xml_tmp_path, gps_xml_path)
- # remove temp folder
- shutil.rmtree(tmp_log_path, ignore_errors=True)
+ # remove temp folder
+ shutil.rmtree(tmp_log_path, ignore_errors=True)
+def bcm_gps_ignore_warmstandby(ad):
+ """ remove warmstandby setting in BCM gps.xml to reset tracking filter
+ Args:
+ ad: An AndroidDevice object.
+ """
+ search_line_tag = '<gll\n'
+ delete_line_str = 'WarmStandbyTimeout1Seconds'
+ bcm_gps_xml_update_option(ad,
+ "delete",
+ search_line_tag,
+ append_txt=None,
+ update_txt=None,
+ delete_txt=delete_line_str)
+
+ search_line_tag = '<gll\n'
+ delete_line_str = 'WarmStandbyTimeout2Seconds'
+ bcm_gps_xml_update_option(ad,
+ "delete",
+ search_line_tag,
+ append_txt=None,
+ update_txt=None,
+ delete_txt=delete_line_str)
def bcm_gps_ignore_rom_alm(ad):
""" Update BCM gps.xml with ignoreRomAlm="True"
Args:
ad: An AndroidDevice object.
"""
+ search_line_tag = '<hal\n'
+ append_line_str = [' IgnoreJniTime=\"true\"\n']
+ bcm_gps_xml_update_option(ad, "add", search_line_tag, append_line_str)
+
search_line_tag = '<gll\n'
- append_line_str = ' IgnoreRomAlm=\"true\"\n'
- bcm_gps_xml_add_option(ad, search_line_tag, append_line_str)
+ append_line_str = [' IgnoreRomAlm=\"true\"\n',
+ ' AutoColdStartSignal=\"SIMULATED\"\n',
+ ' IgnoreJniTime=\"true\"\n']
+ bcm_gps_xml_update_option(ad, "add", search_line_tag, append_line_str)
def check_inject_time(ad):
@@ -2449,35 +2795,477 @@
return True
raise signals.TestFailure("Fail to get time injected within %s attempts." % i)
+def recover_paired_status(watch, phone):
+ """Recover Bluetooth paired status if not paired.
-def enable_framework_log(ad):
- """Enable framework log for wearable to check UTC time download.
+ Args:
+ watch: A wearable project.
+ phone: A pixel phone.
+ """
+ for _ in range(3):
+ watch.log.info("Switch Bluetooth Off-On to recover paired status.")
+ for status in (False, True):
+ watch.droid.bluetoothToggleState(status)
+ phone.droid.bluetoothToggleState(status)
+ # TODO (chenstanley)Need to re-structure for better code and test flow instead of simply waiting
+ watch.log.info("Wait for Bluetooth auto re-connect.")
+ time.sleep(10)
+ if is_bluetooth_connected(watch, phone):
+ watch.log.info("Success to recover paired status.")
+ return True
+ raise signals.TestFailure("Fail to recover BT paired status in 3 attempts.")
+
+def push_lhd_overlay(ad):
+ """Push lhd_overlay.conf to device in /data/vendor/gps/overlay/
+
+ ad:
+ ad: An AndroidDevice object.
+ """
+ overlay_name = "lhd_overlay.conf"
+ overlay_asset = ad.adb.shell("ls /data/vendor/gps/overlay/")
+ if overlay_name in overlay_asset:
+ ad.log.info(f"{overlay_name} already in device, skip.")
+ return
+
+ temp_path = tempfile.mkdtemp()
+ file_path = os.path.join(temp_path, overlay_name)
+ lhd_content = 'Lhe477xDebugFlags=RPC:FACILITY=2097151:LOG_INFO:STDOUT_PUTS:STDOUT_LOG\n'\
+ 'LogLevel=*:E\nLogLevel=*:W\nLogLevel=*:I\nLog=LOGCAT\nLogEnabled=true\n'
+ overlay_path = "/data/vendor/gps/overlay/"
+ with open(file_path, "w") as f:
+ f.write(lhd_content)
+ ad.log.info("Push lhd_overlay to device")
+ ad.adb.push(file_path, overlay_path)
+
+
+def disable_ramdump(ad):
+ """Disable ramdump so device will reboot when about to enter ramdump
+
+ Once device enter ramdump, it will take a while to generate dump file
+ The process may take a while and block all the tests.
+ By disabling the ramdump mode, device will reboot instead of entering ramdump mode
Args:
ad: An AndroidDevice object.
"""
- remount_device(ad)
- time.sleep(3)
- ad.log.info("Start to enable framwork log for wearable.")
- ad.adb.shell("echo 'log.tag.LocationManagerService=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.GnssLocationProvider=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.GpsNetInitiatedHandler=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.GnssNetInitiatedHandler=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.GnssNetworkConnectivityHandler=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.NtpTimeHelper=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.ConnectivityService=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.GnssPsdsDownloader=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.GnssVisibilityControl=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.Gnss=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.GnssConfiguration=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.ImsPhone=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.GsmCdmaPhone=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.Phone=VERBOSE' >> /data/local.prop")
- ad.adb.shell("echo 'log.tag.GCoreFlp=VERBOSE' >> /data/local.prop")
- ad.adb.shell("chmod 644 /data/local.prop")
- ad.adb.shell("echo 'LogEnabled=true' > /data/vendor/gps/libgps.conf")
- ad.adb.shell("chown gps.system /data/vendor/gps/libgps.conf")
- ad.adb.shell("sync")
- reboot(ad)
- ad.log.info("Wait 2 mins for Wearable booting system busy")
- time.sleep(120)
+ ad.log.info("Enter bootloader mode")
+ ad.stop_services()
+ ad.adb.reboot("bootloader")
+ for _ in range(1,9):
+ if ad.is_bootloader:
+ break
+ time.sleep(1)
+ else:
+ raise signals.TestFailure("can't enter bootloader mode")
+ ad.log.info("Disable ramdump")
+ ad.fastboot.oem("ramdump disable")
+ ad.fastboot.reboot()
+ ad.wait_for_boot_completion()
+ ad.root_adb()
+ tutils.bring_up_sl4a(ad)
+ ad.start_adb_logcat()
+
+
+def deep_suspend_device(ad):
+ """Force DUT to enter deep suspend mode
+
+ When DUT is connected to PCs, it won't enter deep suspend mode
+ by pressing power button.
+
+ To force DUT enter deep suspend mode, we need to send the
+ following command to DUT "echo mem >/sys/power/state"
+
+ To make sure the DUT stays in deep suspend mode for a while,
+ it will send the suspend command 3 times with 15s interval
+
+ Args:
+ ad: An AndroidDevice object.
+ """
+ ad.log.info("Ready to go to deep suspend mode")
+ begin_time = get_device_time(ad)
+ ad.droid.goToSleepNow()
+ ensure_power_manager_is_dozing(ad, begin_time)
+ ad.stop_services()
+ try:
+ command = "echo deep > /sys/power/mem_sleep && echo mem > /sys/power/state"
+ for i in range(1, 4):
+ ad.log.debug(f"Send deep suspend command round {i}")
+ ad.adb.shell(command, ignore_status=True)
+ # sleep here to ensure the device stays enough time in deep suspend mode
+ time.sleep(15)
+ if not _is_device_enter_deep_suspend(ad):
+ raise signals.TestFailure("Device didn't enter deep suspend mode")
+ ad.log.info("Wake device up now")
+ except Exception:
+ # when exception happen, it's very likely the device is rebooting
+ # to ensure the test can go on, wait for the device is ready
+ ad.log.warn("Device may be rebooting, wait for it")
+ ad.wait_for_boot_completion()
+ ad.root_adb()
+ raise
+ finally:
+ tutils.bring_up_sl4a(ad)
+ ad.start_adb_logcat()
+ ad.droid.wakeUpNow()
+
+
+def get_device_time(ad):
+ """Get current datetime from device
+
+ Args:
+ ad: An AndroidDevice object.
+
+ Returns:
+ datetime object
+ """
+ result = ad.adb.shell("date +\"%Y-%m-%d %T.%3N\"")
+ return datetime.strptime(result, "%Y-%m-%d %H:%M:%S.%f")
+
+
+def ensure_power_manager_is_dozing(ad, begin_time):
+ """Check if power manager is in dozing
+ When device is sleeping, power manager should goes to doze mode.
+ To ensure that, we check the log every 1 second (maximum to 3 times)
+
+ Args:
+ ad: An AndroidDevice object.
+ begin_time: datetime, used as the starting point to search log
+ """
+ keyword = "PowerManagerService: Dozing"
+ ad.log.debug("Log search start time: %s" % begin_time)
+ for i in range(0,3):
+ result = ad.search_logcat(keyword, begin_time)
+ if result:
+ break
+ ad.log.debug("Power manager is not dozing... retry in 1 second")
+ time.sleep(1)
+ else:
+ ad.log.warn("Power manager didn't enter dozing")
+
+
+
+def _is_device_enter_deep_suspend(ad):
+ """Check device has been enter deep suspend mode
+
+ If device has entered deep suspend mode, we should be able to find keyword
+ "suspend entry (deep)"
+
+ Args:
+ ad: An AndroidDevice object.
+
+ Returns:
+ bool: True / False -> has / has not entered deep suspend
+ """
+ cmd = f"adb -s {ad.serial} logcat -d|grep \"suspend entry (deep)\""
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, shell=True)
+ result, _ = process.communicate()
+ ad.log.debug(f"suspend result = {result}")
+
+ return bool(result)
+
+
+def check_location_report_interval(ad, location_reported_time_src, total_seconds, tolerance):
+ """Validate the interval between two location reported time
+ Normally the interval should be around 1 second but occasionally it may up to nearly 2 seconds
+ So we set up a tolerance - 99% of reported interval should be less than 1.3 seconds
+
+ We validate the interval backward, because the wrong interval mostly happened at the end
+ Args:
+ ad: An AndroidDevice object.
+ location_reported_time_src: A list of reported time(in string) from GPS tool
+ total_seconds: (int) how many seconds has the GPS been enabled
+ tolerance: (float) set how many ratio of error should be accepted
+ if we want to set tolerance to be 1% then pass 0.01 as tolerance value
+ """
+ ad.log.info("Checking location report frequency")
+ error_count = 0
+ error_tolerance = max(1, int(total_seconds * tolerance))
+ expected_longest_interval = 1.3
+ location_reported_time = list(map(lambda x: datetime.strptime(x, "%Y/%m/%d %H:%M:%S.%f"),
+ location_reported_time_src))
+ location_reported_time = sorted(location_reported_time)
+ last_gps_report_time = location_reported_time[-1]
+ ad.log.debug("Location report time: %s" % location_reported_time)
+
+ for reported_time in reversed(location_reported_time):
+ time_diff = last_gps_report_time - reported_time
+ if time_diff.total_seconds() > expected_longest_interval:
+ error_count += 1
+ last_gps_report_time = reported_time
+
+ if error_count > error_tolerance:
+ fail_message = (f"Interval longer than {expected_longest_interval}s "
+ f"exceed tolerance count: {error_tolerance}, error count: {error_count}")
+ ad.log.error(fail_message)
+
+
+@contextmanager
+def set_screen_status(ad, off=True):
+ """Set screen on / off
+
+ A context manager function, can be used with "with" statement.
+ example:
+ with set_screen_status(ad, off=True):
+ do anything you want during screen is off
+ Once the function end, it will turn on the screen
+ Args:
+ ad: AndroidDevice object
+ off: (bool) True -> turn off screen / False -> leave screen as it is
+ """
+ try:
+ if off:
+ ad.droid.goToSleepNow()
+ yield ad
+ finally:
+ ad.droid.wakeUpNow()
+ ensure_device_screen_is_on(ad)
+
+
+@contextmanager
+def full_gnss_measurement(ad):
+ """Context manager function to enable full gnss measurement"""
+ try:
+ ad.adb.shell("settings put global enable_gnss_raw_meas_full_tracking 1")
+ yield ad
+ finally:
+ ad.adb.shell("settings put global enable_gnss_raw_meas_full_tracking 0")
+
+
+def ensure_device_screen_is_on(ad):
+ """Make sure the screen is on
+
+ Will try 3 times, each with 1 second interval
+
+ Raise:
+ GnssTestUtilsError: if screen can't be turn on after 3 tries
+ """
+ for _ in range(3):
+ # when NotificationShade appears in focus window, it indicates the screen is still off
+ if "NotificationShade" not in check_current_focus_app(ad):
+ break
+ time.sleep(1)
+ else:
+ raise GnssTestUtilsError("Device screen is not on after 3 tries")
+
+
+def start_qxdm_and_tcpdump_log(ad, enable):
+ """Start QXDM and adb tcpdump if collect_logs is True.
+ Args:
+ ad: AndroidDevice object
+ enable: (bool) True -> start collecting
+ False -> not start collecting
+ """
+ if enable:
+ start_pixel_logger(ad)
+ tlutils.start_adb_tcpdump(ad)
+
+
+def set_screen_always_on(ad):
+ """Ensure the sceen will not turn off and display the correct app screen
+ for wearable, we also disable the charing screen,
+ otherwise the charing screen will keep popping up and block the GPS tool
+ """
+ if is_device_wearable(ad):
+ ad.adb.shell("settings put global stay_on_while_plugged_in 7")
+ ad.adb.shell("setprop persist.enable_charging_experience false")
+ else:
+ ad.adb.shell("settings put system screen_off_timeout 1800000")
+
+
+def validate_adr_rate(ad, pass_criteria):
+ """Check the ADR rate
+
+ Args:
+ ad: AndroidDevice object
+ pass_criteria: (float) the passing ratio, 1 = 100%, 0.5 = 50%
+ """
+ adr_statistic = GnssMeasurement(ad).get_adr_static()
+
+ ad.log.info("ADR threshold: {0:.1%}".format(pass_criteria))
+ ad.log.info(UPLOAD_TO_SPONGE_PREFIX + "ADR_valid_rate {0:.1%}".format(adr_statistic.valid_rate))
+ ad.log.info(UPLOAD_TO_SPONGE_PREFIX +
+ "ADR_usable_rate {0:.1%}".format(adr_statistic.usable_rate))
+ ad.log.info(UPLOAD_TO_SPONGE_PREFIX + "ADR_total_count %s" % adr_statistic.total_count)
+ ad.log.info(UPLOAD_TO_SPONGE_PREFIX + "ADR_valid_count %s" % adr_statistic.valid_count)
+ ad.log.info(UPLOAD_TO_SPONGE_PREFIX + "ADR_reset_count %s" % adr_statistic.reset_count)
+ ad.log.info(UPLOAD_TO_SPONGE_PREFIX +
+ "ADR_cycle_slip_count %s" % adr_statistic.cycle_slip_count)
+ ad.log.info(UPLOAD_TO_SPONGE_PREFIX +
+ "ADR_half_cycle_reported_count %s" % adr_statistic.half_cycle_reported_count)
+ ad.log.info(UPLOAD_TO_SPONGE_PREFIX +
+ "ADR_half_cycle_resolved_count %s" % adr_statistic.half_cycle_resolved_count)
+
+ asserts.assert_true(
+ (pass_criteria < adr_statistic.valid_rate) and (pass_criteria < adr_statistic.usable_rate),
+ f"ADR valid rate: {adr_statistic.valid_rate:.1%}, "
+ f"ADR usable rate: {adr_statistic.usable_rate:.1%} "
+ f"Lower than expected: {pass_criteria:.1%}"
+ )
+
+
+def pair_to_wearable(watch, phone):
+ """Pair watch to phone.
+
+ Args:
+ watch: A wearable project.
+ phone: A pixel phone.
+ Raise:
+ TestFailure: If pairing process could not success after 3 tries.
+ """
+ for _ in range(3):
+ process_pair(watch, phone)
+ if is_bluetooth_connected(watch, phone):
+ watch.log.info("Pairing successfully.")
+ return True
+ raise signals.TestFailure("Pairing is not successfully.")
+
+
+def disable_battery_defend(ad):
+ """Disable battery defend config to prevent battery defend message pop up
+ after connecting to the same charger for 4 days in a row.
+
+ Args:
+ ad: A wearable project.
+ """
+ for _ in range(5):
+ remount_device(ad)
+ ad.adb.shell("setprop vendor.battery.defender.disable 1")
+ # To simulate cable unplug and the status will be recover after device reboot.
+ ad.adb.shell("cmd battery unplug")
+ # Sleep 3 seconds for waiting adb commend changes config and simulates cable unplug.
+ time.sleep(3)
+ config_setting = ad.adb.shell("getprop vendor.battery.defender.state")
+ if config_setting == "DISABLED":
+ ad.log.info("Disable Battery Defend setting successfully.")
+ break
+
+
+def restart_hal_service(ad):
+ """Restart HAL service by killing the pid.
+
+ Gets the pid by ps command and pass the pid to kill command. Then we get the pid of HAL service
+ again to see if the pid changes(pid should be different after HAL restart). If not, we will
+ retry up to 4 times before raising Test Failure.
+
+ Args:
+ ad: AndroidDevice object
+ """
+ ad.log.info("Restart HAL service")
+ hal_process_name = "'android.hardware.gnss@[[:digit:]]\{1,2\}\.[[:digit:]]\{1,2\}-service'"
+ hal_pid = get_process_pid(ad, hal_process_name)
+ ad.log.info("HAL pid: %s" % hal_pid)
+
+ # Retry kill process if the PID is the same as original one
+ for _ in range(4):
+ ad.log.info("Kill HAL service")
+ ad.adb.shell(f"kill -9 {hal_pid}")
+
+ # Waits for the HAL service to restart up to 4 seconds.
+ for _ in range(4):
+ new_hal_pid = get_process_pid(ad, hal_process_name)
+ ad.log.info("New HAL pid: %s" % new_hal_pid)
+ if new_hal_pid:
+ if hal_pid != new_hal_pid:
+ return
+ break
+ time.sleep(1)
+ else:
+ raise signals.TestFailure("HAL service can't be killed")
+
+
+def run_ttff(ad, mode, criteria, test_cycle, base_lat_long, collect_logs=False):
+ """Verify TTFF functionality with mobile data.
+
+ Args:
+ mode: "cs", "ws" or "hs"
+ criteria: Criteria for the test.
+
+ Returns:
+ ttff_data: A dict of all TTFF data.
+ """
+ start_qxdm_and_tcpdump_log(ad, collect_logs)
+ return run_ttff_via_gtw_gpstool(ad, mode, criteria, test_cycle, base_lat_long)
+
+
+def re_register_measurement_callback(dut):
+ """Send command to unregister then register measurement callback.
+
+ Args:
+ dut: The device under test.
+ """
+ dut.log.info("Reregister measurement callback")
+ dut.adb.shell("am broadcast -a com.android.gpstool.stop_meas_action")
+ time.sleep(1)
+ dut.adb.shell("am broadcast -a com.android.gpstool.start_meas_action")
+ time.sleep(1)
+
+
+def check_power_save_mode_status(ad, full_power, begin_time, brcm_error_allowlist):
+ """Checks the power save mode status.
+
+ For Broadcom:
+ Gets NEMA sentences from pixel logger and retrieve the status [F, S, D].
+ F,S => not in full power mode
+ D => in full power mode
+ For Qualcomm:
+ Gets the HardwareClockDiscontinuityCount from logcat. In full power mode, the
+ HardwareClockDiscontinuityCount should not be increased.
+
+ Args:
+ ad: The device under test.
+ full_power: The device is in full power mode or not.
+ begin_time: It is used to get the correct logcat information for qualcomm.
+ brcm_error_allowlist: It is used to ignore certain error in pixel logger.
+ """
+ if check_chipset_vendor_by_qualcomm(ad):
+ _check_qualcomm_power_save_mode(ad, full_power, begin_time)
+ else:
+ _check_broadcom_power_save_mode(ad, full_power, brcm_error_allowlist)
+
+
+def _check_qualcomm_power_save_mode(ad, full_power, begin_time):
+ dpo_results = _get_dpo_info_from_logcat(ad, begin_time)
+ first_dpo_count = int(dpo_results[0]["log_message"].split()[-1])
+ final_dpo_count = int(dpo_results[-1]["log_message"].split()[-1])
+ dpo_count_diff = final_dpo_count - first_dpo_count
+ ad.log.debug("The DPO count diff is {diff}".format(diff=dpo_count_diff))
+ if full_power:
+ asserts.assert_equal(dpo_count_diff, 0, msg="DPO count diff should be 0")
+ else:
+ asserts.assert_true(dpo_count_diff > 0, msg="DPO count diff should be more than 0")
+
+
+def _check_broadcom_power_save_mode(ad, full_power, brcm_error_allowlist):
+ power_save_log, _ = _get_power_mode_log_from_pixel_logger(
+ ad, brcm_error_allowlist, stop_pixel_logger=False)
+ power_status = re.compile(r',P,(\w),').search(power_save_log[-2]).group(1)
+ ad.log.debug("The power status is {status}".format(status=power_status))
+ if full_power:
+ asserts.assert_true(power_status == "D", msg="Should be in full power mode")
+ else:
+ asserts.assert_true(power_status in ["F", "S"], msg="Should not be in full power mode")
+
+@contextmanager
+def run_gnss_tracking(ad, criteria, meas_flag):
+ """A context manager to enable gnss tracking and stops at the end.
+
+ Args:
+ ad: The device under test.
+ criteria: The criteria for First Fixed.
+ meas_flag: A flag to turn on measurement log or not.
+ """
+ process_gnss_by_gtw_gpstool(ad, criteria=criteria, meas_flag=meas_flag)
+ try:
+ yield
+ finally:
+ start_gnss_by_gtw_gpstool(ad, state=False)
+
+def log_current_epoch_time(ad, sponge_key):
+ """Logs current epoch timestamp in second.
+
+ Args:
+ sponge_key: The key name of the sponge property.
+ """
+ current_epoch_time = get_current_epoch_time() // 1000
+ ad.log.info(f"TestResult {sponge_key} {current_epoch_time}")
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/gnss/gnss_testlog_utils.py b/acts_tests/acts_contrib/test_utils/gnss/gnss_testlog_utils.py
index 4f5df3c..5e7cf26 100644
--- a/acts_tests/acts_contrib/test_utils/gnss/gnss_testlog_utils.py
+++ b/acts_tests/acts_contrib/test_utils/gnss/gnss_testlog_utils.py
@@ -209,17 +209,17 @@
configs=CONFIG_GPSTTFFLOG,
)
ttff_df = parsed_data['ttff_info']
-
- # Data Conversion
- ttff_df['loop'] = ttff_df['loop'].astype(int)
- ttff_df['start_datetime'] = pds.to_datetime(ttff_df['start_datetime'])
- ttff_df['stop_datetime'] = pds.to_datetime(ttff_df['stop_datetime'])
- ttff_df['ttff_time'] = ttff_df['ttff'].astype(float)
- ttff_df['ant_avg_top4_cn0'] = ttff_df['ant_avg_top4_cn0'].astype(float)
- ttff_df['ant_avg_cn0'] = ttff_df['ant_avg_cn0'].astype(float)
- ttff_df['bb_avg_top4_cn0'] = ttff_df['bb_avg_top4_cn0'].astype(float)
- ttff_df['bb_avg_cn0'] = ttff_df['bb_avg_cn0'].astype(float)
- ttff_df['satnum_for_fix'] = ttff_df['satnum_for_fix'].astype(int)
+ if not ttff_df.empty:
+ # Data Conversion
+ ttff_df['loop'] = ttff_df['loop'].astype(int)
+ ttff_df['start_datetime'] = pds.to_datetime(ttff_df['start_datetime'])
+ ttff_df['stop_datetime'] = pds.to_datetime(ttff_df['stop_datetime'])
+ ttff_df['ttff_time'] = ttff_df['ttff'].astype(float)
+ ttff_df['ant_avg_top4_cn0'] = ttff_df['ant_avg_top4_cn0'].astype(float)
+ ttff_df['ant_avg_cn0'] = ttff_df['ant_avg_cn0'].astype(float)
+ ttff_df['bb_avg_top4_cn0'] = ttff_df['bb_avg_top4_cn0'].astype(float)
+ ttff_df['bb_avg_cn0'] = ttff_df['bb_avg_cn0'].astype(float)
+ ttff_df['satnum_for_fix'] = ttff_df['satnum_for_fix'].astype(int)
# return ttff dataframe
return ttff_df
diff --git a/acts_tests/acts_contrib/test_utils/gnss/gnssstatus_utils.py b/acts_tests/acts_contrib/test_utils/gnss/gnssstatus_utils.py
new file mode 100644
index 0000000..457ba86
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/gnssstatus_utils.py
@@ -0,0 +1,200 @@
+#!/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.
+import re
+from acts import signals
+from collections import defaultdict
+
+SVID_RANGE = {
+ 'GPS': [(1, 32)],
+ 'SBA': [(120, 192)],
+ 'GLO': [(1, 24), (93, 106)],
+ 'QZS': [(193, 200)],
+ 'BDS': [(1, 63)],
+ 'GAL': [(1, 36)],
+ 'NIC': [(1, 14)]
+}
+
+CARRIER_FREQUENCIES = {
+ 'GPS': {
+ 'L1': [1575.42],
+ 'L5': [1176.45]
+ },
+ 'SBA': {
+ 'L1': [1575.42]
+ },
+ 'GLO': {
+ 'L1': [round((1602 + i * 0.5625), 3) for i in range(-7, 7)]
+ },
+ 'QZS': {
+ 'L1': [1575.42],
+ 'L5': [1176.45]
+ },
+ 'BDS': {
+ 'B1': [1561.098],
+ 'B2a': [1176.45]
+ },
+ 'GAL': {
+ 'E1': [1575.42],
+ 'E5a': [1176.45]
+ },
+ 'NIC': {
+ 'L5': [1176.45]
+ }
+}
+
+
+class RegexParseException(Exception):
+ pass
+
+
+class GnssSvidContainer:
+ """A class to hold the satellite svid information
+
+ Attributes:
+ used_in_fix: A dict contains unique svid used in fixing location
+ not_used_in_fix: A dict contains unique svid not used in fixing location
+ """
+
+ def __init__(self):
+ self.used_in_fix = defaultdict(set)
+ self.not_used_in_fix = defaultdict(set)
+
+ def add_satellite(self, gnss_status):
+ """Add satellite svid into container
+
+ According to the attributes gnss_status.used_in_fix
+ True: add svid into self.used_in_fix container
+ False: add svid into self.not_used_in_fix container
+
+ Args:
+ gnss_status: A GnssStatus object
+ """
+ key = f'{gnss_status.constellation}_{gnss_status.frequency_band}'
+ if gnss_status.used_in_fix:
+ self.used_in_fix[key].add(gnss_status.svid)
+ else:
+ self.not_used_in_fix[key].add(gnss_status.svid)
+
+
+class GnssStatus:
+ """GnssStatus object, it will create an obj with a raw gnssstatus line.
+
+ Attributes:
+ raw_message: (string) The raw log from GSPTool
+ example:
+ Fix: true Type: NIC SV: 4 C/No: 45.10782, 40.9 Elevation: 78.0
+ Azimuth: 291.0
+ Signal: L5 Frequency: 1176.45 EPH: true ALM: false
+ Fix: false Type: GPS SV: 27 C/No: 34.728134, 30.5 Elevation:
+ 76.0 Azimuth: 15.0
+ Signal: L1 Frequency: 1575.42 EPH: true ALM: true
+ used_in_fix: (boolean) Whether or not this satellite info is used to fix
+ location
+ constellation: (string) The constellation type i.e. GPS
+ svid: (int) The unique id of the constellation
+ cn: (float) The C/No value from antenna
+ base_cn: (float) The C/No value from baseband
+ elev: (float) The value of elevation
+ azim: (float) The value of azimuth
+ frequency_band: (string) The frequency_type of the constellation i.e. L1
+ / L5
+ """
+
+ gnssstatus_re = (
+ r'Fix: (.*) Type: (.*) SV: (.*) C/No: (.*), (.*) '
+ r'Elevation: (.*) Azimuth: (.*) Signal: (.*) Frequency: (.*) EPH')
+ failures = []
+
+ def __init__(self, gnssstatus_raw):
+ status_res = re.search(self.gnssstatus_re, gnssstatus_raw)
+ if not status_res:
+ raise RegexParseException(f'Gnss raw msg parse fail:\n{gnssstatus_raw}\n'
+ f'Please check it manually.')
+ self.raw_message = gnssstatus_raw
+ self.used_in_fix = status_res.group(1).lower() == 'true'
+ self.constellation = status_res.group(2)
+ self.svid = int(status_res.group(3))
+ self.cn = float(status_res.group(4))
+ self.base_cn = float(status_res.group(5))
+ self.elev = float(status_res.group(6))
+ self.azim = float(status_res.group(7))
+ self.frequency_band = status_res.group(8)
+ self.carrier_frequency = float(status_res.group(9))
+
+ def validate_gnssstatus(self):
+ """A validate function for each property."""
+ self._validate_sv()
+ self._validate_cn()
+ self._validate_elev()
+ self._validate_azim()
+ self._validate_carrier_frequency()
+ if self.failures:
+ failure_info = '\n'.join(self.failures)
+ raise signals.TestFailure(
+ f'Gnsstatus validate failed:\n{self.raw_message}\n{failure_info}'
+ )
+
+ def _validate_sv(self):
+ """A validate function for SV ID."""
+ if not self.constellation in SVID_RANGE.keys():
+ raise signals.TestFailure(
+ f'Satellite identify fail: {self.constellation}')
+ for id_range in SVID_RANGE[self.constellation]:
+ if id_range[0] <= self.svid <= id_range[1]:
+ break
+ else:
+ fail_details = f'{self.constellation} ID {self.svid} not in SV Range'
+ self.failures.append(fail_details)
+
+ def _validate_cn(self):
+ """A validate function for CN value."""
+ if not 0 <= self.cn <= 63:
+ self.failures.append(f'Ant CN not in range: {self.cn}')
+ if not 0 <= self.base_cn <= 63:
+ self.failures.append(f'Base CN not in range: {self.base_cn}')
+
+ def _validate_elev(self):
+ """A validate function for Elevation (should between 0-90)."""
+ if not 0 <= self.elev <= 90:
+ self.failures.append(f'Elevation not in range: {self.elev}')
+
+ def _validate_azim(self):
+ """A validate function for Azimuth (should between 0-360)."""
+ if not 0 <= self.azim <= 360:
+ self.failures.append(f'Azimuth not in range: {self.azim}')
+
+ def _validate_carrier_frequency(self):
+ """A validate function for carrier frequency (should fall in below range).
+
+ 'GPS': L1:1575.42, L5:1176.45
+ 'SBA': L1:1575.42
+ 'GLO': L1:Between 1598.0625 and 1605.375
+ 'QZS': L1:1575.42, L5:1176.45
+ 'BDS': B1:1561.098, B2a:1176.45
+ 'GAL': E1:1575.42, E5a:1176.45
+ 'NIC': L5:1176.45
+ """
+ if self.frequency_band in CARRIER_FREQUENCIES[
+ self.constellation].keys():
+ target_freq = CARRIER_FREQUENCIES[self.constellation][
+ self.frequency_band]
+ else:
+ raise signals.TestFailure(
+ f'Carrier frequency identify fail: {self.frequency_band}')
+ if not self.carrier_frequency in target_freq:
+ self.failures.append(
+ f'{self.constellation}_{self.frequency_band} carrier'
+ f'frequency not in range: {self.carrier_frequency}')
diff --git a/acts_tests/acts_contrib/test_utils/gnss/supl.py b/acts_tests/acts_contrib/test_utils/gnss/supl.py
new file mode 100644
index 0000000..f6c1bc6
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/supl.py
@@ -0,0 +1,71 @@
+import os
+import tempfile
+from xml.etree import ElementTree
+
+
+def set_supl_over_wifi_state(ad, turn_on):
+ """Enable / Disable supl over wifi features
+
+ Modify the gps xml file: /vendor/etc/gnss/gps.xml
+ Args:
+ ad: AndroidDevice object
+ turn_on: (bool) True -> enable / False -> disable
+ """
+ ad.adb.remount()
+ folder = tempfile.mkdtemp()
+ xml_path_on_host = os.path.join(folder, "gps.xml")
+ xml_path_on_device = "/vendor/etc/gnss/gps.xml"
+ ad.pull_files(xml_path_on_device, xml_path_on_host)
+
+ # register namespance to aviod adding ns0 into xml attributes
+ ElementTree.register_namespace("", "http://www.glpals.com/")
+ xml_tree = ElementTree.parse(xml_path_on_host)
+ root = xml_tree.getroot()
+ for node in root:
+ if "hal" in node.tag:
+ if turn_on:
+ _enable_supl_over_wifi(ad, node)
+ else:
+ _disable_supl_over_wifi(ad, node)
+ xml_tree.write(xml_path_on_host, xml_declaration=True, encoding="utf-8", method="xml")
+ ad.push_system_file(xml_path_on_host, xml_path_on_device)
+
+
+def _enable_supl_over_wifi(ad, node):
+ """Enable supl over wifi
+ Detail setting:
+ <hal
+ SuplDummyCellInfo="true"
+ SuplUseApn="false"
+ SuplUseApnNI="true"
+ SuplUseFwCellInfo="false"
+ />
+ Args:
+ ad: AndroidDevice object
+ node: ElementTree node
+ """
+ ad.log.info("Enable SUPL over wifi")
+ attributes = {"SuplDummyCellInfo": "true", "SuplUseApn": "false", "SuplUseApnNI": "true",
+ "SuplUseFwCellInfo": "false"}
+ for key, value in attributes.items():
+ node.set(key, value)
+
+
+def _disable_supl_over_wifi(ad, node):
+ """Disable supl over wifi
+ Detail setting:
+ <hal
+ SuplUseApn="true"
+ />
+ Remove following setting
+ SuplDummyCellInfo="true"
+ SuplUseApnNI="true"
+ SuplUseFwCellInfo="false"
+ Args:
+ ad: AndroidDevice object
+ node: ElementTree node
+ """
+ ad.log.info("Disable SUPL over wifi")
+ for attri in ["SuplDummyCellInfo", "SuplUseApnNI", "SuplUseFwCellInfo"]:
+ node.attrib.pop(attri, None)
+ node.set("SuplUseApn", "true")
diff --git a/acts_tests/acts_contrib/test_utils/gnss/testtracker_util.py b/acts_tests/acts_contrib/test_utils/gnss/testtracker_util.py
new file mode 100644
index 0000000..43f7920
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/testtracker_util.py
@@ -0,0 +1,88 @@
+TEST_NAME_BY_TESTTRACKER_UUID = {
+ # GnssFunctionTest
+ "test_cs_first_fixed_system_server_restart": "8169c19d-ba2a-4fef-969b-87f793f4e699",
+ "test_cs_ttff_after_gps_service_restart": "247110d9-1c9e-429e-8e73-f16dd4a1ac74",
+ "test_gnss_one_hour_tracking": "b3d20ecb-3727-48ed-8a03-19694cc726c1",
+ "test_duty_cycle_function": "0bbfb818-da93-41d7-8d83-15bc53d8d2cf",
+ "test_gnss_init_error": "c661780d-4864-4292-9988-88f64448fb78",
+ "test_sap_valid_modes": "89bb8103-a3af-4953-8f07-e43c7e829bdd",
+ "test_network_location_provider_cell": "6f59d0f5-569c-4d52-990b-0042123b70ab",
+ "test_network_location_provider_wifi": "eec8b4bd-6990-4098-ad7a-acc19574bdee",
+ "test_gmap_location_report_battery_saver": "040556bf-1ffc-4db2-b2c5-19c4da19a256",
+ "test_gnss_ttff_cs_airplane_mode_on": "bc3d509c-0392-4af1-a0d0-68fd01167573",
+ "test_gnss_ttff_ws_airplane_mode_on": "dcafc69a-095e-4d58-8afb-5276c5763f4d",
+ "test_gnss_ttff_hs_airplane_mode_on": "090ea66c-19a1-4d0b-8c7e-dbc967597764",
+ "test_cs_ttff_in_weak_gnss_signal": "1980f980-3134-47b0-8dd8-9c5af6b742a6",
+ "test_ws_ttff_in_weak_gnss_signal": "d77c8db1-687b-48c5-8101-e4267da05995",
+ "test_hs_ttff_in_weak_gnss_signal": "dd4ccd93-9e49-45c2-a3ea-40781a40b820",
+ "test_quick_toggle_gnss_state": "36c14727-5de7-4589-ad1b-9119f9d9bb52",
+ "test_gnss_init_after_reboot": "79be8ab6-26cb-4d1a-b3d3-4e5681766901",
+ "test_host_gnssstatus_validation": "767c3024-0db4-4d40-9b03-f30355d72a06",
+ "test_onchip_gnssstatus_validation": "afb08722-2c79-46a6-80fd-9ede5018e384",
+ "test_location_update_after_resuming_from_deep_suspend": "140a7763-f42c-4917-a71f-fbc0626c1609",
+ "test_location_mode_in_battery_saver_with_screen_off": "04b529f1-a99d-4b18-9bba-41e008249f7a",
+ "test_measure_adr_rate_after_10_mins_tracking": "7ebf3b52-229a-4eaf-bbff-7c527e4a1d7c",
+ "test_hal_crashing_should_resume_tracking": "0aee4450-edce-4e1a-8744-70d8c01937b0",
+ "test_power_save_mode_should_apply_latest_measurement_setting": "59a14da2-40df-4106-a190-dcbcd2e877e0",
+ # GnssConcurrencyTest
+ "test_gnss_concurrency_location_1_chre_1": "cbd9ff54-4405-44a4-ac20-de33278406d1",
+ "test_gnss_concurrency_location_1_chre_8": "ab56cb47-384e-4269-b2d8-6e80ce066de2",
+ "test_gnss_concurrency_location_15_chre_8": "e64fa984-6219-43dd-96d6-d4141b2da1cd",
+ "test_gnss_concurrency_location_61_chre_1": "217f3ab6-25c9-4092-8fe1-f4e4199d60c6",
+ "test_gnss_concurrency_location_61_chre_10": "c32ca948-0414-4529-98d0-8351b5f31bab",
+ "test_gnss_chre_1": "9dae57f3-70f9-4328-a448-925da88725ac",
+ "test_gnss_chre_8": "a8c8f7fa-4dfd-42d8-ac6a-c3e3f186e317",
+ "test_variable_interval_via_chre": "53b161e5-335e-44a7-ae2e-eae7464a2b37",
+ "test_variable_interval_via_framework": "6b525afa-1427-4a99-906f-bc0aab6d4d30",
+ "test_gps_engine_switching_host_to_onchip": "07e0e138-4966-4307-b600-0521e626b967",
+ "test_gps_engine_switching_onchip_to_host": "564a229e-e784-43af-b430-4ab14656cfdc",
+ "test_mcu_cs_ttff": "736da33a-a976-44b6-93b3-bcfd847dd03d",
+ "test_mcu_ws_ttff": "e3457e35-9872-4677-8170-bc30d84798c0",
+ "test_mcu_hs_ttff": "0e1ce60d-e257-4dc5-b927-7ae97c8386b6",
+ # GnssBroadcomConfigurationTest
+ "test_gps_logenabled_setting": "d1310171-1641-4fa2-8802-cca7ce33bbd4",
+ "test_gps_supllogenable_setting": "ebe30341-4097-4e2c-b104-0c592f1f9e83",
+ "test_lhe_setting": "099aea19-5078-447c-925f-01a702624884",
+ # GnssSuplTest
+ "test_supl_capabilities": "6c794396-46e8-4674-8985-49a7b3059372",
+ "test_supl_ttff_cs": "ae8b6d54-bdd6-44a1-b1fa-4e90e0318080",
+ "test_supl_ttff_ws": "65f25e0b-c6d0-47c5-ab1f-0b02b621411d",
+ "test_supl_ttff_hs": "a2267586-97e9-465c-8d3a-22882c8671e7",
+ "test_cs_ttff_supl_over_wifi_with_airplane_mode_on": "4b2882f8-2966-4b44-9a31-37318beb84bf",
+ "test_ws_ttff_supl_over_wifi_with_airplane_mode_on": "a7f77afe-c82e-4b1b-ae54-e3fea17bf721",
+ "test_hs_ttff_supl_over_wifi_with_airplane_mode_on": "bc9de22f-90a0-4f2b-8052-cb4529f745e3",
+ "test_ttff_gla_on": "06aa85a2-7c3a-453a-b765-dc9ea6ee6b9b",
+ "test_ttff_gla_off": "36347b6e-d03e-4773-82bf-2e12d4f4dd0d",
+ # GnssVendorFeaturesTest
+ "test_xtra_ttff_cs_mobile_data": "da0bf0a1-d635-4942-808a-30070cfb2c78",
+ "test_xtra_ttff_ws_mobile_data": "95f17477-c88e-4663-a0ab-c09dc1706f75",
+ "test_xtra_ttff_hs_mobile_data": "bf13e2e4-79c9-4769-9dfd-81b085112744",
+ "test_xtra_ttff_cs_wifi": "5c0f95d2-7c76-45ca-95c8-304c742e0c82",
+ "test_xtra_ttff_ws_wifi": "507b2da1-c58d-4bca-810b-b274082a21c4",
+ "test_xtra_ttff_hs_wifi": "69d1998a-dd78-46a9-904d-d28f07dc3ef2",
+ "test_xtra_download_mobile_data": "d260a510-941a-48c7-a545-d7239f8f03dc",
+ "test_xtra_download_wifi": "e1dec4d2-4a85-4680-92df-57c972c084aa",
+ "test_lto_download_after_reboot": "579d249e-d533-4979-915f-b3a7d847546e",
+ "test_ws_with_assist": "938cbc1f-0374-473c-b4c1-1b4af734f16a",
+ "test_cs_with_assist": "9eae7b7d-9356-4fd8-bc18-f9d93aa0a92b",
+ # GnssWearableTetherFunctionTest
+ "test_flp_ttff_cs": "6dec9502-7e74-4590-b174-be822ceefcdb",
+ "test_flp_ttff_ws": "ddb8f09d-4757-42ae-9707-1fdb38187d1f",
+ "test_flp_ttff_hs": "19a997da-7c36-4fef-bf68-497d7c21163f",
+ "test_tracking_during_bt_disconnect_resume": "c9e26620-e518-4c7d-afcc-f81f6d3971bb",
+ "test_oobe_first_fix": "432e799c-3d02-46de-84d5-d5a22feceef8",
+ "test_oobe_first_fix_with_network_connection": "66264dac-50d0-4bc0-be72-6dbe9159587b",
+ "test_far_start_ttff": "4693b424-1a24-4002-b51d-df6b6bb91830",
+}
+
+def log_testtracker_uuid(ad, current_test_name):
+ """Logs testtracker uuid for the current test case.
+
+ Args:
+ ad: Target AndroidDevice object.
+ current_test_name: Current test name used to map testtracker uuid.
+ """
+ current_test_uuid = TEST_NAME_BY_TESTTRACKER_UUID.get(
+ current_test_name, None)
+ if current_test_uuid:
+ ad.log.info(f"TestResult mobly_uid {current_test_uuid}")
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
index 11094fb..7af1e12 100644
--- a/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
@@ -27,6 +27,7 @@
from acts import base_test
from acts import utils
from acts.metrics.loggers.blackbox import BlackboxMetricLogger
+from acts.controllers.adb_lib.error import AdbError
from acts_contrib.test_utils.power.loggers.power_metric_logger import PowerMetricLogger
from acts_contrib.test_utils.power import plot_utils
@@ -35,6 +36,8 @@
THRESHOLD_TOLERANCE_DEFAULT = 0.2
GET_FROM_PHONE = 'get_from_dut'
GET_FROM_AP = 'get_from_ap'
+GET_PROPERTY_HARDWARE_PLATFORM = 'getprop ro.boot.hardware.platform'
+POWER_STATS_DUMPSYS_CMD = 'dumpsys android.hardware.power.stats.IPowerStats/default delta'
PHONE_BATTERY_VOLTAGE_DEFAULT = 4.2
MONSOON_MAX_CURRENT = 8.0
DEFAULT_MONSOON_FREQUENCY = 500
@@ -76,6 +79,7 @@
self.dut = None
self.power_logger = PowerMetricLogger.for_test_case()
self.power_monitor = None
+ self.odpm_folder = None
@property
def final_test(self):
@@ -150,13 +154,23 @@
iperf_duration=None,
pass_fail_tolerance=THRESHOLD_TOLERANCE_DEFAULT,
mon_voltage=PHONE_BATTERY_VOLTAGE_DEFAULT,
- ap_dtim_period=None)
+ ap_dtim_period=None,
+ bits_root_rail_csv_export=False)
# Setup the must have controllers, phone and monsoon
self.dut = self.android_devices[0]
self.mon_data_path = os.path.join(self.log_path, 'Monsoon')
os.makedirs(self.mon_data_path, exist_ok=True)
+ # Make odpm path for P21 or later
+ platform = self.dut.adb.shell(GET_PROPERTY_HARDWARE_PLATFORM)
+ self.log.info('The hardware platform is {}'.format(platform))
+ if platform.startswith('gs'):
+ self.odpm_folder = os.path.join(self.log_path, 'odpm')
+ os.makedirs(self.odpm_folder, exist_ok=True)
+ self.log.info('For P21 or later, create odpm folder {}'.format(
+ self.odpm_folder))
+
# Initialize the power monitor object that will be used to measure
self.initialize_power_monitor()
@@ -279,6 +293,26 @@
def on_pass(self, test_name, begin_time):
self.power_logger.set_pass_fail_status('PASS')
+ def dut_save_odpm(self, tag):
+ """Dumpsys ODPM data and save it to self.odpm_folder.
+
+ Args:
+ tag: the moment of save ODPM data
+ """
+ odpm_file_name = '{}.{}.dumpsys_odpm_{}.txt'.format(
+ self.__class__.__name__,
+ self.current_test_name,
+ tag)
+ odpm_file_path = os.path.join(self.odpm_folder, odpm_file_name)
+
+ try:
+ stats = self.dut.adb.shell(POWER_STATS_DUMPSYS_CMD)
+ with open(odpm_file_path, 'w') as f:
+ f.write(stats)
+ except AdbError as e:
+ self.log.warning('Odpm data with tag {} did not save due to adb '
+ 'error {}'.format(e))
+
def dut_rockbottom(self):
"""Set the dut to rockbottom state
@@ -464,6 +498,11 @@
# Start the power measurement using monsoon.
self.dut.stop_services()
time.sleep(1)
+
+ # P21 or later device, save the odpm data before power measurement
+ if self.odpm_folder:
+ self.dut_save_odpm('before')
+
self.power_monitor.disconnect_usb()
measurement_args = dict(duration=self.mon_info.duration,
measure_after_seconds=self.mon_info.offset,
@@ -473,9 +512,15 @@
start_time=device_to_host_offset,
monsoon_output_path=data_path)
self.power_monitor.release_resources()
+ self.collect_raw_data_samples()
self.power_monitor.connect_usb()
self.dut.wait_for_boot_completion()
time.sleep(10)
+
+ # For P21 or later device, save the odpm data after power measurement
+ if self.odpm_folder:
+ self.dut_save_odpm('after')
+
self.dut.start_services()
return self.power_monitor.get_waveform(file_path=data_path)
@@ -510,3 +555,9 @@
self.log.warning('Cannot get iperf result. Setting to 0')
throughput = 0
return throughput
+
+ def collect_raw_data_samples(self):
+ if hasattr(self, 'bitses') and self.bits_root_rail_csv_export:
+ path = os.path.join(os.path.dirname(self.mon_info.data_path),
+ 'Kibble')
+ self.power_monitor.get_bits_root_rail_csv_export(path, self.test_name)
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerGTWGnssBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerGTWGnssBaseTest.py
index baedb7e..201f17f 100644
--- a/acts_tests/acts_contrib/test_utils/power/PowerGTWGnssBaseTest.py
+++ b/acts_tests/acts_contrib/test_utils/power/PowerGTWGnssBaseTest.py
@@ -41,6 +41,7 @@
self.set_xtra_data()
def setup_test(self):
+ gutils.log_current_epoch_time(self.ad, "test_start_time")
super().setup_test()
# Enable DPO
self.enable_DPO(True)
@@ -53,6 +54,7 @@
begin_time = utils.get_current_epoch_time()
self.ad.take_bug_report(self.test_name, begin_time)
gutils.get_gnss_qxdm_log(self.ad, self.qdsp6m_path)
+ gutils.log_current_epoch_time(self.ad, "test_end_time")
def set_xtra_data(self):
gutils.disable_xtra_throttle(self.ad)
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_power_base_test.py b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_power_base_test.py
index 7285a16..f2ebea9 100644
--- a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_power_base_test.py
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_power_base_test.py
@@ -19,6 +19,7 @@
import acts_contrib.test_utils.power.PowerBaseTest as PBT
import acts_contrib.test_utils.cellular.cellular_base_test as CBT
from acts_contrib.test_utils.power import plot_utils
+from acts import context
class PowerCellularLabBaseTest(CBT.CellularBaseTest, PBT.PowerBaseTest):
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_power_preset_base_test.py b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_power_preset_base_test.py
new file mode 100644
index 0000000..15043d4
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_power_preset_base_test.py
@@ -0,0 +1,414 @@
+import os
+from typing import Optional, List
+import time
+from acts import context
+from acts import signals
+from acts.controllers.cellular_lib import AndroidCellularDut
+import acts_contrib.test_utils.power.cellular.cellular_power_base_test as PWCEL
+
+# TODO: b/261639867
+class AtUtil():
+ """Util class for sending at command.
+
+ Attributes:
+ dut: AndroidDevice controller object.
+ """
+ ADB_CMD_DISABLE_TXAS = 'am instrument -w -e request at+googtxas=2 -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
+
+ def __init__(self, dut, log) -> None:
+ self.dut = dut
+ self.log = log
+
+ # TODO: to be remove when b/261639867 complete,
+ # and we are using parent method.
+ def send(self, cmd: str,) -> Optional[str]:
+ res = str(self.dut.adb.shell(cmd))
+ self.log.info(f'cmd sent: {cmd}')
+ self.log.info(f'response: {res}')
+ if 'SUCCESS' in res:
+ self.log.info('Command executed.')
+ else:
+ self.log.error('Fail to executed command.')
+ return res
+
+ def lock_LTE(self):
+ adb_enable_band_lock_lte = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Band.Select\ Enb\/\ Dis\",00,\"01\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
+ adb_set_band_lock_mode_lte = r'am instrument -w -e request at+GOOGSETNV=\"NASU.SIPC.NetworkMode.ManualMode\",0,\"0D\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
+ adb_set_band_lock_bitmap_0 = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Enabled.RFBands.BitMap\",0,\"09,00,00,00,00,00,00,00\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
+ adb_set_band_lock_bitmap_1 = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Enabled.RFBands.BitMap\",1,\"00,00,00,00,00,00,00,00\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
+ adb_set_band_lock_bitmap_2 = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Enabled.RFBands.BitMap\",2,\"00,00,00,00,00,00,00,00\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
+ adb_set_band_lock_bitmap_3 = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Enabled.RFBands.BitMap\",3,\"00,00,00,00,00,00,00,00\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
+
+ # enable lte
+ self.send(adb_enable_band_lock_lte)
+ time.sleep(2)
+
+ # OD is for NR/LTE in 4412 menu
+ self.send(adb_set_band_lock_mode_lte)
+ time.sleep(2)
+
+ # lock to B1 and B4
+ self.send(adb_set_band_lock_bitmap_0)
+ time.sleep(2)
+ self.send(adb_set_band_lock_bitmap_1)
+ time.sleep(2)
+ self.send(adb_set_band_lock_bitmap_2)
+ time.sleep(2)
+ self.send(adb_set_band_lock_bitmap_3)
+ time.sleep(2)
+
+ def clear_lock_band(self):
+ adb_set_band_lock_mode_auto = r'am instrument -w -e request at+GOOGSETNV=\"NASU.SIPC.NetworkMode.ManualMode\",0,\"03\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
+ adb_disable_band_lock_lte = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Band.Select\ Enb\/\ Dis\",0,\"00\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
+
+ # band lock mode auto
+ self.send(adb_set_band_lock_mode_auto)
+ time.sleep(2)
+
+ # disable band lock lte
+ self.send(adb_disable_band_lock_lte)
+ time.sleep(2)
+
+ def disable_txas(self):
+ cmd = self.ADB_CMD_DISABLE_TXAS
+ response = self.send(cmd)
+ return 'OK' in response
+
+class PowerCellularPresetLabBaseTest(PWCEL.PowerCellularLabBaseTest):
+ # Key for ODPM report
+ ODPM_ENERGY_TABLE_NAME = 'PowerStats HAL 2.0 energy meter'
+ ODPM_MODEM_CHANNEL_NAME = '[VSYS_PWR_MODEM]:Modem'
+
+ # Key for custom_property in Sponge
+ CUSTOM_PROP_KEY_BUILD_ID = 'build_id'
+ CUSTOM_PROP_KEY_INCR_BUILD_ID = 'incremental_build_id'
+ CUSTOM_PROP_KEY_BUILD_TYPE = 'build_type'
+ CUSTOM_PROP_KEY_SYSTEM_POWER = 'system_power'
+ CUSTOM_PROP_KEY_MODEM_BASEBAND = 'baseband'
+ CUSTOM_PROP_KEY_MODEM_ODPM_POWER= 'modem_odpm_power'
+ CUSTOM_PROP_KEY_DEVICE_NAME = 'device'
+ CUSTOM_PROP_KEY_DEVICE_BUILD_PHASE = 'device_build_phase'
+ CUSTOM_PROP_KEY_MODEM_KIBBLE_POWER = 'modem_kibble_power'
+ CUSTOM_PROP_KEY_TEST_NAME = 'test_name'
+ CUSTOM_PROP_KEY_MODEM_KIBBLE_WO_PCIE_POWER = 'modem_kibble_power_wo_pcie'
+ CUSTOM_PROP_KEY_MODEM_KIBBLE_PCIE_POWER = 'modem_kibble_pcie_power'
+ CUSTOM_PROP_KEY_RFFE_POWER = 'rffe_power'
+ CUSTOM_PROP_KEY_MMWAVE_POWER = 'mmwave_power'
+ # kibble report
+ KIBBLE_SYSTEM_RECORD_NAME = '- name: default_device.C10_EVT_1_1.Monsoon:mA'
+ MODEM_PCIE_RAIL_NAME_LIST = [
+ 'PP1800_L2C_PCIEG3',
+ 'PP1200_L9C_PCIE',
+ 'PP0850_L8C_PCIE'
+ ]
+
+ MODEM_RFFE_RAIL_NAME_LIST = [
+ 'PP1200_L31C_RFFE',
+ 'VSYS_PWR_RFFE',
+ 'PP2800_L33C_RFFE'
+ ]
+
+ MODEM_POWER_RAIL_NAME = 'VSYS_PWR_MODEM'
+
+ MODEM_MMWAVE_RAIL_NAME = 'VSYS_PWR_MMWAVE'
+
+ MONSOON_RAIL_NAME = 'Monsoon'
+
+ # params key
+ MONSOON_VOLTAGE_KEY = 'mon_voltage'
+
+ MDSTEST_APP_APK_NAME = 'mdstest.apk'
+ ADB_CMD_INSTALL = 'install {apk_path}'
+ ADB_CMD_DISABLE_TXAS = 'am instrument -w -e request at+googtxas=2 -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"'
+ ADB_CMD_SET_NV = ('am instrument -w '
+ '-e request at+googsetnv=\"{nv_name}\",{nv_index},\"{nv_value}\" '
+ '-e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"')
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ self.retryable_exceptions = signals.TestFailure
+ self.power_rails = {}
+ self.pcie_power = 0
+ self.rffe_power = 0
+ self.mmwave_power = 0
+ self.modem_power = 0
+ self.monsoon_power = 0
+
+ def setup_class(self):
+ super().setup_class()
+
+ # preset callbox
+ is_fr2 = 'Fr2' in self.TAG
+ self.cellular_simulator.switch_HCCU_settings(is_fr2=is_fr2)
+
+ self.at_util = AtUtil(self.cellular_dut.ad, self.log)
+
+ # preset UE.
+ self.log.info('Installing mdstest app.')
+ self.install_apk()
+
+ self.log.info('Disable antenna switch.')
+ is_txas_disabled = self.at_util.disable_txas()
+ self.log.info('Disable txas: ' + str(is_txas_disabled))
+
+ # get sim type
+ self.unpack_userparams(has_3gpp_sim=True)
+
+ def setup_test(self):
+ self.cellular_simulator.set_sim_type(self.has_3gpp_sim)
+ try:
+ if 'LTE' in self.test_name:
+ self.at_util.lock_LTE()
+ super().setup_test()
+ except BrokenPipeError:
+ self.log.info('TA crashed test need retry.')
+ self.need_retry = True
+ self.cellular_simulator.recovery_ta()
+ self.cellular_simulator.socket_connect()
+ raise signals.TestFailure('TA crashed mid test, retry needed.')
+ # except:
+ # # self.log.info('Waiting for device to on.')
+ # # self.dut.adb.wait_for_device()
+ # # self.cellular_dut = AndroidCellularDut.AndroidCellularDut(
+ # # self.android_devices[0], self.log)
+ # # self.dut.root_adb()
+ # # # Restart SL4A
+ # # self.dut.start_services()
+ # # self.need_retry = True
+ # raise signals.TestError('Device reboot mid test, retry needed.')
+
+ def install_apk(self):
+ sleep_time = 3
+ for file in self.custom_files:
+ if self.MDSTEST_APP_APK_NAME in file:
+ if not self.cellular_dut.ad.is_apk_installed("com.google.mdstest"):
+ self.cellular_dut.ad.adb.install("-r -g %s" % file, timeout=300, ignore_status=True)
+ time.sleep(sleep_time)
+ if self.cellular_dut.ad.is_apk_installed("com.google.mdstest"):
+ self.log.info('mdstest installed.')
+ else:
+ self.log.warning('fail to install mdstest.')
+
+ def set_nv(self, nv_name, index, value):
+ cmd = self.ADB_CMD_SET_NV.format(
+ nv_name=nv_name,
+ nv_index=index,
+ nv_value=value
+ )
+ response = str(self.cellular_dut.ad.adb.shell(cmd))
+ self.log.info(response)
+
+ def enable_ims_nr(self):
+ # set !NRCAPA.Gen.VoiceOverNr
+ self.set_nv(
+ nv_name = '!NRCAPA.Gen.VoiceOverNr',
+ index = '0',
+ value = '01'
+ )
+ # set PSS.AIMS.Enable.NRSACONTROL
+ self.set_nv(
+ nv_name = 'PSS.AIMS.Enable.NRSACONTROL',
+ index = '0',
+ value = '00'
+ )
+ # set DS.PSS.AIMS.Enable.NRSACONTROL
+ self.set_nv(
+ nv_name = 'DS.PSS.AIMS.Enable.NRSACONTROL',
+ index = '0',
+ value = '00'
+ )
+ if self.cellular_dut.ad.model == 'oriole':
+ # For P21, NR.CONFIG.MODE/DS.NR.CONFIG.MODE
+ self.set_nv(
+ nv_name = 'NR.CONFIG.MODE',
+ index = '0',
+ value = '11'
+ )
+ # set DS.NR.CONFIG.MODE
+ self.set_nv(
+ nv_name = 'DS.NR.CONFIG.MODE',
+ index = '0',
+ value = '11'
+ )
+ else:
+ # For P22, NASU.NR.CONFIG.MODE to 11
+ self.set_nv(
+ nv_name = 'NASU.NR.CONFIG.MODE',
+ index = '0',
+ value = '11'
+ )
+
+ def get_odpm_values(self):
+ """Get power measure from ODPM.
+
+ Parsing energy table in ODPM file
+ and convert to.
+ Returns:
+ odpm_power_results: a dictionary
+ has key as channel name,
+ and value as power measurement of that channel.
+ """
+ self.log.info('Start calculating power by channel from ODPM report.')
+ odpm_power_results = {}
+
+ # device before P21 don't have ODPM reading
+ if not self.odpm_folder:
+ return odpm_power_results
+
+ # getting ODPM modem power value
+ odpm_file_name = '{}.{}.dumpsys_odpm_{}.txt'.format(
+ self.__class__.__name__,
+ self.current_test_name,
+ 'after')
+ odpm_file_path = os.path.join(self.odpm_folder, odpm_file_name)
+ if os.path.exists(odpm_file_path):
+ elapsed_time = None
+ with open(odpm_file_path, 'r') as f:
+ # find energy table in ODPM report
+ for line in f:
+ if self.ODPM_ENERGY_TABLE_NAME in line:
+ break
+
+ # get elapse time 2 adb ODPM cmd (mS)
+ elapsed_time_str = f.readline()
+ elapsed_time = float(elapsed_time_str
+ .split(':')[1]
+ .strip()
+ .split(' ')[0])
+ self.log.info(elapsed_time_str)
+
+ # skip column name row
+ next(f)
+
+ # get power of different channel from odpm report
+ for line in f:
+ if 'End' in line:
+ break
+ else:
+ # parse columns
+ # example result of line.strip().split()
+ # ['[VSYS_PWR_DISPLAY]:Display', '1039108.42', 'mWs', '(', '344.69)']
+ channel, _, _, _, delta_str = line.strip().split()
+ delta = float(delta_str[:-2].strip())
+
+ # calculate OPDM power
+ # delta is a different in cumulative energy
+ # between 2 adb ODPM cmd
+ elapsed_time_s = elapsed_time / 1000
+ power = delta / elapsed_time_s
+ odpm_power_results[channel] = power
+ self.log.info(
+ channel + ' ' + str(power) + ' mW'
+ )
+ return odpm_power_results
+
+ def _is_any_substring(self, longer_word: str, word_list: List[str]) -> bool:
+ """Check if any word in word list a substring of a longer word."""
+ return any(w in longer_word for w in word_list)
+
+ def parse_power_rails_csv(self):
+ kibble_dir = os.path.join(self.root_output_path, 'Kibble')
+ kibble_csv_path = None
+ if os.path.exists(kibble_dir):
+ for f in os.listdir(kibble_dir):
+ if self.test_name in f and '.csv' in f:
+ kibble_csv_path = os.path.join(kibble_dir, f)
+ self.log.info('Kibble csv file path: ' + kibble_csv_path)
+ break
+
+ self.log.info('Parsing power rails from csv.')
+ if kibble_csv_path:
+ with open(kibble_csv_path, 'r') as f:
+ for line in f:
+ # railname,val,mA,val,mV,val,mW
+ railname, _, _, _, _, power, _ = line.split(',')
+ # parse pcie power
+ if self._is_any_substring(railname, self.MODEM_PCIE_RAIL_NAME_LIST):
+ self.log.info(railname + ': ' + power)
+ self.pcie_power += float(power)
+ elif self.MODEM_POWER_RAIL_NAME in railname:
+ self.log.info(railname + ': ' + power)
+ self.modem_power = float(power)
+ elif self._is_any_substring(railname, self.MODEM_RFFE_RAIL_NAME_LIST):
+ self.log.info(railname + ': ' + power)
+ self.rffe_power = float(power)
+ elif self.MODEM_MMWAVE_RAIL_NAME in railname:
+ self.log.info(railname + ': ' + power)
+ self.mmwave_power = float(power)
+ elif self.MONSOON_RAIL_NAME == railname:
+ self.log.info(railname + ': ' + power)
+ self.monsoon_power = float(power)
+ if self.modem_power:
+ self.power_results[self.test_name] = self.modem_power
+
+ def sponge_upload(self):
+ """Upload result to sponge as custom field."""
+ # test name
+ test_name_arr = self.current_test_name.split('_')
+ test_name_for_sponge = ''.join(
+ word[0].upper() + word[1:].lower()
+ for word in test_name_arr
+ if word not in ('preset', 'test')
+ )
+
+ # build info
+ build_info = self.cellular_dut.ad.build_info
+ build_id = build_info.get('build_id', 'Unknown')
+ incr_build_id = build_info.get('incremental_build_id', 'Unknown')
+ modem_base_band = self.cellular_dut.ad.adb.getprop(
+ 'gsm.version.baseband')
+ build_type = build_info.get('build_type', 'Unknown')
+
+ # device info
+ device_info = self.cellular_dut.ad.device_info
+ device_name = device_info.get('model', 'Unknown')
+ device_build_phase = self.cellular_dut.ad.adb.getprop(
+ 'ro.boot.hardware.revision'
+ )
+
+ # power measurement results
+ odpm_power_results = self.get_odpm_values()
+ odpm_power = odpm_power_results.get(self.ODPM_MODEM_CHANNEL_NAME, 0)
+ system_power = 0
+
+ # if kibbles are using, get power from kibble
+ modem_kibble_power_wo_pcie = 0
+ if hasattr(self, 'bitses'):
+ self.parse_power_rails_csv()
+ modem_kibble_power_wo_pcie = self.modem_power - self.pcie_power
+ system_power = self.monsoon_power
+ else:
+ system_power = self.power_results.get(self.test_name, 0)
+
+ self.record_data({
+ 'Test Name': self.test_name,
+ 'sponge_properties': {
+ self.CUSTOM_PROP_KEY_SYSTEM_POWER: system_power,
+ self.CUSTOM_PROP_KEY_BUILD_ID: build_id,
+ self.CUSTOM_PROP_KEY_INCR_BUILD_ID: incr_build_id,
+ self.CUSTOM_PROP_KEY_MODEM_BASEBAND: modem_base_band,
+ self.CUSTOM_PROP_KEY_BUILD_TYPE: build_type,
+ self.CUSTOM_PROP_KEY_MODEM_ODPM_POWER: odpm_power,
+ self.CUSTOM_PROP_KEY_DEVICE_NAME: device_name,
+ self.CUSTOM_PROP_KEY_DEVICE_BUILD_PHASE: device_build_phase,
+ self.CUSTOM_PROP_KEY_MODEM_KIBBLE_POWER: self.modem_power,
+ self.CUSTOM_PROP_KEY_TEST_NAME: test_name_for_sponge,
+ self.CUSTOM_PROP_KEY_MODEM_KIBBLE_WO_PCIE_POWER: modem_kibble_power_wo_pcie,
+ self.CUSTOM_PROP_KEY_MODEM_KIBBLE_PCIE_POWER: self.pcie_power,
+ self.CUSTOM_PROP_KEY_RFFE_POWER: self.rffe_power,
+ self.CUSTOM_PROP_KEY_MMWAVE_POWER: self.mmwave_power
+ },
+ })
+
+ def teardown_test(self):
+ super().teardown_test()
+ # restore device to ready state for next test
+ self.log.info('Enable mobile data.')
+ self.dut.adb.shell('svc data enable')
+ self.cellular_simulator.detach()
+ self.cellular_dut.toggle_airplane_mode(True)
+
+ # processing result
+ self.sponge_upload()
+ if 'LTE' in self.test_name:
+ self.at_util.clear_lock_band()
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/ims_api_connector_utils.py b/acts_tests/acts_contrib/test_utils/power/cellular/ims_api_connector_utils.py
new file mode 100644
index 0000000..cfdc67a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/ims_api_connector_utils.py
@@ -0,0 +1,228 @@
+# TODO(hmtuan): add type annotation.
+import requests
+import time
+
+class ImsApiConnector():
+ """A wrapper class for Keysight Ims API Connector.
+
+ Keysight provided an API connector application
+ which is a HTTP server running on the same host
+ as Keysight IMS server simulator and client simulator.
+ It allows IMS simulator/app to be controlled via HTTP request.
+
+ Attributes:
+ api_connector_ip: ip of http server.
+ api_connector_port: port of http server.
+ ims_app: type of ims app (client/server).
+ api_token: an arbitrary and unique token-string
+ to identify the link between API connector
+ and ims app.
+ log: logger object.
+ """
+
+ def __init__(self, api_connector_ip,
+ api_connector_port, ims_app,
+ api_token, ims_app_ip,
+ ims_app_port, log):
+ # api connector info
+ self.api_connector_ip = api_connector_ip
+ self.api_connector_port = api_connector_port
+
+ # ims app info
+ self.ims_app = ims_app
+ self.api_token = api_token
+ self.ims_app_ip = ims_app_ip
+ self.ims_app_port = ims_app_port
+
+ self.log = log
+ # construct base url
+ self.base_url = 'http://{addr}:{port}/ims/api/{app}s/{api_token}'.format(
+ addr = self.api_connector_ip,
+ port = self.api_connector_port,
+ app = self.ims_app,
+ api_token = self.api_token
+ )
+
+ def get_base_url(self):
+ return self.base_url
+
+ def create_ims_app_link(self):
+ """Create link between Keysight API Connector to ims app."""
+ self.log.info('Create ims app link: token:ip:port')
+ self.log.info('Creating ims_{app} link: {token}:{target_ip}:{target_port}'.format(
+ app = self.ims_app,
+ token = self.api_token,
+ target_ip = self.ims_app_ip,
+ target_port= self.ims_app_port)
+ )
+
+ request_data = {
+ "targetIpAddress": self.ims_app_ip,
+ "targetWcfPort": self.ims_app_port
+ }
+ self.log.debug(f'Payload to create ims app link: {request_data}')
+ r = requests.post(url = self.get_base_url(), json = request_data)
+
+ self.log.info('HTTP request sent:')
+ self.log.info('-> method: ' + str(r.request.method))
+ self.log.info('-> url: ' + str(r.url))
+ self.log.info('-> status_code: ' + str(r.status_code))
+
+ return (r.status_code == 201)
+
+ def remove_ims_app_link(self):
+ """Remove link between Keysight API Connector to ims app."""
+ self.log.info('Remove ims_{app} link: {token}'.format(
+ app = self.ims_app,
+ token = self.api_token)
+ )
+
+ r = requests.delete(url = self.get_base_url())
+
+ self.log.info('-> method: ' + str(r.request.method))
+ self.log.info('-> url: ' + str(r.url))
+ self.log.info('-> status_code: ' + str(r.status_code))
+
+ return (r.status_code == 200)
+
+ def get_ims_app_property(self, property_name):
+ """Get property value of IMS app.
+
+ Attributes:
+ property_name: name of property to get value.
+ """
+ self.log.info('Getting ims app property: ' + property_name)
+
+ request_url = self.get_base_url() + '/get_property'
+ request_params = {"propertyName": property_name}
+ r = requests.get(url = request_url, params = request_params)
+
+ self.log.info('-> method: ' + str(r.request.method))
+ self.log.info('-> url: ' + str(r.url))
+ self.log.info('-> status_code: ' + str(r.status_code))
+
+ try:
+ res_json = r.json()
+ except:
+ res_json = {'propertyValue': None }
+ prop_value = res_json['propertyValue']
+
+ return prop_value
+
+ def set_ims_app_property(self, property_name, property_value):
+ """Set property value of IMS app.
+
+ Attributes:
+ property_name: name of property to set value.
+ property_value: value to be set.
+ """
+ self.log.info('Setting ims property: ' + property_name + ' = ' + str(property_value))
+
+ request_url = self.get_base_url() + '/set_property'
+ data = {
+ 'propertyName': property_name,
+ 'propertyValue': property_value
+ }
+ r = requests.post(url = request_url, json = data)
+
+ self.log.info('-> method: ' + str(r.request.method))
+ self.log.info('-> url: ' + str(r.url))
+ self.log.info('-> status_code: ' + str(r.status_code))
+
+ return (r.status_code == 200)
+
+ def ims_api_call_method(self, method_name, method_args=None):
+ """
+ Attributes:
+ method_name: a name of method from Keysight API in string.
+ method_args: a python-array contains
+ arguments for the called API method.
+ Returns:
+ a tuple of (STATUS_BOOL, FUNC_RET_VAL),
+ if STATUS_BOOL is false, FUNC_RET_VAL is questionable/undefined,
+ if STATUS_BOOL is true, FUNC_RET_VAL will be the API function return value
+ or FUNC_RET_VAL is None if the called method return nothing.
+ """
+ self.log.info('Calling ims method: ' + method_name)
+
+ if (method_args == None):
+ method_args = []
+ elif (type(method_args) != list):
+ method_args = [method_args]
+ data = {
+ 'methodName': method_name,
+ 'arguments': method_args
+ }
+ request_url = self.get_base_url() + '/call_method'
+ r = requests.post(url = request_url, json = data)
+
+ ret_val = None
+
+ if ( ('Content-Type' in r.headers.keys()) and r.headers['Content-Type'] == 'application/json'):
+ # TODO(hmtuan): try json.loads() instead
+ response_body = r.json()
+ if ((response_body != None) and ('returnValue' in response_body.keys())) :
+ ret_val = response_body['returnValue']
+
+ self.log.info('-> method: ' + str(r.request.method))
+ self.log.info('-> url: ' + str(r.url))
+ self.log.info('-> status_code: ' + str(r.status_code))
+ self.log.info('-> ret_val: ' + str(ret_val))
+
+ return (r.status_code == 200), ret_val
+
+ def _is_line_idle(self, call_line_number):
+ is_line_idle_prop = self.get_ims_app_property(
+ f'IVoip.CallLineParams({call_line_number}).SessionState')
+ return is_line_idle_prop == 'Idle'
+
+ def _is_ims_client_app_registered(self):
+ is_registered_prop = self.get_ims_app_property('IComponentControl.IsRegistered')
+ return is_registered_prop == 'True'
+
+ def initiate_call(self, callee_number, call_line_idx=0):
+ """Dial to callee_number.
+
+ Attributes:
+ callee_number: number to be dialed to.
+ """
+ # create IMS-Client API link
+ ret_val = self.create_ims_app_link()
+
+ if not ret_val:
+ raise RuntimeError('Fail to create link to IMS app.')
+
+ # check if IMS-Client is registered, and if not, request client to perform Registration
+ self.log.info('Ensuring client registered.')
+ is_registered = self._is_ims_client_app_registered()
+ if not is_registered:
+ self.log.info('Client not currently registered - registering.')
+ self.ims_api_call_method('ISipConnection.Register()')
+
+ is_registered = self._is_ims_client_app_registered()
+ if not is_registered:
+ raise RuntimeError('Failed to register IMS-client to IMS-server.')
+
+ # switch to call-line #1 (idx = 0)
+ self.log.info('Switching to call-line #1.')
+ self.set_ims_app_property('IVoip.SelectedCallLine', call_line_idx)
+
+ # check whether the call-line #1 is ready for dialling
+ is_line1_idle = self._is_line_idle(call_line_idx)
+ if not is_line1_idle:
+ raise RuntimeError('Call-line not is not in indle state.')
+
+ # entering callee number for call-line #1
+ self.log.info(f'Enter callee number: {callee_number}.')
+ self.set_ims_app_property('IVoip.CallLineParams(0).CallLocation', callee_number)
+
+ # dial entered callee number
+ self.log.info('Dialling call.')
+ self.ims_api_call_method('IVoip.Dial()')
+
+ time.sleep(5)
+
+ # check if dial success (not idle)
+ is_line1_idle = self._is_line_idle(call_line_idx)
+ if is_line1_idle:
+ raise RuntimeError('Fail to dial.')
diff --git a/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py
index f0f385b..3226263 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py
@@ -61,6 +61,7 @@
DISCOVERY_KEY_RANGING_ENABLED = "RangingEnabled"
DISCOVERY_KEY_MIN_DISTANCE_MM = "MinDistanceMm"
DISCOVERY_KEY_MAX_DISTANCE_MM = "MaxDistanceMm"
+DISCOVERY_KEY_INSTANT_COMMUNICATION_MODE = "InstantModeEnabled"
PUBLISH_TYPE_UNSOLICITED = 0
PUBLISH_TYPE_SOLICITED = 1
@@ -150,6 +151,7 @@
CAP_MAX_QUEUED_TRANSMIT_MESSAGES = "maxQueuedTransmitMessages"
CAP_MAX_SUBSCRIBE_INTERFACE_ADDRESSES = "maxSubscribeInterfaceAddresses"
CAP_SUPPORTED_CIPHER_SUITES = "supportedCipherSuites"
+CAP_SUPPORTED_INSTANT_COMMUNICATION_MODE = "isInstantCommunicationModeSupported"
######################################################
# WifiAwareNetworkCapabilities keys
diff --git a/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py
index 4f22289..06b2e04 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py
@@ -714,7 +714,8 @@
match_filter=None,
match_filter_list=None,
ttl=0,
- term_cb_enable=True):
+ term_cb_enable=True,
+ instant_mode=None):
"""Create a publish discovery configuration based on input parameters.
Args:
@@ -726,6 +727,7 @@
ttl: Time-to-live - defaults to 0 (i.e. non-self terminating)
term_cb_enable: True (default) to enable callback on termination, False
means that no callback is called when session terminates.
+ instant_mode: set the band to use instant communication mode, 2G or 5G
Returns:
publish discovery configuration object.
"""
@@ -738,6 +740,8 @@
config[aconsts.DISCOVERY_KEY_MATCH_FILTER] = match_filter
if match_filter_list is not None:
config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = match_filter_list
+ if instant_mode is not None:
+ config[aconsts.DISCOVERY_KEY_INSTANT_COMMUNICATION_MODE] = instant_mode
config[aconsts.DISCOVERY_KEY_TTL] = ttl
config[aconsts.DISCOVERY_KEY_TERM_CB_ENABLED] = term_cb_enable
return config
diff --git a/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py b/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py
index d74e785..280e72e 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py
@@ -16,6 +16,7 @@
import contextlib
import io
+import requests
import serial
import time
from acts import logger
@@ -52,6 +53,7 @@
Base class provides functions whose implementation is shared by all
chambers.
"""
+
def reset_chamber(self):
"""Resets the chamber to its zero/home state."""
raise NotImplementedError
@@ -83,6 +85,7 @@
class MockChamber(OtaChamber):
"""Class that implements mock chamber for test development and debug."""
+
def __init__(self, config):
self.config = config.copy()
self.device_id = self.config['device_id']
@@ -120,6 +123,7 @@
class OctoboxChamber(OtaChamber):
"""Class that implements Octobox chamber."""
+
def __init__(self, config):
self.config = config.copy()
self.device_id = self.config['device_id']
@@ -129,7 +133,9 @@
utils.exe_cmd('sudo {} -d {} -i 0'.format(self.TURNTABLE_FILE_PATH,
self.device_id))
self.current_mode = None
- self.SUPPORTED_BANDS = ['2.4GHz', 'UNII-1', 'UNII-2', 'UNII-3', '6GHz']
+ self.SUPPORTED_BANDS = [
+ '2.4GHz', 'UNII-1', 'UNII-2', 'UNII-3', 'UNII-4', '6GHz'
+ ]
def set_orientation(self, orientation):
self.log.info('Setting orientation to {} degrees.'.format(orientation))
@@ -142,12 +148,49 @@
self.set_orientation(0)
+class OctoboxChamberV2(OtaChamber):
+ """Class that implements Octobox chamber."""
+
+ def __init__(self, config):
+ self.config = config.copy()
+ self.address = config['ip_address']
+ self.data = requests.get("http://{}/api/turntable".format(
+ self.address))
+ self.vel_target = '10000'
+ self.current_mode = None
+ self.SUPPORTED_BANDS = [
+ '2.4GHz', 'UNII-1', 'UNII-2', 'UNII-3', 'UNII-4', '6GHz'
+ ]
+ self.log = logger.create_tagged_trace_logger('OtaChamber|{}'.format(
+ self.address))
+
+ def set_orientation(self, orientation):
+ self.log.info('Setting orientation to {} degrees.'.format(orientation))
+ if orientation > 720:
+ raise ValueError('Orientation may not exceed 720.')
+ set_position_submission = {
+ "action": "pos",
+ "enable": "1",
+ "pos_target": orientation,
+ "vel_target": self.vel_target
+ }
+ result = requests.post("http://{}/api/turntable".format(self.address),
+ json=set_position_submission)
+ self.log.debug(result)
+
+ def reset_chamber(self):
+ self.log.info('Resetting chamber to home state')
+ self.set_orientation(0)
+
+
class ChamberAutoConnect(object):
+
def __init__(self, chamber, chamber_config):
self._chamber = chamber
self._config = chamber_config
def __getattr__(self, item):
+
def chamber_call(*args, **kwargs):
self._chamber.connect(self._config['ip_address'],
self._config['username'],
@@ -159,6 +202,7 @@
class BluetestChamber(OtaChamber):
"""Class that implements Octobox chamber."""
+
def __init__(self, config):
import flow
self.config = config.copy()
@@ -167,7 +211,9 @@
self.chamber = ChamberAutoConnect(flow.Flow(), self.config)
self.stirrer_ids = [0, 1, 2]
self.current_mode = None
- self.SUPPORTED_BANDS = ['2.4GHz', 'UNII-1', 'UNII-2', 'UNII-3']
+ self.SUPPORTED_BANDS = [
+ '2.4GHz', 'UNII-1', 'UNII-2', 'UNII-3', 'UNII-4', '6GHz'
+ ]
# Capture print output decorator
@staticmethod
@@ -248,6 +294,7 @@
class EInstrumentChamber(OtaChamber):
"""Class that implements Einstrument Chamber."""
+
def __init__(self, config):
self.config = config.copy()
self.device_id = self.config['device_id']
diff --git a/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py b/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py
index 395fed2..7cc52da 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py
@@ -18,6 +18,7 @@
import os
import posixpath
import time
+import zipfile
import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
from acts import context
@@ -126,6 +127,7 @@
class MockSniffer(OtaSnifferBase):
"""Class that implements mock sniffer for test development and debug."""
+
def __init__(self, config):
self.log = logger.create_tagged_trace_logger('Mock Sniffer')
@@ -443,6 +445,11 @@
if self.sniffer_output_file_type == 'csv':
log_file = self._process_tshark_dump(log_file)
+ if self.sniffer_output_file_type == 'pcap':
+ zip_file_path = log_file[:-4] + "zip"
+ zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED).write(
+ log_file, arcname=log_file.split('/')[-1])
+ os.remove(log_file)
self.sniffer_proc_pid = None
return log_file
@@ -450,6 +457,7 @@
class TsharkSnifferOnUnix(TsharkSnifferBase):
"""Class that implements Tshark based sniffer controller on Unix systems."""
+
def _scan_for_networks(self):
"""Scans the wireless networks on the sniffer.
@@ -480,6 +488,7 @@
class TsharkSnifferOnLinux(TsharkSnifferBase):
"""Class that implements Tshark based sniffer controller on Linux."""
+
def __init__(self, config):
super().__init__(config)
self._init_sniffer()
diff --git a/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py
index b5832d0..10f3ea0 100755
--- a/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py
@@ -339,7 +339,6 @@
while not p2p_find_result:
ad2.droid.wifiP2pStopPeerDiscovery()
ad1.droid.wifiP2pStopPeerDiscovery()
- ad2.droid.wifiP2pDiscoverPeers()
ad1.droid.wifiP2pDiscoverPeers()
ad1_event = ad1.ed.pop_event(p2pconsts.PEER_AVAILABLE_EVENT,
p2pconsts.P2P_FIND_TIMEOUT)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/__init__.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/__init__.py
index 831ddc8..9da3529 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/__init__.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/__init__.py
@@ -66,7 +66,7 @@
def detect_wifi_platform(dut):
if hasattr(dut, 'wifi_platform'):
return dut.wifi_platform
- qcom_check = len(dut.get_file_names('/vendor/firmware/wlan/qca_cld/'))
+ qcom_check = len(dut.get_file_names('/vendor/firmware/wlan/'))
if qcom_check:
dut.wifi_platform = 'qcom'
else:
@@ -75,6 +75,7 @@
def detect_wifi_decorator(f):
+
def wrap(*args, **kwargs):
if 'dut' in kwargs:
dut = kwargs['dut']
@@ -274,7 +275,8 @@
socket_size=None,
num_processes=1,
udp_throughput='1000M',
- ipv6=False):
+ ipv6=False,
+ udp_length=1470):
"""Function to format iperf client arguments.
This function takes in iperf client parameters and returns a properly
@@ -296,8 +298,8 @@
if ipv6:
iperf_args = iperf_args + '-6 '
if traffic_type.upper() == 'UDP':
- iperf_args = iperf_args + '-u -b {} -l 1470 -P {} '.format(
- udp_throughput, num_processes)
+ iperf_args = iperf_args + '-u -b {} -l {} -P {} '.format(
+ udp_throughput, udp_length, num_processes)
elif traffic_type.upper() == 'TCP':
iperf_args = iperf_args + '-P {} '.format(num_processes)
if socket_size:
@@ -728,6 +730,7 @@
# Link layer stats utilities
class LinkLayerStats():
+
def __new__(self, dut, llstats_enabled=True):
if detect_wifi_platform(dut) == 'qcom':
return qcom_utils.LinkLayerStats(dut, llstats_enabled)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py
index 5a8433e..d91803b 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/bokeh_figure.py
@@ -19,6 +19,7 @@
import itertools
import json
import math
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
# Plotting Utilities
@@ -98,8 +99,8 @@
def init_plot(self):
self.plot = bokeh.plotting.figure(
sizing_mode=self.fig_property['sizing_mode'],
- plot_width=self.fig_property['width'],
- plot_height=self.fig_property['height'],
+ width=self.fig_property['width'],
+ height=self.fig_property['height'],
title=self.fig_property['title'],
tools=self.TOOLS,
x_axis_type=self.fig_property['x_axis_type'],
@@ -326,7 +327,7 @@
figure_data=self.figure_data)
output_file = output_file.replace('.html', '_plot_data.json')
with open(output_file, 'w') as outfile:
- json.dump(figure_dict, outfile, indent=4)
+ json.dump(wputils.serialize_dict(figure_dict), outfile, indent=4)
def save_figure(self, output_file, save_json=True):
"""Function to save BokehFigure.
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py
index afa5f32..fe2d3e7 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/brcm_utils.py
@@ -378,16 +378,17 @@
LL_STATS_CLEAR_CMD = 'wl dump_clear ampdu; wl reset_cnts;'
BW_REGEX = re.compile(r'Chanspec:.+ (?P<bandwidth>[0-9]+)MHz')
MCS_REGEX = re.compile(r'(?P<count>[0-9]+)\((?P<percent>[0-9]+)%\)')
- RX_REGEX = re.compile(r'RX (?P<mode>\S+)\s+:\s*(?P<nss1>[0-9, ,(,),%]*)'
- '\n\s*:?\s*(?P<nss2>[0-9, ,(,),%]*)')
- TX_REGEX = re.compile(r'TX (?P<mode>\S+)\s+:\s*(?P<nss1>[0-9, ,(,),%]*)'
- '\n\s*:?\s*(?P<nss2>[0-9, ,(,),%]*)')
+ RX_REGEX = re.compile(
+ r'RX (?P<mode>MCS|VHT|HE|EHT)\s+:\s*(?P<nss1>[0-9, ,(,),%]*)'
+ '\n\s*:?\s*(?P<nss2>[0-9, ,(,),%]*)')
+ TX_REGEX = re.compile(
+ r'TX (?P<mode>MCS|VHT|HE|EHT)\s+:\s*(?P<nss1>[0-9, ,(,),%]*)'
+ '\n\s*:?\s*(?P<nss2>[0-9, ,(,),%]*)')
TX_PER_REGEX = re.compile(
r'(?P<mode>\S+) PER\s+:\s*(?P<nss1>[0-9, ,(,),%]*)'
'\n\s*:?\s*(?P<nss2>[0-9, ,(,),%]*)')
- RX_FCS_REGEX = re.compile(
- r'rxbadfcs (?P<rx_bad_fcs>[0-9]*).+\n.+goodfcs (?P<rx_good_fcs>[0-9]*)'
- )
+ RX_GOOD_FCS_REGEX = re.compile(r'goodfcs (?P<rx_good_fcs>[0-9]*)')
+ RX_BAD_FCS_REGEX = re.compile(r'rxbadfcs (?P<rx_bad_fcs>[0-9]*)')
RX_AGG_REGEX = re.compile(r'rxmpduperampdu (?P<aggregation>[0-9]*)')
TX_AGG_REGEX = re.compile(r' mpduperampdu (?P<aggregation>[0-9]*)')
TX_AGG_STOP_REGEX = re.compile(
@@ -405,6 +406,7 @@
self.llstats_enabled = llstats_enabled
self.llstats_cumulative = self._empty_llstats()
self.llstats_incremental = self._empty_llstats()
+ self.bandwidth = None
def update_stats(self):
if self.llstats_enabled:
@@ -414,8 +416,9 @@
self.dut.adb.shell_nb(self.LL_STATS_CLEAR_CMD)
wl_join = self.dut.adb.shell("wl status")
- self.bandwidth = int(
- re.search(self.BW_REGEX, wl_join).group('bandwidth'))
+ if not self.bandwidth:
+ self.bandwidth = int(
+ re.search(self.BW_REGEX, wl_join).group('bandwidth'))
except:
llstats_output = ''
else:
@@ -494,33 +497,32 @@
rx_agg_match = re.search(self.RX_AGG_REGEX, llstats_output)
tx_agg_match = re.search(self.TX_AGG_REGEX, llstats_output)
tx_agg_stop_match = re.search(self.TX_AGG_STOP_REGEX, llstats_output)
- rx_fcs_match = re.search(self.RX_FCS_REGEX, llstats_output)
+ rx_good_fcs_match = re.search(self.RX_GOOD_FCS_REGEX, llstats_output)
+ rx_bad_fcs_match = re.search(self.RX_BAD_FCS_REGEX, llstats_output)
- if rx_agg_match and tx_agg_match and tx_agg_stop_match and rx_fcs_match:
- agg_stop_dict = collections.OrderedDict(
- rx_aggregation=int(rx_agg_match.group('aggregation')),
- tx_aggregation=int(tx_agg_match.group('aggregation')),
- tx_agg_tried=int(tx_agg_stop_match.group('agg_tried')),
- tx_agg_canceled=int(tx_agg_stop_match.group('agg_canceled')),
- rx_good_fcs=int(rx_fcs_match.group('rx_good_fcs')),
- rx_bad_fcs=int(rx_fcs_match.group('rx_bad_fcs')),
- agg_stop_reason=collections.OrderedDict())
+ mpdu_stats = collections.OrderedDict(
+ rx_aggregation=int(rx_agg_match.group('aggregation'))
+ if rx_agg_match else 0,
+ tx_aggregation=int(tx_agg_match.group('aggregation'))
+ if tx_agg_match else 0,
+ tx_agg_tried=int(tx_agg_stop_match.group('agg_tried'))
+ if tx_agg_stop_match else 0,
+ tx_agg_canceled=int(tx_agg_stop_match.group('agg_canceled'))
+ if tx_agg_stop_match else 0,
+ rx_good_fcs=int(rx_good_fcs_match.group('rx_good_fcs'))
+ if rx_good_fcs_match else 0,
+ rx_bad_fcs=int(rx_bad_fcs_match.group('rx_bad_fcs'))
+ if rx_bad_fcs_match else 0,
+ agg_stop_reason=collections.OrderedDict())
+ if tx_agg_stop_match:
agg_reason_match = re.finditer(
self.TX_AGG_STOP_REASON_REGEX,
tx_agg_stop_match.group('agg_stop_reason'))
for reason_match in agg_reason_match:
- agg_stop_dict['agg_stop_reason'][reason_match.group(
+ mpdu_stats['agg_stop_reason'][reason_match.group(
'reason')] = reason_match.group('value')
- else:
- agg_stop_dict = collections.OrderedDict(rx_aggregation=0,
- tx_aggregation=0,
- tx_agg_tried=0,
- tx_agg_canceled=0,
- rx_good_fcs=0,
- rx_bad_fcs=0,
- agg_stop_reason=None)
- return agg_stop_dict
+ return mpdu_stats
def _generate_stats_summary(self, llstats_dict):
llstats_summary = collections.OrderedDict(common_tx_mcs=None,
@@ -547,13 +549,17 @@
llstats_summary['common_tx_mcs_count'] = numpy.max(tx_mpdu)
llstats_summary['common_rx_mcs'] = mcs_ids[numpy.argmax(rx_mpdu)]
llstats_summary['common_rx_mcs_count'] = numpy.max(rx_mpdu)
- if sum(tx_mpdu) and sum(rx_mpdu):
+ if sum(tx_mpdu):
llstats_summary['mean_tx_phy_rate'] = numpy.average(
phy_rates, weights=tx_mpdu)
- llstats_summary['mean_rx_phy_rate'] = numpy.average(
- phy_rates, weights=rx_mpdu)
llstats_summary['common_tx_mcs_freq'] = (
llstats_summary['common_tx_mcs_count'] / sum(tx_mpdu))
+ else:
+ llstats_summary['mean_tx_phy_rate'] = 0
+ llstats_summary['common_tx_mcs_freq'] = 0
+ if sum(rx_mpdu):
+ llstats_summary['mean_rx_phy_rate'] = numpy.average(
+ phy_rates, weights=rx_mpdu)
llstats_summary['common_rx_mcs_freq'] = (
llstats_summary['common_rx_mcs_count'] / sum(rx_mpdu))
total_rx_frames = llstats_dict['mpdu_stats'][
@@ -561,7 +567,11 @@
if total_rx_frames:
llstats_summary['rx_per'] = (
llstats_dict['mpdu_stats']['rx_bad_fcs'] /
- (total_rx_frames)) * 100
+ total_rx_frames) * 100
+ else:
+ llstats_summary['mean_rx_phy_rate'] = 0
+ llstats_summary['common_rx_mcs_freq'] = 0
+ llstats_summary['rx_per'] = 0
return llstats_summary
def _update_stats(self, llstats_output):
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/qcom_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/qcom_utils.py
index 53321bc..f29e4fa 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/qcom_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils/qcom_utils.py
@@ -21,6 +21,7 @@
import os
import re
import statistics
+import numpy
import time
from acts import asserts
@@ -275,14 +276,15 @@
def _edit_dut_ini(dut, ini_fields):
"""Function to edit Wifi ini files."""
- dut_ini_path = '/vendor/firmware/wlan/qca_cld/WCNSS_qcom_cfg.ini'
- local_ini_path = os.path.expanduser('~/WCNSS_qcom_cfg.ini')
+ dut_ini_path = '/vendor/firmware/wlan/qcom_cfg.ini'
+ local_ini_path = os.path.expanduser('~/qcom_cfg.ini')
dut.pull_files(dut_ini_path, local_ini_path)
_set_ini_fields(local_ini_path, ini_fields)
dut.push_system_file(local_ini_path, dut_ini_path)
- dut.reboot()
+ # For 1x1 mode, we need to wait for sl4a to load (To avoid crashes)
+ dut.reboot(timeout=300, wait_after_reboot_complete=120)
def set_chain_mask(dut, chain_mask):
@@ -336,6 +338,7 @@
class LinkLayerStats():
LLSTATS_CMD = 'cat /d/wlan0/ll_stats'
+ MOUNT_CMD = 'mount -t debugfs debugfs /sys/kernel/debug'
PEER_REGEX = 'LL_STATS_PEER_ALL'
MCS_REGEX = re.compile(
r'preamble: (?P<mode>\S+), nss: (?P<num_streams>\S+), bw: (?P<bw>\S+), '
@@ -345,8 +348,8 @@
'retries_long: (?P<retries_long>\S+)')
MCS_ID = collections.namedtuple(
'mcs_id', ['mode', 'num_streams', 'bandwidth', 'mcs', 'rate'])
- MODE_MAP = {'0': '11a/g', '1': '11b', '2': '11n', '3': '11ac'}
- BW_MAP = {'0': 20, '1': 40, '2': 80}
+ MODE_MAP = {'0': '11a/g', '1': '11b', '2': '11n', '3': '11ac', '4': '11ax'}
+ BW_MAP = {'0': 20, '1': 40, '2': 80, '3':160}
def __init__(self, dut, llstats_enabled=True):
self.dut = dut
@@ -356,6 +359,12 @@
def update_stats(self):
if self.llstats_enabled:
+ # Checking the files to see if the device is mounted to enable
+ # llstats capture
+ mount_check = len(self.dut.get_file_names('/d/wlan0'))
+ if not(mount_check):
+ self.dut.adb.shell(self.MOUNT_CMD, timeout=10)
+
try:
llstats_output = self.dut.adb.shell(self.LLSTATS_CMD,
timeout=0.1)
@@ -400,7 +409,7 @@
current_mcs = self.MCS_ID(self.MODE_MAP[match.group('mode')],
int(match.group('num_streams')) + 1,
self.BW_MAP[match.group('bw')],
- int(match.group('mcs')),
+ int(match.group('mcs'), 16),
int(match.group('rate'), 16) / 1000)
current_stats = collections.OrderedDict(
txmpdu=int(match.group('txmpdu')),
@@ -427,9 +436,17 @@
common_rx_mcs_freq=0,
rx_per=float('nan'))
+ phy_rates=[]
+ tx_mpdu=[]
+ rx_mpdu=[]
txmpdu_count = 0
rxmpdu_count = 0
for mcs_id, mcs_stats in llstats_dict['mcs_stats'].items():
+ # Extract the phy-rates
+ mcs_id_split=mcs_id.split();
+ phy_rates.append(float(mcs_id_split[len(mcs_id_split)-1].split('M')[0]))
+ rx_mpdu.append(mcs_stats['rxmpdu'])
+ tx_mpdu.append(mcs_stats['txmpdu'])
if mcs_stats['txmpdu'] > llstats_summary['common_tx_mcs_count']:
llstats_summary['common_tx_mcs'] = mcs_id
llstats_summary['common_tx_mcs_count'] = mcs_stats['txmpdu']
@@ -438,6 +455,15 @@
llstats_summary['common_rx_mcs_count'] = mcs_stats['rxmpdu']
txmpdu_count += mcs_stats['txmpdu']
rxmpdu_count += mcs_stats['rxmpdu']
+
+ if len(tx_mpdu) == 0 or len(rx_mpdu) == 0:
+ return llstats_summary
+
+ # Calculate the average tx/rx -phy rates
+ if sum(tx_mpdu) and sum(rx_mpdu):
+ llstats_summary['mean_tx_phy_rate'] = numpy.average(phy_rates, weights=tx_mpdu)
+ llstats_summary['mean_rx_phy_rate'] = numpy.average(phy_rates, weights=rx_mpdu)
+
if txmpdu_count:
llstats_summary['common_tx_mcs_freq'] = (
llstats_summary['common_tx_mcs_count'] / txmpdu_count)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py
index adefa7e..3063423 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py
@@ -33,6 +33,24 @@
ENABLED_MODULATED_DTIM = 'gEnableModulatedDTIM='
MAX_MODULATED_DTIM = 'gMaxLIModulatedDTIM='
+CHRE_WIFI_SCAN_TYPE = {
+ 'active': 'active',
+ 'passive': 'passive',
+ 'activePassiveDfs': 'active_passive_dfs',
+ 'noPreference': 'no_preference'
+}
+
+CHRE_WIFI_RADIO_CHAIN = {
+ 'lowLatency': 'low_latency',
+ 'lowPower': 'low_power',
+ 'highAccuracy': 'high_accuracy'
+}
+
+CHRE_WIFI_CHANNEL_SET = {
+ 'all': 'all',
+ 'nonDfs': 'non_dfs'
+}
+
def change_dtim(ad, gEnableModulatedDTIM, gMaxLIModulatedDTIM=10):
"""Function to change the DTIM setting in the phone.
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/__init__.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/__init__.py
index 214401c..da47cb8 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/__init__.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/__init__.py
@@ -18,11 +18,17 @@
import copy
import fcntl
import importlib
+import logging
import os
import selenium
-import splinter
import time
from acts import logger
+from selenium.webdriver.support.ui import Select
+from selenium.webdriver.chrome.service import Service as ChromeService
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support import expected_conditions as expected_conditions
+from webdriver_manager.chrome import ChromeDriverManager
+
BROWSER_WAIT_SHORT = 1
BROWSER_WAIT_MED = 3
@@ -106,7 +112,7 @@
obj.teardown()
-class BlockingBrowser(splinter.driver.webdriver.chrome.WebDriver):
+class BlockingBrowser(selenium.webdriver.chrome.webdriver.WebDriver):
"""Class that implements a blocking browser session on top of selenium.
The class inherits from and builds upon splinter/selenium's webdriver class
@@ -123,10 +129,14 @@
headless: boolean to control visible/headless browser operation
timeout: maximum time allowed to launch browser
"""
+ if int(selenium.__version__[0]) < 4:
+ raise RuntimeError(
+ 'BlockingBrowser now requires selenium==4.0.0 or later. ')
self.log = logger.create_tagged_trace_logger('ChromeDriver')
- self.chrome_options = splinter.driver.webdriver.chrome.Options()
+ self.chrome_options = selenium.webdriver.chrome.webdriver.Options()
self.chrome_options.add_argument('--no-proxy-server')
self.chrome_options.add_argument('--no-sandbox')
+ self.chrome_options.add_argument('--crash-dumps-dir=/tmp')
self.chrome_options.add_argument('--allow-running-insecure-content')
self.chrome_options.add_argument('--ignore-certificate-errors')
self.chrome_capabilities = selenium.webdriver.common.desired_capabilities.DesiredCapabilities.CHROME.copy(
@@ -136,7 +146,8 @@
if headless:
self.chrome_options.add_argument('--headless')
self.chrome_options.add_argument('--disable-gpu')
- self.lock_file_path = '/usr/local/bin/chromedriver'
+ os.environ['WDM_LOG'] = str(logging.NOTSET)
+ self.executable_path = ChromeDriverManager().install()
self.timeout = timeout
def __enter__(self):
@@ -147,7 +158,7 @@
session. If an exception occurs while starting the browser, the lock
file is released.
"""
- self.lock_file = open(self.lock_file_path, 'r')
+ self.lock_file = open(self.executable_path, 'r')
start_time = time.time()
while time.time() < start_time + self.timeout:
try:
@@ -157,14 +168,11 @@
continue
try:
self.driver = selenium.webdriver.Chrome(
+ service=ChromeService(self.executable_path),
options=self.chrome_options,
desired_capabilities=self.chrome_capabilities)
- self.element_class = splinter.driver.webdriver.WebDriverElement
- self._cookie_manager = splinter.driver.webdriver.cookie_manager.CookieManager(
- self.driver)
- super(splinter.driver.webdriver.chrome.WebDriver,
- self).__init__(2)
- return super(BlockingBrowser, self).__enter__()
+ self.session_id = self.driver.session_id
+ return self
except:
fcntl.flock(self.lock_file, fcntl.LOCK_UN)
self.lock_file.close()
@@ -179,8 +187,7 @@
releases the lock file.
"""
try:
- super(BlockingBrowser, self).__exit__(exc_type, exc_value,
- traceback)
+ self.driver.quit()
except:
raise RuntimeError('Failed to quit browser. Releasing lock file.')
finally:
@@ -189,7 +196,7 @@
def restart(self):
"""Method to restart browser session without releasing lock file."""
- self.quit()
+ self.driver.quit()
self.__enter__()
def visit_persistent(self,
@@ -215,21 +222,25 @@
self.driver.set_page_load_timeout(page_load_timeout)
for idx in range(num_tries):
try:
- self.visit(url)
+ self.driver.get(url)
except:
self.restart()
- page_reached = self.url.split('/')[-1] == url.split('/')[-1]
- if check_for_element:
- time.sleep(BROWSER_WAIT_MED)
- element = self.find_by_id(check_for_element)
- if not element:
- page_reached = 0
+ page_reached = self.driver.current_url.split('/')[-1] == url.split(
+ '/')[-1]
if page_reached:
- break
+ if check_for_element:
+ time.sleep(BROWSER_WAIT_MED)
+ if self.is_element_visible(check_for_element):
+ break
+ else:
+ raise RuntimeError(
+ 'Page reached but expected element not found.')
+ else:
+ break
else:
try:
- self.visit(backup_url)
+ self.driver.get(backup_url)
except:
self.restart()
@@ -238,6 +249,125 @@
self.url))
raise RuntimeError('URL unreachable.')
+ def get_element_value(self, element_name):
+ """Function to look up and get webpage element value.
+
+ Args:
+ element_name: name of element to look up
+ Returns:
+ Value of element
+ """
+ #element = self.driver.find_element_by_name(element_name)
+ element = self.driver.find_element(By.NAME, element_name)
+ element_type = self.get_element_type(element_name)
+ if element_type == 'checkbox':
+ return element.is_selected()
+ elif element_type == 'radio':
+ items = self.driver.find_elements(By.NAME, element_name)
+ for item in items:
+ if item.is_selected():
+ return item.get_attribute('value')
+ else:
+ return element.get_attribute('value')
+
+ def get_element_type(self, element_name):
+ """Function to look up and get webpage element type.
+
+ Args:
+ element_name: name of element to look up
+ Returns:
+ Type of element
+ """
+ item = self.driver.find_element(By.NAME, element_name)
+ type = item.get_attribute('type')
+ return type
+
+ def is_element_enabled(self, element_name):
+ """Function to check if element is enabled/interactable.
+
+ Args:
+ element_name: name of element to look up
+ Returns:
+ Boolean indicating if element is interactable
+ """
+ item = self.driver.find_element(By.NAME, element_name)
+ return item.is_enabled()
+
+ def is_element_visible(self, element_name):
+ """Function to check if element is visible.
+
+ Args:
+ element_name: name of element to look up
+ Returns:
+ Boolean indicating if element is visible
+ """
+ item = self.driver.find_element(By.NAME, element_name)
+ return item.is_displayed()
+
+ def set_element_value(self, element_name, value, select_method='value'):
+ """Function to set webpage element value.
+
+ Args:
+ element_name: name of element to set
+ value: value of element
+ select_method: select method for dropdown lists (value/index/text)
+ """
+ element_type = self.get_element_type(element_name)
+ if element_type == 'text' or element_type == 'password':
+ item = self.driver.find_element(By.NAME, element_name)
+ item.clear()
+ item.send_keys(value)
+ elif element_type == 'checkbox':
+ item = self.driver.find_element(By.NAME, element_name)
+ if value != item.is_selected():
+ item.click()
+ elif element_type == 'radio':
+ items = self.driver.find_elements(By.NAME, element_name)
+ for item in items:
+ if item.get_attribute('value') == value:
+ item.click()
+ elif element_type == 'select-one':
+ select = Select(self.driver.find_element(By.NAME, element_name))
+ if select_method == 'value':
+ select.select_by_value(str(value))
+ elif select_method == 'text':
+ select.select_by_visible_text(value)
+ elif select_method == 'index':
+ select.select_by_index(value)
+ else:
+ raise RuntimeError(
+ '{} is not a valid select method.'.format(select_method))
+ else:
+ raise RuntimeError(
+ 'Element type {} not supported.'.format(element_type))
+
+ def click_button(self, button_name):
+ """Function to click button on webpage
+
+ Args:
+ button_name: name of button to click
+ """
+ button = self.driver.find_element(By.NAME, button_name)
+ if button.get_attribute('type') == 'submit':
+ button.click()
+ else:
+ raise RuntimeError('{} is not a button.'.format(button_name))
+
+ def accept_alert_if_present(self, wait_for_alert=1):
+ """Function to check for alert and accept if present
+
+ Args:
+ wait_for_alert: time (seconds) to wait for alert
+ """
+ try:
+ selenium.webdriver.support.ui.WebDriverWait(
+ self.driver,
+ wait_for_alert).until(expected_conditions.alert_is_present())
+ alert = self.driver.switch_to.alert
+ alert.accept()
+ except selenium.common.exceptions.TimeoutException:
+ pass
+
class WifiRetailAP(object):
"""Base class implementation for retail ap.
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/brcm_ref.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/brcm_ref.py
index 1b09533..c05ff40 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/brcm_ref.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/brcm_ref.py
@@ -1,5 +1,6 @@
import collections
import numpy
+import paramiko
import time
from acts_contrib.test_utils.wifi.wifi_retail_ap import WifiRetailAP
from acts_contrib.test_utils.wifi.wifi_retail_ap import BlockingBrowser
@@ -8,6 +9,8 @@
BROWSER_WAIT_MED = 3
BROWSER_WAIT_LONG = 10
BROWSER_WAIT_EXTRA_LONG = 60
+SSH_WAIT_SHORT = 0.1
+SSH_READ_BYTES = 600000
class BrcmRefAP(WifiRetailAP):
@@ -16,13 +19,45 @@
Since most of the class' implementation is shared with the R7000, this
class inherits from NetgearR7000AP and simply redefines config parameters
"""
+
def __init__(self, ap_settings):
super().__init__(ap_settings)
self.init_gui_data()
+ # Initialize SSH connection
+ self.init_ssh_connection()
# Read and update AP settings
self.read_ap_settings()
self.update_ap_settings(ap_settings)
+ def teardown(self):
+ """Function to perform destroy operations."""
+ if self.ap_settings.get('lock_ap', 0):
+ self._unlock_ap()
+ self.close_ssh_connection()
+
+ def init_ssh_connection(self):
+ self.ssh_client = paramiko.SSHClient()
+ self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ self.ssh_client.connect(hostname=self.ap_settings['ip_address'],
+ username=self.ap_settings['admin_username'],
+ password=self.ap_settings['admin_password'],
+ look_for_keys=False,
+ allow_agent=False)
+
+ def close_ssh_connection(self):
+ self.ssh_client.close()
+
+ def run_ssh_cmd(self, command):
+ with self.ssh_client.invoke_shell() as shell:
+ shell.send('sh\n')
+ time.sleep(SSH_WAIT_SHORT)
+ shell.recv(SSH_READ_BYTES)
+ shell.send('{}\n'.format(command))
+ time.sleep(SSH_WAIT_SHORT)
+ response = shell.recv(SSH_READ_BYTES).decode('utf-8').splitlines()
+ response = [line for line in response[1:] if line != '# ']
+ return response
+
def init_gui_data(self):
self.config_page = ('{protocol}://{username}:{password}@'
'{ip_address}:{port}/info.html').format(
@@ -240,4 +275,146 @@
config_item.first.click()
time.sleep(BROWSER_WAIT_LONG)
browser.visit_persistent(self.config_page, BROWSER_WAIT_LONG,
- 10)
+ 10)
+
+ def set_power(self, interface, power):
+ """Function that sets interface transmit power.
+
+ Args:
+ interface: string containing interface identifier (2G_5G, 6G)
+ power: power level in dBm
+ """
+ wl_interface = 'wl0' if interface == '6G' else 'wl1'
+
+ if power == 'auto':
+ response = self.run_ssh_cmd(
+ 'wl -i {} txpwr1 -1'.format(wl_interface))
+ else:
+ power_qdbm = int(power * 4)
+ response = self.run_ssh_cmd('wl -i {} txpwr1 -o -q {}'.format(
+ wl_interface, power_qdbm))
+
+ self.ap_settings[interface]['power'] = power_qdbm / 4
+
+ def get_power(self, interface):
+ """Function to get power used by AP
+
+ Args:
+ interface: interface to get rate on (2G_5G, 6G)
+ Returns:
+ power string returned by AP.
+ """
+ wl_interface = 'wl0' if interface == '6G' else 'wl1'
+ return self.run_ssh_cmd('wl -i {} txpwr1'.format(wl_interface))
+
+ def set_rate(self,
+ interface,
+ mode=None,
+ num_streams=None,
+ rate='auto',
+ short_gi=0,
+ tx_expansion=0):
+ """Function that sets rate.
+
+ Args:
+ interface: string containing interface identifier (2G, 5G_1)
+ mode: string indicating the WiFi standard to use
+ num_streams: number of MIMO streams. used only for VHT
+ rate: data rate of MCS index to use
+ short_gi: boolean controlling the use of short guard interval
+ """
+ wl_interface = 'wl0' if interface == '6G' else 'wl1'
+
+ if interface == '6G':
+ band_rate = '6g_rate'
+ elif self.ap_settings['2G_5G']['channel'] < 13:
+ band_rate = '2g_rate'
+ else:
+ band_rate = '5g_rate'
+
+ if rate == 'auto':
+ cmd_string = 'wl -i {} {} auto'.format(wl_interface, band_rate)
+ elif 'legacy' in mode.lower():
+ cmd_string = 'wl -i {} {} -r {} -x {}'.format(
+ wl_interface, band_rate, rate, tx_expansion)
+ elif 'ht' in mode.lower():
+ cmd_string = 'wl -i {} {} -h {} -x {}'.format(
+ wl_interface, band_rate, rate, tx_expansion)
+ if short_gi:
+ cmd_string = cmd_string + '--sgi'
+ elif 'vht' in mode.lower():
+ cmd_string = 'wl -i {} {} -v {}x{} -x {}'.format(
+ wl_interface, band_rate, rate, num_streams, tx_expansion)
+ if short_gi:
+ cmd_string = cmd_string + '--sgi'
+ elif 'he' in mode.lower():
+ cmd_string = 'wl -i {} {} -e {}x{} -l -x {}'.format(
+ wl_interface, band_rate, rate, num_streams, tx_expansion)
+ if short_gi:
+ cmd_string = cmd_string + '-i {}'.format(short_gi)
+
+ response = self.run_ssh_cmd(cmd_string)
+
+ self.ap_settings[interface]['mode'] = mode
+ self.ap_settings[interface]['num_streams'] = num_streams
+ self.ap_settings[interface]['rate'] = rate
+ self.ap_settings[interface]['short_gi'] = short_gi
+
+ def get_rate(self, interface):
+ """Function to get rate used by AP
+
+ Args:
+ interface: interface to get rate on (2G_5G, 6G)
+ Returns:
+ rate string returned by AP.
+ """
+
+ wl_interface = 'wl0' if interface == '6G' else 'wl1'
+
+ if interface == '6G':
+ band_rate = '6g_rate'
+ elif self.ap_settings['2G_5G']['channel'] < 13:
+ band_rate = '2g_rate'
+ else:
+ band_rate = '5g_rate'
+ return self.run_ssh_cmd('wl -i {} {}'.format(wl_interface, band_rate))
+
+ def set_rts_enable(self, interface, enable):
+ """Function to enable or disable RTS/CTS
+
+ Args:
+ interface: interface to be configured (2G_5G, 6G)
+ enable: boolean controlling RTS/CTS behavior
+ """
+ wl_interface = 'wl0' if interface == '6G' else 'wl1'
+ if enable:
+ self.run_ssh_cmd('wl -i {} ampdu_rts 1'.format(wl_interface))
+ self.run_ssh_cmd('wl -i {} rtsthresh 2437'.format(wl_interface))
+ else:
+ self.run_ssh_cmd('wl -i {} ampdu_rts 0'.format(wl_interface))
+ self.run_ssh_cmd('wl -i {} rtsthresh 15000'.format(wl_interface))
+
+ def set_tx_beamformer(self, interface, enable):
+ """Function to enable or disable transmit beamforming
+
+ Args:
+ interface: interface to be configured (2G_5G, 6G)
+ enable: boolean controlling beamformer behavior
+ """
+ wl_interface = 'wl0' if interface == '6G' else 'wl1'
+
+ self.run_ssh_cmd('wl down')
+ self.run_ssh_cmd('wl -i {} txbf {}'.format(wl_interface, int(enable)))
+ self.run_ssh_cmd('wl up')
+
+ def get_sta_rssi(self, interface, sta_macaddr):
+ """Function to get RSSI from connected STA
+
+ Args:
+ interface: interface to be configured (2G_5G, 6G)
+ sta_macaddr: mac address of STA of interest
+ """
+ wl_interface = 'wl0' if interface == '6G' else 'wl1'
+
+ return self.run_ssh_cmd('wl -i {} phy_rssi_ant {}'.format(
+ wl_interface, sta_macaddr))
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/google_wifi.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/google_wifi.py
index a39c516..6c3230e 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/google_wifi.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/google_wifi.py
@@ -119,6 +119,10 @@
self.access_point = access_point.AccessPoint(init_settings)
self.configure_ap()
+ def teardown(self):
+ self.access_point.stop_all_aps()
+ super().teardown()
+
def read_ap_settings(self):
"""Function that reads current ap settings."""
return self.ap_settings.copy()
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7000.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7000.py
index ac118df..9c491a1 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7000.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7000.py
@@ -26,7 +26,11 @@
class NetgearR7000AP(WifiRetailAP):
"""Class that implements Netgear R7000 AP."""
+
def __init__(self, ap_settings):
+ self.log.warning(
+ 'This AP model is no longer maintained and must be updated/verified.'
+ )
super().__init__(ap_settings)
self.init_gui_data()
# Read and update AP settings
@@ -276,6 +280,7 @@
class NetgearR7000NAAP(NetgearR7000AP):
"""Class that implements Netgear R7000 NA AP."""
+
def init_gui_data(self):
"""Function to initialize data used while interacting with web GUI"""
super().init_gui_data()
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7500.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7500.py
index 06329b6..f554e0d 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7500.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_r7500.py
@@ -27,7 +27,11 @@
class NetgearR7500AP(WifiRetailAP):
"""Class that implements Netgear R7500 AP."""
+
def __init__(self, ap_settings):
+ self.log.warning(
+ 'This AP model is no longer maintained and must be updated/verified.'
+ )
super().__init__(ap_settings)
self.init_gui_data()
# Read and update AP settings
@@ -329,7 +333,8 @@
class NetgearR7500NAAP(NetgearR7500AP):
"""Class that implements Netgear R7500 NA AP."""
+
def init_gui_data(self):
"""Function to initialize data used while interacting with web GUI"""
super().init_gui_data()
- self.region_map['10'] = 'North America'
\ No newline at end of file
+ self.region_map['10'] = 'North America'
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax200.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax200.py
index d6c6fad..9363bbe 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax200.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_rax200.py
@@ -31,6 +31,7 @@
Since most of the class' implementation is shared with the R7000, this
class inherits from NetgearR7000AP and simply redefines config parameters
"""
+
def __init__(self, ap_settings):
super().__init__(ap_settings)
self.init_gui_data()
@@ -271,42 +272,38 @@
# Visit URL
browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
- for key, value in self.config_page_fields.items():
- if 'status' in key:
+ for field_key, field_name in self.config_page_fields.items():
+ if 'status' in field_key:
browser.visit_persistent(self.config_page_advanced,
BROWSER_WAIT_MED, 10)
- config_item = browser.find_by_name(value)
- self.ap_settings[key[0]][key[1]] = int(
- config_item.first.checked)
+ field_value = browser.get_element_value(field_name)
+ self.ap_settings[field_key[0]][field_key[1]] = int(
+ field_value)
browser.visit_persistent(self.config_page,
BROWSER_WAIT_MED, 10)
else:
- config_item = browser.find_by_name(value)
- if 'enable_ax' in key:
- self.ap_settings[key] = int(config_item.first.checked)
- elif 'bandwidth' in key:
- self.ap_settings[key[0]][key[1]] = self.bw_mode_values[
- self.ap_settings['enable_ax']][
- config_item.first.value]
- elif 'power' in key:
- self.ap_settings[key[0]][
- key[1]] = self.power_mode_values[
- config_item.first.value]
- elif 'region' in key:
+ field_value = browser.get_element_value(field_name)
+ if 'enable_ax' in field_key:
+ self.ap_settings[field_key] = int(field_value)
+ elif 'bandwidth' in field_key:
+ self.ap_settings[field_key[0]][
+ field_key[1]] = self.bw_mode_values[
+ self.ap_settings['enable_ax']][field_value]
+ elif 'power' in field_key:
+ self.ap_settings[field_key[0]][
+ field_key[1]] = self.power_mode_values[field_value]
+ elif 'region' in field_key:
self.ap_settings['region'] = self.region_map[
- config_item.first.value]
- elif 'security_type' in key:
- for item in config_item:
- if item.checked:
- self.ap_settings[key[0]][key[1]] = item.value
- elif 'channel' in key:
- config_item = browser.find_by_name(value)
- self.ap_settings[key[0]][key[1]] = int(
- config_item.first.value)
+ field_value]
+ elif 'security_type' in field_key:
+ self.ap_settings[field_key[0]][
+ field_key[1]] = field_value
+ elif 'channel' in field_key:
+ self.ap_settings[field_key[0]][field_key[1]] = int(
+ field_value)
else:
- config_item = browser.find_by_name(value)
- self.ap_settings[key[0]][
- key[1]] = config_item.first.value
+ self.ap_settings[field_key[0]][
+ field_key[1]] = field_value
return self.ap_settings.copy()
def configure_ap(self, **config_flags):
@@ -323,70 +320,67 @@
BROWSER_WAIT_MED, 10, self.config_page)
# Update region, and power/bandwidth for each network
- try:
- config_item = browser.find_by_name(
- self.config_page_fields['region']).first
- config_item.select_by_text(self.ap_settings['region'])
- except:
+ if browser.is_element_enabled(self.config_page_fields['region']):
+ browser.set_element_value(self.config_page_fields['region'],
+ self.ap_settings['region'],
+ select_method='text')
+ else:
self.log.warning('Cannot change region.')
- for key, value in self.config_page_fields.items():
- if 'enable_ax' in key:
- config_item = browser.find_by_name(value).first
- if self.ap_settings['enable_ax']:
- config_item.check()
- else:
- config_item.uncheck()
- if 'power' in key:
- config_item = browser.find_by_name(value).first
- config_item.select_by_text(
- self.ap_settings[key[0]][key[1]])
- elif 'bandwidth' in key:
- config_item = browser.find_by_name(value).first
+ for field_key, field_name in self.config_page_fields.items():
+ if 'enable_ax' in field_key:
+ browser.set_element_value(field_name,
+ self.ap_settings['enable_ax'])
+ if 'power' in field_key:
+ browser.set_element_value(
+ field_name,
+ self.ap_settings[field_key[0]][field_key[1]],
+ select_method='text')
+ elif 'bandwidth' in field_key:
try:
- config_item.select_by_text(self.bw_mode_text[key[0]][
- self.ap_settings[key[0]][key[1]]])
+ browser.set_element_value(
+ field_name,
+ self.bw_mode_text[field_key[0]][self.ap_settings[
+ field_key[0]][field_key[1]]],
+ select_method='text')
except AttributeError:
self.log.warning(
'Cannot select bandwidth. Keeping AP default.')
# Update security settings (passwords updated only if applicable)
- for key, value in self.config_page_fields.items():
- if 'security_type' in key:
- browser.choose(value, self.ap_settings[key[0]][key[1]])
- if 'WPA' in self.ap_settings[key[0]][key[1]]:
- config_item = browser.find_by_name(
- self.config_page_fields[(key[0],
- 'password')]).first
- config_item.fill(self.ap_settings[key[0]]['password'])
+ for field_key, field_name in self.config_page_fields.items():
+ if 'security_type' in field_key:
+ browser.set_element_value(
+ field_name,
+ self.ap_settings[field_key[0]][field_key[1]])
+ if 'WPA' in self.ap_settings[field_key[0]][field_key[1]]:
+ browser.set_element_value(
+ self.config_page_fields[(field_key[0],
+ 'password')],
+ self.ap_settings[field_key[0]]['password'])
- for key, value in self.config_page_fields.items():
- if 'ssid' in key:
- config_item = browser.find_by_name(value).first
- config_item.fill(self.ap_settings[key[0]][key[1]])
- elif 'channel' in key:
- config_item = browser.find_by_name(value).first
+ for field_key, field_name in self.config_page_fields.items():
+ if 'ssid' in field_key:
+ browser.set_element_value(
+ field_name,
+ self.ap_settings[field_key[0]][field_key[1]])
+ elif 'channel' in field_key:
+ config_item = browser.find_by_name(field_name).first
try:
- config_item.select(self.ap_settings[key[0]][key[1]])
- time.sleep(BROWSER_WAIT_SHORT)
+ browser.set_element_value(
+ field_name,
+ self.ap_settings[field_key[0]][field_key[1]])
except AttributeError:
self.log.warning(
'Cannot select channel. Keeping AP default.')
try:
for idx in range(0, 2):
- alert = browser.get_alert()
- alert.accept()
- time.sleep(BROWSER_WAIT_SHORT)
+ browser.accept_alert_if_present(BROWSER_WAIT_SHORT)
except:
pass
time.sleep(BROWSER_WAIT_SHORT)
- browser.find_by_name('Apply').first.click()
+ browser.click_button('Apply')
+ browser.accept_alert_if_present(BROWSER_WAIT_SHORT)
time.sleep(BROWSER_WAIT_SHORT)
- try:
- alert = browser.get_alert()
- alert.accept()
- time.sleep(BROWSER_WAIT_SHORT)
- except:
- time.sleep(BROWSER_WAIT_SHORT)
browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
10)
@@ -400,16 +394,14 @@
BROWSER_WAIT_MED, 10)
# Turn radios on or off
- for key, value in self.config_page_fields.items():
- if 'status' in key:
- config_item = browser.find_by_name(value).first
- if self.ap_settings[key[0]][key[1]]:
- config_item.check()
- else:
- config_item.uncheck()
+ for field_key, field_name in self.config_page_fields.items():
+ if 'status' in field_key:
+ browser.set_element_value(
+ field_name,
+ self.ap_settings[field_key[0]][field_key[1]])
time.sleep(BROWSER_WAIT_SHORT)
- browser.find_by_name('Apply').first.click()
+ browser.click_button('Apply')
time.sleep(BROWSER_WAIT_EXTRA_LONG)
browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
10)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_raxe500.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_raxe500.py
index 9dc60aa..c885e05 100644
--- a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_raxe500.py
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap/netgear_raxe500.py
@@ -33,6 +33,7 @@
Since most of the class' implementation is shared with the R7000, this
class inherits from NetgearR7000AP and simply redefines config parameters
"""
+
def __init__(self, ap_settings):
super().__init__(ap_settings)
self.init_gui_data()
@@ -173,8 +174,6 @@
(('2G', 'bandwidth'), 'opmode'),
(('5G_1', 'bandwidth'), 'opmode_an'),
(('6G', 'bandwidth'), 'opmode_an_2'),
- (('2G', 'power'), 'enable_tpc'),
- (('5G_1', 'power'), 'enable_tpc_an'),
(('6G', 'security_type'), 'security_type_an_2'),
(('5G_1', 'security_type'), 'security_type_an'),
(('2G', 'security_type'), 'security_type'),
@@ -183,13 +182,6 @@
(('6G', 'password'), 'passphrase_an_2')
])
- self.power_mode_values = {
- '1': '100%',
- '2': '75%',
- '3': '50%',
- '4': '25%'
- }
-
def _set_channel_and_bandwidth(self,
network,
channel=None,
@@ -286,7 +278,9 @@
browser.visit_persistent(self.firmware_page, BROWSER_WAIT_MED, 10)
firmware_regex = re.compile(
r'Firmware Version[\s\S]+V(?P<version>[0-9._]+)')
- firmware_version = re.search(firmware_regex, browser.html)
+ #firmware_version = re.search(firmware_regex, browser.html)
+ firmware_version = re.search(firmware_regex,
+ browser.driver.page_source)
if firmware_version:
self.ap_settings['firmware_version'] = firmware_version.group(
'version')
@@ -300,42 +294,35 @@
# Visit URL
browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
- for key, value in self.config_page_fields.items():
- if 'status' in key:
+ for field_key, field_name in self.config_page_fields.items():
+ if 'status' in field_key:
browser.visit_persistent(self.config_page_advanced,
BROWSER_WAIT_MED, 10)
- config_item = browser.find_by_name(value)
- self.ap_settings[key[0]][key[1]] = int(
- config_item.first.checked)
+ field_value = browser.get_element_value(field_name)
+ self.ap_settings[field_key[0]][field_key[1]] = int(
+ field_value)
browser.visit_persistent(self.config_page,
BROWSER_WAIT_MED, 10)
else:
- config_item = browser.find_by_name(value)
- if 'enable_ax' in key:
- self.ap_settings[key] = int(config_item.first.checked)
- elif 'bandwidth' in key:
- self.ap_settings[key[0]][key[1]] = self.bw_mode_values[
- self.ap_settings['enable_ax']][
- config_item.first.value]
- elif 'power' in key:
- self.ap_settings[key[0]][
- key[1]] = self.power_mode_values[
- config_item.first.value]
- elif 'region' in key:
+ field_value = browser.get_element_value(field_name)
+ if 'enable_ax' in field_key:
+ self.ap_settings[field_key] = int(field_value)
+ elif 'bandwidth' in field_key:
+ self.ap_settings[field_key[0]][
+ field_key[1]] = self.bw_mode_values[
+ self.ap_settings['enable_ax']][field_value]
+ elif 'region' in field_key:
self.ap_settings['region'] = self.region_map[
- config_item.first.value]
- elif 'security_type' in key:
- for item in config_item:
- if item.checked:
- self.ap_settings[key[0]][key[1]] = item.value
- elif 'channel' in key:
- config_item = browser.find_by_name(value)
- self.ap_settings[key[0]][key[1]] = int(
- config_item.first.value)
+ field_value]
+ elif 'security_type' in field_key:
+ self.ap_settings[field_key[0]][
+ field_key[1]] = field_value
+ elif 'channel' in field_key:
+ self.ap_settings[field_key[0]][field_key[1]] = int(
+ field_value)
else:
- config_item = browser.find_by_name(value)
- self.ap_settings[key[0]][
- key[1]] = config_item.first.value
+ self.ap_settings[field_key[0]][
+ field_key[1]] = field_value
return self.ap_settings.copy()
def configure_ap(self, **config_flags):
@@ -352,68 +339,58 @@
BROWSER_WAIT_MED, 10, self.config_page)
# Update region, and power/bandwidth for each network
- try:
- config_item = browser.find_by_name(
- self.config_page_fields['region']).first
- config_item.select_by_text(self.ap_settings['region'])
- except:
+ if browser.is_element_enabled(self.config_page_fields['region']):
+ browser.set_element_value(self.config_page_fields['region'],
+ self.ap_settings['region'],
+ select_method='text')
+ else:
self.log.warning('Cannot change region.')
- for key, value in self.config_page_fields.items():
- if 'enable_ax' in key:
- config_item = browser.find_by_name(value).first
- if self.ap_settings['enable_ax']:
- config_item.check()
- else:
- config_item.uncheck()
- if 'power' in key:
- config_item = browser.find_by_name(value).first
- config_item.select_by_text(
- self.ap_settings[key[0]][key[1]])
- elif 'bandwidth' in key:
- config_item = browser.find_by_name(value).first
+ for field_key, field_name in self.config_page_fields.items():
+ if 'enable_ax' in field_key:
+ browser.set_element_value(field_name,
+ self.ap_settings['enable_ax'])
+ elif 'bandwidth' in field_key:
try:
- config_item.select_by_text(self.bw_mode_text[key[0]][
- self.ap_settings[key[0]][key[1]]])
+ browser.set_element_value(
+ field_name,
+ self.bw_mode_text[field_key[0]][self.ap_settings[
+ field_key[0]][field_key[1]]],
+ select_method='text')
except AttributeError:
self.log.warning(
'Cannot select bandwidth. Keeping AP default.')
# Update security settings (passwords updated only if applicable)
- for key, value in self.config_page_fields.items():
- if 'security_type' in key:
- browser.choose(value, self.ap_settings[key[0]][key[1]])
- if 'WPA' in self.ap_settings[key[0]][key[1]]:
- config_item = browser.find_by_name(
- self.config_page_fields[(key[0],
- 'password')]).first
- config_item.fill(self.ap_settings[key[0]]['password'])
+ for field_key, field_name in self.config_page_fields.items():
+ if 'security_type' in field_key:
+ browser.set_element_value(
+ field_name,
+ self.ap_settings[field_key[0]][field_key[1]])
+ if 'WPA' in self.ap_settings[field_key[0]][field_key[1]]:
+ browser.set_element_value(
+ self.config_page_fields[(field_key[0],
+ 'password')],
+ self.ap_settings[field_key[0]]['password'])
- for key, value in self.config_page_fields.items():
- if 'ssid' in key:
- config_item = browser.find_by_name(value).first
- config_item.fill(self.ap_settings[key[0]][key[1]])
- elif 'channel' in key:
- config_item = browser.find_by_name(value).first
+ for field_key, field_name in self.config_page_fields.items():
+ if 'ssid' in field_key:
+ browser.set_element_value(
+ field_name,
+ self.ap_settings[field_key[0]][field_key[1]])
+ elif 'channel' in field_key:
try:
- config_item.select(self.ap_settings[key[0]][key[1]])
- time.sleep(BROWSER_WAIT_SHORT)
+ browser.set_element_value(
+ field_name,
+ self.ap_settings[field_key[0]][field_key[1]])
except AttributeError:
self.log.warning(
'Cannot select channel. Keeping AP default.')
- try:
- alert = browser.get_alert()
- alert.accept()
- except:
- pass
+ browser.accept_alert_if_present(BROWSER_WAIT_SHORT)
+
time.sleep(BROWSER_WAIT_SHORT)
- browser.find_by_name('Apply').first.click()
+ browser.click_button('Apply')
+ browser.accept_alert_if_present(BROWSER_WAIT_SHORT)
time.sleep(BROWSER_WAIT_SHORT)
- try:
- alert = browser.get_alert()
- alert.accept()
- time.sleep(BROWSER_WAIT_SHORT)
- except:
- time.sleep(BROWSER_WAIT_SHORT)
browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
10)
@@ -427,16 +404,14 @@
BROWSER_WAIT_MED, 10)
# Turn radios on or off
- for key, value in self.config_page_fields.items():
- if 'status' in key:
- config_item = browser.find_by_name(value).first
- if self.ap_settings[key[0]][key[1]]:
- config_item.check()
- else:
- config_item.uncheck()
+ for field_key, field_name in self.config_page_fields.items():
+ if 'status' in field_key:
+ browser.set_element_value(
+ field_name,
+ self.ap_settings[field_key[0]][field_key[1]])
time.sleep(BROWSER_WAIT_SHORT)
- browser.find_by_name('Apply').first.click()
+ browser.click_button('Apply')
time.sleep(BROWSER_WAIT_EXTRA_LONG)
browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
10)
diff --git a/acts_tests/tests/OWNERS b/acts_tests/tests/OWNERS
index 189ff2e..eb3d0c0 100644
--- a/acts_tests/tests/OWNERS
+++ b/acts_tests/tests/OWNERS
@@ -18,6 +18,8 @@
# Pixel GTW
jasonkmlu@google.com
hongscott@google.com
+diegowchung@google.com
+kuoyuanchiang@google.com
markusliu@google.com
jerrypcchen@google.com
martschneider@google.com
diff --git a/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py b/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
index c775dd7..3104452 100644
--- a/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
+++ b/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
@@ -21,20 +21,26 @@
class BtA2dpRangeTest(A2dpBaseTest):
+
def __init__(self, configs):
super().__init__(configs)
req_params = ['attenuation_vector', 'codecs']
+ opt_params = ['gain_mismatch', 'dual_chain']
#'attenuation_vector' is a dict containing: start, stop and step of
#attenuation changes
#'codecs' is a list containing all codecs required in the tests
self.unpack_userparams(req_params)
+ self.unpack_userparams(opt_params, dual_chain=None, gain_mismatch=None)
+
+ def setup_generated_tests(self):
for codec_config in self.codecs:
- self.generate_test_case(codec_config)
+ arg_set = [(codec_config, )]
+ self.generate_tests(test_logic=self.BtA2dp_test_logic,
+ name_func=self.create_test_name,
+ arg_sets=arg_set)
def setup_class(self):
super().setup_class()
- opt_params = ['gain_mismatch', 'dual_chain']
- self.unpack_userparams(opt_params, dual_chain=None, gain_mismatch=None)
# Enable BQR on all android devices
btutils.enable_bqr(self.android_devices)
if hasattr(self, 'dual_chain') and self.dual_chain == 1:
@@ -49,14 +55,14 @@
self.atten_c0.set_atten(INIT_ATTEN)
self.atten_c1.set_atten(INIT_ATTEN)
- def generate_test_case(self, codec_config):
- def test_case_fn():
- self.run_a2dp_to_max_range(codec_config)
+ def BtA2dp_test_logic(self, codec_config):
+ self.run_a2dp_to_max_range(codec_config)
+ def create_test_name(self, arg_set):
if hasattr(self, 'dual_chain') and self.dual_chain == 1:
- test_case_name = 'test_dual_bt_a2dp_range_codec_{}_gainmimatch_{}dB'.format(
- codec_config['codec_type'], self.gain_mismatch)
+ test_case_name = 'test_dual_bt_a2dp_range_codec_{}_gainmismatch_{}dB'.format(
+ arg_set['codec_type'], self.gain_mismatch)
else:
test_case_name = 'test_bt_a2dp_range_codec_{}'.format(
- codec_config['codec_type'])
- setattr(self, test_case_name, test_case_fn)
+ arg_set['codec_type'])
+ return test_case_name
diff --git a/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleAdvTest.py b/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleAdvTest.py
index 5fe9503..16fedcd 100644
--- a/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleAdvTest.py
+++ b/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleAdvTest.py
@@ -57,35 +57,39 @@
test_bt_a2dp_range_codec_SBC_adv_mode_low_latency_adv_tx_power_low
test_bt_a2dp_range_codec_SBC_adv_mode_low_latency_adv_tx_power_medium
test_bt_a2dp_range_codec_SBC_adv_mode_low_latency_adv_tx_power_high
-
"""
+
def __init__(self, configs):
super().__init__(configs)
req_params = ['attenuation_vector', 'codecs']
- #'attenuation_vector' is a dict containing: start, stop and step of
- #attenuation changes
+ opt_params = ['gain_mismatch', 'dual_chain']
+ #'attenuation_vector' is a dict containing: start, stop and step of attenuation changes
#'codecs' is a list containing all codecs required in the tests
+ #'gain_mismatch' is an offset value between the BT two chains
+ #'dual_chain' set to 1 enable sweeping attenuation for BT two chains
self.unpack_userparams(req_params)
+ self.unpack_userparams(opt_params, dual_chian=None, gain_mismatch=None)
+
+ def setup_generated_tests(self):
for codec_config in self.codecs:
- # Loop all advertise modes and power levels
for adv_mode in ble_advertise_settings_modes.items():
- for adv_power_level in ble_advertise_settings_tx_powers.items(
- ):
- self.generate_test_case(codec_config, adv_mode,
- adv_power_level)
+ for adv_power_level in ble_advertise_settings_tx_powers.items():
+ arg_set = [(codec_config, adv_mode, adv_power_level)]
+ self.generate_tests(
+ test_logic=self.BtA2dp_with_ble_adv_test_logic,
+ name_func=self.create_test_name,
+ arg_sets=arg_set)
def setup_class(self):
super().setup_class()
- opt_params = ['gain_mismatch', 'dual_chain']
- self.unpack_userparams(opt_params, dual_chain=None, gain_mismatch=None)
- return setup_multiple_devices_for_bt_test(self.android_devices)
- # Enable BQR on all android devices
+ #Enable BQR on all android devices
btutils.enable_bqr(self.android_devices)
if hasattr(self, 'dual_chain') and self.dual_chain == 1:
self.atten_c0 = self.attenuators[0]
self.atten_c1 = self.attenuators[1]
self.atten_c0.set_atten(INIT_ATTEN)
self.atten_c1.set_atten(INIT_ATTEN)
+ return setup_multiple_devices_for_bt_test(self.android_devices)
def teardown_class(self):
super().teardown_class()
@@ -93,20 +97,22 @@
self.atten_c0.set_atten(INIT_ATTEN)
self.atten_c1.set_atten(INIT_ATTEN)
- def generate_test_case(self, codec_config, adv_mode, adv_power_level):
- def test_case_fn():
- adv_callback = self.start_ble_adv(adv_mode[1], adv_power_level[1])
- self.run_a2dp_to_max_range(codec_config)
- self.dut.droid.bleStopBleAdvertising(adv_callback)
- self.log.info("Advertisement stopped Successfully")
+ def BtA2dp_with_ble_adv_test_logic(self, codec_config, adv_mode,
+ adv_power_level):
+ adv_callback = self.start_ble_adv(adv_mode[1], adv_power_level[1])
+ self.run_a2dp_to_max_range(codec_config)
+ self.dut.droid.bleStopBleAdvertising(adv_callback)
+ self.log.info("Advertisement stopped Successfully")
+ def create_test_name(self, codec_config, adv_mode, adv_power_level):
if hasattr(self, 'dual_chain') and self.dual_chain == 1:
- test_case_name = 'test_dual_bt_a2dp_range_codec_{}_gainmimatch_{}dB'.format(
- codec_config['codec_type'], self.gain_mismatch)
+ test_case_name = 'test_dual_bt_a2dp_range_codec_{}_gainmismatch_{}dB_adv_mode_{}_adv_tx_power_{}'.format(
+ codec_config['codec_type'], self.gain_mismatch, adv_mode[0],
+ adv_power_level[0])
else:
test_case_name = 'test_bt_a2dp_range_codec_{}_adv_mode_{}_adv_tx_power_{}'.format(
codec_config['codec_type'], adv_mode[0], adv_power_level[0])
- setattr(self, test_case_name, test_case_fn)
+ return test_case_name
def start_ble_adv(self, adv_mode, adv_power_level):
"""Function to start an LE advertisement
@@ -140,3 +146,4 @@
raise BtTestUtilsError(
"Advertiser did not start successfully {}".format(err))
return advertise_callback
+
diff --git a/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleScanTest.py b/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleScanTest.py
index 6020c4a..4cd2dd7 100644
--- a/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleScanTest.py
+++ b/acts_tests/tests/google/bt/performance/BtA2dpRangeWithBleScanTest.py
@@ -25,32 +25,50 @@
class BtA2dpRangeWithBleScanTest(A2dpBaseTest):
- default_timeout = 10
+ """User can generate test case with below format.
+ test_bt_a2dp_range_codec_"Codec"_with_BLE_scan_"Scan Mode"
+
+ Below are the list of test cases:
+ test_bt_a2dp_range_codec_AAC_with_BLE_scan_balanced
+ test_bt_a2dp_range_codec_AAC_with_BLE_scan_low_latency
+ test_bt_a2dp_range_codec_AAC_with_BLE_scan_low_power
+ test_bt_a2dp_range_codec_AAC_with_BLE_scan_opportunistic
+ test_bt_a2dp_range_codec_SBC_with_BLE_scan_balanced
+ test_bt_a2dp_range_codec_SBC_with_BLE_scan_low_latency
+ test_bt_a2dp_range_codec_SBC_with_BLE_scan_low_power
+ test_bt_a2dp_range_codec_SBC_with_BLE_scan_opportunistic
+ """
def __init__(self, configs):
super().__init__(configs)
req_params = ['attenuation_vector', 'codecs']
- #'attenuation_vector' is a dict containing: start, stop and step of
- #attenuation changes
+ opt_params = ['gain_mismatch', 'dual_chain']
+ #'attenuation_vector' is a dict containing: start, stop and step of attenuation changes
#'codecs' is a list containing all codecs required in the tests
+ #'gain_mismatch' is an offset value between the BT two chains
+ #'dual_chain' set to 1 enable sweeping attenuation for BT two chains
self.unpack_userparams(req_params)
+ self.unpack_userparams(opt_params, dual_chian=None, gain_mismatch=None)
+
+ def setup_generated_tests(self):
for codec_config in self.codecs:
- # Loop all BLE Scan modes
for scan_mode in ble_scan_settings_modes.items():
- self.generate_test_case(codec_config, scan_mode)
+ arg_set = [(codec_config, scan_mode)]
+ self.generate_tests(
+ test_logic=self.BtA2dp_with_ble_scan_test_logic,
+ name_func=self.create_test_name,
+ arg_sets=arg_set)
def setup_class(self):
super().setup_class()
- opt_params = ['gain_mismatch', 'dual_chain']
- self.unpack_userparams(opt_params, dual_chain=None, gain_mismatch=None)
- return setup_multiple_devices_for_bt_test(self.android_devices)
- # Enable BQR on all android devices
+ #Enable BQR on all android devices
btutils.enable_bqr(self.android_devices)
if hasattr(self, 'dual_chain') and self.dual_chain == 1:
self.atten_c0 = self.attenuators[0]
self.atten_c1 = self.attenuators[1]
self.atten_c0.set_atten(INIT_ATTEN)
self.atten_c1.set_atten(INIT_ATTEN)
+ return setup_multiple_devices_for_bt_test(self.android_devices)
def teardown_class(self):
super().teardown_class()
@@ -58,31 +76,20 @@
self.atten_c0.set_atten(INIT_ATTEN)
self.atten_c1.set_atten(INIT_ATTEN)
- def generate_test_case(self, codec_config, scan_mode):
- """ Below are the list of test case's user can choose to run.
- Test case list:
- "test_bt_a2dp_range_codec_AAC_with_BLE_scan_balanced"
- "test_bt_a2dp_range_codec_AAC_with_BLE_scan_low_latency"
- "test_bt_a2dp_range_codec_AAC_with_BLE_scan_low_power"
- "test_bt_a2dp_range_codec_AAC_with_BLE_scan_opportunistic"
- "test_bt_a2dp_range_codec_SBC_with_BLE_scan_balanced"
- "test_bt_a2dp_range_codec_SBC_with_BLE_scan_low_latency"
- "test_bt_a2dp_range_codec_SBC_with_BLE_scan_low_power"
- "test_bt_a2dp_range_codec_SBC_with_BLE_scan_opportunistic"
- """
- def test_case_fn():
- scan_callback = self.start_ble_scan(scan_mode[1])
- self.run_a2dp_to_max_range(codec_config)
- self.dut.droid.bleStopBleScan(scan_callback)
- self.log.info("BLE Scan stopped succssfully")
+ def BtA2dp_with_ble_scan_test_logic(self, codec_config, scan_mode):
+ scan_callback = self.start_ble_scan(scan_mode[1])
+ self.run_a2dp_to_max_range(codec_config)
+ self.dut.droid.bleStopBleScan(scan_callback)
+ self.log.info("BLE Scan stopped successfully")
+ def create_test_name(self, codec_config, scan_mode):
if hasattr(self, 'dual_chain') and self.dual_chain == 1:
- test_case_name = 'test_dual_bt_a2dp_range_codec_{}_gainmimatch_{}dB'.format(
- codec_config['codec_type'], self.gain_mismatch)
+ test_case_name = 'test_dual_bt_a2dp_range_codec_{}_gainmismatch_{}dB_with_BLE_scan_{}'.format(
+ codec_config['codec_type'], self.gain_mismatch, scan_mode[0])
else:
test_case_name = 'test_bt_a2dp_range_codec_{}_with_BLE_scan_{}'.format(
codec_config['codec_type'], scan_mode[0])
- setattr(self, test_case_name, test_case_fn)
+ return test_case_name
def start_ble_scan(self, scan_mode):
""" This function will start Ble Scan with different scan mode.
@@ -99,5 +106,6 @@
self.dut.droid)
self.dut.droid.bleStartBleScan(filter_list, scan_settings,
scan_callback)
- self.log.info("BLE Scanning started succssfully")
+ self.log.info("BLE Scanning started successfully")
return scan_callback
+
diff --git a/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py b/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py
index 99218b0..6ec585d 100644
--- a/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py
+++ b/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py
@@ -29,49 +29,39 @@
class BtInterferenceStaticTest(BtInterferenceBaseTest):
+
def __init__(self, configs):
super().__init__(configs)
self.bt_attenuation_range = range(self.attenuation_vector['start'],
self.attenuation_vector['stop'] + 1,
self.attenuation_vector['step'])
-
self.iperf_duration = self.audio_params['duration'] + TIME_OVERHEAD
- for level in list(
- self.static_wifi_interference['interference_level'].keys()):
- for channels in self.static_wifi_interference['channels']:
- self.generate_test_case(
- self.static_wifi_interference['interference_level'][level],
- channels)
-
test_metrics = [
- 'wifi_chan1_rssi', 'wifi_chan6_rssi', 'wifi_chan11_rssi',
- 'bt_range'
+ 'wifi_chan1_rssi', 'wifi_chan6_rssi', 'wifi_chan11_rssi', 'bt_range'
]
for metric in test_metrics:
setattr(self, '{}_metric'.format(metric),
BlackboxMetricLogger.for_test_case(metric_name=metric))
- def generate_test_case(self, interference_level, channels):
- """Function to generate test cases with different parameters.
+ def setup_generated_tests(self):
+ for level in list(
+ self.static_wifi_interference['interference_level'].values()):
+ for channels in self.static_wifi_interference['channels']:
+ arg_set = [(level, channels)]
+ self.generate_tests(
+ test_logic=self.bt_range_with_static_wifi_interference,
+ name_func=self.create_test_name,
+ arg_sets=arg_set)
- Args:
- interference_level: wifi interference signal level
- channels: wifi interference channel or channel combination
- """
- def test_case_fn():
- self.bt_range_with_static_wifi_interference(
- interference_level, channels)
-
+ def create_test_name(self, level, channels):
str_channel_test = ''
for i in channels:
- str_channel_test = str_channel_test + str(i) + '_'
+ str_channel_test = str_channel_test + str(i) + "_"
test_case_name = ('test_bt_range_with_static_interference_level_{}_'
- 'channel_{}'.format(interference_level,
- str_channel_test))
- setattr(self, test_case_name, test_case_fn)
+ 'channel_{}'.format(level, str_channel_test))
+ return test_case_name
- def bt_range_with_static_wifi_interference(self, interference_level,
- channels):
+ def bt_range_with_static_wifi_interference(self, level, channels):
"""Test function to measure bt range under interference.
Args:
@@ -79,8 +69,7 @@
channels: wifi interference channels
"""
#setup wifi interference by setting the correct attenuator
- inject_static_wifi_interference(self.wifi_int_pairs,
- interference_level, channels)
+ inject_static_wifi_interference(self.wifi_int_pairs, level, channels)
# Read interference RSSI
self.get_interference_rssi()
self.wifi_chan1_rssi_metric.metric_value = self.interference_rssi[0][
@@ -113,8 +102,7 @@
self.iperf_duration, obj.iperf_server.port)
tag = 'chan_{}'.format(obj.channel)
proc_iperf = Process(target=obj.iperf_client.start,
- args=(obj.server_address, iperf_args,
- tag))
+ args=(obj.server_address, iperf_args, tag))
procs_iperf.append(proc_iperf)
#play a2dp streaming and run thdn analysis
@@ -140,8 +128,8 @@
self.log.info('THDN results are {} at {} dB attenuation'.format(
thdns, atten))
self.log.info('DUT rssi {} dBm, master tx power level {}, '
- 'RemoteDevice rssi {} dBm'.format(rssi_primary, pwl_primary,
- rssi_secondary))
+ 'RemoteDevice rssi {} dBm'.format(
+ rssi_primary, pwl_primary, rssi_secondary))
for thdn in thdns:
if thdn >= self.audio_params['thdn_threshold']:
self.log.info('Under the WiFi interference condition: '
@@ -154,3 +142,4 @@
raise TestPass(
'Max range for this test is {}, with BT master RSSI at'
' {} dBm'.format(atten, rssi_primary))
+
diff --git a/acts_tests/tests/google/bt/performance/BtRfcommThroughputRangeTest.py b/acts_tests/tests/google/bt/performance/BtRfcommThroughputRangeTest.py
new file mode 100644
index 0000000..342f2f0
--- /dev/null
+++ b/acts_tests/tests/google/bt/performance/BtRfcommThroughputRangeTest.py
@@ -0,0 +1,174 @@
+#!/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.
+
+import os
+import logging
+import pandas as pd
+import time
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.ble_performance_test_utils import plot_graph
+from acts_contrib.test_utils.power.PowerBTBaseTest import ramp_attenuation
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
+from acts.signals import TestPass
+from acts import utils
+from acts_contrib.test_utils.bt.bt_test_utils import write_read_verify_data
+
+INIT_ATTEN = 0
+WRITE_ITERATIONS = 500
+
+
+class BtRfcommThroughputRangeTest(BluetoothBaseTest):
+ def __init__(self, configs):
+ super().__init__(configs)
+ req_params = ['attenuation_vector', 'system_path_loss']
+ #'attenuation_vector' is a dict containing: start, stop and step of
+ #attenuation changes
+ self.unpack_userparams(req_params)
+
+ def setup_class(self):
+ super().setup_class()
+ self.dut = self.android_devices[0]
+ self.remote_device = self.android_devices[1]
+ btutils.enable_bqr(self.android_devices)
+ if hasattr(self, 'attenuators'):
+ self.attenuator = self.attenuators[0]
+ self.attenuator.set_atten(INIT_ATTEN)
+ self.attenuation_range = range(self.attenuation_vector['start'],
+ self.attenuation_vector['stop'] + 1,
+ self.attenuation_vector['step'])
+ self.log_path = os.path.join(logging.log_path, 'results')
+ os.makedirs(self.log_path, exist_ok=True)
+ return setup_multiple_devices_for_bt_test(self.android_devices)
+
+ def teardown_test(self):
+ self.dut.droid.bluetoothSocketConnStop()
+ self.remote_device.droid.bluetoothSocketConnStop()
+ if hasattr(self, 'attenuator'):
+ self.attenuator.set_atten(INIT_ATTEN)
+
+ def test_rfcomm_throughput_range(self):
+ data_points = []
+ message = "x" * 990
+ self.file_output = os.path.join(
+ self.log_path, '{}.csv'.format(self.current_test_name))
+ if not orchestrate_rfcomm_connection(self.dut, self.remote_device):
+ return False
+ self.log.info("RFCOMM Connection established")
+ for atten in self.attenuation_range:
+ ramp_attenuation(self.attenuator, atten)
+ self.log.info('Set attenuation to %d dB', atten)
+ process_data_dict = btutils.get_bt_metric(self.dut)
+ rssi_primary = process_data_dict.get('rssi')
+ pwlv_primary = process_data_dict.get('pwlv')
+ rssi_primary = rssi_primary.get(self.dut.serial)
+ pwlv_primary = pwlv_primary.get(self.dut.serial)
+ self.log.info("DUT RSSI:{} and PwLv:{} with attenuation:{}".format(
+ rssi_primary, pwlv_primary, atten))
+ if type(rssi_primary) != str:
+ data_rate = self.write_read_verify_rfcommdata(
+ self.dut, self.remote_device, message)
+ data_point = {
+ 'attenuation_db': atten,
+ 'Dut_RSSI': rssi_primary,
+ 'DUT_PwLv': pwlv_primary,
+ 'Pathloss': atten + self.system_path_loss,
+ 'RfcommThroughput': data_rate
+ }
+ data_points.append(data_point)
+ df = pd.DataFrame(data_points)
+ # bokeh data for generating BokehFigure
+ bokeh_data = {
+ 'x_label': 'Pathloss (dBm)',
+ 'primary_y_label': 'RSSI (dBm)',
+ 'log_path': self.log_path,
+ 'current_test_name': self.current_test_name
+ }
+ # plot_data for adding line to existing BokehFigure
+ plot_data = {
+ 'line_one': {
+ 'x_column': 'Pathloss',
+ 'y_column': 'Dut_RSSI',
+ 'legend': 'DUT RSSI (dBm)',
+ 'marker': 'circle_x',
+ 'y_axis': 'default'
+ },
+ 'line_two': {
+ 'x_column': 'Pathloss',
+ 'y_column': 'RfcommThroughput',
+ 'legend': 'RFCOMM Throughput (bits/sec)',
+ 'marker': 'hex',
+ 'y_axis': 'secondary'
+ }
+ }
+ else:
+ df.to_csv(self.file_output, index=False)
+ plot_graph(df,
+ plot_data,
+ bokeh_data,
+ secondary_y_label='RFCOMM Throughput (bits/sec)')
+ raise TestPass("Reached RFCOMM Max Range,RFCOMM disconnected.")
+ # Save Data points to csv
+ df.to_csv(self.file_output, index=False)
+ # Plot graph
+ plot_graph(df,
+ plot_data,
+ bokeh_data,
+ secondary_y_label='RFCOMM Throughput (bits/sec)')
+ self.dut.droid.bluetoothRfcommStop()
+ self.remote_device.droid.bluetoothRfcommStop()
+ return True
+
+ def write_read_verify_rfcommdata(self, dut, remote_device, msg):
+ """Verify that the client wrote data to the remote Android device correctly.
+
+ Args:
+ dut: the Android device to perform the write.
+ remote_device: the Android device to read the data written.
+ msg: the message to write.
+ Returns:
+ True if the data written matches the data read, false if not.
+ """
+ start_write_time = time.perf_counter()
+ for n in range(WRITE_ITERATIONS):
+ try:
+ dut.droid.bluetoothSocketConnWrite(msg)
+ except Exception as err:
+ dut.log.error("Failed to write data: {}".format(err))
+ return False
+ try:
+ read_msg = remote_device.droid.bluetoothSocketConnRead()
+ except Exception as err:
+ remote_device.log.error("Failed to read data: {}".format(err))
+ return False
+ if msg != read_msg:
+ self.log.error("Mismatch! Read: {}, Expected: {}".format(
+ read_msg, msg))
+ return False
+ end_read_time = time.perf_counter()
+ total_num_bytes = 990 * WRITE_ITERATIONS
+ test_time = (end_read_time - start_write_time)
+ if (test_time == 0):
+ dut.log.error("Buffer transmits cannot take zero time")
+ return 0
+ data_rate = (1.000 * total_num_bytes) / test_time
+ self.log.info(
+ "Calculated using total write and read times: total_num_bytes={}, "
+ "test_time={}, data rate={:08.0f} bytes/sec, {:08.0f} bits/sec".
+ format(total_num_bytes, test_time, data_rate, (data_rate * 8)))
+ data_rate = data_rate * 8
+ return data_rate
diff --git a/acts_tests/tests/google/cellular/performance/Cellular5GFR2SensitivityTest.py b/acts_tests/tests/google/cellular/performance/Cellular5GFR2SensitivityTest.py
new file mode 100644
index 0000000..f3e49b8
--- /dev/null
+++ b/acts_tests/tests/google/cellular/performance/Cellular5GFR2SensitivityTest.py
@@ -0,0 +1,235 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 collections
+import itertools
+import json
+import numpy
+import os
+from functools import partial
+from acts import asserts
+from acts import context
+from acts import base_test
+from acts import utils
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.controllers.utils_lib import ssh
+from acts_contrib.test_utils.cellular.keysight_5g_testapp import Keysight5GTestApp
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from Cellular5GFR2ThroughputTest import Cellular5GFR2ThroughputTest
+
+
+class Cellular5GFR2SensitivityTest(Cellular5GFR2ThroughputTest):
+ """Class to test cellular throughput
+
+ This class implements cellular throughput tests on a lab/callbox setup.
+ The class setups up the callbox in the desired configurations, configures
+ and connects the phone, and runs traffic/iperf throughput.
+ """
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+
+ def setup_class(self):
+ """Initializes common test hardware and parameters.
+
+ This function initializes hardwares and compiles parameters that are
+ common to all tests in this class.
+ """
+ self.dut = self.android_devices[-1]
+ self.testclass_params = self.user_params['sensitivity_test_params']
+ self.keysight_test_app = Keysight5GTestApp(
+ self.user_params['Keysight5GTestApp'])
+ self.testclass_results = collections.OrderedDict()
+ self.iperf_server = self.iperf_servers[0]
+ self.iperf_client = self.iperf_clients[0]
+ self.remote_server = ssh.connection.SshConnection(
+ ssh.settings.from_config(
+ self.user_params['RemoteServer']['ssh_config']))
+ if self.testclass_params.get('reload_scpi', 1):
+ self.keysight_test_app.import_scpi_file(
+ self.testclass_params['scpi_file'])
+ # Configure test retries
+ self.user_params['retry_tests'] = [self.__class__.__name__]
+
+ # Turn Airplane mode on
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+
+ def process_testcase_results(self):
+ if self.current_test_name not in self.testclass_results:
+ return
+ testcase_results = self.testclass_results[self.current_test_name]
+ cell_power_list = [
+ result['cell_power'] for result in testcase_results['results']
+ ]
+ dl_bler_list = [
+ result['bler_result']['total']['DL']['nack_ratio']
+ for result in testcase_results['results']
+ ]
+ bler_above_threshold = [
+ x > self.testclass_params['bler_threshold'] for x in dl_bler_list
+ ]
+ for idx in range(len(bler_above_threshold)):
+ if all(bler_above_threshold[idx:]):
+ sensitivity_index = max(idx, 1) - 1
+ cell_power_at_sensitivity = cell_power_list[sensitivity_index]
+ break
+ else:
+ sensitivity_index = -1
+ cell_power_at_sensitivity = float('nan')
+ if min(dl_bler_list) < 0.05:
+ testcase_results['sensitivity'] = cell_power_at_sensitivity
+ else:
+ testcase_results['sensitivity'] = float('nan')
+
+ testcase_results['cell_power_list'] = cell_power_list
+ testcase_results['dl_bler_list'] = dl_bler_list
+
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ '{}.json'.format(self.current_test_name))
+ with open(results_file_path, 'w') as results_file:
+ json.dump(wputils.serialize_dict(testcase_results),
+ results_file,
+ indent=4)
+
+ result_string = ('DL {}CC MCS {} Sensitivity = {}dBm.'.format(
+ testcase_results['testcase_params']['num_dl_cells'],
+ testcase_results['testcase_params']['dl_mcs'],
+ testcase_results['sensitivity']))
+ if min(dl_bler_list) < 0.05:
+ self.log.info('Test Passed. {}'.format(result_string))
+ else:
+ self.log.info('Result unreliable. {}'.format(result_string))
+
+ def process_testclass_results(self):
+ Cellular5GFR2ThroughputTest.process_testclass_results(self)
+
+ plots = collections.OrderedDict()
+ id_fields = ['band', 'num_dl_cells']
+ for testcase, testcase_data in self.testclass_results.items():
+ testcase_params = testcase_data['testcase_params']
+ plot_id = cputils.extract_test_id(testcase_params, id_fields)
+ plot_id = tuple(plot_id.items())
+ if plot_id not in plots:
+ plots[plot_id] = BokehFigure(title='{} {}CC'.format(
+ testcase_params['band'], testcase_params['num_dl_cells']),
+ x_label='Cell Power (dBm)',
+ primary_y_label='BLER (%)')
+ plots[plot_id].add_line(
+ testcase_data['cell_power_list'],
+ testcase_data['dl_bler_list'],
+ 'Channel {}, MCS {}'.format(testcase_params['channel'],
+ testcase_params['dl_mcs']))
+ figure_list = []
+ for plot_id, plot in plots.items():
+ plot.generate_figure()
+ figure_list.append(plot)
+ output_file_path = os.path.join(self.log_path, 'results.html')
+ BokehFigure.save_figures(figure_list, output_file_path)
+
+ def generate_test_cases(self, bands, channels, mcs_pair_list,
+ num_dl_cells_list, num_ul_cells_list, **kwargs):
+ """Function that auto-generates test cases for a test class."""
+ test_cases = []
+
+ for band, channel, num_ul_cells, num_dl_cells, mcs_pair in itertools.product(
+ bands, channels, num_ul_cells_list, num_dl_cells_list,
+ mcs_pair_list):
+ if num_ul_cells > num_dl_cells:
+ continue
+ test_name = 'test_nr_sensitivity_{}_{}_DL_{}CC_mcs{}'.format(
+ band, channel, num_dl_cells, mcs_pair[0])
+ test_params = collections.OrderedDict(
+ band=band,
+ channel=channel,
+ dl_mcs=mcs_pair[0],
+ ul_mcs=mcs_pair[1],
+ num_dl_cells=num_dl_cells,
+ num_ul_cells=num_ul_cells,
+ dl_cell_list=list(range(1, num_dl_cells + 1)),
+ ul_cell_list=list(range(1, num_ul_cells + 1)),
+ **kwargs)
+ setattr(self, test_name,
+ partial(self._test_nr_throughput_bler, test_params))
+ test_cases.append(test_name)
+ return test_cases
+
+
+class Cellular5GFR2_AllBands_SensitivityTest(Cellular5GFR2SensitivityTest):
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(16, 4), (27, 4)],
+ list(range(1, 9)), [1],
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='DL',
+ transform_precoding=0)
+
+
+class Cellular5GFR2_FrequencySweep_SensitivityTest(Cellular5GFR2SensitivityTest
+ ):
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ frequency_sweep_params = self.user_params['sensitivity_test_params'][
+ 'frequency_sweep']
+ self.tests = self.generate_test_cases(frequency_sweep_params,
+ [(16, 4), (27, 4)],
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='DL',
+ transform_precoding=0)
+
+ def generate_test_cases(self, dl_frequency_sweep_params, mcs_pair_list,
+ **kwargs):
+ """Function that auto-generates test cases for a test class."""
+ test_cases = ['test_load_scpi']
+
+ for band, band_config in dl_frequency_sweep_params.items():
+ for num_dl_cells_str, sweep_config in band_config.items():
+ num_dl_cells = int(num_dl_cells_str[0])
+ num_ul_cells = 1
+ freq_vector = numpy.arange(sweep_config[0], sweep_config[1],
+ sweep_config[2])
+ for freq in freq_vector:
+ for mcs_pair in mcs_pair_list:
+ test_name = 'test_nr_sensitivity_{}_{}_DL_{}CC_mcs{}'.format(
+ band, freq, num_dl_cells, mcs_pair[0])
+ test_params = collections.OrderedDict(
+ band=band,
+ channel=freq,
+ dl_mcs=mcs_pair[0],
+ ul_mcs=mcs_pair[1],
+ num_dl_cells=num_dl_cells,
+ num_ul_cells=num_ul_cells,
+ dl_cell_list=list(range(1, num_dl_cells + 1)),
+ ul_cell_list=list(range(1, num_ul_cells + 1)),
+ **kwargs)
+ setattr(
+ self, test_name,
+ partial(self._test_nr_throughput_bler,
+ test_params))
+ test_cases.append(test_name)
+ return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/Cellular5GFR2ThroughputTest.py b/acts_tests/tests/google/cellular/performance/Cellular5GFR2ThroughputTest.py
new file mode 100644
index 0000000..9e848a3
--- /dev/null
+++ b/acts_tests/tests/google/cellular/performance/Cellular5GFR2ThroughputTest.py
@@ -0,0 +1,664 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 collections
+import csv
+import itertools
+import json
+import numpy
+import os
+import time
+from acts import asserts
+from acts import context
+from acts import base_test
+from acts import utils
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.controllers.utils_lib import ssh
+from acts.controllers import iperf_server as ipf
+from acts_contrib.test_utils.cellular.keysight_5g_testapp import Keysight5GTestApp
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+
+from functools import partial
+
+LONG_SLEEP = 10
+MEDIUM_SLEEP = 2
+IPERF_TIMEOUT = 10
+SHORT_SLEEP = 1
+SUBFRAME_LENGTH = 0.001
+STOP_COUNTER_LIMIT = 3
+
+
+class Cellular5GFR2ThroughputTest(base_test.BaseTestClass):
+ """Class to test cellular throughput
+
+ This class implements cellular throughput tests on a lab/callbox setup.
+ The class setups up the callbox in the desired configurations, configures
+ and connects the phone, and runs traffic/iperf throughput.
+ """
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+
+ def setup_class(self):
+ """Initializes common test hardware and parameters.
+
+ This function initializes hardwares and compiles parameters that are
+ common to all tests in this class.
+ """
+ self.dut = self.android_devices[-1]
+ self.testclass_params = self.user_params['throughput_test_params']
+ self.keysight_test_app = Keysight5GTestApp(
+ self.user_params['Keysight5GTestApp'])
+ self.testclass_results = collections.OrderedDict()
+ self.iperf_server = self.iperf_servers[0]
+ self.iperf_client = self.iperf_clients[0]
+ self.remote_server = ssh.connection.SshConnection(
+ ssh.settings.from_config(
+ self.user_params['RemoteServer']['ssh_config']))
+ if self.testclass_params.get('reload_scpi', 1):
+ self.keysight_test_app.import_scpi_file(
+ self.testclass_params['scpi_file'])
+ # Configure test retries
+ self.user_params['retry_tests'] = [self.__class__.__name__]
+
+ # Turn Airplane mode on
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+
+ def teardown_class(self):
+ self.log.info('Turning airplane mode on')
+ try:
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ except:
+ self.log.warning('Cannot perform teardown operations on DUT.')
+ try:
+ self.keysight_test_app.set_cell_state('LTE', 1, 0)
+ self.keysight_test_app.destroy()
+ except:
+ self.log.warning('Cannot perform teardown operations on tester.')
+ self.process_testclass_results()
+
+ def setup_test(self):
+ if self.testclass_params['enable_pixel_logs']:
+ cputils.start_pixel_logger(self.dut)
+
+ def on_retry(self):
+ """Function to control test logic on retried tests.
+
+ This function is automatically executed on tests that are being
+ retried. In this case the function resets wifi, toggles it off and on
+ and sets a retry_flag to enable further tweaking the test logic on
+ second attempts.
+ """
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ if self.keysight_test_app.get_cell_state('LTE', 'CELL1'):
+ self.log.info('Turning LTE off.')
+ self.keysight_test_app.set_cell_state('LTE', 'CELL1', 0)
+
+ def teardown_test(self):
+ self.log.info('Turing airplane mode on')
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ log_path = os.path.join(
+ context.get_current_context().get_full_output_path(), 'pixel_logs')
+ os.makedirs(self.log_path, exist_ok=True)
+ if self.testclass_params['enable_pixel_logs']:
+ cputils.stop_pixel_logger(self.dut, log_path)
+ self.process_testcase_results()
+ self.pass_fail_check()
+
+ def process_testcase_results(self):
+ if self.current_test_name not in self.testclass_results:
+ return
+ testcase_data = self.testclass_results[self.current_test_name]
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ '{}.json'.format(self.current_test_name))
+ with open(results_file_path, 'w') as results_file:
+ json.dump(wputils.serialize_dict(testcase_data),
+ results_file,
+ indent=4)
+ testcase_result = testcase_data['results'][0]
+ metric_map = {
+ 'min_dl_tput':
+ testcase_result['tput_result']['total']['DL']['min_tput'],
+ 'max_dl_tput':
+ testcase_result['tput_result']['total']['DL']['max_tput'],
+ 'avg_dl_tput':
+ testcase_result['tput_result']['total']['DL']['average_tput'],
+ 'theoretical_dl_tput':
+ testcase_result['tput_result']['total']['DL']['theoretical_tput'],
+ 'dl_bler':
+ testcase_result['bler_result']['total']['DL']['nack_ratio'] * 100,
+ 'min_dl_tput':
+ testcase_result['tput_result']['total']['UL']['min_tput'],
+ 'max_dl_tput':
+ testcase_result['tput_result']['total']['UL']['max_tput'],
+ 'avg_dl_tput':
+ testcase_result['tput_result']['total']['UL']['average_tput'],
+ 'theoretical_dl_tput':
+ testcase_result['tput_result']['total']['UL']['theoretical_tput'],
+ 'ul_bler':
+ testcase_result['bler_result']['total']['UL']['nack_ratio'] * 100,
+ 'tcp_udp_tput':
+ testcase_result.get('iperf_throughput', float('nan'))
+ }
+ if self.publish_testcase_metrics:
+ for metric_name, metric_value in metric_map.items():
+ self.testcase_metric_logger.add_metric(metric_name,
+ metric_value)
+
+ def pass_fail_check(self):
+ pass
+
+ def process_testclass_results(self):
+ """Saves CSV with all test results to enable comparison."""
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ 'results.csv')
+ with open(results_file_path, 'w', newline='') as csvfile:
+ field_names = [
+ 'Band', 'Channel', 'DL Carriers', 'UL Carriers', 'DL MCS',
+ 'DL MIMO', 'UL MCS', 'UL MIMO', 'Cell Power',
+ 'DL Min. Throughput', 'DL Max. Throughput',
+ 'DL Avg. Throughput', 'DL Theoretical Throughput',
+ 'UL Min. Throughput', 'UL Max. Throughput',
+ 'UL Avg. Throughput', 'UL Theoretical Throughput',
+ 'DL BLER (%)', 'UL BLER (%)', 'TCP/UDP Throughput'
+ ]
+ writer = csv.DictWriter(csvfile, fieldnames=field_names)
+ writer.writeheader()
+
+ for testcase_name, testcase_results in self.testclass_results.items(
+ ):
+ for result in testcase_results['results']:
+ writer.writerow({
+ 'Band':
+ testcase_results['testcase_params']['band'],
+ 'Channel':
+ testcase_results['testcase_params']['channel'],
+ 'DL Carriers':
+ testcase_results['testcase_params']['num_dl_cells'],
+ 'UL Carriers':
+ testcase_results['testcase_params']['num_ul_cells'],
+ 'DL MCS':
+ testcase_results['testcase_params']['dl_mcs'],
+ 'DL MIMO':
+ testcase_results['testcase_params']['dl_mimo_config'],
+ 'UL MCS':
+ testcase_results['testcase_params']['ul_mcs'],
+ 'UL MIMO':
+ testcase_results['testcase_params']['ul_mimo_config'],
+ 'Cell Power':
+ result['cell_power'],
+ 'DL Min. Throughput':
+ result['tput_result']['total']['DL']['min_tput'],
+ 'DL Max. Throughput':
+ result['tput_result']['total']['DL']['max_tput'],
+ 'DL Avg. Throughput':
+ result['tput_result']['total']['DL']['average_tput'],
+ 'DL Theoretical Throughput':
+ result['tput_result']['total']['DL']
+ ['theoretical_tput'],
+ 'UL Min. Throughput':
+ result['tput_result']['total']['UL']['min_tput'],
+ 'UL Max. Throughput':
+ result['tput_result']['total']['UL']['max_tput'],
+ 'UL Avg. Throughput':
+ result['tput_result']['total']['UL']['average_tput'],
+ 'UL Theoretical Throughput':
+ result['tput_result']['total']['UL']
+ ['theoretical_tput'],
+ 'DL BLER (%)':
+ result['bler_result']['total']['DL']['nack_ratio'] *
+ 100,
+ 'UL BLER (%)':
+ result['bler_result']['total']['UL']['nack_ratio'] *
+ 100,
+ 'TCP/UDP Throughput':
+ result.get('iperf_throughput', 0)
+ })
+
+ def setup_tester(self, testcase_params):
+ if not self.keysight_test_app.get_cell_state('LTE', 'CELL1'):
+ self.log.info('Turning LTE on.')
+ self.keysight_test_app.set_cell_state('LTE', 'CELL1', 1)
+ self.log.info('Turning off airplane mode')
+ asserts.assert_true(utils.force_airplane_mode(self.dut, False),
+ 'Can not turn on airplane mode.')
+ for cell in testcase_params['dl_cell_list']:
+ self.keysight_test_app.set_cell_band('NR5G', cell,
+ testcase_params['band'])
+ self.keysight_test_app.set_cell_mimo_config(
+ 'NR5G', cell, 'DL', testcase_params['dl_mimo_config'])
+ self.keysight_test_app.set_cell_dl_power(
+ 'NR5G', cell, testcase_params['cell_power_list'][0], 1)
+ for cell in testcase_params['ul_cell_list']:
+ self.keysight_test_app.set_cell_mimo_config(
+ 'NR5G', cell, 'UL', testcase_params['ul_mimo_config'])
+ self.keysight_test_app.configure_contiguous_nr_channels(
+ testcase_params['dl_cell_list'][0], testcase_params['band'],
+ testcase_params['channel'])
+ # Consider configuring schedule quick config
+ self.keysight_test_app.set_nr_cell_schedule_scenario(
+ testcase_params['dl_cell_list'][0],
+ testcase_params['schedule_scenario'])
+ self.keysight_test_app.set_nr_ul_dft_precoding(
+ testcase_params['dl_cell_list'][0],
+ testcase_params['transform_precoding'])
+ self.keysight_test_app.set_nr_cell_mcs(
+ testcase_params['dl_cell_list'][0], testcase_params['dl_mcs'],
+ testcase_params['ul_mcs'])
+ self.keysight_test_app.set_dl_carriers(testcase_params['dl_cell_list'])
+ self.keysight_test_app.set_ul_carriers(testcase_params['ul_cell_list'])
+ self.log.info('Waiting for LTE and applying aggregation')
+ if not self.keysight_test_app.wait_for_cell_status(
+ 'LTE', 'CELL1', 'CONN', 60):
+ asserts.fail('DUT did not connect to LTE.')
+ self.keysight_test_app.apply_carrier_agg()
+ self.log.info('Waiting for 5G connection')
+ connected = self.keysight_test_app.wait_for_cell_status(
+ 'NR5G', testcase_params['dl_cell_list'][-1], ['ACT', 'CONN'], 60)
+ if not connected:
+ asserts.fail('DUT did not connect to NR.')
+ time.sleep(SHORT_SLEEP)
+
+ def run_iperf_traffic(self, testcase_params):
+ self.iperf_server.start(tag=0)
+ dut_ip = self.dut.droid.connectivityGetIPv4Addresses('rmnet0')[0]
+ if 'iperf_server_address' in self.testclass_params:
+ iperf_server_address = self.testclass_params[
+ 'iperf_server_address']
+ elif isinstance(self.iperf_server, ipf.IPerfServerOverAdb):
+ iperf_server_address = dut_ip
+ else:
+ iperf_server_address = wputils.get_server_address(
+ self.remote_server, dut_ip, '255.255.255.0')
+ client_output_path = self.iperf_client.start(
+ iperf_server_address, testcase_params['iperf_args'], 0,
+ self.testclass_params['traffic_duration'] + IPERF_TIMEOUT)
+ server_output_path = self.iperf_server.stop()
+ # Parse and log result
+ if testcase_params['use_client_output']:
+ iperf_file = client_output_path
+ else:
+ iperf_file = server_output_path
+ try:
+ iperf_result = ipf.IPerfResult(iperf_file)
+ current_throughput = numpy.mean(iperf_result.instantaneous_rates[
+ self.testclass_params['iperf_ignored_interval']:-1]) * 8 * (
+ 1.024**2)
+ except:
+ self.log.warning(
+ 'ValueError: Cannot get iperf result. Setting to 0')
+ current_throughput = 0
+ return current_throughput
+
+ def _test_nr_throughput_bler(self, testcase_params):
+ """Test function to run cellular throughput and BLER measurements.
+
+ The function runs BLER/throughput measurement after configuring the
+ callbox and DUT. The test supports running PHY or TCP/UDP layer traffic
+ in a variety of band/carrier/mcs/etc configurations.
+
+ Args:
+ testcase_params: dict containing test-specific parameters
+ Returns:
+ result: dict containing throughput results and meta data
+ """
+ testcase_params = self.compile_test_params(testcase_params)
+ testcase_results = collections.OrderedDict()
+ testcase_results['testcase_params'] = testcase_params
+ testcase_results['results'] = []
+ # Setup tester and wait for DUT to connect
+ self.setup_tester(testcase_params)
+ # Run test
+ stop_counter = 0
+ for cell_power in testcase_params['cell_power_list']:
+ result = collections.OrderedDict()
+ result['cell_power'] = cell_power
+ # Set DL cell power
+ for cell in testcase_params['dl_cell_list']:
+ self.keysight_test_app.set_cell_dl_power(
+ 'NR5G', cell, result['cell_power'], 1)
+ self.keysight_test_app.select_display_tab(
+ 'NR5G', testcase_params['dl_cell_list'][0], 'BTHR', 'OTAGRAPH')
+ time.sleep(SHORT_SLEEP)
+ # Start BLER and throughput measurements
+ self.keysight_test_app.start_bler_measurement(
+ 'NR5G', testcase_params['dl_cell_list'],
+ testcase_params['bler_measurement_length'])
+ if self.testclass_params['traffic_type'] != 'PHY':
+ result['iperf_throughput'] = self.run_iperf_traffic(
+ testcase_params)
+ if self.testclass_params['log_power_metrics']:
+ if testcase_params[
+ 'bler_measurement_length'] >= 5000 and self.testclass_params[
+ 'traffic_type'] == 'PHY':
+ time.sleep(testcase_params['bler_measurement_length'] /
+ 1000 - 5)
+ cputils.log_system_power_metrics(self.dut, verbose=0)
+ else:
+ self.log.warning('Test too short to log metrics')
+
+ result['bler_result'] = self.keysight_test_app.get_bler_result(
+ 'NR5G', testcase_params['dl_cell_list'],
+ testcase_params['bler_measurement_length'])
+ result['tput_result'] = self.keysight_test_app.get_throughput(
+ 'NR5G', testcase_params['dl_cell_list'])
+
+ # Print Test Summary
+ self.log.info("Cell Power: {}dBm".format(cell_power))
+ self.log.info(
+ "DL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
+ .format(
+ result['tput_result']['total']['DL']['min_tput'],
+ result['tput_result']['total']['DL']['average_tput'],
+ result['tput_result']['total']['DL']['max_tput'],
+ result['tput_result']['total']['DL']['theoretical_tput']))
+ self.log.info(
+ "UL PHY Tput (Mbps):\tMin: {:.2f},\tAvg: {:.2f},\tMax: {:.2f},\tTheoretical: {:.2f}"
+ .format(
+ result['tput_result']['total']['UL']['min_tput'],
+ result['tput_result']['total']['UL']['average_tput'],
+ result['tput_result']['total']['UL']['max_tput'],
+ result['tput_result']['total']['UL']['theoretical_tput']))
+ self.log.info("DL BLER: {:.2f}%\tUL BLER: {:.2f}%".format(
+ result['bler_result']['total']['DL']['nack_ratio'] * 100,
+ result['bler_result']['total']['UL']['nack_ratio'] * 100))
+ testcase_results['results'].append(result)
+ if self.testclass_params['traffic_type'] != 'PHY':
+ self.log.info("{} {} Tput: {:.2f} Mbps".format(
+ self.testclass_params['traffic_type'],
+ testcase_params['traffic_direction'],
+ result['iperf_throughput']))
+
+ if result['bler_result']['total']['DL']['nack_ratio'] * 100 > 99:
+ stop_counter = stop_counter + 1
+ else:
+ stop_counter = 0
+ if stop_counter == STOP_COUNTER_LIMIT:
+ break
+ # Turn off NR cells
+ for cell in testcase_params['dl_cell_list'][::-1]:
+ self.keysight_test_app.set_cell_state('NR5G', cell, 0)
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+
+ # Save results
+ self.testclass_results[self.current_test_name] = testcase_results
+
+ def compile_test_params(self, testcase_params):
+ """Function that completes all test params based on the test name.
+
+ Args:
+ testcase_params: dict containing test-specific parameters
+ """
+ testcase_params['bler_measurement_length'] = int(
+ self.testclass_params['traffic_duration'] / SUBFRAME_LENGTH)
+ testcase_params['cell_power_list'] = numpy.arange(
+ self.testclass_params['cell_power_start'],
+ self.testclass_params['cell_power_stop'],
+ self.testclass_params['cell_power_step'])
+ if self.testclass_params['traffic_type'] == 'PHY':
+ return testcase_params
+ if self.testclass_params['traffic_type'] == 'TCP':
+ testcase_params['iperf_socket_size'] = self.testclass_params.get(
+ 'tcp_socket_size', None)
+ testcase_params['iperf_processes'] = self.testclass_params.get(
+ 'tcp_processes', 1)
+ elif self.testclass_params['traffic_type'] == 'UDP':
+ testcase_params['iperf_socket_size'] = self.testclass_params.get(
+ 'udp_socket_size', None)
+ testcase_params['iperf_processes'] = self.testclass_params.get(
+ 'udp_processes', 1)
+ if (testcase_params['traffic_direction'] == 'DL'
+ and not isinstance(self.iperf_server, ipf.IPerfServerOverAdb)
+ ) or (testcase_params['traffic_direction'] == 'UL'
+ and isinstance(self.iperf_server, ipf.IPerfServerOverAdb)):
+ testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+ duration=self.testclass_params['traffic_duration'],
+ reverse_direction=1,
+ traffic_type=self.testclass_params['traffic_type'],
+ socket_size=testcase_params['iperf_socket_size'],
+ num_processes=testcase_params['iperf_processes'],
+ udp_throughput=self.testclass_params['UDP_rates'].get(
+ testcase_params['num_dl_cells'],
+ self.testclass_params['UDP_rates']["default"]),
+ udp_length=1440)
+ testcase_params['use_client_output'] = True
+ elif (testcase_params['traffic_direction'] == 'UL'
+ and not isinstance(self.iperf_server, ipf.IPerfServerOverAdb)
+ ) or (testcase_params['traffic_direction'] == 'DL'
+ and isinstance(self.iperf_server, ipf.IPerfServerOverAdb)):
+ testcase_params['iperf_args'] = wputils.get_iperf_arg_string(
+ duration=self.testclass_params['traffic_duration'],
+ reverse_direction=0,
+ traffic_type=self.testclass_params['traffic_type'],
+ socket_size=testcase_params['iperf_socket_size'],
+ num_processes=testcase_params['iperf_processes'],
+ udp_throughput=self.testclass_params['UDP_rates'].get(
+ testcase_params['num_dl_cells'],
+ self.testclass_params['UDP_rates']["default"]),
+ udp_length=1440)
+ testcase_params['use_client_output'] = False
+ return testcase_params
+
+ def generate_test_cases(self, bands, channels, mcs_pair_list,
+ num_dl_cells_list, num_ul_cells_list,
+ dl_mimo_config, ul_mimo_config, **kwargs):
+ """Function that auto-generates test cases for a test class."""
+ test_cases = ['test_load_scpi']
+
+ for band, channel, num_ul_cells, num_dl_cells, mcs_pair in itertools.product(
+ bands, channels, num_ul_cells_list, num_dl_cells_list,
+ mcs_pair_list):
+ if num_ul_cells > num_dl_cells:
+ continue
+ if channel not in cputils.PCC_PRESET_MAPPING[band]:
+ continue
+ test_name = 'test_nr_throughput_bler_{}_{}_DL_{}CC_mcs{}_{}_UL_{}CC_mcs{}_{}'.format(
+ band, channel, num_dl_cells, mcs_pair[0], dl_mimo_config,
+ num_ul_cells, mcs_pair[1], ul_mimo_config)
+ test_params = collections.OrderedDict(
+ band=band,
+ channel=channel,
+ dl_mcs=mcs_pair[0],
+ ul_mcs=mcs_pair[1],
+ num_dl_cells=num_dl_cells,
+ num_ul_cells=num_ul_cells,
+ dl_mimo_config=dl_mimo_config,
+ ul_mimo_config=ul_mimo_config,
+ dl_cell_list=list(range(1, num_dl_cells + 1)),
+ ul_cell_list=list(range(1, num_ul_cells + 1)),
+ **kwargs)
+ setattr(self, test_name,
+ partial(self._test_nr_throughput_bler, test_params))
+ test_cases.append(test_name)
+ return test_cases
+
+
+class Cellular5GFR2_DL_ThroughputTest(Cellular5GFR2ThroughputTest):
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(16, 4), (27, 4)],
+ list(range(1, 9)),
+ list(range(1, 3)),
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N1X1',
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='DL',
+ transform_precoding=0)
+
+
+class Cellular5GFR2_CP_UL_ThroughputTest(Cellular5GFR2ThroughputTest):
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [1], [1],
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N1X1',
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=0)
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [1], [1],
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N2X2',
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=0))
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [2], [2],
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N2X2',
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=0))
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [3], [3],
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N2X2',
+ schedule_scenario="UL_RMC",
+ traffic_direction='UL',
+ transform_precoding=0))
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [4], [4],
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N2X2',
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=0))
+
+
+class Cellular5GFR2_DFTS_UL_ThroughputTest(Cellular5GFR2ThroughputTest):
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [1], [1],
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N1X1',
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=1)
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [1], [1],
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N2X2',
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=1))
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [2], [2],
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N2X2',
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=1))
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [3], [3],
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N2X2',
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=1))
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [4], [4],
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N2X2',
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=1))
+
+
+class Cellular5GFR2_DL_FrequecySweep_ThroughputTest(Cellular5GFR2ThroughputTest
+ ):
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ dl_frequency_sweep_params = self.user_params['throughput_test_params'][
+ 'dl_frequency_sweep']
+ self.tests = self.generate_test_cases(dl_frequency_sweep_params,
+ [(16, 4), (27, 4)],
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='DL',
+ transform_precoding=0,
+ dl_mimo_config='N2X2',
+ ul_mimo_config='N1X1')
+
+ def generate_test_cases(self, dl_frequency_sweep_params, mcs_pair_list,
+ **kwargs):
+ """Function that auto-generates test cases for a test class."""
+ test_cases = ['test_load_scpi']
+
+ for band, band_config in dl_frequency_sweep_params.items():
+ for num_dl_cells_str, sweep_config in band_config.items():
+ num_dl_cells = int(num_dl_cells_str[0])
+ num_ul_cells = 1
+ freq_vector = numpy.arange(sweep_config[0], sweep_config[1],
+ sweep_config[2])
+ for freq in freq_vector:
+ for mcs_pair in mcs_pair_list:
+ test_name = 'test_nr_throughput_bler_{}_{}MHz_DL_{}CC_mcs{}_UL_{}CC_mcs{}'.format(
+ band, freq, num_dl_cells, mcs_pair[0],
+ num_ul_cells, mcs_pair[1])
+ test_params = collections.OrderedDict(
+ band=band,
+ channel=freq,
+ dl_mcs=mcs_pair[0],
+ ul_mcs=mcs_pair[1],
+ num_dl_cells=num_dl_cells,
+ num_ul_cells=num_ul_cells,
+ dl_cell_list=list(range(1, num_dl_cells + 1)),
+ ul_cell_list=list(range(1, num_ul_cells + 1)),
+ **kwargs)
+ setattr(
+ self, test_name,
+ partial(self._test_nr_throughput_bler,
+ test_params))
+ test_cases.append(test_name)
+ return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/CellularFr1SensitivityTest.py b/acts_tests/tests/google/cellular/performance/CellularFr1SensitivityTest.py
new file mode 100644
index 0000000..e483e59
--- /dev/null
+++ b/acts_tests/tests/google/cellular/performance/CellularFr1SensitivityTest.py
@@ -0,0 +1,225 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 collections
+import csv
+import itertools
+import numpy
+import json
+import os
+from acts import context
+from acts import base_test
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
+from CellularLtePlusFr1PeakThroughputTest import CellularFr1SingleCellPeakThroughputTest
+
+from functools import partial
+
+
+class CellularFr1SensitivityTest(CellularFr1SingleCellPeakThroughputTest):
+ """Class to test single cell FR1 NSA sensitivity"""
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+ self.testclass_params = self.user_params['nr_sensitivity_test_params']
+ self.tests = self.generate_test_cases(
+ channel_list=['LOW', 'MID', 'HIGH'],
+ dl_mcs_list=list(numpy.arange(27, -1, -1)),
+ nr_ul_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_dl_mcs=4,
+ lte_ul_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ transform_precoding=0)
+
+ def process_testclass_results(self):
+ # Plot individual test id results raw data and compile metrics
+ plots = collections.OrderedDict()
+ compiled_data = collections.OrderedDict()
+ for testcase_name, testcase_data in self.testclass_results.items():
+ cell_config = testcase_data['testcase_params'][
+ 'endc_combo_config']['cell_list'][1]
+ test_id = tuple(('band', cell_config['band']))
+ if test_id not in plots:
+ # Initialize test id data when not present
+ compiled_data[test_id] = {
+ 'mcs': [],
+ 'average_throughput': [],
+ 'theoretical_throughput': [],
+ 'cell_power': [],
+ }
+ plots[test_id] = BokehFigure(
+ title='Band {} - BLER Curves'.format(cell_config['band']),
+ x_label='Cell Power (dBm)',
+ primary_y_label='BLER (Mbps)')
+ test_id_rvr = test_id + tuple('RvR')
+ plots[test_id_rvr] = BokehFigure(
+ title='Band {} - RvR'.format(cell_config['band']),
+ x_label='Cell Power (dBm)',
+ primary_y_label='PHY Rate (Mbps)')
+ # Compile test id data and metrics
+ compiled_data[test_id]['average_throughput'].append(
+ testcase_data['average_throughput_list'])
+ compiled_data[test_id]['cell_power'].append(
+ testcase_data['cell_power_list'])
+ compiled_data[test_id]['mcs'].append(
+ testcase_data['testcase_params']['nr_dl_mcs'])
+ # Add test id to plots
+ plots[test_id].add_line(
+ testcase_data['cell_power_list'],
+ testcase_data['bler_list'],
+ 'MCS {}'.format(testcase_data['testcase_params']['nr_dl_mcs']),
+ width=1)
+ plots[test_id_rvr].add_line(
+ testcase_data['cell_power_list'],
+ testcase_data['average_throughput_list'],
+ 'MCS {}'.format(testcase_data['testcase_params']['nr_dl_mcs']),
+ width=1,
+ style='dashed')
+
+ # Compute average RvRs and compute metrics over orientations
+ for test_id, test_data in compiled_data.items():
+ test_id_rvr = test_id + tuple('RvR')
+ cell_power_interp = sorted(set(sum(test_data['cell_power'], [])))
+ average_throughput_interp = []
+ for mcs, cell_power, throughput in zip(
+ test_data['mcs'], test_data['cell_power'],
+ test_data['average_throughput']):
+ throughput_interp = numpy.interp(cell_power_interp,
+ cell_power[::-1],
+ throughput[::-1])
+ average_throughput_interp.append(throughput_interp)
+ rvr = numpy.max(average_throughput_interp, 0)
+ plots[test_id_rvr].add_line(cell_power_interp, rvr,
+ 'Rate vs. Range')
+
+ figure_list = []
+ for plot_id, plot in plots.items():
+ plot.generate_figure()
+ figure_list.append(plot)
+ output_file_path = os.path.join(self.log_path, 'results.html')
+ BokehFigure.save_figures(figure_list, output_file_path)
+
+ def process_testcase_results(self):
+ if self.current_test_name not in self.testclass_results:
+ return
+ testcase_data = self.testclass_results[self.current_test_name]
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ '{}.json'.format(self.current_test_name))
+ with open(results_file_path, 'w') as results_file:
+ json.dump(wputils.serialize_dict(testcase_data),
+ results_file,
+ indent=4)
+
+ bler_list = []
+ average_throughput_list = []
+ theoretical_throughput_list = []
+ cell_power_list = testcase_data['testcase_params']['cell_power_sweep'][
+ 1]
+ for result in testcase_data['results']:
+ bler_list.append(
+ result['nr_bler_result']['total']['DL']['nack_ratio'])
+ average_throughput_list.append(
+ result['nr_tput_result']['total']['DL']['average_tput'])
+ theoretical_throughput_list.append(
+ result['nr_tput_result']['total']['DL']['theoretical_tput'])
+ padding_len = len(cell_power_list) - len(average_throughput_list)
+ average_throughput_list.extend([0] * padding_len)
+ theoretical_throughput_list.extend([0] * padding_len)
+
+ bler_above_threshold = [
+ bler > self.testclass_params['bler_threshold']
+ for bler in bler_list
+ ]
+ for idx in range(len(bler_above_threshold)):
+ if all(bler_above_threshold[idx:]):
+ sensitivity_idx = max(idx, 1) - 1
+ break
+ else:
+ sensitivity_idx = -1
+ sensitivity = cell_power_list[sensitivity_idx]
+ self.log.info('NR Band {} MCS {} Sensitivity = {}dBm'.format(
+ testcase_data['testcase_params']['endc_combo_config']['cell_list']
+ [1]['band'], testcase_data['testcase_params']['nr_dl_mcs'],
+ sensitivity))
+
+ testcase_data['bler_list'] = bler_list
+ testcase_data['average_throughput_list'] = average_throughput_list
+ testcase_data[
+ 'theoretical_throughput_list'] = theoretical_throughput_list
+ testcase_data['cell_power_list'] = cell_power_list
+ testcase_data['sensitivity'] = sensitivity
+
+ def get_per_cell_power_sweeps(self, testcase_params):
+ # get reference test
+ current_band = testcase_params['endc_combo_config']['cell_list'][1][
+ 'band']
+ reference_test = None
+ reference_sensitivity = None
+ for testcase_name, testcase_data in self.testclass_results.items():
+ if testcase_data['testcase_params']['endc_combo_config'][
+ 'cell_list'][1]['band'] == current_band:
+ reference_test = testcase_name
+ reference_sensitivity = testcase_data['sensitivity']
+ if reference_test and reference_sensitivity and not self.retry_flag:
+ start_atten = reference_sensitivity + self.testclass_params[
+ 'adjacent_mcs_gap']
+ self.log.info(
+ "Reference test {} found. Sensitivity {} dBm. Starting at {} dBm"
+ .format(reference_test, reference_sensitivity, start_atten))
+ else:
+ start_atten = self.testclass_params['nr_cell_power_start']
+ self.log.info(
+ "Reference test not found. Starting at {} dBm".format(
+ start_atten))
+ # get current cell power start
+ nr_cell_sweep = list(
+ numpy.arange(start_atten,
+ self.testclass_params['nr_cell_power_stop'],
+ self.testclass_params['nr_cell_power_step']))
+ lte_sweep = [self.testclass_params['lte_cell_power']
+ ] * len(nr_cell_sweep)
+ cell_power_sweeps = [lte_sweep, nr_cell_sweep]
+ return cell_power_sweeps
+
+ def generate_test_cases(self, channel_list, dl_mcs_list, **kwargs):
+ test_cases = []
+ with open(self.testclass_params['nr_single_cell_configs'],
+ 'r') as csvfile:
+ test_configs = csv.DictReader(csvfile)
+ for test_config, channel, nr_dl_mcs in itertools.product(
+ test_configs, channel_list, dl_mcs_list):
+ if int(test_config['skip_test']):
+ continue
+ endc_combo_config = self.generate_endc_combo_config(
+ test_config)
+ test_name = 'test_fr1_{}_{}_dl_mcs{}'.format(
+ test_config['nr_band'], channel.lower(), nr_dl_mcs)
+ test_params = collections.OrderedDict(
+ endc_combo_config=endc_combo_config,
+ nr_dl_mcs=nr_dl_mcs,
+ **kwargs)
+ setattr(self, test_name,
+ partial(self._test_throughput_bler, test_params))
+ test_cases.append(test_name)
+ return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/CellularFr2PeakThroughputTest.py b/acts_tests/tests/google/cellular/performance/CellularFr2PeakThroughputTest.py
new file mode 100644
index 0000000..848832e
--- /dev/null
+++ b/acts_tests/tests/google/cellular/performance/CellularFr2PeakThroughputTest.py
@@ -0,0 +1,602 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 collections
+import csv
+import itertools
+import json
+import re
+import numpy
+import os
+from acts import context
+from acts import base_test
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.cellular.performance.CellularThroughputBaseTest import CellularThroughputBaseTest
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+
+from functools import partial
+
+LONG_SLEEP = 10
+MEDIUM_SLEEP = 2
+IPERF_TIMEOUT = 10
+SHORT_SLEEP = 1
+SUBFRAME_LENGTH = 0.001
+STOP_COUNTER_LIMIT = 3
+
+
+class CellularFr2PeakThroughputTest(CellularThroughputBaseTest):
+ """Base class to test cellular FR2 throughput
+
+ This class implements cellular FR2 throughput tests on a callbox setup.
+ The class setups up the callbox in the desired configurations, configures
+ and connects the phone, and runs traffic/iperf throughput.
+ """
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+
+ def process_testcase_results(self):
+ """Publish test case metrics and save results"""
+ if self.current_test_name not in self.testclass_results:
+ return
+ testcase_data = self.testclass_results[self.current_test_name]
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ '{}.json'.format(self.current_test_name))
+ with open(results_file_path, 'w') as results_file:
+ json.dump(wputils.serialize_dict(testcase_data),
+ results_file,
+ indent=4)
+ testcase_result = testcase_data['results'][0]
+ metric_map = {
+ 'tcp_udp_tput': testcase_result.get('iperf_throughput',
+ float('nan'))
+ }
+ if testcase_data['testcase_params']['endc_combo_config'][
+ 'nr_cell_count']:
+ metric_map.update({
+ 'nr_min_dl_tput':
+ testcase_result['nr_tput_result']['total']['DL']['min_tput'],
+ 'nr_max_dl_tput':
+ testcase_result['nr_tput_result']['total']['DL']['max_tput'],
+ 'nr_avg_dl_tput':
+ testcase_result['nr_tput_result']['total']['DL']
+ ['average_tput'],
+ 'nr_theoretical_dl_tput':
+ testcase_result['nr_tput_result']['total']['DL']
+ ['theoretical_tput'],
+ 'nr_dl_bler':
+ testcase_result['nr_bler_result']['total']['DL']['nack_ratio']
+ * 100,
+ 'nr_min_dl_tput':
+ testcase_result['nr_tput_result']['total']['UL']['min_tput'],
+ 'nr_max_dl_tput':
+ testcase_result['nr_tput_result']['total']['UL']['max_tput'],
+ 'nr_avg_dl_tput':
+ testcase_result['nr_tput_result']['total']['UL']
+ ['average_tput'],
+ 'nr_theoretical_dl_tput':
+ testcase_result['nr_tput_result']['total']['UL']
+ ['theoretical_tput'],
+ 'nr_ul_bler':
+ testcase_result['nr_bler_result']['total']['UL']['nack_ratio']
+ * 100
+ })
+ if testcase_data['testcase_params']['endc_combo_config'][
+ 'lte_cell_count']:
+ metric_map.update({
+ 'lte_min_dl_tput':
+ testcase_result['lte_tput_result']['total']['DL']['min_tput'],
+ 'lte_max_dl_tput':
+ testcase_result['lte_tput_result']['total']['DL']['max_tput'],
+ 'lte_avg_dl_tput':
+ testcase_result['lte_tput_result']['total']['DL']
+ ['average_tput'],
+ 'lte_theoretical_dl_tput':
+ testcase_result['lte_tput_result']['total']['DL']
+ ['theoretical_tput'],
+ 'lte_dl_bler':
+ testcase_result['lte_bler_result']['total']['DL']['nack_ratio']
+ * 100,
+ 'lte_min_dl_tput':
+ testcase_result['lte_tput_result']['total']['UL']['min_tput'],
+ 'lte_max_dl_tput':
+ testcase_result['lte_tput_result']['total']['UL']['max_tput'],
+ 'lte_avg_dl_tput':
+ testcase_result['lte_tput_result']['total']['UL']
+ ['average_tput'],
+ 'lte_theoretical_dl_tput':
+ testcase_result['lte_tput_result']['total']['UL']
+ ['theoretical_tput'],
+ 'lte_ul_bler':
+ testcase_result['lte_bler_result']['total']['UL']['nack_ratio']
+ * 100
+ })
+ if self.publish_testcase_metrics:
+ for metric_name, metric_value in metric_map.items():
+ self.testcase_metric_logger.add_metric(metric_name,
+ metric_value)
+
+ def process_testclass_results(self):
+ """Saves CSV with all test results to enable comparison."""
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ 'results.csv')
+ with open(results_file_path, 'w', newline='') as csvfile:
+ field_names = [
+ 'Test Name', 'NR DL Min. Throughput', 'NR DL Max. Throughput',
+ 'NR DL Avg. Throughput', 'NR DL Theoretical Throughput',
+ 'NR UL Min. Throughput', 'NR UL Max. Throughput',
+ 'NR UL Avg. Throughput', 'NR UL Theoretical Throughput',
+ 'NR DL BLER (%)', 'NR UL BLER (%)', 'LTE DL Min. Throughput',
+ 'LTE DL Max. Throughput', 'LTE DL Avg. Throughput',
+ 'LTE DL Theoretical Throughput', 'LTE UL Min. Throughput',
+ 'LTE UL Max. Throughput', 'LTE UL Avg. Throughput',
+ 'LTE UL Theoretical Throughput', 'LTE DL BLER (%)',
+ 'LTE UL BLER (%)', 'TCP/UDP Throughput'
+ ]
+ writer = csv.DictWriter(csvfile, fieldnames=field_names)
+ writer.writeheader()
+
+ for testcase_name, testcase_results in self.testclass_results.items(
+ ):
+ for result in testcase_results['results']:
+ row_dict = {
+ 'Test Name': testcase_name,
+ 'TCP/UDP Throughput':
+ result.get('iperf_throughput', 0)
+ }
+ if testcase_results['testcase_params'][
+ 'endc_combo_config']['nr_cell_count']:
+ row_dict.update({
+ 'NR DL Min. Throughput':
+ result['nr_tput_result']['total']['DL']
+ ['min_tput'],
+ 'NR DL Max. Throughput':
+ result['nr_tput_result']['total']['DL']
+ ['max_tput'],
+ 'NR DL Avg. Throughput':
+ result['nr_tput_result']['total']['DL']
+ ['average_tput'],
+ 'NR DL Theoretical Throughput':
+ result['nr_tput_result']['total']['DL']
+ ['theoretical_tput'],
+ 'NR UL Min. Throughput':
+ result['nr_tput_result']['total']['UL']
+ ['min_tput'],
+ 'NR UL Max. Throughput':
+ result['nr_tput_result']['total']['UL']
+ ['max_tput'],
+ 'NR UL Avg. Throughput':
+ result['nr_tput_result']['total']['UL']
+ ['average_tput'],
+ 'NR UL Theoretical Throughput':
+ result['nr_tput_result']['total']['UL']
+ ['theoretical_tput'],
+ 'NR DL BLER (%)':
+ result['nr_bler_result']['total']['DL']
+ ['nack_ratio'] * 100,
+ 'NR UL BLER (%)':
+ result['nr_bler_result']['total']['UL']
+ ['nack_ratio'] * 100
+ })
+ if testcase_results['testcase_params'][
+ 'endc_combo_config']['lte_cell_count']:
+ row_dict.update({
+ 'LTE DL Min. Throughput':
+ result['lte_tput_result']['total']['DL']
+ ['min_tput'],
+ 'LTE DL Max. Throughput':
+ result['lte_tput_result']['total']['DL']
+ ['max_tput'],
+ 'LTE DL Avg. Throughput':
+ result['lte_tput_result']['total']['DL']
+ ['average_tput'],
+ 'LTE DL Theoretical Throughput':
+ result['lte_tput_result']['total']['DL']
+ ['theoretical_tput'],
+ 'LTE UL Min. Throughput':
+ result['lte_tput_result']['total']['UL']
+ ['min_tput'],
+ 'LTE UL Max. Throughput':
+ result['lte_tput_result']['total']['UL']
+ ['max_tput'],
+ 'LTE UL Avg. Throughput':
+ result['lte_tput_result']['total']['UL']
+ ['average_tput'],
+ 'LTE UL Theoretical Throughput':
+ result['lte_tput_result']['total']['UL']
+ ['theoretical_tput'],
+ 'LTE DL BLER (%)':
+ result['lte_bler_result']['total']['DL']
+ ['nack_ratio'] * 100,
+ 'LTE UL BLER (%)':
+ result['lte_bler_result']['total']['UL']
+ ['nack_ratio'] * 100
+ })
+ writer.writerow(row_dict)
+
+ def get_per_cell_power_sweeps(self, testcase_params):
+ """Function to get per cell power sweep lists
+
+ Args:
+ testcase_params: dict containing all test case params
+ Returns:
+ cell_power_sweeps: list of cell power sweeps for each cell under test
+ """
+ cell_power_sweeps = []
+ for cell in testcase_params['endc_combo_config']['cell_list']:
+ if cell['cell_type'] == 'LTE':
+ sweep = [self.testclass_params['lte_cell_power']]
+ else:
+ sweep = [self.testclass_params['nr_cell_power']]
+ cell_power_sweeps.append(sweep)
+ return cell_power_sweeps
+
+ def generate_endc_combo_config(self, test_config):
+ """Function to generate ENDC combo config from CSV test config
+
+ Args:
+ test_config: dict containing ENDC combo config from CSV
+ Returns:
+ endc_combo_config: dictionary with all ENDC combo settings
+ """
+ endc_combo_config = collections.OrderedDict()
+ cell_config_list = []
+
+ lte_cell_count = 1
+ lte_carriers = [1]
+ lte_scc_list = []
+ endc_combo_config['lte_pcc'] = 1
+ lte_cell = {
+ 'cell_type':
+ 'LTE',
+ 'cell_number':
+ 1,
+ 'pcc':
+ 1,
+ 'band':
+ test_config['lte_band'],
+ 'dl_bandwidth':
+ test_config['lte_bandwidth'],
+ 'ul_enabled':
+ 1,
+ 'duplex_mode':
+ test_config['lte_duplex_mode'],
+ 'dl_mimo_config':
+ 'D{nss}U{nss}'.format(nss=test_config['lte_dl_mimo_config']),
+ 'ul_mimo_config':
+ 'D{nss}U{nss}'.format(nss=test_config['lte_ul_mimo_config']),
+ 'transmission_mode':
+ 'TM1'
+ }
+ cell_config_list.append(lte_cell)
+
+ nr_cell_count = 0
+ nr_dl_carriers = []
+ nr_ul_carriers = []
+ for nr_cell_idx in range(1, test_config['num_dl_cells'] + 1):
+ nr_cell = {
+ 'cell_type':
+ 'NR5G',
+ 'cell_number':
+ nr_cell_idx,
+ 'band':
+ test_config['nr_band'],
+ 'duplex_mode':
+ test_config['nr_duplex_mode'],
+ 'channel':
+ test_config['nr_channel'],
+ 'dl_mimo_config':
+ 'N{nss}X{nss}'.format(nss=test_config['nr_dl_mimo_config']),
+ 'dl_bandwidth_class':
+ 'A',
+ 'dl_bandwidth':
+ test_config['nr_bandwidth'],
+ 'ul_enabled':
+ 1 if nr_cell_idx <= test_config['num_ul_cells'] else 0,
+ 'ul_bandwidth_class':
+ 'A',
+ 'ul_mimo_config':
+ 'N{nss}X{nss}'.format(nss=test_config['nr_ul_mimo_config']),
+ 'subcarrier_spacing':
+ 'MU3'
+ }
+ cell_config_list.append(nr_cell)
+ nr_cell_count = nr_cell_count + 1
+ nr_dl_carriers.append(nr_cell_idx)
+ if nr_cell_idx <= test_config['num_ul_cells']:
+ nr_ul_carriers.append(nr_cell_idx)
+
+ endc_combo_config['lte_cell_count'] = lte_cell_count
+ endc_combo_config['nr_cell_count'] = nr_cell_count
+ endc_combo_config['nr_dl_carriers'] = nr_dl_carriers
+ endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
+ endc_combo_config['cell_list'] = cell_config_list
+ endc_combo_config['lte_scc_list'] = lte_scc_list
+ endc_combo_config['lte_carriers'] = lte_carriers
+ return endc_combo_config
+
+ def generate_test_cases(self, bands, channels, nr_mcs_pair_list,
+ num_dl_cells_list, num_ul_cells_list,
+ dl_mimo_config, ul_mimo_config, **kwargs):
+ """Function that auto-generates test cases for a test class."""
+ test_cases = []
+ for band, channel, num_ul_cells, num_dl_cells, nr_mcs_pair in itertools.product(
+ bands, channels, num_ul_cells_list, num_dl_cells_list,
+ nr_mcs_pair_list):
+ if num_ul_cells > num_dl_cells:
+ continue
+ if channel not in cputils.PCC_PRESET_MAPPING[band]:
+ continue
+ test_config = {
+ 'lte_band': 2,
+ 'lte_bandwidth': 'BW20',
+ 'lte_duplex_mode': 'FDD',
+ 'lte_dl_mimo_config': 1,
+ 'lte_ul_mimo_config': 1,
+ 'nr_band': band,
+ 'nr_bandwidth': 'BW100',
+ 'nr_duplex_mode': 'TDD',
+ 'nr_channel': channel,
+ 'num_dl_cells': num_dl_cells,
+ 'num_ul_cells': num_ul_cells,
+ 'nr_dl_mimo_config': dl_mimo_config,
+ 'nr_ul_mimo_config': ul_mimo_config
+ }
+ endc_combo_config = self.generate_endc_combo_config(test_config)
+ test_name = 'test_fr2_{}_{}_DL_{}CC_mcs{}_{}x{}_UL_{}CC_mcs{}_{}x{}'.format(
+ band, channel, num_dl_cells, nr_mcs_pair[0], dl_mimo_config,
+ dl_mimo_config, num_ul_cells, nr_mcs_pair[1], ul_mimo_config,
+ ul_mimo_config)
+ test_params = collections.OrderedDict(
+ endc_combo_config=endc_combo_config,
+ nr_dl_mcs=nr_mcs_pair[0],
+ nr_ul_mcs=nr_mcs_pair[1],
+ **kwargs)
+ setattr(self, test_name,
+ partial(self._test_throughput_bler, test_params))
+ test_cases.append(test_name)
+ return test_cases
+
+
+class CellularFr2DlPeakThroughputTest(CellularFr2PeakThroughputTest):
+ """Base class to test cellular FR2 throughput
+
+ This class implements cellular FR2 throughput tests on a callbox setup.
+ The class setups up the callbox in the desired configurations, configures
+ and connects the phone, and runs traffic/iperf throughput.
+ """
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ self.testclass_params = self.user_params['throughput_test_params']
+ self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(16, 4), (27, 4)],
+ list(range(1, 9)),
+ list(range(1, 3)),
+ force_contiguous_nr_channel=True,
+ dl_mimo_config=2,
+ ul_mimo_config=1,
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='DL',
+ transform_precoding=0,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64')
+
+
+class CellularFr2CpOfdmUlPeakThroughputTest(CellularFr2PeakThroughputTest):
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ self.testclass_params = self.user_params['throughput_test_params']
+ self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [1], [1],
+ force_contiguous_nr_channel=True,
+ dl_mimo_config=2,
+ ul_mimo_config=1,
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=0,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64')
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [1], [1],
+ force_contiguous_nr_channel=True,
+ dl_mimo_config=2,
+ ul_mimo_config=2,
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=0,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64'))
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [2], [2],
+ force_contiguous_nr_channel=True,
+ dl_mimo_config=2,
+ ul_mimo_config=2,
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=0,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64'))
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [4], [4],
+ force_contiguous_nr_channel=True,
+ dl_mimo_config=2,
+ ul_mimo_config=2,
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=0,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64'))
+
+
+class CellularFr2DftsOfdmUlPeakThroughputTest(CellularFr2PeakThroughputTest):
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ self.testclass_params = self.user_params['throughput_test_params']
+ self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [1], [1],
+ force_contiguous_nr_channel=True,
+ dl_mimo_config=2,
+ ul_mimo_config=1,
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=1,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64')
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [1], [1],
+ force_contiguous_nr_channel=True,
+ dl_mimo_config=2,
+ ul_mimo_config=2,
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=1,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64'))
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [2], [2],
+ force_contiguous_nr_channel=True,
+ dl_mimo_config=2,
+ ul_mimo_config=2,
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=1,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64'))
+ self.tests.extend(
+ self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ ['low', 'mid', 'high'],
+ [(4, 16), (4, 27)], [4], [4],
+ force_contiguous_nr_channel=True,
+ dl_mimo_config=2,
+ ul_mimo_config=2,
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='UL',
+ transform_precoding=1,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64'))
+
+
+class CellularFr2DlFrequencySweepPeakThroughputTest(
+ CellularFr2PeakThroughputTest):
+ """Base class to test cellular FR2 throughput
+
+ This class implements cellular FR2 throughput tests on a callbox setup.
+ The class setups up the callbox in the desired configurations, configures
+ and connects the phone, and runs traffic/iperf throughput.
+ """
+
+ def __init__(self, controllers):
+ super().__init__(controllers)
+ self.testclass_params = self.user_params['throughput_test_params']
+ self.tests = self.generate_test_cases(
+ ['N257', 'N258', 'N260', 'N261'],
+ self.user_params['throughput_test_params']['frequency_sweep'],
+ [(16, 4), (27, 4)],
+ force_contiguous_nr_channel=False,
+ dl_mimo_config=2,
+ ul_mimo_config=1,
+ schedule_scenario="FULL_TPUT",
+ traffic_direction='DL',
+ transform_precoding=0,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64')
+
+ def generate_test_cases(self, bands, channels, nr_mcs_pair_list,
+ num_dl_cells_list, num_ul_cells_list,
+ dl_mimo_config, ul_mimo_config, **kwargs):
+ """Function that auto-generates test cases for a test class."""
+ test_cases = []
+ for band, channel, num_ul_cells, num_dl_cells, nr_mcs_pair in itertools.product(
+ bands, channels, num_ul_cells_list, num_dl_cells_list,
+ nr_mcs_pair_list):
+ if num_ul_cells > num_dl_cells:
+ continue
+ if channel not in cputils.PCC_PRESET_MAPPING[band]:
+ continue
+ test_config = {
+ 'lte_band': 2,
+ 'lte_bandwidth': 'BW20',
+ 'lte_duplex_mode': 'FDD',
+ 'lte_dl_mimo_config': 1,
+ 'lte_ul_mimo_config': 1,
+ 'nr_band': band,
+ 'nr_bandwidth': 'BW100',
+ 'nr_duplex_mode': 'TDD',
+ 'nr_channel': channel,
+ 'num_dl_cells': num_dl_cells,
+ 'num_ul_cells': num_ul_cells,
+ 'nr_dl_mimo_config': dl_mimo_config,
+ 'nr_ul_mimo_config': ul_mimo_config
+ }
+ endc_combo_config = self.generate_endc_combo_config(test_config)
+ test_name = 'test_fr2_{}_{}_DL_{}CC_mcs{}_{}x{}_UL_{}CC_mcs{}_{}x{}'.format(
+ band, channel, num_dl_cells, nr_mcs_pair[0], dl_mimo_config,
+ dl_mimo_config, num_ul_cells, nr_mcs_pair[1], ul_mimo_config,
+ ul_mimo_config)
+ test_params = collections.OrderedDict(
+ endc_combo_config=endc_combo_config,
+ nr_dl_mcs=nr_mcs_pair[0],
+ nr_ul_mcs=nr_mcs_pair[1],
+ **kwargs)
+ setattr(self, test_name,
+ partial(self._test_throughput_bler, test_params))
+ test_cases.append(test_name)
+ return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/CellularFr2SensitivityTest.py b/acts_tests/tests/google/cellular/performance/CellularFr2SensitivityTest.py
new file mode 100644
index 0000000..4176a62
--- /dev/null
+++ b/acts_tests/tests/google/cellular/performance/CellularFr2SensitivityTest.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 collections
+import csv
+import itertools
+import numpy
+import json
+import os
+from acts import context
+from acts import base_test
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
+from CellularFr2PeakThroughputTest import CellularFr2PeakThroughputTest
+
+from functools import partial
+
+
+class CellularFr2SensitivityTest(CellularFr2PeakThroughputTest):
+ """Class to test single cell FR1 NSA sensitivity"""
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+ self.testclass_params = self.user_params['nr_sensitivity_test_params']
+ self.log.info('Hello')
+ self.tests = self.generate_test_cases(
+ band_list=['N257', 'N258', 'N260', 'N261'],
+ channel_list=['low', 'mid', 'high'],
+ dl_mcs_list=list(numpy.arange(27, -1, -1)),
+ num_dl_cells_list=[1, 2, 4, 8],
+ dl_mimo_config=2,
+ nr_ul_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_dl_mcs=4,
+ lte_ul_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ schedule_scenario="FULL_TPUT",
+ force_contiguous_nr_channel=True,
+ transform_precoding=0)
+
+ def process_testclass_results(self):
+ # Plot individual test id results raw data and compile metrics
+ plots = collections.OrderedDict()
+ compiled_data = collections.OrderedDict()
+ for testcase_name, testcase_data in self.testclass_results.items():
+ cell_config = testcase_data['testcase_params'][
+ 'endc_combo_config']['cell_list'][1]
+ test_id = tuple(('band', cell_config['band']))
+ if test_id not in plots:
+ # Initialize test id data when not present
+ compiled_data[test_id] = {
+ 'mcs': [],
+ 'average_throughput': [],
+ 'theoretical_throughput': [],
+ 'cell_power': [],
+ }
+ plots[test_id] = BokehFigure(
+ title='Band {} - BLER Curves'.format(cell_config['band']),
+ x_label='Cell Power (dBm)',
+ primary_y_label='BLER (Mbps)')
+ test_id_rvr = test_id + tuple('RvR')
+ plots[test_id_rvr] = BokehFigure(
+ title='Band {} - RvR'.format(cell_config['band']),
+ x_label='Cell Power (dBm)',
+ primary_y_label='PHY Rate (Mbps)')
+ # Compile test id data and metrics
+ compiled_data[test_id]['average_throughput'].append(
+ testcase_data['average_throughput_list'])
+ compiled_data[test_id]['cell_power'].append(
+ testcase_data['cell_power_list'])
+ compiled_data[test_id]['mcs'].append(
+ testcase_data['testcase_params']['nr_dl_mcs'])
+ # Add test id to plots
+ plots[test_id].add_line(
+ testcase_data['cell_power_list'],
+ testcase_data['bler_list'],
+ 'MCS {}'.format(testcase_data['testcase_params']['nr_dl_mcs']),
+ width=1)
+ plots[test_id_rvr].add_line(
+ testcase_data['cell_power_list'],
+ testcase_data['average_throughput_list'],
+ 'MCS {}'.format(testcase_data['testcase_params']['nr_dl_mcs']),
+ width=1,
+ style='dashed')
+
+ # Compute average RvRs and compute metrics over orientations
+ for test_id, test_data in compiled_data.items():
+ test_id_rvr = test_id + tuple('RvR')
+ cell_power_interp = sorted(set(sum(test_data['cell_power'], [])))
+ average_throughput_interp = []
+ for mcs, cell_power, throughput in zip(
+ test_data['mcs'], test_data['cell_power'],
+ test_data['average_throughput']):
+ throughput_interp = numpy.interp(cell_power_interp,
+ cell_power[::-1],
+ throughput[::-1])
+ average_throughput_interp.append(throughput_interp)
+ rvr = numpy.max(average_throughput_interp, 0)
+ plots[test_id_rvr].add_line(cell_power_interp, rvr,
+ 'Rate vs. Range')
+
+ figure_list = []
+ for plot_id, plot in plots.items():
+ plot.generate_figure()
+ figure_list.append(plot)
+ output_file_path = os.path.join(self.log_path, 'results.html')
+ BokehFigure.save_figures(figure_list, output_file_path)
+
+ def process_testcase_results(self):
+ if self.current_test_name not in self.testclass_results:
+ return
+ testcase_data = self.testclass_results[self.current_test_name]
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ '{}.json'.format(self.current_test_name))
+ with open(results_file_path, 'w') as results_file:
+ json.dump(wputils.serialize_dict(testcase_data),
+ results_file,
+ indent=4)
+
+ bler_list = []
+ average_throughput_list = []
+ theoretical_throughput_list = []
+ cell_power_list = testcase_data['testcase_params']['cell_power_sweep'][
+ 1]
+ for result in testcase_data['results']:
+ bler_list.append(
+ result['nr_bler_result']['total']['DL']['nack_ratio'])
+ average_throughput_list.append(
+ result['nr_tput_result']['total']['DL']['average_tput'])
+ theoretical_throughput_list.append(
+ result['nr_tput_result']['total']['DL']['theoretical_tput'])
+ padding_len = len(cell_power_list) - len(average_throughput_list)
+ average_throughput_list.extend([0] * padding_len)
+ theoretical_throughput_list.extend([0] * padding_len)
+
+ bler_above_threshold = [
+ bler > self.testclass_params['bler_threshold']
+ for bler in bler_list
+ ]
+ for idx in range(len(bler_above_threshold)):
+ if all(bler_above_threshold[idx:]):
+ sensitivity_idx = max(idx, 1) - 1
+ break
+ else:
+ sensitivity_idx = -1
+ sensitivity = cell_power_list[sensitivity_idx]
+ self.log.info('NR Band {} MCS {} Sensitivity = {}dBm'.format(
+ testcase_data['testcase_params']['endc_combo_config']['cell_list']
+ [1]['band'], testcase_data['testcase_params']['nr_dl_mcs'],
+ sensitivity))
+
+ testcase_data['bler_list'] = bler_list
+ testcase_data['average_throughput_list'] = average_throughput_list
+ testcase_data[
+ 'theoretical_throughput_list'] = theoretical_throughput_list
+ testcase_data['cell_power_list'] = cell_power_list
+ testcase_data['sensitivity'] = sensitivity
+
+ def get_per_cell_power_sweeps(self, testcase_params):
+ # get reference test
+ current_band = testcase_params['endc_combo_config']['cell_list'][1][
+ 'band']
+ reference_test = None
+ reference_sensitivity = None
+ for testcase_name, testcase_data in self.testclass_results.items():
+ if testcase_data['testcase_params']['endc_combo_config'][
+ 'cell_list'][1]['band'] == current_band:
+ reference_test = testcase_name
+ reference_sensitivity = testcase_data['sensitivity']
+ if reference_test and reference_sensitivity and not self.retry_flag:
+ start_atten = reference_sensitivity + self.testclass_params[
+ 'adjacent_mcs_gap']
+ self.log.info(
+ "Reference test {} found. Sensitivity {} dBm. Starting at {} dBm"
+ .format(reference_test, reference_sensitivity, start_atten))
+ else:
+ start_atten = self.testclass_params['nr_cell_power_start']
+ self.log.info(
+ "Reference test not found. Starting at {} dBm".format(
+ start_atten))
+ # get current cell power start
+ nr_cell_sweep = list(
+ numpy.arange(start_atten,
+ self.testclass_params['nr_cell_power_stop'],
+ self.testclass_params['nr_cell_power_step']))
+ lte_sweep = [self.testclass_params['lte_cell_power']
+ ] * len(nr_cell_sweep)
+ cell_power_sweeps = [lte_sweep]
+ cell_power_sweeps.extend(
+ [nr_cell_sweep] *
+ testcase_params['endc_combo_config']['nr_cell_count'])
+ return cell_power_sweeps
+
+ def generate_test_cases(self, band_list, channel_list, dl_mcs_list,
+ num_dl_cells_list, dl_mimo_config, **kwargs):
+ """Function that auto-generates test cases for a test class."""
+ test_cases = []
+ for band, channel, num_dl_cells, nr_dl_mcs in itertools.product(
+ band_list, channel_list, num_dl_cells_list, dl_mcs_list):
+ if channel not in cputils.PCC_PRESET_MAPPING[band]:
+ continue
+ test_config = {
+ 'lte_band': 2,
+ 'lte_bandwidth': 'BW20',
+ 'lte_duplex_mode': 'FDD',
+ 'lte_dl_mimo_config': 1,
+ 'lte_ul_mimo_config': 1,
+ 'nr_band': band,
+ 'nr_bandwidth': 'BW100',
+ 'nr_duplex_mode': 'TDD',
+ 'nr_channel': channel,
+ 'num_dl_cells': num_dl_cells,
+ 'num_ul_cells': 1,
+ 'nr_dl_mimo_config': dl_mimo_config,
+ 'nr_ul_mimo_config': 1
+ }
+ endc_combo_config = self.generate_endc_combo_config(test_config)
+ test_name = 'test_fr2_{}_{}_{}CC_mcs{}_{}x{}'.format(
+ band, channel.lower(), num_dl_cells, nr_dl_mcs, dl_mimo_config,
+ dl_mimo_config)
+ test_params = collections.OrderedDict(
+ endc_combo_config=endc_combo_config,
+ nr_dl_mcs=nr_dl_mcs,
+ **kwargs)
+ setattr(self, test_name,
+ partial(self._test_throughput_bler, test_params))
+ test_cases.append(test_name)
+ self.log.info(test_cases)
+ return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/CellularLtePlusFr1PeakThroughputTest.py b/acts_tests/tests/google/cellular/performance/CellularLtePlusFr1PeakThroughputTest.py
new file mode 100644
index 0000000..b422a30
--- /dev/null
+++ b/acts_tests/tests/google/cellular/performance/CellularLtePlusFr1PeakThroughputTest.py
@@ -0,0 +1,566 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 collections
+import csv
+import itertools
+import json
+import re
+import os
+from acts import context
+from acts import base_test
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.cellular.performance.CellularThroughputBaseTest import CellularThroughputBaseTest
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+
+from functools import partial
+
+LONG_SLEEP = 10
+MEDIUM_SLEEP = 2
+IPERF_TIMEOUT = 10
+SHORT_SLEEP = 1
+SUBFRAME_LENGTH = 0.001
+STOP_COUNTER_LIMIT = 3
+
+
+class CellularLtePlusFr1PeakThroughputTest(CellularThroughputBaseTest):
+ """Base class to test cellular LTE and FR1 throughput
+
+ This class implements cellular LTE & FR1 throughput tests on a callbox setup.
+ The class setups up the callbox in the desired configurations, configures
+ and connects the phone, and runs traffic/iperf throughput.
+ """
+
+ def process_testcase_results(self):
+ """Publish test case metrics and save results"""
+ if self.current_test_name not in self.testclass_results:
+ return
+ testcase_data = self.testclass_results[self.current_test_name]
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ '{}.json'.format(self.current_test_name))
+ with open(results_file_path, 'w') as results_file:
+ json.dump(wputils.serialize_dict(testcase_data),
+ results_file,
+ indent=4)
+ testcase_result = testcase_data['results'][0]
+ metric_map = {
+ 'tcp_udp_tput': testcase_result.get('iperf_throughput',
+ float('nan'))
+ }
+ if testcase_data['testcase_params']['endc_combo_config'][
+ 'nr_cell_count']:
+ metric_map.update({
+ 'nr_min_dl_tput':
+ testcase_result['nr_tput_result']['total']['DL']['min_tput'],
+ 'nr_max_dl_tput':
+ testcase_result['nr_tput_result']['total']['DL']['max_tput'],
+ 'nr_avg_dl_tput':
+ testcase_result['nr_tput_result']['total']['DL']
+ ['average_tput'],
+ 'nr_theoretical_dl_tput':
+ testcase_result['nr_tput_result']['total']['DL']
+ ['theoretical_tput'],
+ 'nr_dl_bler':
+ testcase_result['nr_bler_result']['total']['DL']['nack_ratio']
+ * 100,
+ 'nr_min_dl_tput':
+ testcase_result['nr_tput_result']['total']['UL']['min_tput'],
+ 'nr_max_dl_tput':
+ testcase_result['nr_tput_result']['total']['UL']['max_tput'],
+ 'nr_avg_dl_tput':
+ testcase_result['nr_tput_result']['total']['UL']
+ ['average_tput'],
+ 'nr_theoretical_dl_tput':
+ testcase_result['nr_tput_result']['total']['UL']
+ ['theoretical_tput'],
+ 'nr_ul_bler':
+ testcase_result['nr_bler_result']['total']['UL']['nack_ratio']
+ * 100
+ })
+ if testcase_data['testcase_params']['endc_combo_config'][
+ 'lte_cell_count']:
+ metric_map.update({
+ 'lte_min_dl_tput':
+ testcase_result['lte_tput_result']['total']['DL']['min_tput'],
+ 'lte_max_dl_tput':
+ testcase_result['lte_tput_result']['total']['DL']['max_tput'],
+ 'lte_avg_dl_tput':
+ testcase_result['lte_tput_result']['total']['DL']
+ ['average_tput'],
+ 'lte_theoretical_dl_tput':
+ testcase_result['lte_tput_result']['total']['DL']
+ ['theoretical_tput'],
+ 'lte_dl_bler':
+ testcase_result['lte_bler_result']['total']['DL']['nack_ratio']
+ * 100,
+ 'lte_min_dl_tput':
+ testcase_result['lte_tput_result']['total']['UL']['min_tput'],
+ 'lte_max_dl_tput':
+ testcase_result['lte_tput_result']['total']['UL']['max_tput'],
+ 'lte_avg_dl_tput':
+ testcase_result['lte_tput_result']['total']['UL']
+ ['average_tput'],
+ 'lte_theoretical_dl_tput':
+ testcase_result['lte_tput_result']['total']['UL']
+ ['theoretical_tput'],
+ 'lte_ul_bler':
+ testcase_result['lte_bler_result']['total']['UL']['nack_ratio']
+ * 100
+ })
+ if self.publish_testcase_metrics:
+ for metric_name, metric_value in metric_map.items():
+ self.testcase_metric_logger.add_metric(metric_name,
+ metric_value)
+
+ def process_testclass_results(self):
+ """Saves CSV with all test results to enable comparison."""
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ 'results.csv')
+ with open(results_file_path, 'w', newline='') as csvfile:
+ field_names = [
+ 'Test Name', 'NR DL Min. Throughput', 'NR DL Max. Throughput',
+ 'NR DL Avg. Throughput', 'NR DL Theoretical Throughput',
+ 'NR UL Min. Throughput', 'NR UL Max. Throughput',
+ 'NR UL Avg. Throughput', 'NR UL Theoretical Throughput',
+ 'NR DL BLER (%)', 'NR UL BLER (%)', 'LTE DL Min. Throughput',
+ 'LTE DL Max. Throughput', 'LTE DL Avg. Throughput',
+ 'LTE DL Theoretical Throughput', 'LTE UL Min. Throughput',
+ 'LTE UL Max. Throughput', 'LTE UL Avg. Throughput',
+ 'LTE UL Theoretical Throughput', 'LTE DL BLER (%)',
+ 'LTE UL BLER (%)', 'TCP/UDP Throughput'
+ ]
+ writer = csv.DictWriter(csvfile, fieldnames=field_names)
+ writer.writeheader()
+
+ for testcase_name, testcase_results in self.testclass_results.items(
+ ):
+ for result in testcase_results['results']:
+ row_dict = {
+ 'Test Name': testcase_name,
+ 'TCP/UDP Throughput':
+ result.get('iperf_throughput', 0)
+ }
+ if testcase_results['testcase_params'][
+ 'endc_combo_config']['nr_cell_count']:
+ row_dict.update({
+ 'NR DL Min. Throughput':
+ result['nr_tput_result']['total']['DL']
+ ['min_tput'],
+ 'NR DL Max. Throughput':
+ result['nr_tput_result']['total']['DL']
+ ['max_tput'],
+ 'NR DL Avg. Throughput':
+ result['nr_tput_result']['total']['DL']
+ ['average_tput'],
+ 'NR DL Theoretical Throughput':
+ result['nr_tput_result']['total']['DL']
+ ['theoretical_tput'],
+ 'NR UL Min. Throughput':
+ result['nr_tput_result']['total']['UL']
+ ['min_tput'],
+ 'NR UL Max. Throughput':
+ result['nr_tput_result']['total']['UL']
+ ['max_tput'],
+ 'NR UL Avg. Throughput':
+ result['nr_tput_result']['total']['UL']
+ ['average_tput'],
+ 'NR UL Theoretical Throughput':
+ result['nr_tput_result']['total']['UL']
+ ['theoretical_tput'],
+ 'NR DL BLER (%)':
+ result['nr_bler_result']['total']['DL']
+ ['nack_ratio'] * 100,
+ 'NR UL BLER (%)':
+ result['nr_bler_result']['total']['UL']
+ ['nack_ratio'] * 100
+ })
+ if testcase_results['testcase_params'][
+ 'endc_combo_config']['lte_cell_count']:
+ row_dict.update({
+ 'LTE DL Min. Throughput':
+ result['lte_tput_result']['total']['DL']
+ ['min_tput'],
+ 'LTE DL Max. Throughput':
+ result['lte_tput_result']['total']['DL']
+ ['max_tput'],
+ 'LTE DL Avg. Throughput':
+ result['lte_tput_result']['total']['DL']
+ ['average_tput'],
+ 'LTE DL Theoretical Throughput':
+ result['lte_tput_result']['total']['DL']
+ ['theoretical_tput'],
+ 'LTE UL Min. Throughput':
+ result['lte_tput_result']['total']['UL']
+ ['min_tput'],
+ 'LTE UL Max. Throughput':
+ result['lte_tput_result']['total']['UL']
+ ['max_tput'],
+ 'LTE UL Avg. Throughput':
+ result['lte_tput_result']['total']['UL']
+ ['average_tput'],
+ 'LTE UL Theoretical Throughput':
+ result['lte_tput_result']['total']['UL']
+ ['theoretical_tput'],
+ 'LTE DL BLER (%)':
+ result['lte_bler_result']['total']['DL']
+ ['nack_ratio'] * 100,
+ 'LTE UL BLER (%)':
+ result['lte_bler_result']['total']['UL']
+ ['nack_ratio'] * 100
+ })
+ writer.writerow(row_dict)
+
+ def get_per_cell_power_sweeps(self, testcase_params):
+ """Function to get per cell power sweep lists
+
+ Args:
+ testcase_params: dict containing all test case params
+ Returns:
+ cell_power_sweeps: list of cell power sweeps for each cell under test
+ """
+ cell_power_sweeps = []
+ for cell in testcase_params['endc_combo_config']['cell_list']:
+ if cell['cell_type'] == 'LTE':
+ sweep = [self.testclass_params['lte_cell_power']]
+ else:
+ sweep = [self.testclass_params['nr_cell_power']]
+ cell_power_sweeps.append(sweep)
+ return cell_power_sweeps
+
+
+class CellularLteFr1EndcPeakThroughputTest(CellularLtePlusFr1PeakThroughputTest
+ ):
+ """Class to test cellular LTE/FR1 ENDC combo list"""
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+ self.testclass_params = self.user_params['throughput_test_params']
+ self.tests = self.generate_test_cases([(27, 4), (4, 27)],
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs_table='QAM256',
+ transform_precoding=0)
+
+ def generate_endc_combo_config(self, endc_combo_str):
+ """Function to generate ENDC combo config from combo string
+
+ Args:
+ endc_combo_str: ENDC combo descriptor (e.g. B48A[4];A[1]+N5A[2];A[1])
+ Returns:
+ endc_combo_config: dictionary with all ENDC combo settings
+ """
+ endc_combo_str = endc_combo_str.replace(' ', '')
+ endc_combo_list = endc_combo_str.split('+')
+ endc_combo_list = [combo.split(';') for combo in endc_combo_list]
+ endc_combo_config = collections.OrderedDict()
+ cell_config_list = list()
+ lte_cell_count = 0
+ nr_cell_count = 0
+ lte_scc_list = []
+ nr_dl_carriers = []
+ nr_ul_carriers = []
+ lte_carriers = []
+
+ for cell in endc_combo_list:
+ cell_config = {}
+ dl_config_str = cell[0]
+ dl_config_regex = re.compile(
+ r'(?P<cell_type>[B,N])(?P<band>[0-9]+)(?P<bandwidth_class>[A-Z])\[(?P<mimo_config>[0-9])\]'
+ )
+ dl_config_match = re.match(dl_config_regex, dl_config_str)
+ if dl_config_match.group('cell_type') == 'B':
+ cell_config['cell_type'] = 'LTE'
+ lte_cell_count = lte_cell_count + 1
+ cell_config['cell_number'] = lte_cell_count
+ if cell_config['cell_number'] == 1:
+ cell_config['pcc'] = 1
+ endc_combo_config['lte_pcc'] = cell_config['cell_number']
+ else:
+ cell_config['pcc'] = 0
+ lte_scc_list.append(cell_config['cell_number'])
+ cell_config['band'] = dl_config_match.group('band')
+ cell_config['duplex_mode'] = 'FDD' if int(
+ cell_config['band']
+ ) in cputils.DUPLEX_MODE_TO_BAND_MAPPING['LTE'][
+ 'FDD'] else 'TDD'
+ cell_config['dl_mimo_config'] = 'D{nss}U{nss}'.format(
+ nss=dl_config_match.group('mimo_config'))
+ if int(dl_config_match.group('mimo_config')) == 1:
+ cell_config['transmission_mode'] = 'TM1'
+ elif int(dl_config_match.group('mimo_config')) == 2:
+ cell_config['transmission_mode'] = 'TM2'
+ else:
+ cell_config['transmission_mode'] = 'TM3'
+ lte_carriers.append(cell_config['cell_number'])
+ else:
+ cell_config['cell_type'] = 'NR5G'
+ nr_cell_count = nr_cell_count + 1
+ cell_config['cell_number'] = nr_cell_count
+ nr_dl_carriers.append(cell_config['cell_number'])
+ cell_config['band'] = 'N' + dl_config_match.group('band')
+ cell_config['duplex_mode'] = 'FDD' if cell_config[
+ 'band'] in cputils.DUPLEX_MODE_TO_BAND_MAPPING['NR5G'][
+ 'FDD'] else 'TDD'
+ cell_config['subcarrier_spacing'] = 'MU0' if cell_config[
+ 'duplex_mode'] == 'FDD' else 'MU1'
+ cell_config['dl_mimo_config'] = 'N{nss}X{nss}'.format(
+ nss=dl_config_match.group('mimo_config'))
+
+ cell_config['dl_bandwidth_class'] = dl_config_match.group(
+ 'bandwidth_class')
+ cell_config['dl_bandwidth'] = 'BW20'
+ cell_config['ul_enabled'] = len(cell) > 1
+ if cell_config['ul_enabled']:
+ ul_config_str = cell[1]
+ ul_config_regex = re.compile(
+ r'(?P<bandwidth_class>[A-Z])\[(?P<mimo_config>[0-9])\]')
+ ul_config_match = re.match(ul_config_regex, ul_config_str)
+ cell_config['ul_bandwidth_class'] = ul_config_match.group(
+ 'bandwidth_class')
+ cell_config['ul_mimo_config'] = 'N{nss}X{nss}'.format(
+ nss=ul_config_match.group('mimo_config'))
+ if cell_config['cell_type'] == 'NR5G':
+ nr_ul_carriers.append(cell_config['cell_number'])
+ cell_config_list.append(cell_config)
+ endc_combo_config['lte_cell_count'] = lte_cell_count
+ endc_combo_config['nr_cell_count'] = nr_cell_count
+ endc_combo_config['nr_dl_carriers'] = nr_dl_carriers
+ endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
+ endc_combo_config['cell_list'] = cell_config_list
+ endc_combo_config['lte_scc_list'] = lte_scc_list
+ endc_combo_config['lte_carriers'] = lte_carriers
+ return endc_combo_config
+
+ def generate_test_cases(self, mcs_pair_list, **kwargs):
+ test_cases = []
+
+ with open(self.testclass_params['endc_combo_file'],
+ 'r') as endc_combos:
+ for endc_combo_str in endc_combos:
+ if endc_combo_str[0] == '#':
+ continue
+ endc_combo_config = self.generate_endc_combo_config(
+ endc_combo_str)
+ special_chars = '+[];\n'
+ for char in special_chars:
+ endc_combo_str = endc_combo_str.replace(char, '_')
+ endc_combo_str = endc_combo_str.replace('__', '_')
+ endc_combo_str = endc_combo_str.strip('_')
+ for mcs_pair in mcs_pair_list:
+ test_name = 'test_lte_fr1_endc_{}_dl_mcs{}_ul_mcs{}'.format(
+ endc_combo_str, mcs_pair[0], mcs_pair[1])
+ test_params = collections.OrderedDict(
+ endc_combo_config=endc_combo_config,
+ nr_dl_mcs=mcs_pair[0],
+ nr_ul_mcs=mcs_pair[1],
+ lte_dl_mcs=mcs_pair[0],
+ lte_ul_mcs=mcs_pair[1],
+ **kwargs)
+ setattr(self, test_name,
+ partial(self._test_throughput_bler, test_params))
+ test_cases.append(test_name)
+ return test_cases
+
+
+class CellularSingleCellThroughputTest(CellularLtePlusFr1PeakThroughputTest):
+ """Base Class to test single cell LTE or LTE/FR1"""
+
+ def generate_endc_combo_config(self, test_config):
+ """Function to generate ENDC combo config from CSV test config
+
+ Args:
+ test_config: dict containing ENDC combo config from CSV
+ Returns:
+ endc_combo_config: dictionary with all ENDC combo settings
+ """
+ endc_combo_config = collections.OrderedDict()
+ lte_cell_count = 0
+ nr_cell_count = 0
+ lte_scc_list = []
+ nr_dl_carriers = []
+ nr_ul_carriers = []
+ lte_carriers = []
+
+ cell_config_list = []
+ if test_config['lte_band']:
+ lte_cell = {
+ 'cell_type':
+ 'LTE',
+ 'cell_number':
+ 1,
+ 'pcc':
+ 1,
+ 'band':
+ test_config['lte_band'],
+ 'dl_bandwidth':
+ test_config['lte_bandwidth'],
+ 'ul_enabled':
+ 1,
+ 'duplex_mode':
+ test_config['lte_duplex_mode'],
+ 'dl_mimo_config':
+ 'D{nss}U{nss}'.format(nss=test_config['lte_dl_mimo_config']),
+ 'ul_mimo_config':
+ 'D{nss}U{nss}'.format(nss=test_config['lte_ul_mimo_config'])
+ }
+ if int(test_config['lte_dl_mimo_config']) == 1:
+ lte_cell['transmission_mode'] = 'TM1'
+ elif int(test_config['lte_dl_mimo_config']) == 2:
+ lte_cell['transmission_mode'] = 'TM2'
+ else:
+ lte_cell['transmission_mode'] = 'TM3'
+ cell_config_list.append(lte_cell)
+ endc_combo_config['lte_pcc'] = 1
+ lte_cell_count = 1
+ lte_carriers = [1]
+
+ if test_config['nr_band']:
+ nr_cell = {
+ 'cell_type':
+ 'NR5G',
+ 'cell_number':
+ 1,
+ 'band':
+ test_config['nr_band'],
+ 'duplex_mode':
+ test_config['nr_duplex_mode'],
+ 'dl_mimo_config':
+ 'N{nss}X{nss}'.format(nss=test_config['nr_dl_mimo_config']),
+ 'dl_bandwidth_class':
+ 'A',
+ 'dl_bandwidth':
+ test_config['nr_bandwidth'],
+ 'ul_enabled':
+ 1,
+ 'ul_bandwidth_class':
+ 'A',
+ 'ul_mimo_config':
+ 'N{nss}X{nss}'.format(nss=test_config['nr_ul_mimo_config']),
+ 'subcarrier_spacing':
+ 'MU0' if test_config['nr_scs'] == '15' else 'MU1'
+ }
+ cell_config_list.append(nr_cell)
+ nr_cell_count = 1
+ nr_dl_carriers = [1]
+ nr_ul_carriers = [1]
+
+ endc_combo_config['lte_cell_count'] = lte_cell_count
+ endc_combo_config['nr_cell_count'] = nr_cell_count
+ endc_combo_config['nr_dl_carriers'] = nr_dl_carriers
+ endc_combo_config['nr_ul_carriers'] = nr_ul_carriers
+ endc_combo_config['cell_list'] = cell_config_list
+ endc_combo_config['lte_scc_list'] = lte_scc_list
+ endc_combo_config['lte_carriers'] = lte_carriers
+ return endc_combo_config
+
+
+class CellularFr1SingleCellPeakThroughputTest(CellularSingleCellThroughputTest
+ ):
+ """Class to test single cell FR1 NSA mode"""
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+ self.testclass_params = self.user_params['throughput_test_params']
+ self.tests = self.generate_test_cases(
+ nr_mcs_pair_list=[(27, 4), (4, 27)],
+ nr_channel_list=['LOW', 'MID', 'HIGH'],
+ transform_precoding=0,
+ lte_dl_mcs=4,
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ lte_ul_mcs_table='QAM64')
+
+ def generate_test_cases(self, nr_mcs_pair_list, nr_channel_list, **kwargs):
+
+ test_cases = []
+ with open(self.testclass_params['nr_single_cell_configs'],
+ 'r') as csvfile:
+ test_configs = csv.DictReader(csvfile)
+ for test_config, nr_channel, nr_mcs_pair in itertools.product(
+ test_configs, nr_channel_list, nr_mcs_pair_list):
+ if int(test_config['skip_test']):
+ continue
+ endc_combo_config = self.generate_endc_combo_config(
+ test_config)
+ endc_combo_config['cell_list'][1]['channel'] = nr_channel
+ test_name = 'test_fr1_{}_{}_dl_mcs{}_ul_mcs{}'.format(
+ test_config['nr_band'], nr_channel.lower(), nr_mcs_pair[0],
+ nr_mcs_pair[1])
+ test_params = collections.OrderedDict(
+ endc_combo_config=endc_combo_config,
+ nr_dl_mcs=nr_mcs_pair[0],
+ nr_ul_mcs=nr_mcs_pair[1],
+ **kwargs)
+ setattr(self, test_name,
+ partial(self._test_throughput_bler, test_params))
+ test_cases.append(test_name)
+ return test_cases
+
+
+class CellularLteSingleCellPeakThroughputTest(CellularSingleCellThroughputTest
+ ):
+ """Class to test single cell LTE"""
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+ self.testclass_params = self.user_params['throughput_test_params']
+ self.tests = self.generate_test_cases(lte_mcs_pair_list=[
+ (('QAM256', 27), ('QAM256', 4)), (('QAM256', 4), ('QAM256', 27))
+ ],
+ transform_precoding=0)
+
+ def generate_test_cases(self, lte_mcs_pair_list, **kwargs):
+ test_cases = []
+ with open(self.testclass_params['lte_single_cell_configs'],
+ 'r') as csvfile:
+ test_configs = csv.DictReader(csvfile)
+ for test_config, lte_mcs_pair in itertools.product(
+ test_configs, lte_mcs_pair_list):
+ if int(test_config['skip_test']):
+ continue
+ endc_combo_config = self.generate_endc_combo_config(
+ test_config)
+ test_name = 'test_lte_B{}_dl_{}_mcs{}_ul_{}_mcs{}'.format(
+ test_config['lte_band'], lte_mcs_pair[0][0],
+ lte_mcs_pair[0][1], lte_mcs_pair[1][0], lte_mcs_pair[1][1])
+ test_params = collections.OrderedDict(
+ endc_combo_config=endc_combo_config,
+ lte_dl_mcs_table=lte_mcs_pair[0][0],
+ lte_dl_mcs=lte_mcs_pair[0][1],
+ lte_ul_mcs_table=lte_mcs_pair[1][0],
+ lte_ul_mcs=lte_mcs_pair[1][1],
+ **kwargs)
+ setattr(self, test_name,
+ partial(self._test_throughput_bler, test_params))
+ test_cases.append(test_name)
+ return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/CellularLteSensitivityTest.py b/acts_tests/tests/google/cellular/performance/CellularLteSensitivityTest.py
new file mode 100644
index 0000000..5e27bca
--- /dev/null
+++ b/acts_tests/tests/google/cellular/performance/CellularLteSensitivityTest.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 collections
+import csv
+import itertools
+import numpy
+import json
+import re
+import os
+from acts import context
+from acts import base_test
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
+from CellularLtePlusFr1PeakThroughputTest import CellularLteSingleCellPeakThroughputTest
+
+from functools import partial
+
+
+class CellularLteSensitivityTest(CellularLteSingleCellPeakThroughputTest):
+ """Class to test single cell LTE sensitivity"""
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+ self.testclass_params = self.user_params['lte_sensitivity_test_params']
+ self.tests = self.generate_test_cases(dl_mcs_list=list(
+ numpy.arange(27, -1, -1)),
+ lte_dl_mcs_table='QAM256',
+ lte_ul_mcs_table='QAM256',
+ lte_ul_mcs=4,
+ transform_precoding=0)
+
+ def process_testclass_results(self):
+ # Plot individual test id results raw data and compile metrics
+ plots = collections.OrderedDict()
+ compiled_data = collections.OrderedDict()
+ for testcase_name, testcase_data in self.testclass_results.items():
+ cell_config = testcase_data['testcase_params'][
+ 'endc_combo_config']['cell_list'][0]
+ test_id = tuple(('band', cell_config['band']))
+ if test_id not in plots:
+ # Initialize test id data when not present
+ compiled_data[test_id] = {
+ 'mcs': [],
+ 'average_throughput': [],
+ 'theoretical_throughput': [],
+ 'cell_power': [],
+ }
+ plots[test_id] = BokehFigure(
+ title='Band {} ({}) - BLER Curves'.format(
+ cell_config['band'],
+ testcase_data['testcase_params']['lte_dl_mcs_table']),
+ x_label='Cell Power (dBm)',
+ primary_y_label='BLER (Mbps)')
+ test_id_rvr = test_id + tuple('RvR')
+ plots[test_id_rvr] = BokehFigure(
+ title='Band {} ({}) - RvR'.format(
+ cell_config['band'],
+ testcase_data['testcase_params']['lte_dl_mcs_table']),
+ x_label='Cell Power (dBm)',
+ primary_y_label='PHY Rate (Mbps)')
+ # Compile test id data and metrics
+ compiled_data[test_id]['average_throughput'].append(
+ testcase_data['average_throughput_list'])
+ compiled_data[test_id]['cell_power'].append(
+ testcase_data['cell_power_list'])
+ compiled_data[test_id]['mcs'].append(
+ testcase_data['testcase_params']['lte_dl_mcs'])
+ # Add test id to plots
+ plots[test_id].add_line(
+ testcase_data['cell_power_list'],
+ testcase_data['bler_list'],
+ 'MCS {}'.format(
+ testcase_data['testcase_params']['lte_dl_mcs']),
+ width=1)
+ plots[test_id_rvr].add_line(
+ testcase_data['cell_power_list'],
+ testcase_data['average_throughput_list'],
+ 'MCS {}'.format(
+ testcase_data['testcase_params']['lte_dl_mcs']),
+ width=1,
+ style='dashed')
+
+ # Compute average RvRs and compute metrics over orientations
+ for test_id, test_data in compiled_data.items():
+ test_id_rvr = test_id + tuple('RvR')
+ cell_power_interp = sorted(set(sum(test_data['cell_power'], [])))
+ average_throughput_interp = []
+ for mcs, cell_power, throughput in zip(
+ test_data['mcs'], test_data['cell_power'],
+ test_data['average_throughput']):
+ throughput_interp = numpy.interp(cell_power_interp,
+ cell_power[::-1],
+ throughput[::-1])
+ average_throughput_interp.append(throughput_interp)
+ rvr = numpy.max(average_throughput_interp, 0)
+ plots[test_id_rvr].add_line(cell_power_interp, rvr,
+ 'Rate vs. Range')
+
+ figure_list = []
+ for plot_id, plot in plots.items():
+ plot.generate_figure()
+ figure_list.append(plot)
+ output_file_path = os.path.join(self.log_path, 'results.html')
+ BokehFigure.save_figures(figure_list, output_file_path)
+
+ def process_testcase_results(self):
+ if self.current_test_name not in self.testclass_results:
+ return
+ testcase_data = self.testclass_results[self.current_test_name]
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ '{}.json'.format(self.current_test_name))
+ with open(results_file_path, 'w') as results_file:
+ json.dump(wputils.serialize_dict(testcase_data),
+ results_file,
+ indent=4)
+
+ bler_list = []
+ average_throughput_list = []
+ theoretical_throughput_list = []
+ cell_power_list = testcase_data['testcase_params']['cell_power_sweep'][
+ 0]
+ for result in testcase_data['results']:
+ bler_list.append(
+ result['lte_bler_result']['total']['DL']['nack_ratio'])
+ average_throughput_list.append(
+ result['lte_tput_result']['total']['DL']['average_tput'])
+ theoretical_throughput_list.append(
+ result['lte_tput_result']['total']['DL']['theoretical_tput'])
+ padding_len = len(cell_power_list) - len(average_throughput_list)
+ average_throughput_list.extend([0] * padding_len)
+ theoretical_throughput_list.extend([0] * padding_len)
+
+ bler_above_threshold = [
+ bler > self.testclass_params['bler_threshold']
+ for bler in bler_list
+ ]
+ for idx in range(len(bler_above_threshold)):
+ if all(bler_above_threshold[idx:]):
+ sensitivity_idx = max(idx, 1) - 1
+ break
+ else:
+ sensitivity_idx = -1
+ sensitivity = cell_power_list[sensitivity_idx]
+ self.log.info('LTE Band {} Table {} MCS {} Sensitivity = {}dBm'.format(
+ testcase_data['testcase_params']['endc_combo_config']['cell_list']
+ [0]['band'], testcase_data['testcase_params']['lte_dl_mcs_table'],
+ testcase_data['testcase_params']['lte_dl_mcs'], sensitivity))
+
+ testcase_data['bler_list'] = bler_list
+ testcase_data['average_throughput_list'] = average_throughput_list
+ testcase_data[
+ 'theoretical_throughput_list'] = theoretical_throughput_list
+ testcase_data['cell_power_list'] = cell_power_list
+ testcase_data['sensitivity'] = sensitivity
+
+ def get_per_cell_power_sweeps(self, testcase_params):
+ # get reference test
+ current_band = testcase_params['endc_combo_config']['cell_list'][0][
+ 'band']
+ reference_test = None
+ reference_sensitivity = None
+ for testcase_name, testcase_data in self.testclass_results.items():
+ if testcase_data['testcase_params']['endc_combo_config'][
+ 'cell_list'][0]['band'] == current_band:
+ reference_test = testcase_name
+ reference_sensitivity = testcase_data['sensitivity']
+ if reference_test and reference_sensitivity and not self.retry_flag:
+ start_atten = reference_sensitivity + self.testclass_params[
+ 'adjacent_mcs_gap']
+ self.log.info(
+ "Reference test {} found. Sensitivity {} dBm. Starting at {} dBm"
+ .format(reference_test, reference_sensitivity, start_atten))
+ else:
+ start_atten = self.testclass_params['lte_cell_power_start']
+ self.log.info(
+ "Reference test not found. Starting at {} dBm".format(
+ start_atten))
+ # get current cell power start
+ cell_power_sweeps = [
+ list(
+ numpy.arange(start_atten,
+ self.testclass_params['lte_cell_power_stop'],
+ self.testclass_params['lte_cell_power_step']))
+ ]
+ return cell_power_sweeps
+
+ def generate_test_cases(self, dl_mcs_list, lte_dl_mcs_table,
+ lte_ul_mcs_table, lte_ul_mcs, **kwargs):
+ test_cases = []
+ with open(self.testclass_params['lte_single_cell_configs'],
+ 'r') as csvfile:
+ test_configs = csv.DictReader(csvfile)
+ for test_config, lte_dl_mcs in itertools.product(
+ test_configs, dl_mcs_list):
+ if int(test_config['skip_test']):
+ continue
+ endc_combo_config = self.generate_endc_combo_config(
+ test_config)
+ test_name = 'test_lte_B{}_dl_{}_mcs{}'.format(
+ test_config['lte_band'], lte_dl_mcs_table, lte_dl_mcs)
+ test_params = collections.OrderedDict(
+ endc_combo_config=endc_combo_config,
+ lte_dl_mcs_table=lte_dl_mcs_table,
+ lte_dl_mcs=lte_dl_mcs,
+ lte_ul_mcs_table=lte_ul_mcs_table,
+ lte_ul_mcs=lte_ul_mcs,
+ **kwargs)
+ setattr(self, test_name,
+ partial(self._test_throughput_bler, test_params))
+ test_cases.append(test_name)
+ return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/CellularRxPowerTest.py b/acts_tests/tests/google/cellular/performance/CellularRxPowerTest.py
new file mode 100644
index 0000000..9615c91
--- /dev/null
+++ b/acts_tests/tests/google/cellular/performance/CellularRxPowerTest.py
@@ -0,0 +1,233 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2022 - 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 collections
+import itertools
+import json
+import numpy
+import os
+import time
+from acts import asserts
+from acts import context
+from acts import base_test
+from acts import utils
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts_contrib.test_utils.cellular.keysight_5g_testapp import Keysight5GTestApp
+from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
+from acts_contrib.test_utils.cellular.performance.shannon_log_parser import ShannonLogger
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure import BokehFigure
+from functools import partial
+
+
+class CellularRxPowerTest(base_test.BaseTestClass):
+ """Class to test cellular throughput."""
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+ self.tests = self.generate_test_cases(['N257', 'N258', 'N260', 'N261'],
+ list(range(1, 9)))
+
+ def setup_class(self):
+ """Initializes common test hardware and parameters.
+
+ This function initializes hardwares and compiles parameters that are
+ common to all tests in this class.
+ """
+ self.dut = self.android_devices[-1]
+ self.testclass_params = self.user_params['rx_power_params']
+ self.keysight_test_app = Keysight5GTestApp(
+ self.user_params['Keysight5GTestApp'])
+ self.sdm_logger = ShannonLogger(self.dut)
+ self.testclass_results = collections.OrderedDict()
+ # Configure test retries
+ self.user_params['retry_tests'] = [self.__class__.__name__]
+
+ # Turn Airplane mode on
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+
+ def teardown_class(self):
+ self.log.info('Turning airplane mode on')
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ self.keysight_test_app.set_cell_state('LTE', 1, 0)
+ self.keysight_test_app.destroy()
+
+ def setup_test(self):
+ cputils.start_pixel_logger(self.dut)
+
+ def on_retry(self):
+ """Function to control test logic on retried tests.
+
+ This function is automatically executed on tests that are being
+ retried. In this case the function resets wifi, toggles it off and on
+ and sets a retry_flag to enable further tweaking the test logic on
+ second attempts.
+ """
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ if self.keysight_test_app.get_cell_state('LTE', 'CELL1'):
+ self.log.info('Turning LTE off.')
+ self.keysight_test_app.set_cell_state('LTE', 'CELL1', 0)
+
+ def teardown_test(self):
+ self.log.info('Turning airplane mode on')
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ log_path = os.path.join(
+ context.get_current_context().get_full_output_path(), 'pixel_logs')
+ os.makedirs(log_path, exist_ok=True)
+ self.log.info(self.current_test_info)
+ self.testclass_results.setdefault(self.current_test_name,
+ collections.OrderedDict())
+ self.testclass_results[self.current_test_name].setdefault(
+ 'log_path', [])
+ self.testclass_results[self.current_test_name]['log_path'].append(
+ cputils.stop_pixel_logger(self.dut, log_path))
+ self.process_test_results()
+
+ def process_test_results(self):
+ test_result = self.testclass_results[self.current_test_name]
+
+ # Save output as text file
+ results_file_path = os.path.join(
+ self.log_path, '{}.json'.format(self.current_test_name))
+ with open(results_file_path, 'w') as results_file:
+ json.dump(wputils.serialize_dict(test_result),
+ results_file,
+ indent=4)
+ # Plot and save
+ if test_result['log_path']:
+ log_data = self.sdm_logger.process_log(test_result['log_path'][-1])
+ else:
+ return
+ figure = BokehFigure(title=self.current_test_name,
+ x_label='Cell Power Setting (dBm)',
+ primary_y_label='Time')
+ figure.add_line(log_data.lte.rsrp_time, log_data.lte.rsrp_rx0,
+ 'LTE RSRP (Rx0)')
+ figure.add_line(log_data.lte.rsrp_time, log_data.lte.rsrp_rx1,
+ 'LTE RSRP (Rx1)')
+ figure.add_line(log_data.lte.rsrp2_time, log_data.lte.rsrp2_rx0,
+ 'LTE RSRP2 (Rx0)')
+ figure.add_line(log_data.lte.rsrp2_time, log_data.lte.rsrp2_rx1,
+ 'LTE RSRP2 (Rx0)')
+ figure.add_line(log_data.nr.rsrp_time, log_data.nr.rsrp_rx0,
+ 'NR RSRP (Rx0)')
+ figure.add_line(log_data.nr.rsrp_time, log_data.nr.rsrp_rx1,
+ 'NR RSRP (Rx1)')
+ figure.add_line(log_data.nr.rsrp2_time, log_data.nr.rsrp2_rx0,
+ 'NR RSRP2 (Rx0)')
+ figure.add_line(log_data.nr.rsrp2_time, log_data.nr.rsrp2_rx1,
+ 'NR RSRP2 (Rx0)')
+ figure.add_line(log_data.fr2.rsrp0_time, log_data.fr2.rsrp0,
+ 'NR RSRP (Rx0)')
+ figure.add_line(log_data.fr2.rsrp1_time, log_data.fr2.rsrp1,
+ 'NR RSRP2 (Rx1)')
+ output_file_path = os.path.join(
+ self.log_path, '{}.html'.format(self.current_test_name))
+ figure.generate_figure(output_file_path)
+
+ def _test_nr_rsrp(self, testcase_params):
+ """Test function to run cellular RSRP tests.
+
+ The function runs a sweep of cell powers while collecting pixel logs
+ for later postprocessing and RSRP analysis.
+
+ Args:
+ testcase_params: dict containing test-specific parameters
+ """
+
+ result = collections.OrderedDict()
+ testcase_params['power_range_vector'] = list(
+ numpy.arange(self.testclass_params['cell_power_start'],
+ self.testclass_params['cell_power_stop'],
+ self.testclass_params['cell_power_step']))
+
+ if not self.keysight_test_app.get_cell_state('LTE', 'CELL1'):
+ self.log.info('Turning LTE on.')
+ self.keysight_test_app.set_cell_state('LTE', 'CELL1', 1)
+ self.log.info('Turning off airplane mode')
+ asserts.assert_true(utils.force_airplane_mode(self.dut, False),
+ 'Can not turn on airplane mode.')
+
+ for cell in testcase_params['dl_cell_list']:
+ self.keysight_test_app.set_cell_band('NR5G', cell,
+ testcase_params['band'])
+ # Consider configuring schedule quick config
+ self.keysight_test_app.set_nr_cell_schedule_scenario(
+ testcase_params['dl_cell_list'][0], 'BASIC')
+ self.keysight_test_app.set_dl_carriers(testcase_params['dl_cell_list'])
+ self.keysight_test_app.set_ul_carriers(
+ testcase_params['dl_cell_list'][0])
+ self.log.info('Waiting for LTE and applying aggregation')
+ if not self.keysight_test_app.wait_for_cell_status(
+ 'LTE', 'CELL1', 'CONN', 60):
+ asserts.fail('DUT did not connect to LTE.')
+ self.keysight_test_app.apply_carrier_agg()
+ self.log.info('Waiting for 5G connection')
+ connected = self.keysight_test_app.wait_for_cell_status(
+ 'NR5G', testcase_params['dl_cell_list'][-1], ['ACT', 'CONN'], 60)
+ if not connected:
+ asserts.fail('DUT did not connect to NR.')
+ for cell_power in testcase_params['power_range_vector']:
+ self.log.info('Setting power to {} dBm'.format(cell_power))
+ for cell in testcase_params['dl_cell_list']:
+ self.keysight_test_app.set_cell_dl_power(
+ 'NR5G', cell, cell_power, True)
+ #measure RSRP
+ self.keysight_test_app.start_nr_rsrp_measurement(
+ testcase_params['dl_cell_list'],
+ self.testclass_params['rsrp_measurement_duration'])
+ time.sleep(self.testclass_params['rsrp_measurement_duration'] *
+ 1.5 / 1000)
+ self.keysight_test_app.get_nr_rsrp_measurement_state(
+ testcase_params['dl_cell_list'])
+ self.keysight_test_app.get_nr_rsrp_measurement_results(
+ testcase_params['dl_cell_list'])
+
+ for cell in testcase_params['dl_cell_list'][::-1]:
+ self.keysight_test_app.set_cell_state('NR5G', cell, 0)
+ asserts.assert_true(utils.force_airplane_mode(self.dut, True),
+ 'Can not turn on airplane mode.')
+ # Save results
+ result['testcase_params'] = testcase_params
+ self.testclass_results[self.current_test_name] = result
+ results_file_path = os.path.join(
+ context.get_current_context().get_full_output_path(),
+ '{}.json'.format(self.current_test_name))
+ with open(results_file_path, 'w') as results_file:
+ json.dump(wputils.serialize_dict(result), results_file, indent=4)
+
+ def generate_test_cases(self, bands, num_cells_list):
+ """Function that auto-generates test cases for a test class."""
+ test_cases = []
+
+ for band, num_cells in itertools.product(bands, num_cells_list):
+ test_name = 'test_nr_rsrp_{}_{}CC'.format(band, num_cells)
+ test_params = collections.OrderedDict(band=band,
+ num_cells=num_cells,
+ dl_cell_list=list(
+ range(1, num_cells + 1)))
+ setattr(self, test_name, partial(self._test_nr_rsrp, test_params))
+ test_cases.append(test_name)
+ return test_cases
diff --git a/acts_tests/tests/google/cellular/performance/__init__.py b/acts_tests/tests/google/cellular/performance/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/tests/google/cellular/performance/__init__.py
diff --git a/acts_tests/tests/google/gnss/FlpTtffTest.py b/acts_tests/tests/google/gnss/FlpTtffTest.py
index 0a30fe8..2d1f194 100644
--- a/acts_tests/tests/google/gnss/FlpTtffTest.py
+++ b/acts_tests/tests/google/gnss/FlpTtffTest.py
@@ -17,7 +17,6 @@
from acts import asserts
from acts import signals
from acts.base_test import BaseTestClass
-from acts.test_decorators import test_tracker_info
from acts.utils import get_current_epoch_time
from acts_contrib.test_utils.wifi.wifi_test_utils import wifi_toggle_state
from acts_contrib.test_utils.tel.tel_logging_utils import start_qxdm_logger
@@ -40,6 +39,8 @@
from acts_contrib.test_utils.gnss.gnss_test_utils import connect_to_wifi_network
from acts_contrib.test_utils.gnss.gnss_test_utils import gnss_tracking_via_gtw_gpstool
from acts_contrib.test_utils.gnss.gnss_test_utils import parse_gtw_gpstool_log
+from acts_contrib.test_utils.gnss.gnss_test_utils import log_current_epoch_time
+from acts_contrib.test_utils.gnss.testtracker_util import log_testtracker_uuid
class FlpTtffTest(BaseTestClass):
@@ -63,6 +64,8 @@
_init_device(self.ad)
def setup_test(self):
+ log_current_epoch_time(self.ad, "test_start_time")
+ log_testtracker_uuid(self.ad, self.current_test_name)
get_baseband_and_gms_version(self.ad)
if self.collect_logs:
clear_logd_gnss_qxdm_log(self.ad)
@@ -85,6 +88,7 @@
set_wifi_and_bt_scanning(self.ad, True)
if self.ad.droid.wifiCheckState():
wifi_toggle_state(self.ad, False)
+ log_current_epoch_time(self.ad, "test_end_time")
def on_pass(self, test_name, begin_time):
if self.collect_logs:
@@ -106,11 +110,11 @@
for mode in ttff.keys():
begin_time = get_current_epoch_time()
process_gnss_by_gtw_gpstool(
- self.ad, self.standalone_cs_criteria, type="flp")
+ self.ad, self.standalone_cs_criteria, api_type="flp")
start_ttff_by_gtw_gpstool(
self.ad, ttff_mode=mode, iteration=self.ttff_test_cycle)
ttff_data = process_ttff_by_gtw_gpstool(
- self.ad, begin_time, location, type="flp")
+ self.ad, begin_time, location, api_type="flp")
result = check_ttff_data(self.ad, ttff_data, ttff[mode], criteria)
flp_results.append(result)
asserts.assert_true(
@@ -124,7 +128,6 @@
""" Test Cases """
- @test_tracker_info(uuid="c11ada6a-d7ad-4dc8-9d4a-0ae3cb9dfa8e")
def test_flp_one_hour_tracking(self):
"""Verify FLP tracking performance of position error.
@@ -137,10 +140,9 @@
"""
self.start_qxdm_and_tcpdump_log()
gnss_tracking_via_gtw_gpstool(self.ad, self.standalone_cs_criteria,
- type="flp", testtime=60)
- parse_gtw_gpstool_log(self.ad, self.pixel_lab_location, type="flp")
+ api_type="flp", testtime=60)
+ parse_gtw_gpstool_log(self.ad, self.pixel_lab_location, api_type="flp")
- @test_tracker_info(uuid="8bc4e82d-fdce-4ee8-af8c-5e4a925b5360")
def test_flp_ttff_strong_signal_wifiscan_on_wifi_connect(self):
"""Verify FLP TTFF Hot Start and Cold Start under strong GNSS signals
with WiFi scanning on and connected.
@@ -163,7 +165,6 @@
self.flp_ttff_hs_and_cs(self.flp_ttff_max_threshold,
self.pixel_lab_location)
- @test_tracker_info(uuid="adc1a0c7-3635-420d-9481-0f5816c58334")
def test_flp_ttff_strong_signal_wifiscan_on_wifi_not_connect(self):
"""Verify FLP TTFF Hot Start and Cold Start under strong GNSS signals
with WiFi scanning on and not connected.
@@ -183,7 +184,6 @@
self.flp_ttff_hs_and_cs(self.flp_ttff_max_threshold,
self.pixel_lab_location)
- @test_tracker_info(uuid="3ec3cee2-b881-4c61-9df1-b6b81fcd4527")
def test_flp_ttff_strong_signal_wifiscan_off(self):
"""Verify FLP TTFF Hot Start and Cold Start with WiFi scanning OFF
under strong GNSS signals.
@@ -202,7 +202,6 @@
self.flp_ttff_hs_and_cs(self.flp_ttff_max_threshold,
self.pixel_lab_location)
- @test_tracker_info(uuid="03c0d34f-8312-48d5-8753-93b09151233a")
def test_flp_ttff_weak_signal_wifiscan_on_wifi_connect(self):
"""Verify FLP TTFF Hot Start and Cold Start under Weak GNSS signals
with WiFi scanning on and connected
@@ -228,7 +227,6 @@
self.flp_ttff_hs_and_cs(self.flp_ttff_max_threshold,
self.pixel_lab_location)
- @test_tracker_info(uuid="13daf7b3-5ac5-4107-b3dc-a3a8b5589fed")
def test_flp_ttff_weak_signal_wifiscan_on_wifi_not_connect(self):
"""Verify FLP TTFF Hot Start and Cold Start under Weak GNSS signals
with WiFi scanning on and not connected.
@@ -251,7 +249,6 @@
self.flp_ttff_hs_and_cs(self.flp_ttff_max_threshold,
self.pixel_lab_location)
- @test_tracker_info(uuid="1831f80f-099f-46d2-b484-f332046d5a4d")
def test_flp_ttff_weak_signal_wifiscan_off(self):
"""Verify FLP TTFF Hot Start and Cold Start with WiFi scanning OFF
under weak GNSS signals.
diff --git a/acts_tests/tests/google/gnss/GnssBlankingThTest.py b/acts_tests/tests/google/gnss/GnssBlankingThTest.py
index e625171..171a453 100644
--- a/acts_tests/tests/google/gnss/GnssBlankingThTest.py
+++ b/acts_tests/tests/google/gnss/GnssBlankingThTest.py
@@ -28,12 +28,14 @@
first_wait = self.user_params.get('first_wait', 300)
# Start the test item with gnss_init_power_setting.
- if self.gnss_init_power_setting(first_wait):
- self.log.info('Successfully set the GNSS power level to %d' %
- self.sa_sensitivity)
+ ret, pwr_lvl = self.gnss_init_power_setting(first_wait)
+ if ret:
+ self.log.info(f'Successfully set the GNSS power level to {pwr_lvl}')
self.log.info('Start searching for cellular power level threshold')
# After the GNSS power initialization is done, start the cellular power sweep.
self.result_cell_pwr = self.cell_power_sweep()
+ else:
+ raise AttributeError('Init power sweep is missing')
def test_gnss_gsm850_sweep(self):
"""
diff --git a/acts_tests/tests/google/gnss/GnssUserBuildBroadcomConfigurationTest.py b/acts_tests/tests/google/gnss/GnssBroadcomConfigurationTest.py
similarity index 88%
rename from acts_tests/tests/google/gnss/GnssUserBuildBroadcomConfigurationTest.py
rename to acts_tests/tests/google/gnss/GnssBroadcomConfigurationTest.py
index 9f3ccfd..64fe9c8 100644
--- a/acts_tests/tests/google/gnss/GnssUserBuildBroadcomConfigurationTest.py
+++ b/acts_tests/tests/google/gnss/GnssBroadcomConfigurationTest.py
@@ -8,6 +8,7 @@
For more details, please refer to : go/p22_user_build_verification
"""
import os
+import re
import shutil
import tempfile
import time
@@ -15,9 +16,10 @@
from acts import asserts
from acts import signals
from acts.base_test import BaseTestClass
+from acts_contrib.test_utils.gnss.testtracker_util import log_testtracker_uuid
from acts.controllers.adb_lib.error import AdbCommandError
from acts.libs.proc.job import TimeoutError
-from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
@@ -147,7 +149,6 @@
self.lheconsole = "LheConsole"
self.lheconsole_hub = self.get_lheconsole_value()
self.esw_crash_dump_pattern = self.get_esw_crash_dump_pattern()
- self.ad.log.info(f"here is {self.esw_crash_dump_pattern}")
def _adjust_lhe_setting(self, key, enable):
"""Set lhe setting.
@@ -251,14 +252,16 @@
return False
-class GnssUserBuildBroadcomConfigurationTest(BaseTestClass):
- """ GNSS user build configuration Tests on Broadcom device."""
+class GnssBroadcomConfigurationTest(BaseTestClass):
+ """ GNSS configuration Tests on Broadcom device."""
def setup_class(self):
super().setup_class()
self.ad = self.android_devices[0]
+ req_params = ["standalone_cs_criteria"]
+ self.unpack_userparams(req_param_names=req_params)
if not gutils.check_chipset_vendor_by_qualcomm(self.ad):
- gutils._init_device(self.ad)
+ self.init_device()
self.gps_config_path = tempfile.mkdtemp()
self.gps_xml = GpsXml(self.ad)
self.lhd_conf = LhdConf(self.ad)
@@ -266,19 +269,31 @@
self.enable_testing_setting()
self.backup_gps_config()
+ def init_device(self):
+ gutils._init_device(self.ad)
+ gutils.enable_supl_mode(self.ad)
+ gutils.enable_vendor_orbit_assistance_data(self.ad)
+ wutils.wifi_toggle_state(self.ad, True)
+ gutils.set_mobile_data(self.ad, state=True)
+
+
def teardown_class(self):
if hasattr(self, "gps_config_path") and os.path.isdir(self.gps_config_path):
shutil.rmtree(self.gps_config_path)
def setup_test(self):
+ gutils.log_current_epoch_time(self.ad, "test_start_time")
+ log_testtracker_uuid(self.ad, self.current_test_name)
if gutils.check_chipset_vendor_by_qualcomm(self.ad):
raise signals.TestSkip("Device is Qualcomm, skip the test")
+ gutils.get_baseband_and_gms_version(self.ad)
gutils.clear_logd_gnss_qxdm_log(self.ad)
def teardown_test(self):
if not gutils.check_chipset_vendor_by_qualcomm(self.ad):
self.revert_gps_config()
self.ad.reboot()
+ gutils.log_current_epoch_time(self.ad, "test_end_time")
def on_fail(self, test_name, begin_time):
self.ad.take_bug_report(test_name, begin_time)
@@ -317,10 +332,7 @@
def run_gps_and_capture_log(self):
"""Enable GPS via gps tool for 15s and capture pixel log"""
gutils.start_pixel_logger(self.ad)
- gutils.start_gnss_by_gtw_gpstool(self.ad, state=True)
- time.sleep(15)
- gutils.start_gnss_by_gtw_gpstool(self.ad, state=False)
- gutils.stop_pixel_logger(self.ad)
+ gutils.gnss_tracking_via_gtw_gpstool(self.ad, self.standalone_cs_criteria, testtime=1)
def set_gps_logenabled(self, enable):
"""Set LogEnabled in gps.xml / lhd.conf / scd.conf
@@ -337,44 +349,50 @@
self.scd_conf.disable_diagnostic_log()
self.lhd_conf.disable_diagnostic_log()
- @test_tracker_info(uuid="1dd68d9c-38b0-4fbc-8635-1228c72872ff")
def test_gps_logenabled_setting(self):
"""Verify the LogEnabled setting in gps.xml / scd.conf / lhd.conf
Steps:
1. default setting is on in user_debug build
- 2. enable gps for 15s
- 3. assert gps log pattern "slog :" in pixel logger
+ 2. run gps tracking for 1 min
+ 3. should find slog in pixel logger log files
4. disable LogEnabled in all the gps conf
- 5. enable gps for 15s
- 6. assert gps log pattern "slog :" in pixel logger
+ 5. run gps tracking for 1 min
+ 6. should not find slog in pixel logger log files
"""
self.run_gps_and_capture_log()
- result, _ = gutils.parse_brcm_nmea_log(self.ad, "slog :", [])
+ pattern = re.compile(f".*slog\s+:.*")
+ result, _ = gutils.parse_brcm_nmea_log(self.ad, pattern, [])
asserts.assert_true(bool(result), "LogEnabled is set to true, but no gps log was found")
self.set_gps_logenabled(enable=False)
gutils.clear_logd_gnss_qxdm_log(self.ad)
+ # Removes pixel logger path again in case pixel logger still writes log unexpectedly.
+ gutils.remove_pixel_logger_folder(self.ad)
self.run_gps_and_capture_log()
- result, _ = gutils.parse_brcm_nmea_log(self.ad, "slog :", [])
- asserts.assert_false(bool(result), ("LogEnabled is set to False but still found %d slog",
- len(result)))
+ try:
+ result, _ = gutils.parse_brcm_nmea_log(self.ad, pattern, [])
+ asserts.assert_false(
+ bool(result),
+ ("LogEnabled is set to False but still found %d slog" % len(result)))
+ except FileNotFoundError:
+ self.ad.log.info("Test pass because no BRCM log files/folders was found")
- @test_tracker_info(uuid="152a12e0-7957-47e0-9ea7-14725254fd1d")
def test_gps_supllogenable_setting(self):
"""Verify SuplLogEnable in gps.xml
Steps:
1. default setting is on in user_debug build
2. remove existing supl log
- 3. enable gps for 15s
+ 3. run gps tracking for 1 min
4. supl log should exist
5. disable SuplLogEnable in gps.xml
6. remove existing supl log
- 7. enable gps for 15s
+ 7. run gps tracking for 1 min
8. supl log should not exist
"""
def is_supl_log_exist_after_supl_request():
self.gps_xml.remove_supl_logs()
+ self.ad.reboot()
self.run_gps_and_capture_log()
return self.gps_xml.is_supl_log_file_exist()
@@ -382,12 +400,10 @@
asserts.assert_true(result, "SuplLogEnable is enable, should find supl log file")
self.gps_xml.disable_supl_log()
- self.ad.reboot()
result = is_supl_log_exist_after_supl_request()
asserts.assert_false(result, "SuplLogEnable is disable, should not find supl log file")
- @test_tracker_info(uuid="892d0037-8c0c-45b6-bd0f-9e4073d37232")
def test_lhe_setting(self):
"""Verify lhefailsafe / lheconsole setting in lhd.conf
Steps:
diff --git a/acts_tests/tests/google/gnss/GnssConcurrencyTest.py b/acts_tests/tests/google/gnss/GnssConcurrencyTest.py
index 9169f4e..3cbb2a7 100644
--- a/acts_tests/tests/google/gnss/GnssConcurrencyTest.py
+++ b/acts_tests/tests/google/gnss/GnssConcurrencyTest.py
@@ -15,15 +15,17 @@
# limitations under the License.
import time
-import datetime
import re
+import statistics
+from datetime import datetime
from acts import utils
from acts import signals
from acts.base_test import BaseTestClass
-from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.gnss.testtracker_util import log_testtracker_uuid
from acts_contrib.test_utils.tel.tel_logging_utils import start_adb_tcpdump
from acts_contrib.test_utils.tel.tel_logging_utils import stop_adb_tcpdump
from acts_contrib.test_utils.tel.tel_logging_utils import get_tcpdump_log
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
CONCURRENCY_TYPE = {
@@ -32,6 +34,24 @@
"ap_location": "reportLocation"
}
+GPS_XML_CONFIG = {
+ "CS": [
+ ' IgnorePosition=\"true\"\n', ' IgnoreEph=\"true\"\n',
+ ' IgnoreTime=\"true\"\n', ' AsstIgnoreLto=\"true\"\n',
+ ' IgnoreJniTime=\"true\"\n'
+ ],
+ "WS": [
+ ' IgnorePosition=\"true\"\n', ' AsstIgnoreLto=\"true\"\n',
+ ' IgnoreJniTime=\"true\"\n'
+ ],
+ "HS": []
+}
+
+ONCHIP_CONFIG = [
+ ' EnableOnChipStopNotification=\"1\"\n',
+ ' EnableOnChipStopNotification=\"2\"\n'
+]
+
class GnssConcurrencyTest(BaseTestClass):
""" GNSS Concurrency TTFF Tests. """
@@ -42,15 +62,16 @@
req_params = [
"standalone_cs_criteria", "chre_tolerate_rate", "qdsp6m_path",
"outlier_criteria", "max_outliers", "pixel_lab_location",
- "max_interval", "onchip_interval"
+ "max_interval", "onchip_interval", "ttff_test_cycle"
]
self.unpack_userparams(req_param_names=req_params)
gutils._init_device(self.ad)
self.ad.adb.shell("setprop persist.vendor.radio.adb_log_on 0")
self.ad.adb.shell("sync")
- gutils.reboot(self.ad)
def setup_test(self):
+ gutils.log_current_epoch_time(self.ad, "test_start_time")
+ log_testtracker_uuid(self.ad, self.current_test_name)
gutils.clear_logd_gnss_qxdm_log(self.ad)
gutils.start_pixel_logger(self.ad)
start_adb_tcpdump(self.ad)
@@ -62,6 +83,7 @@
def teardown_test(self):
gutils.stop_pixel_logger(self.ad)
stop_adb_tcpdump(self.ad)
+ gutils.log_current_epoch_time(self.ad, "test_end_time")
def on_fail(self, test_name, begin_time):
self.ad.take_bug_report(test_name, begin_time)
@@ -78,8 +100,12 @@
for _ in range(0, 3):
try:
self.ad.log.info("Start to load the nanoapp")
- res = self.ad.adb.shell("chre_power_test_client load")
- if "success: 1" in res:
+ cmd = "chre_power_test_client load"
+ if gutils.is_device_wearable(self.ad):
+ extra_cmd = "tcm /vendor/etc/chre/power_test_tcm.so"
+ cmd = " ".join([cmd, extra_cmd])
+ res = self.ad.adb.shell(cmd)
+ if "result 1" in res:
self.ad.log.info("Nano app loaded successfully")
break
except Exception as e:
@@ -88,65 +114,79 @@
else:
raise signals.TestError("Failed to load CHRE nanoapp")
- def enable_chre(self, freq):
+ def enable_chre(self, interval_sec):
""" Enable or disable gnss concurrency via nanoapp.
Args:
- freq: an int for frequency, set 0 as disable.
+ interval_sec: an int for frequency, set 0 as disable.
"""
- freq = freq * 1000
+ if interval_sec == 0:
+ self.ad.log.info(f"Stop CHRE request")
+ else:
+ self.ad.log.info(
+ f"Initiate CHRE with {interval_sec} seconds interval")
+ interval_msec = interval_sec * 1000
cmd = "chre_power_test_client"
- option = "enable %d" % freq if freq != 0 else "disable"
+ option = "enable %d" % interval_msec if interval_msec != 0 else "disable"
for type in CONCURRENCY_TYPE.keys():
if "ap" not in type:
self.ad.adb.shell(" ".join([cmd, type, option]))
- def parse_concurrency_result(self, begin_time, type, criteria):
+ def parse_concurrency_result(self,
+ begin_time,
+ request_type,
+ criteria,
+ exam_lower=True):
""" Parse the test result with given time and criteria.
Args:
begin_time: test begin time.
- type: str for location request type.
+ request_type: str for location request type.
criteria: dictionary for test criteria.
+ exam_lower: a boolean to identify the lower bond or not.
Return: List for the failure and outlier loops and results.
"""
results = []
failures = []
outliers = []
- search_results = self.ad.search_logcat(CONCURRENCY_TYPE[type],
+ upper_bound = criteria * (
+ 1 + self.chre_tolerate_rate) + self.outlier_criteria
+ lower_bound = criteria * (
+ 1 - self.chre_tolerate_rate) - self.outlier_criteria
+ search_results = self.ad.search_logcat(CONCURRENCY_TYPE[request_type],
begin_time)
- start_time = utils.epoch_to_human_time(begin_time)
- start_time = datetime.datetime.strptime(start_time,
- "%m-%d-%Y %H:%M:%S ")
if not search_results:
raise signals.TestFailure(f"No log entry found for keyword:"
- f"{CONCURRENCY_TYPE[type]}")
- results.append(
- (search_results[0]["datetime_obj"] - start_time).total_seconds())
- samples = len(search_results) - 1
- for i in range(samples):
+ f"{CONCURRENCY_TYPE[request_type]}")
+
+ for i in range(len(search_results) - 1):
target = search_results[i + 1]
timedelt = target["datetime_obj"] - search_results[i]["datetime_obj"]
timedelt_sec = timedelt.total_seconds()
results.append(timedelt_sec)
- if timedelt_sec > (criteria *
- self.chre_tolerate_rate) + self.outlier_criteria:
- failures.append(target)
- self.ad.log.error("[Failure][%s]:%.2f sec" %
- (target["time_stamp"], timedelt_sec))
- elif timedelt_sec > criteria * self.chre_tolerate_rate:
- outliers.append(target)
- self.ad.log.info("[Outlier][%s]:%.2f sec" %
- (target["time_stamp"], timedelt_sec))
+ res_tag = ""
+ if timedelt_sec > upper_bound:
+ failures.append(timedelt_sec)
+ res_tag = "Failure"
+ elif timedelt_sec < lower_bound and exam_lower:
+ failures.append(timedelt_sec)
+ res_tag = "Failure"
+ elif timedelt_sec > criteria * (1 + self.chre_tolerate_rate):
+ outliers.append(timedelt_sec)
+ res_tag = "Outlier"
+ if res_tag:
+ self.ad.log.error(
+ f"[{res_tag}][{target['time_stamp']}]:{timedelt_sec:.2f} sec"
+ )
- res_summary = " ".join([str(res) for res in results])
- self.ad.log.info("[%s]Overall Result: %s" % (type, res_summary))
- self.ad.log.info("TestResult %s_samples %d" % (type, samples))
- self.ad.log.info("TestResult %s_outliers %d" % (type, len(outliers)))
- self.ad.log.info("TestResult %s_failures %d" % (type, len(failures)))
- self.ad.log.info("TestResult %s_max_time %.2f" %
- (type, max(results[1:])))
+ res_summary = " ".join([str(res) for res in results[1:]])
+ self.ad.log.info(f"[{request_type}]Overall Result: {res_summary}")
+ log_prefix = f"TestResult {request_type}"
+ self.ad.log.info(f"{log_prefix}_samples {len(search_results)}")
+ self.ad.log.info(f"{log_prefix}_outliers {len(outliers)}")
+ self.ad.log.info(f"{log_prefix}_failures {len(failures)}")
+ self.ad.log.info(f"{log_prefix}_max_time {max(results):.2f}")
return outliers, failures, results
@@ -157,12 +197,14 @@
criteria: int for test criteria.
test_duration: int for test duration.
"""
- begin_time = utils.get_current_epoch_time()
- self.ad.log.info("Tests Start at %s" %
- utils.epoch_to_human_time(begin_time))
- gutils.start_gnss_by_gtw_gpstool(
- self.ad, True, freq=criteria["ap_location"])
self.enable_chre(criteria["gnss"])
+ TTFF_criteria = criteria["ap_location"] + self.standalone_cs_criteria
+ gutils.process_gnss_by_gtw_gpstool(
+ self.ad, TTFF_criteria, freq=criteria["ap_location"])
+ self.ad.log.info("Tracking 10 sec to prevent flakiness.")
+ time.sleep(10)
+ begin_time = datetime.now()
+ self.ad.log.info(f"Test Start at {begin_time}")
time.sleep(test_duration)
self.enable_chre(0)
gutils.start_gnss_by_gtw_gpstool(self.ad, False)
@@ -175,9 +217,8 @@
criteria: int for test criteria.
test_duration: int for test duration.
"""
- begin_time = utils.get_current_epoch_time()
- self.ad.log.info("Tests Start at %s" %
- utils.epoch_to_human_time(begin_time))
+ begin_time = datetime.now()
+ self.ad.log.info(f"Test Start at {begin_time}")
self.enable_chre(criteria["gnss"])
time.sleep(test_duration)
self.enable_chre(0)
@@ -199,12 +240,12 @@
self.ad.log.info("Starting process %s result" % request_type)
outliers[request_type], failures[request_type], results[
request_type] = self.parse_concurrency_result(
- begin_time, request_type, criteria)
+ begin_time, request_type, criteria, exam_lower=False)
if not results[request_type]:
failure_log += "[%s] Fail to find location report.\n" % request_type
if len(failures[request_type]) > 0:
- failure_log += "[%s] Test exceeds criteria: %.2f\n" % (
- request_type, criteria)
+ failure_log += "[%s] Test exceeds criteria(%.2f): %.2f\n" % (
+ request_type, criteria, max(failures[request_type]))
if len(outliers[request_type]) > self.max_outliers:
failure_log += "[%s] Outliers excceds max amount: %d\n" % (
request_type, len(outliers[request_type]))
@@ -219,7 +260,7 @@
freq: a list identify source1/2 frequency [freq1, freq2]
"""
request = {"ap_location": self.max_interval}
- begin_time = utils.get_current_epoch_time()
+ begin_time = datetime.now()
self.ad.droid.startLocating(freq[0] * 1000, 0)
time.sleep(10)
for i in range(5):
@@ -253,73 +294,183 @@
self.ad.log.info("TestResult max_position_error %.2f" %
max(position_errors))
+ def get_chre_ttff(self, interval_sec, duration):
+ """ Get the TTFF for the first CHRE report.
+
+ Args:
+ interval_sec: test interval in seconds for CHRE.
+ duration: test duration.
+ """
+ begin_time = datetime.now()
+ self.ad.log.info(f"Test start at {begin_time}")
+ self.enable_chre(interval_sec)
+ time.sleep(duration)
+ self.enable_chre(0)
+ for type, pattern in CONCURRENCY_TYPE.items():
+ if type == "ap_location":
+ continue
+ search_results = self.ad.search_logcat(pattern, begin_time)
+ if not search_results:
+ raise signals.TestFailure(
+ f"Unable to receive {type} report in {duration} seconds")
+ else:
+ ttff_stamp = search_results[0]["datetime_obj"]
+ self.ad.log.info(search_results[0]["time_stamp"])
+ ttff = (ttff_stamp - begin_time).total_seconds()
+ self.ad.log.info(f"CHRE {type} TTFF = {ttff}")
+
+ def add_ttff_conf(self, conf_type):
+ """ Add mcu ttff config to gps.xml
+
+ Args:
+ conf_type: a string identify the config type
+ """
+ search_line_tag = "<gll\n"
+ append_line_str = GPS_XML_CONFIG[conf_type]
+ gutils.bcm_gps_xml_update_option(self.ad, "add", search_line_tag,
+ append_line_str)
+
+ def update_gps_conf(self, search_line, update_line):
+ """ Update gps.xml content
+
+ Args:
+ search_line: target content
+ update_line: update content
+ """
+ gutils.bcm_gps_xml_update_option(
+ self.ad, "update", search_line, update_txt=update_line)
+
+ def delete_gps_conf(self, conf_type):
+ """ Delete gps.xml content
+
+ Args:
+ conf_type: a string identify the config type
+ """
+ search_line_tag = GPS_XML_CONFIG[conf_type]
+ gutils.bcm_gps_xml_update_option(
+ self.ad, "delete", delete_txt=search_line_tag)
+
+ def preset_mcu_test(self, mode):
+ """ Preseting mcu test with config and device state
+
+ mode:
+ mode: a string identify the test type
+ """
+ self.add_ttff_conf(mode)
+ gutils.push_lhd_overlay(self.ad)
+ toggle_airplane_mode(self.ad.log, self.ad, new_state=True)
+ self.update_gps_conf(ONCHIP_CONFIG[1], ONCHIP_CONFIG[0])
+ gutils.clear_aiding_data_by_gtw_gpstool(self.ad)
+ self.ad.reboot(self.ad)
+ self.load_chre_nanoapp()
+
+ def reset_mcu_test(self, mode):
+ """ Resetting mcu test with config and device state
+
+ mode:
+ mode: a string identify the test type
+ """
+ self.delete_gps_conf(mode)
+ self.update_gps_conf(ONCHIP_CONFIG[0], ONCHIP_CONFIG[1])
+
+ def get_mcu_ttff(self):
+ """ Get mcu ttff seconds
+
+ Return:
+ ttff: a float identify ttff seconds
+ """
+ search_res = ""
+ search_pattern = "$PGLOR,0,FIX"
+ ttff_regex = r"FIX,(.*)\*"
+ cmd_base = "chre_power_test_client gnss tcm"
+ cmd_start = " ".join([cmd_base, "enable 1000"])
+ cmd_stop = " ".join([cmd_base, "disable"])
+ begin_time = datetime.now()
+
+ self.ad.log.info("Send CHRE enable to DUT")
+ self.ad.adb.shell(cmd_start)
+ for i in range(6):
+ search_res = self.ad.search_logcat(search_pattern, begin_time)
+ if search_res:
+ break
+ time.sleep(10)
+ else:
+ self.ad.adb.shell(cmd_stop)
+ self.ad.log.error("Unable to get mcu ttff in 60 seconds")
+ return 60
+ self.ad.adb.shell(cmd_stop)
+
+ res = re.search(ttff_regex, search_res[0]["log_message"])
+ ttff = res.group(1)
+ self.ad.log.info(f"TTFF = {ttff}")
+ return float(ttff)
+
+ def run_mcu_ttff_loops(self, mode, loops):
+ """ Run mcu ttff with given mode and loops
+
+ Args:
+ mode: a string identify mode cs/ws/hs.
+ loops: a int to identify the number of loops
+ """
+ ttff_res = []
+ for i in range(10):
+ ttff = self.get_mcu_ttff()
+ self.ad.log.info(f"{mode} TTFF LOOP{i+1} = {ttff}")
+ ttff_res.append(ttff)
+ time.sleep(10)
+ self.ad.log.info(f"TestResult {mode}_MAX_TTFF {max(ttff_res)}")
+ self.ad.log.info(
+ f"TestResult {mode}_AVG_TTFF {statistics.mean(ttff_res)}")
+
# Concurrency Test Cases
- @test_tracker_info(uuid="9b0daebf-461e-4005-9773-d5d10aaeaaa4")
- def test_gnss_concurrency_ct1(self):
+ def test_gnss_concurrency_location_1_chre_1(self):
test_duration = 15
criteria = {"ap_location": 1, "gnss": 1, "gnss_meas": 1}
self.run_gnss_concurrency_test(criteria, test_duration)
- @test_tracker_info(uuid="f423db2f-12a0-4858-b66f-99e7ca6010c3")
- def test_gnss_concurrency_ct2(self):
+ def test_gnss_concurrency_location_1_chre_8(self):
test_duration = 30
criteria = {"ap_location": 1, "gnss": 8, "gnss_meas": 8}
self.run_gnss_concurrency_test(criteria, test_duration)
- @test_tracker_info(uuid="f72d2df0-f70a-4a11-9f68-2a38f6974454")
- def test_gnss_concurrency_ct3(self):
+ def test_gnss_concurrency_location_15_chre_8(self):
test_duration = 60
criteria = {"ap_location": 15, "gnss": 8, "gnss_meas": 8}
self.run_gnss_concurrency_test(criteria, test_duration)
- @test_tracker_info(uuid="8e5563fd-afcd-40d3-9392-7fc0d10f49da")
- def test_gnss_concurrency_aoc1(self):
+ def test_gnss_concurrency_location_61_chre_1(self):
test_duration = 120
criteria = {"ap_location": 61, "gnss": 1, "gnss_meas": 1}
self.run_gnss_concurrency_test(criteria, test_duration)
- @test_tracker_info(uuid="fb258565-6ac8-4bf7-a554-01d63fc4ef54")
- def test_gnss_concurrency_aoc2(self):
+ def test_gnss_concurrency_location_61_chre_10(self):
test_duration = 120
criteria = {"ap_location": 61, "gnss": 10, "gnss_meas": 10}
self.run_gnss_concurrency_test(criteria, test_duration)
# CHRE Only Test Cases
- @test_tracker_info(uuid="cb85fa60-9f1a-4957-b5e3-0f2e5db70b47")
- def test_gnss_chre1(self):
+ def test_gnss_chre_1(self):
test_duration = 15
criteria = {"gnss": 1, "gnss_meas": 1}
self.run_chre_only_test(criteria, test_duration)
- @test_tracker_info(uuid="6ab17866-0d0e-4d9e-b3af-441d9db0e324")
- def test_gnss_chre2(self):
+ def test_gnss_chre_8(self):
test_duration = 30
criteria = {"gnss": 8, "gnss_meas": 8}
self.run_chre_only_test(criteria, test_duration)
# Interval tests
- @test_tracker_info(uuid="53b161e5-335e-44a7-ae2e-eae7464a2b37")
def test_variable_interval_via_chre(self):
test_duration = 10
- intervals = [{
- "gnss": 0.1,
- "gnss_meas": 0.1
- }, {
- "gnss": 0.5,
- "gnss_meas": 0.5
- }, {
- "gnss": 1.5,
- "gnss_meas": 1.5
- }]
+ intervals = [0.1, 0.5, 1.5]
for interval in intervals:
- self.run_chre_only_test(interval, test_duration)
+ self.get_chre_ttff(interval, test_duration)
- @test_tracker_info(uuid="ee0a46fe-aa5f-4dfd-9cb7-d4924f9e9cea")
def test_variable_interval_via_framework(self):
test_duration = 10
intervals = [0, 0.5, 1.5]
for interval in intervals:
- begin_time = utils.get_current_epoch_time()
+ begin_time = datetime.now()
self.ad.droid.startLocating(interval * 1000, 0)
time.sleep(test_duration)
self.ad.droid.stopLocating()
@@ -327,14 +478,30 @@
self.parse_concurrency_result(begin_time, "ap_location", criteria)
# Engine switching test
- @test_tracker_info(uuid="8b42bcb2-cb8c-4ef9-bd98-4fb74a521224")
def test_gps_engine_switching_host_to_onchip(self):
self.is_brcm_test()
freq = [1, self.onchip_interval]
self.run_engine_switching_test(freq)
- @test_tracker_info(uuid="636041dc-2bd6-4854-aa5d-61c87943d99c")
def test_gps_engine_switching_onchip_to_host(self):
self.is_brcm_test()
freq = [self.onchip_interval, 1]
self.run_engine_switching_test(freq)
+
+ def test_mcu_cs_ttff(self):
+ mode = "CS"
+ self.preset_mcu_test(mode)
+ self.run_mcu_ttff_loops(mode, self.ttff_test_cycle)
+ self.reset_mcu_test(mode)
+
+ def test_mcu_ws_ttff(self):
+ mode = "WS"
+ self.preset_mcu_test(mode)
+ self.run_mcu_ttff_loops(mode, self.ttff_test_cycle)
+ self.reset_mcu_test(mode)
+
+ def test_mcu_hs_ttff(self):
+ mode = "HS"
+ self.preset_mcu_test(mode)
+ self.run_mcu_ttff_loops(mode, self.ttff_test_cycle)
+ self.reset_mcu_test(mode)
diff --git a/acts_tests/tests/google/gnss/GnssFunctionTest.py b/acts_tests/tests/google/gnss/GnssFunctionTest.py
index d45a997..b22cd4f 100644
--- a/acts_tests/tests/google/gnss/GnssFunctionTest.py
+++ b/acts_tests/tests/google/gnss/GnssFunctionTest.py
@@ -13,18 +13,14 @@
# 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 time
+
import os
import re
import fnmatch
-from multiprocessing import Process
from acts import asserts
from acts import signals
from acts.base_test import BaseTestClass
-from acts.test_decorators import test_tracker_info
-from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
-from acts_contrib.test_utils.tel import tel_test_utils as tutils
from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
from acts.utils import get_current_epoch_time
from acts.utils import unzip_maintain_permissions
@@ -32,9 +28,8 @@
from acts_contrib.test_utils.tel.tel_bootloader_utils import flash_radio
from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
from acts_contrib.test_utils.tel.tel_test_utils import check_call_state_connected_by_adb
-from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
-from acts_contrib.test_utils.tel.tel_data_utils import http_file_download_by_sl4a
from acts_contrib.test_utils.gnss.gnss_test_utils import get_baseband_and_gms_version
from acts_contrib.test_utils.gnss.gnss_test_utils import set_attenuator_gnss_signal
from acts_contrib.test_utils.gnss.gnss_test_utils import _init_device
@@ -49,28 +44,18 @@
from acts_contrib.test_utils.gnss.gnss_test_utils import launch_google_map
from acts_contrib.test_utils.gnss.gnss_test_utils import check_location_api
from acts_contrib.test_utils.gnss.gnss_test_utils import set_battery_saver_mode
-from acts_contrib.test_utils.gnss.gnss_test_utils import kill_xtra_daemon
from acts_contrib.test_utils.gnss.gnss_test_utils import start_gnss_by_gtw_gpstool
from acts_contrib.test_utils.gnss.gnss_test_utils import process_gnss_by_gtw_gpstool
-from acts_contrib.test_utils.gnss.gnss_test_utils import start_ttff_by_gtw_gpstool
-from acts_contrib.test_utils.gnss.gnss_test_utils import process_ttff_by_gtw_gpstool
-from acts_contrib.test_utils.gnss.gnss_test_utils import check_ttff_data
-from acts_contrib.test_utils.gnss.gnss_test_utils import start_youtube_video
-from acts_contrib.test_utils.gnss.gnss_test_utils import fastboot_factory_reset
-from acts_contrib.test_utils.gnss.gnss_test_utils import gnss_trigger_modem_ssr_by_mds
-from acts_contrib.test_utils.gnss.gnss_test_utils import disable_supl_mode
from acts_contrib.test_utils.gnss.gnss_test_utils import connect_to_wifi_network
-from acts_contrib.test_utils.gnss.gnss_test_utils import check_xtra_download
from acts_contrib.test_utils.gnss.gnss_test_utils import gnss_tracking_via_gtw_gpstool
from acts_contrib.test_utils.gnss.gnss_test_utils import parse_gtw_gpstool_log
-from acts_contrib.test_utils.gnss.gnss_test_utils import enable_supl_mode
from acts_contrib.test_utils.gnss.gnss_test_utils import start_toggle_gnss_by_gtw_gpstool
from acts_contrib.test_utils.gnss.gnss_test_utils import grant_location_permission
from acts_contrib.test_utils.gnss.gnss_test_utils import is_mobile_data_on
from acts_contrib.test_utils.gnss.gnss_test_utils import is_wearable_btwifi
-from acts_contrib.test_utils.gnss.gnss_test_utils import delete_lto_file
from acts_contrib.test_utils.gnss.gnss_test_utils import is_device_wearable
-from acts_contrib.test_utils.tel.tel_logging_utils import start_adb_tcpdump
+from acts_contrib.test_utils.gnss.gnss_test_utils import log_current_epoch_time
+from acts_contrib.test_utils.gnss.testtracker_util import log_testtracker_uuid
from acts_contrib.test_utils.tel.tel_logging_utils import stop_adb_tcpdump
from acts_contrib.test_utils.tel.tel_logging_utils import get_tcpdump_log
@@ -80,25 +65,20 @@
def setup_class(self):
super().setup_class()
self.ad = self.android_devices[0]
- req_params = ["pixel_lab_network", "standalone_cs_criteria",
+ req_params = ["pixel_lab_network",
"standalone_ws_criteria", "standalone_hs_criteria",
- "supl_cs_criteria", "supl_ws_criteria",
- "supl_hs_criteria", "xtra_cs_criteria",
- "xtra_ws_criteria", "xtra_hs_criteria",
- "weak_signal_supl_cs_criteria",
- "weak_signal_supl_ws_criteria",
- "weak_signal_supl_hs_criteria",
- "weak_signal_xtra_cs_criteria",
- "weak_signal_xtra_ws_criteria",
- "weak_signal_xtra_hs_criteria",
+ "supl_cs_criteria",
+ "supl_hs_criteria",
+ "standalone_cs_criteria",
"wearable_reboot_hs_criteria",
"default_gnss_signal_attenuation",
"weak_gnss_signal_attenuation",
- "no_gnss_signal_attenuation", "gnss_init_error_list",
+ "gnss_init_error_list",
"gnss_init_error_allowlist", "pixel_lab_location",
- "qdsp6m_path", "supl_capabilities", "ttff_test_cycle",
+ "qdsp6m_path", "ttff_test_cycle",
"collect_logs", "dpo_threshold",
- "brcm_error_log_allowlist"]
+ "brcm_error_log_allowlist", "onchip_interval", "adr_ratio_threshold",
+ "set_attenuator", "weak_signal_criteria", "weak_signal_cs_criteria"]
self.unpack_userparams(req_param_names=req_params)
# create hashmap for SSID
self.ssid_map = {}
@@ -109,23 +89,35 @@
"ws": "Warm Start",
"hs": "Hot Start",
"csa": "CSWith Assist"}
- if self.collect_logs and \
- gutils.check_chipset_vendor_by_qualcomm(self.ad):
+ if self.collect_logs and gutils.check_chipset_vendor_by_qualcomm(self.ad):
self.flash_new_radio_or_mbn()
self.push_gnss_cfg()
- _init_device(self.ad)
+ self.init_device()
+
+ def init_device(self):
+ gutils._init_device(self.ad)
+ gutils.enable_supl_mode(self.ad)
+ gutils.enable_vendor_orbit_assistance_data(self.ad)
+ gutils.disable_ramdump(self.ad)
def setup_test(self):
+ log_current_epoch_time(self.ad, "test_start_time")
+ log_testtracker_uuid(self.ad, self.current_test_name)
get_baseband_and_gms_version(self.ad)
if self.collect_logs:
clear_logd_gnss_qxdm_log(self.ad)
+ if self.set_attenuator:
set_attenuator_gnss_signal(self.ad, self.attenuators,
self.default_gnss_signal_attenuation)
# TODO (b/202101058:chenstanley): Need to double check how to disable wifi successfully in wearable projects.
if is_wearable_btwifi(self.ad):
wifi_toggle_state(self.ad, True)
connect_to_wifi_network(
- self.ad, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
+ self.ad, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
+ else:
+ wifi_toggle_state(self.ad, False)
+ set_mobile_data(self.ad, True)
+
if not verify_internet_connection(self.ad.log, self.ad, retries=3,
expected_state=True):
raise signals.TestFailure("Fail to connect to LTE network.")
@@ -134,6 +126,7 @@
if self.collect_logs:
gutils.stop_pixel_logger(self.ad)
stop_adb_tcpdump(self.ad)
+ if self.set_attenuator:
set_attenuator_gnss_signal(self.ad, self.attenuators,
self.default_gnss_signal_attenuation)
# TODO(chenstanley): sim structure issue
@@ -142,17 +135,11 @@
hangup_call(self.ad.log, self.ad)
if self.ad.droid.connectivityCheckAirplaneMode():
self.ad.log.info("Force airplane mode off")
- self.ad.droid.connectivityToggleAirplaneMode(False)
- if not is_wearable_btwifi and self.ad.droid.wifiCheckState():
- wifi_toggle_state(self.ad, False)
- if not is_mobile_data_on(self.ad):
- set_mobile_data(self.ad, True)
+ toggle_airplane_mode(self.ad.log, self.ad, new_state=False)
if int(self.ad.adb.shell(
"settings get global wifi_scan_always_enabled")) != 1:
set_wifi_and_bt_scanning(self.ad, True)
- if not verify_internet_connection(self.ad.log, self.ad, retries=3,
- expected_state=True):
- raise signals.TestFailure("Fail to connect to LTE network.")
+ log_current_epoch_time(self.ad, "test_end_time")
def on_fail(self, test_name, begin_time):
if self.collect_logs:
@@ -248,41 +235,6 @@
self.ad.log.error("cat mcfg.version with error %s", e)
return False
- def run_ttff_via_gtw_gpstool(self, mode, criteria):
- """Run GNSS TTFF test with selected mode and parse the results.
-
- Args:
- mode: "cs", "ws" or "hs"
- criteria: Criteria for the TTFF.
- """
- begin_time = get_current_epoch_time()
- process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
- start_ttff_by_gtw_gpstool(self.ad, mode, self.ttff_test_cycle)
- ttff_data = process_ttff_by_gtw_gpstool(
- self.ad, begin_time, self.pixel_lab_location)
- result = check_ttff_data(
- self.ad, ttff_data, self.ttff_mode.get(mode), criteria)
- asserts.assert_true(
- result, "TTFF %s fails to reach designated criteria of %d "
- "seconds." % (self.ttff_mode.get(mode), criteria))
-
- def start_qxdm_and_tcpdump_log(self):
- """Start QXDM and adb tcpdump if collect_logs is True."""
- if self.collect_logs:
- gutils.start_pixel_logger(self.ad)
- start_adb_tcpdump(self.ad)
-
- def supl_ttff_with_sim(self, mode, criteria):
- """Verify SUPL TTFF functionality.
-
- Args:
- mode: "cs", "ws" or "hs"
- criteria: Criteria for the test.
- """
- kill_xtra_daemon(self.ad)
- self.start_qxdm_and_tcpdump_log()
- self.run_ttff_via_gtw_gpstool(mode, criteria)
-
def standalone_ttff_airplane_mode_on(self, mode, criteria):
"""Verify Standalone GNSS TTFF functionality while airplane mode is on.
@@ -290,92 +242,79 @@
mode: "cs", "ws" or "hs"
criteria: Criteria for the test.
"""
- self.start_qxdm_and_tcpdump_log()
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
self.ad.log.info("Turn airplane mode on")
- self.ad.droid.connectivityToggleAirplaneMode(True)
- self.run_ttff_via_gtw_gpstool(mode, criteria)
-
- def supl_ttff_weak_gnss_signal(self, mode, criteria):
- """Verify SUPL TTFF functionality under weak GNSS signal.
-
- Args:
- mode: "cs", "ws" or "hs"
- criteria: Criteria for the test.
- """
- set_attenuator_gnss_signal(self.ad, self.attenuators,
- self.weak_gnss_signal_attenuation)
- kill_xtra_daemon(self.ad)
- self.start_qxdm_and_tcpdump_log()
- self.run_ttff_via_gtw_gpstool(mode, criteria)
-
- def xtra_ttff_mobile_data(self, mode, criteria):
- """Verify XTRA\LTO TTFF functionality with mobile data.
-
- Args:
- mode: "cs", "ws" or "hs"
- criteria: Criteria for the test.
- """
- disable_supl_mode(self.ad)
- self.start_qxdm_and_tcpdump_log()
- self.run_ttff_via_gtw_gpstool(mode, criteria)
-
- def xtra_ttff_weak_gnss_signal(self, mode, criteria):
- """Verify XTRA\LTO TTFF functionality under weak GNSS signal.
-
- Args:
- mode: "cs", "ws" or "hs"
- criteria: Criteria for the test.
- """
- set_attenuator_gnss_signal(self.ad, self.attenuators,
- self.weak_gnss_signal_attenuation)
- disable_supl_mode(self.ad)
- self.start_qxdm_and_tcpdump_log()
- self.run_ttff_via_gtw_gpstool(mode, criteria)
-
- def xtra_ttff_wifi(self, mode, criteria):
- """Verify XTRA\LTO TTFF functionality with WiFi.
-
- Args:
- mode: "cs", "ws" or "hs"
- criteria: Criteria for the test.
- """
- disable_supl_mode(self.ad)
- self.start_qxdm_and_tcpdump_log()
- self.ad.log.info("Turn airplane mode on")
- self.ad.droid.connectivityToggleAirplaneMode(True)
- wifi_toggle_state(self.ad, True)
- connect_to_wifi_network(
- self.ad, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
- self.run_ttff_via_gtw_gpstool(mode, criteria)
-
- def ttff_with_assist(self, mode, criteria):
- """Verify CS/WS TTFF functionality with Assist data.
-
- Args:
- mode: "csa" or "ws"
- criteria: Criteria for the test.
- """
- disable_supl_mode(self.ad)
- begin_time = get_current_epoch_time()
- process_gnss_by_gtw_gpstool(
- self.ad, self.standalone_cs_criteria)
- check_xtra_download(self.ad, begin_time)
- self.ad.log.info("Turn airplane mode on")
- self.ad.droid.connectivityToggleAirplaneMode(True)
- start_gnss_by_gtw_gpstool(self.ad, True)
- start_ttff_by_gtw_gpstool(
- self.ad, mode, iteration=self.ttff_test_cycle)
- ttff_data = process_ttff_by_gtw_gpstool(
- self.ad, begin_time, self.pixel_lab_location)
- result = check_ttff_data(
- self.ad, ttff_data, mode, criteria)
- asserts.assert_true(
- result, "TTFF %s fails to reach designated criteria of %d "
- "seconds." % (self.ttff_mode.get(mode), criteria))
+ toggle_airplane_mode(self.ad.log, self.ad, new_state=True)
+ gutils.run_ttff_via_gtw_gpstool(
+ self.ad, mode, criteria, self.ttff_test_cycle, self.pixel_lab_location)
""" Test Cases """
- @test_tracker_info(uuid="ab859f2a-2c95-4d15-bb7f-bd0e3278340f")
+ def test_cs_first_fixed_system_server_restart(self):
+ """Verify cs first fixed after system server restart.
+
+ Steps:
+ 1. Get location fixed within supl_cs_criteria.
+ 2. Restarts android runtime.
+ 3. Get location fixed within supl_cs_criteria.
+
+ Expected Results:
+ Location fixed within supl_cs_criteria.
+ """
+ overall_test_result = []
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
+ for test_loop in range(1, 6):
+ gutils.process_gnss_by_gtw_gpstool(self.ad, self.supl_cs_criteria)
+ gutils.start_gnss_by_gtw_gpstool(self.ad, False)
+ self.ad.restart_runtime()
+ self.ad.unlock_screen(password=None)
+ test_result = gutils.process_gnss_by_gtw_gpstool(self.ad, self.supl_cs_criteria)
+ gutils.start_gnss_by_gtw_gpstool(self.ad, False)
+ self.ad.log.info("Iteration %d => %s" % (test_loop, test_result))
+ overall_test_result.append(test_result)
+
+ asserts.assert_true(all(overall_test_result),
+ "SUPL fail after system server restart.")
+
+ def test_cs_ttff_after_gps_service_restart(self):
+ """Verify cs ttff after modem silent reboot / GPS daemons restart.
+
+ Steps:
+ 1. Trigger modem crash by adb/Restart GPS daemons by killing PID.
+ 2. Wait 1 minute for modem to recover.
+ 3. TTFF Cold Start for 3 iteration.
+ 4. Repeat Step 1. to Step 3. for 5 times.
+
+ Expected Results:
+ All SUPL TTFF Cold Start results should be within supl_cs_criteria.
+ """
+ supl_ssr_test_result_all = []
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
+ for times in range(1, 6):
+ begin_time = get_current_epoch_time()
+ if gutils.check_chipset_vendor_by_qualcomm(self.ad):
+ test_info = "Modem SSR"
+ gutils.gnss_trigger_modem_ssr_by_mds(self.ad)
+ else:
+ test_info = "restarting GPS daemons"
+ gutils.restart_gps_daemons(self.ad)
+ if not verify_internet_connection(self.ad.log, self.ad, retries=3,
+ expected_state=True):
+ raise signals.TestFailure("Fail to connect to LTE network.")
+ gutils.process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
+ gutils.start_ttff_by_gtw_gpstool(self.ad, ttff_mode="cs", iteration=3)
+ ttff_data = gutils.process_ttff_by_gtw_gpstool(self.ad, begin_time,
+ self.pixel_lab_location)
+ supl_ssr_test_result = gutils.check_ttff_data(
+ self.ad, ttff_data, ttff_mode="Cold Start",
+ criteria=self.supl_cs_criteria)
+ self.ad.log.info("SUPL after %s test %d times -> %s" % (
+ test_info, times, supl_ssr_test_result))
+ supl_ssr_test_result_all.append(supl_ssr_test_result)
+
+ asserts.assert_true(all(supl_ssr_test_result_all),
+ "TTFF fails to reach designated criteria")
+
def test_gnss_one_hour_tracking(self):
"""Verify GNSS tracking performance of signal strength and position
error.
@@ -387,14 +326,17 @@
Expected Results:
DUT could finish 60 minutes test and output track data.
"""
- self.start_qxdm_and_tcpdump_log()
+ test_time = 60
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
gnss_tracking_via_gtw_gpstool(self.ad, self.standalone_cs_criteria,
- type="gnss", testtime=60)
- parse_gtw_gpstool_log(self.ad, self.pixel_lab_location, type="gnss")
+ api_type="gnss", testtime=test_time)
+ location_data = parse_gtw_gpstool_log(self.ad, self.pixel_lab_location, api_type="gnss")
+ gutils.validate_location_fix_rate(self.ad, location_data, run_time=test_time,
+ fix_rate_criteria=0.99)
+ gutils.verify_gps_time_should_be_close_to_device_time(self.ad, location_data)
- @test_tracker_info(uuid="623628ab-fdab-449d-9025-ebf4e9a404c2")
- def test_dpo_function(self):
- """Verify DPO Functionality.
+ def test_duty_cycle_function(self):
+ """Verify duty cycle Functionality.
Steps:
1. Launch GTW_GPSTool.
@@ -403,14 +345,14 @@
4. Calculate the count diff of "HardwareClockDiscontinuityCount"
Expected Results:
- DPO should be engaged in 5 minutes GNSS tracking.
+ Duty cycle should be engaged in 5 minutes GNSS tracking.
"""
tracking_minutes = 5
- self.start_qxdm_and_tcpdump_log()
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
dpo_begin_time = get_current_epoch_time()
gnss_tracking_via_gtw_gpstool(self.ad,
self.standalone_cs_criteria,
- type="gnss",
+ api_type="gnss",
testtime=tracking_minutes,
meas_flag=True)
if gutils.check_chipset_vendor_by_qualcomm(self.ad):
@@ -422,7 +364,6 @@
self.dpo_threshold,
self.brcm_error_log_allowlist)
- @test_tracker_info(uuid="499d2091-640a-4735-9c58-de67370e4421")
def test_gnss_init_error(self):
"""Check if there is any GNSS initialization error after reboot.
@@ -452,30 +393,6 @@
asserts.assert_true(error_mismatch, "Error message found after GNSS "
"init")
- @test_tracker_info(uuid="ff318483-411c-411a-8b1a-422bd54f4a3f")
- def test_supl_capabilities(self):
- """Verify SUPL capabilities.
-
- Steps:
- 1. Root DUT.
- 2. Check SUPL capabilities.
-
- Expected Results:
- CAPABILITIES=0x37 which supports MSA + MSB.
- CAPABILITIES=0x17 = ON_DEMAND_TIME | MSA | MSB | SCHEDULING
- """
- if not gutils.check_chipset_vendor_by_qualcomm(self.ad):
- raise signals.TestSkip("Not Qualcomm chipset. Skip the test.")
- capabilities_state = str(
- self.ad.adb.shell(
- "cat vendor/etc/gps.conf | grep CAPABILITIES")).split("=")[-1]
- self.ad.log.info("SUPL capabilities - %s" % capabilities_state)
- asserts.assert_true(capabilities_state in self.supl_capabilities,
- "Wrong default SUPL capabilities is set. Found %s, "
- "expected any of %r" % (capabilities_state,
- self.supl_capabilities))
-
- @test_tracker_info(uuid="dcae6979-ddb4-4cad-9d14-fbdd9439cf42")
def test_sap_valid_modes(self):
"""Verify SAP Valid Modes.
@@ -494,7 +411,6 @@
asserts.assert_true("SAP=PREMIUM" in sap_state,
"Wrong SAP Valid Modes is set")
- @test_tracker_info(uuid="14daaaba-35b4-42d9-8d2c-2a803dd746a6")
def test_network_location_provider_cell(self):
"""Verify LocationManagerService API reports cell Network Location.
@@ -508,7 +424,7 @@
Test devices could report cell Network Location.
"""
test_result_all = []
- self.start_qxdm_and_tcpdump_log()
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
set_wifi_and_bt_scanning(self.ad, False)
for i in range(1, 6):
test_result = check_network_location(
@@ -519,7 +435,6 @@
asserts.assert_true(all(test_result_all),
"Fail to get networkLocationType=cell")
- @test_tracker_info(uuid="a45bdc7d-29fa-4a1d-ba34-6340b90e308d")
def test_network_location_provider_wifi(self):
"""Verify LocationManagerService API reports wifi Network Location.
@@ -533,7 +448,7 @@
Test devices could report wifi Network Location.
"""
test_result_all = []
- self.start_qxdm_and_tcpdump_log()
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
set_wifi_and_bt_scanning(self.ad, True)
for i in range(1, 6):
test_result = check_network_location(
@@ -543,62 +458,6 @@
asserts.assert_true(all(test_result_all),
"Fail to get networkLocationType=wifi")
- @test_tracker_info(uuid="0919d375-baf2-4fe7-b66b-3f72d386f791")
- def test_gmap_location_report_gps_network(self):
- """Verify GnssLocationProvider API reports location to Google Map
- when GPS and Location Accuracy are on.
-
- Steps:
- 1. GPS and NLP are on.
- 2. Launch Google Map.
- 3. Verify whether test devices could report location.
- 4. Repeat Step 2. to Step 3. for 5 times.
-
- Expected Results:
- Test devices could report location to Google Map.
- """
- test_result_all = []
- self.start_qxdm_and_tcpdump_log()
- for i in range(1, 6):
- grant_location_permission(self.ad, True)
- launch_google_map(self.ad)
- test_result = check_location_api(self.ad, retries=3)
- self.ad.send_keycode("HOME")
- test_result_all.append(test_result)
- self.ad.log.info("Iteration %d => %s" % (i, test_result))
- asserts.assert_true(all(test_result_all), "Fail to get location update")
-
- @test_tracker_info(uuid="513361d2-7d72-41b0-a944-fb259c606b81")
- def test_gmap_location_report_gps(self):
- """Verify GnssLocationProvider API reports location to Google Map
- when GPS is on and Location Accuracy is off.
-
- Steps:
- 1. GPS is on.
- 2. Location Accuracy is off.
- 3. Launch Google Map.
- 4. Verify whether test devices could report location.
- 5. Repeat Step 3. to Step 4. for 5 times.
-
- Expected Results:
- Test devices could report location to Google Map.
- """
- test_result_all = []
- self.start_qxdm_and_tcpdump_log()
- self.ad.adb.shell("settings put secure location_mode 1")
- out = int(self.ad.adb.shell("settings get secure location_mode"))
- self.ad.log.info("Modify current Location Mode to %d" % out)
- for i in range(1, 6):
- grant_location_permission(self.ad, True)
- launch_google_map(self.ad)
- test_result = check_location_api(self.ad, retries=3)
- self.ad.send_keycode("HOME")
- test_result_all.append(test_result)
- self.ad.log.info("Iteration %d => %s" % (i, test_result))
- check_location_service(self.ad)
- asserts.assert_true(all(test_result_all), "Fail to get location update")
-
- @test_tracker_info(uuid="91a65121-b87d-450d-bd0f-387ade450ab7")
def test_gmap_location_report_battery_saver(self):
"""Verify GnssLocationProvider API reports location to Google Map
when Battery Saver is enabled.
@@ -615,7 +474,7 @@
Test devices could report location to Google Map.
"""
test_result_all = []
- self.start_qxdm_and_tcpdump_log()
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
set_battery_saver_mode(self.ad, True)
for i in range(1, 6):
grant_location_permission(self.ad, True)
@@ -627,174 +486,6 @@
set_battery_saver_mode(self.ad, False)
asserts.assert_true(all(test_result_all), "Fail to get location update")
- @test_tracker_info(uuid="a59c72af-5d56-4d88-9746-ae2749cac671")
- def test_supl_ttff_cs(self):
- """Verify SUPL functionality of TTFF Cold Start.
-
- Steps:
- 1. Kill XTRA/LTO daemon to support SUPL only case.
- 2. SUPL TTFF Cold Start for 10 iteration.
-
- Expected Results:
- All SUPL TTFF Cold Start results should be less than
- supl_cs_criteria.
- """
- self.supl_ttff_with_sim("cs", self.supl_cs_criteria)
-
- @test_tracker_info(uuid="9a91c8ad-1978-414a-a9ac-8ebc782f77ff")
- def test_supl_ttff_ws(self):
- """Verify SUPL functionality of TTFF Warm Start.
-
- Steps:
- 1. Kill XTRA/LTO daemon to support SUPL only case.
- 2. SUPL TTFF Warm Start for 10 iteration.
-
- Expected Results:
- All SUPL TTFF Warm Start results should be less than
- supl_ws_criteria.
- """
- self.supl_ttff_with_sim("ws", self.supl_ws_criteria)
-
- @test_tracker_info(uuid="bbd5aad4-3309-4579-a3b2-a06bfb674dfa")
- def test_supl_ttff_hs(self):
- """Verify SUPL functionality of TTFF Hot Start.
-
- Steps:
- 1. Kill XTRA/LTO daemon to support SUPL only case.
- 2. SUPL TTFF Hot Start for 10 iteration.
-
- Expected Results:
- All SUPL TTFF Hot Start results should be less than
- supl_hs_criteria.
- """
- self.supl_ttff_with_sim("hs", self.supl_hs_criteria)
-
- @test_tracker_info(uuid="60c0aeec-0c8f-4a96-bc6c-05cba1260e73")
- def test_supl_ongoing_call(self):
- """Verify SUPL functionality during phone call.
-
- Steps:
- 1. Kill XTRA/LTO daemon to support SUPL only case.
- 2. Initiate call on DUT.
- 3. SUPL TTFF Cold Start for 10 iteration.
- 4. DUT hang up call.
-
- Expected Results:
- All SUPL TTFF Cold Start results should be less than
- supl_cs_criteria.
- """
- kill_xtra_daemon(self.ad)
- self.start_qxdm_and_tcpdump_log()
- self.ad.droid.setVoiceCallVolume(25)
- initiate_call(self.ad.log, self.ad, "99117")
- time.sleep(5)
- if not check_call_state_connected_by_adb(self.ad):
- raise signals.TestFailure("Call is not connected.")
- self.run_ttff_via_gtw_gpstool("cs", self.supl_cs_criteria)
-
- @test_tracker_info(uuid="df605509-328f-43e8-b6d8-00635bf701ef")
- def test_supl_downloading_files(self):
- """Verify SUPL functionality when downloading files.
-
- Steps:
- 1. Kill XTRA/LTO daemon to support SUPL only case.
- 2. DUT start downloading files by sl4a.
- 3. SUPL TTFF Cold Start for 10 iteration.
- 4. DUT cancel downloading files.
-
- Expected Results:
- All SUPL TTFF Cold Start results should be within supl_cs_criteria.
- """
- begin_time = get_current_epoch_time()
- kill_xtra_daemon(self.ad)
- self.start_qxdm_and_tcpdump_log()
- download = Process(target=http_file_download_by_sl4a,
- args=(self.ad, "https://speed.hetzner.de/10GB.bin",
- None, None, True, 3600))
- download.start()
- time.sleep(10)
- process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
- start_ttff_by_gtw_gpstool(
- self.ad, ttff_mode="cs", iteration=self.ttff_test_cycle)
- ttff_data = process_ttff_by_gtw_gpstool(self.ad, begin_time,
- self.pixel_lab_location)
- download.terminate()
- time.sleep(3)
- result = check_ttff_data(self.ad, ttff_data, ttff_mode="Cold Start",
- criteria=self.supl_cs_criteria)
- asserts.assert_true(result, "TTFF fails to reach designated criteria")
-
- @test_tracker_info(uuid="66b9f9d4-1397-4da7-9e55-8b89b1732017")
- def test_supl_watching_youtube(self):
- """Verify SUPL functionality when watching video on youtube.
-
- Steps:
- 1. Kill XTRA/LTO daemon to support SUPL only case.
- 2. DUT start watching video on youtube.
- 3. SUPL TTFF Cold Start for 10 iteration at the background.
- 4. DUT stop watching video on youtube.
-
- Expected Results:
- All SUPL TTFF Cold Start results should be within supl_cs_criteria.
- """
- begin_time = get_current_epoch_time()
- kill_xtra_daemon(self.ad)
- self.start_qxdm_and_tcpdump_log()
- self.ad.droid.setMediaVolume(25)
- process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
- start_ttff_by_gtw_gpstool(
- self.ad, ttff_mode="cs", iteration=self.ttff_test_cycle)
- start_youtube_video(self.ad,
- url="https://www.youtube.com/watch?v=AbdVsi1VjQY",
- retries=3)
- ttff_data = process_ttff_by_gtw_gpstool(self.ad, begin_time,
- self.pixel_lab_location)
- result = check_ttff_data(self.ad, ttff_data, ttff_mode="Cold Start",
- criteria=self.supl_cs_criteria)
- asserts.assert_true(result, "TTFF fails to reach designated criteria")
-
- @test_tracker_info(uuid="a748af8b-e1eb-4ec6-bde3-74bcefa1c680")
- def test_supl_modem_ssr(self):
- """Verify SUPL functionality after modem silent reboot /
- GPS daemons restart.
-
- Steps:
- 1. Trigger modem crash by adb/Restart GPS daemons by killing PID.
- 2. Wait 1 minute for modem to recover.
- 3. SUPL TTFF Cold Start for 3 iteration.
- 4. Repeat Step 1. to Step 3. for 5 times.
-
- Expected Results:
- All SUPL TTFF Cold Start results should be within supl_cs_criteria.
- """
- supl_ssr_test_result_all = []
- kill_xtra_daemon(self.ad)
- self.start_qxdm_and_tcpdump_log()
- for times in range(1, 6):
- begin_time = get_current_epoch_time()
- if gutils.check_chipset_vendor_by_qualcomm(self.ad):
- test_info = "Modem SSR"
- gnss_trigger_modem_ssr_by_mds(self.ad)
- else:
- test_info = "restarting GPS daemons"
- gutils.restart_gps_daemons(self.ad)
- if not verify_internet_connection(self.ad.log, self.ad, retries=3,
- expected_state=True):
- raise signals.TestFailure("Fail to connect to LTE network.")
- process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
- start_ttff_by_gtw_gpstool(self.ad, ttff_mode="cs", iteration=3)
- ttff_data = process_ttff_by_gtw_gpstool(self.ad, begin_time,
- self.pixel_lab_location)
- supl_ssr_test_result = check_ttff_data(
- self.ad, ttff_data, ttff_mode="Cold Start",
- criteria=self.supl_cs_criteria)
- self.ad.log.info("SUPL after %s test %d times -> %s" % (
- test_info, times, supl_ssr_test_result))
- supl_ssr_test_result_all.append(supl_ssr_test_result)
- asserts.assert_true(all(supl_ssr_test_result_all),
- "TTFF fails to reach designated criteria")
-
- @test_tracker_info(uuid="01602e65-8ded-4459-8df1-7df70a1bfe8a")
def test_gnss_ttff_cs_airplane_mode_on(self):
"""Verify Standalone GNSS functionality of TTFF Cold Start while
airplane mode is on.
@@ -809,7 +500,6 @@
"""
self.standalone_ttff_airplane_mode_on("cs", self.standalone_cs_criteria)
- @test_tracker_info(uuid="30b9e7c2-0048-4ccd-b3ae-f385eb5f4e46")
def test_gnss_ttff_ws_airplane_mode_on(self):
"""Verify Standalone GNSS functionality of TTFF Warm Start while
airplane mode is on.
@@ -824,7 +514,6 @@
"""
self.standalone_ttff_airplane_mode_on("ws", self.standalone_ws_criteria)
- @test_tracker_info(uuid="8f3c323a-c625-4339-ab7a-6a41d34cba8f")
def test_gnss_ttff_hs_airplane_mode_on(self):
"""Verify Standalone GNSS functionality of TTFF Hot Start while
airplane mode is on.
@@ -839,372 +528,54 @@
"""
self.standalone_ttff_airplane_mode_on("hs", self.standalone_hs_criteria)
- @test_tracker_info(uuid="23731b0d-cb80-4c79-a877-cfe7c2faa447")
- def test_gnss_mobile_data_off(self):
- """Verify Standalone GNSS functionality while mobile radio is off.
-
- Steps:
- 1. Disable mobile data.
- 2. TTFF Cold Start for 10 iteration.
- 3. Enable mobile data.
-
- Expected Results:
- All Standalone TTFF Cold Start results should be within
- standalone_cs_criteria.
- """
- kill_xtra_daemon(self.ad)
- self.start_qxdm_and_tcpdump_log()
- set_mobile_data(self.ad, False)
- self.run_ttff_via_gtw_gpstool("cs", self.standalone_cs_criteria)
-
- @test_tracker_info(uuid="085b86a9-0212-4c0f-8ca1-2e467a0a2e6e")
- def test_supl_after_regain_gnss_signal(self):
- """Verify SUPL functionality after regain GNSS signal.
-
- Steps:
- 1. Get location fixed.
- 2 Let device do GNSS tracking for 1 minute.
- 3. Set attenuation value to block GNSS signal.
- 4. Let DUT stay in no GNSS signal for 5 minutes.
- 5. Set attenuation value to regain GNSS signal.
- 6. Try to get location reported again.
- 7. Repeat Step 1. to Step 6. for 5 times.
-
- Expected Results:
- After setting attenuation value to 10 (GPS signal regain),
- DUT could get location fixed again.
- """
- supl_no_gnss_signal_all = []
- enable_supl_mode(self.ad)
- self.start_qxdm_and_tcpdump_log()
- for times in range(1, 6):
- process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
- self.ad.log.info("Let device do GNSS tracking for 1 minute.")
- time.sleep(60)
- set_attenuator_gnss_signal(self.ad, self.attenuators,
- self.no_gnss_signal_attenuation)
- self.ad.log.info("Let device stay in no GNSS signal for 5 minutes.")
- time.sleep(300)
- set_attenuator_gnss_signal(self.ad, self.attenuators,
- self.default_gnss_signal_attenuation)
- supl_no_gnss_signal = check_location_api(self.ad, retries=3)
- start_gnss_by_gtw_gpstool(self.ad, False)
- self.ad.log.info("SUPL without GNSS signal test %d times -> %s"
- % (times, supl_no_gnss_signal))
- supl_no_gnss_signal_all.append(supl_no_gnss_signal)
- asserts.assert_true(all(supl_no_gnss_signal_all),
- "Fail to get location update")
-
- @test_tracker_info(uuid="3ff2f2fa-42d8-47fa-91de-060816cca9df")
- def test_supl_ttff_cs_weak_gnss_signal(self):
- """Verify SUPL functionality of TTFF Cold Start under weak GNSS signal.
+ def test_cs_ttff_in_weak_gnss_signal(self):
+ """Verify TTFF cold start under weak GNSS signal.
Steps:
1. Set attenuation value to weak GNSS signal.
- 2. Kill XTRA/LTO daemon to support SUPL only case.
- 3. SUPL TTFF Cold Start for 10 iteration.
-
- Expected Results:
- All SUPL TTFF Cold Start results should be less than
- weak_signal_supl_cs_criteria.
- """
- self.supl_ttff_weak_gnss_signal("cs", self.weak_signal_supl_cs_criteria)
-
- @test_tracker_info(uuid="d72364d4-dad8-4d46-8190-87183def9822")
- def test_supl_ttff_ws_weak_gnss_signal(self):
- """Verify SUPL functionality of TTFF Warm Start under weak GNSS signal.
-
- Steps:
- 1. Set attenuation value to weak GNSS signal.
- 2. Kill XTRA/LTO daemon to support SUPL only case.
- 3. SUPL TTFF Warm Start for 10 iteration.
-
- Expected Results:
- All SUPL TTFF Warm Start results should be less than
- weak_signal_supl_ws_criteria.
- """
- self.supl_ttff_weak_gnss_signal("ws", self.weak_signal_supl_ws_criteria)
-
- @test_tracker_info(uuid="aeb95733-9829-470d-bfc7-e3b059bf881f")
- def test_supl_ttff_hs_weak_gnss_signal(self):
- """Verify SUPL functionality of TTFF Hot Start under weak GNSS signal.
-
- Steps:
- 1. Set attenuation value to weak GNSS signal.
- 2. Kill XTRA/LTO daemon to support SUPL only case.
- 3. SUPL TTFF Hot Start for 10 iteration.
-
- Expected Results:
- All SUPL TTFF Hot Start results should be less than
- weak_signal_supl_hs_criteria.
- """
- self.supl_ttff_weak_gnss_signal("hs", self.weak_signal_supl_hs_criteria)
-
- @test_tracker_info(uuid="4ad4a371-949a-42e1-b1f4-628c79fa8ddc")
- def test_supl_factory_reset(self):
- """Verify SUPL functionality after factory reset.
-
- Steps:
- 1. Factory reset device.
- 2. Kill XTRA/LTO daemon to support SUPL only case.
- 3. SUPL TTFF Cold Start for 10 iteration.
- 4. Repeat Step 1. to Step 3. for 3 times.
-
- Expected Results:
- All SUPL TTFF Cold Start results should be within supl_cs_criteria.
- """
- for times in range(1, 4):
- fastboot_factory_reset(self.ad, True)
- self.ad.unlock_screen(password=None)
- _init_device(self.ad)
- begin_time = get_current_epoch_time()
- kill_xtra_daemon(self.ad)
- self.start_qxdm_and_tcpdump_log()
- process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
- start_ttff_by_gtw_gpstool(
- self.ad, ttff_mode="cs", iteration=self.ttff_test_cycle)
- ttff_data = process_ttff_by_gtw_gpstool(self.ad, begin_time,
- self.pixel_lab_location)
- if not check_ttff_data(self.ad, ttff_data, ttff_mode="Cold Start",
- criteria=self.supl_cs_criteria):
- raise signals.TestFailure("SUPL after Factory Reset test %d "
- "times -> FAIL" % times)
- self.ad.log.info("SUPL after Factory Reset test %d times -> "
- "PASS" % times)
-
- @test_tracker_info(uuid="ea3096cf-4f72-4e91-bfb3-0bcbfe865ab4")
- def test_xtra_ttff_cs_mobile_data(self):
- """Verify XTRA/LTO functionality of TTFF Cold Start with mobile data.
-
- Steps:
- 1. Disable SUPL mode.
2. TTFF Cold Start for 10 iteration.
Expected Results:
- XTRA/LTO TTFF Cold Start results should be within xtra_cs_criteria.
+ TTFF CS results should be within weak_signal_cs_criteria.
"""
- self.xtra_ttff_mobile_data("cs", self.xtra_cs_criteria)
+ gutils.set_attenuator_gnss_signal(self.ad, self.attenuators,
+ self.weak_gnss_signal_attenuation)
+ gutils.run_ttff(self.ad, mode="cs", criteria=self.weak_signal_cs_criteria,
+ test_cycle=self.ttff_test_cycle, base_lat_long=self.pixel_lab_location,
+ collect_logs=self.collect_logs)
- @test_tracker_info(uuid="c9b22894-deb3-4dc2-af14-4dcbb8ebad66")
- def test_xtra_ttff_ws_mobile_data(self):
- """Verify XTRA/LTO functionality of TTFF Warm Start with mobile data.
+ def test_ws_ttff_in_weak_gnss_signal(self):
+ """Verify TTFF warm start under weak GNSS signal.
Steps:
- 1. Disable SUPL mode.
- 2. TTFF Warm Start for 10 iteration.
-
- Expected Results:
- XTRA/LTO TTFF Warm Start results should be within xtra_ws_criteria.
- """
- self.xtra_ttff_mobile_data("ws", self.xtra_ws_criteria)
-
- @test_tracker_info(uuid="273741e2-0815-4817-96df-9c13401119dd")
- def test_xtra_ttff_hs_mobile_data(self):
- """Verify XTRA/LTO functionality of TTFF Hot Start with mobile data.
-
- Steps:
- 1. Disable SUPL mode.
- 2. TTFF Hot Start for 10 iteration.
-
- Expected Results:
- XTRA/LTO TTFF Hot Start results should be within xtra_hs_criteria.
- """
- self.xtra_ttff_mobile_data("hs", self.xtra_hs_criteria)
-
- @test_tracker_info(uuid="c91ba740-220e-41de-81e5-43af31f63907")
- def test_xtra_ttff_cs_weak_gnss_signal(self):
- """Verify XTRA/LTO functionality of TTFF Cold Start under weak GNSS
- signal.
-
- Steps:
- 1. Disable SUPL mode.
- 2. Set attenuation value to weak GNSS signal.
- 3. TTFF Cold Start for 10 iteration.
-
- Expected Results:
- XTRA/LTO TTFF Cold Start results should be within
- weak_signal_xtra_cs_criteria.
- """
- self.xtra_ttff_weak_gnss_signal("cs", self.weak_signal_xtra_cs_criteria)
-
- @test_tracker_info(uuid="2a285be7-3571-49fb-8825-01efa2e65f10")
- def test_xtra_ttff_ws_weak_gnss_signal(self):
- """Verify XTRA/LTO functionality of TTFF Warm Start under weak GNSS
- signal.
-
- Steps:
- 1. Disable SUPL mode.
2. Set attenuation value to weak GNSS signal.
3. TTFF Warm Start for 10 iteration.
Expected Results:
- XTRA/LTO TTFF Warm Start results should be within
- weak_signal_xtra_ws_criteria.
+ TTFF WS result should be within weak_signal_criteria.
"""
- self.xtra_ttff_weak_gnss_signal("ws", self.weak_signal_xtra_ws_criteria)
+ gutils.set_attenuator_gnss_signal(self.ad, self.attenuators,
+ self.weak_gnss_signal_attenuation)
+ gutils.run_ttff(self.ad, mode="ws", criteria=self.weak_signal_criteria,
+ test_cycle=self.ttff_test_cycle, base_lat_long=self.pixel_lab_location,
+ collect_logs=self.collect_logs)
- @test_tracker_info(uuid="249bf484-8b04-4cd9-a372-aa718e5f4ec6")
- def test_xtra_ttff_hs_weak_gnss_signal(self):
- """Verify XTRA/LTO functionality of TTFF Hot Start under weak GNSS
- signal.
+ def test_hs_ttff_in_weak_gnss_signal(self):
+ """Verify TTFF hot start under weak GNSS signal.
Steps:
- 1. Disable SUPL mode.
2. Set attenuation value to weak GNSS signal.
3. TTFF Hot Start for 10 iteration.
Expected Results:
- XTRA/LTO TTFF Hot Start results should be within
- weak_signal_xtra_hs_criteria.
+ TTFF HS result should be within weak_signal_criteria.
"""
- self.xtra_ttff_weak_gnss_signal("hs", self.weak_signal_xtra_hs_criteria)
+ gutils.set_attenuator_gnss_signal(self.ad, self.attenuators,
+ self.weak_gnss_signal_attenuation)
+ gutils.run_ttff(self.ad, mode="hs", criteria=self.weak_signal_criteria,
+ test_cycle=self.ttff_test_cycle, base_lat_long=self.pixel_lab_location,
+ collect_logs=self.collect_logs)
- @test_tracker_info(uuid="beeb3454-bcb2-451e-83fb-26289e89b515")
- def test_xtra_ttff_cs_wifi(self):
- """Verify XTRA/LTO functionality of TTFF Cold Start with WiFi.
-
- Steps:
- 1. Disable SUPL mode and turn airplane mode on.
- 2. Connect to WiFi.
- 3. TTFF Cold Start for 10 iteration.
-
- Expected Results:
- XTRA/LTO TTFF Cold Start results should be within
- xtra_cs_criteria.
- """
- self.xtra_ttff_wifi("cs", self.xtra_cs_criteria)
-
- @test_tracker_info(uuid="f6e79b31-99d5-49ca-974f-4543957ea449")
- def test_xtra_ttff_ws_wifi(self):
- """Verify XTRA/LTO functionality of TTFF Warm Start with WiFi.
-
- Steps:
- 1. Disable SUPL mode and turn airplane mode on.
- 2. Connect to WiFi.
- 3. TTFF Warm Start for 10 iteration.
-
- Expected Results:
- XTRA/LTO TTFF Warm Start results should be within xtra_ws_criteria.
- """
- self.xtra_ttff_wifi("ws", self.xtra_ws_criteria)
-
- @test_tracker_info(uuid="8981363c-f64f-4c37-9674-46733c40473b")
- def test_xtra_ttff_hs_wifi(self):
- """Verify XTRA/LTO functionality of TTFF Hot Start with WiFi.
-
- Steps:
- 1. Disable SUPL mode and turn airplane mode on.
- 2. Connect to WiFi.
- 3. TTFF Hot Start for 10 iteration.
-
- Expected Results:
- XTRA/LTO TTFF Hot Start results should be within xtra_hs_criteria.
- """
- self.xtra_ttff_wifi("hs", self.xtra_hs_criteria)
-
- @test_tracker_info(uuid="1745b8a4-5925-4aa0-809a-1b17e848dc9c")
- def test_xtra_modem_ssr(self):
- """Verify XTRA/LTO functionality after modem silent reboot /
- GPS daemons restart.
-
- Steps:
- 1. Trigger modem crash by adb/Restart GPS daemons by killing PID.
- 2. Wait 1 minute for modem to recover.
- 3. XTRA/LTO TTFF Cold Start for 3 iteration.
- 4. Repeat Step1. to Step 3. for 5 times.
-
- Expected Results:
- All XTRA/LTO TTFF Cold Start results should be within
- xtra_cs_criteria.
- """
- xtra_ssr_test_result_all = []
- disable_supl_mode(self.ad)
- self.start_qxdm_and_tcpdump_log()
- for times in range(1, 6):
- begin_time = get_current_epoch_time()
- if gutils.check_chipset_vendor_by_qualcomm(self.ad):
- test_info = "XTRA after Modem SSR"
- gnss_trigger_modem_ssr_by_mds(self.ad)
- else:
- test_info = "LTO after restarting GPS daemons"
- gutils.restart_gps_daemons(self.ad)
- if not verify_internet_connection(self.ad.log, self.ad, retries=3,
- expected_state=True):
- raise signals.TestFailure("Fail to connect to LTE network.")
- process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
- start_ttff_by_gtw_gpstool(self.ad, ttff_mode="cs", iteration=3)
- ttff_data = process_ttff_by_gtw_gpstool(self.ad, begin_time,
- self.pixel_lab_location)
- xtra_ssr_test_result = check_ttff_data(
- self.ad, ttff_data, ttff_mode="Cold Start",
- criteria=self.xtra_cs_criteria)
- self.ad.log.info("%s test %d times -> %s" % (
- test_info, times, xtra_ssr_test_result))
- xtra_ssr_test_result_all.append(xtra_ssr_test_result)
- asserts.assert_true(all(xtra_ssr_test_result_all),
- "TTFF fails to reach designated criteria")
-
- @test_tracker_info(uuid="4d6e81e1-3abb-4e03-b732-7b6b497a2258")
- def test_xtra_download_mobile_data(self):
- """Verify XTRA/LTO data could be downloaded via mobile data.
-
- Steps:
- 1. Delete all GNSS aiding data.
- 2. Get location fixed.
- 3. Verify whether XTRA/LTO is downloaded and injected.
- 4. Repeat Step 1. to Step 3. for 5 times.
-
- Expected Results:
- XTRA/LTO data is properly downloaded and injected via mobile data.
- """
- mobile_xtra_result_all = []
- disable_supl_mode(self.ad)
- self.start_qxdm_and_tcpdump_log()
- for i in range(1, 6):
- begin_time = get_current_epoch_time()
- process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
- time.sleep(5)
- start_gnss_by_gtw_gpstool(self.ad, False)
- mobile_xtra_result = check_xtra_download(self.ad, begin_time)
- self.ad.log.info("Iteration %d => %s" % (i, mobile_xtra_result))
- mobile_xtra_result_all.append(mobile_xtra_result)
- asserts.assert_true(all(mobile_xtra_result_all),
- "Fail to Download and Inject XTRA/LTO File.")
-
- @test_tracker_info(uuid="625ac665-1446-4406-a722-e6a19645222c")
- def test_xtra_download_wifi(self):
- """Verify XTRA/LTO data could be downloaded via WiFi.
-
- Steps:
- 1. Connect to WiFi.
- 2. Delete all GNSS aiding data.
- 3. Get location fixed.
- 4. Verify whether XTRA/LTO is downloaded and injected.
- 5. Repeat Step 2. to Step 4. for 5 times.
-
- Expected Results:
- XTRA data is properly downloaded and injected via WiFi.
- """
- wifi_xtra_result_all = []
- disable_supl_mode(self.ad)
- self.start_qxdm_and_tcpdump_log()
- self.ad.log.info("Turn airplane mode on")
- self.ad.droid.connectivityToggleAirplaneMode(True)
- wifi_toggle_state(self.ad, True)
- connect_to_wifi_network(
- self.ad, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
- for i in range(1, 6):
- begin_time = get_current_epoch_time()
- process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
- time.sleep(5)
- start_gnss_by_gtw_gpstool(self.ad, False)
- wifi_xtra_result = check_xtra_download(self.ad, begin_time)
- wifi_xtra_result_all.append(wifi_xtra_result)
- self.ad.log.info("Iteration %d => %s" % (i, wifi_xtra_result))
- asserts.assert_true(all(wifi_xtra_result_all),
- "Fail to Download and Inject XTRA/LTO File.")
-
- @test_tracker_info(uuid="2a9f2890-3c0a-48b8-821d-bf97e36355e9")
def test_quick_toggle_gnss_state(self):
"""Verify GNSS can still work properly after quick toggle GNSS off
to on.
@@ -1219,70 +590,10 @@
Expected Results:
No single Timeout is seen in 10 iterations.
"""
- enable_supl_mode(self.ad)
- self.start_qxdm_and_tcpdump_log()
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
start_toggle_gnss_by_gtw_gpstool(
self.ad, iteration=self.ttff_test_cycle)
- @test_tracker_info(uuid="9f565b32-9938-42c0-a29d-f4d28b5f4d75")
- def test_supl_system_server_restart(self):
- """Verify SUPL functionality after system server restart.
-
- Steps:
- 1. Kill XTRA/LTO daemon to support SUPL only case.
- 2. Get location fixed within supl_cs_criteria.
- 3. Restarts android runtime.
- 4. Get location fixed within supl_cs_criteria.
-
- Expected Results:
- Location fixed within supl_cs_criteria.
- """
- overall_test_result = []
- kill_xtra_daemon(self.ad)
- self.start_qxdm_and_tcpdump_log()
- for test_loop in range(1, 6):
- process_gnss_by_gtw_gpstool(self.ad, self.supl_cs_criteria)
- start_gnss_by_gtw_gpstool(self.ad, False)
- self.ad.restart_runtime()
- self.ad.unlock_screen(password=None)
- test_result = process_gnss_by_gtw_gpstool(self.ad,
- self.supl_cs_criteria)
- start_gnss_by_gtw_gpstool(self.ad, False)
- self.ad.log.info("Iteration %d => %s" % (test_loop, test_result))
- overall_test_result.append(test_result)
- asserts.assert_true(all(overall_test_result),
- "SUPL fail after system server restart.")
-
- @test_tracker_info(uuid="a9a64900-9016-46d0-ad7e-cab30e8152cd")
- def test_xtra_system_server_restart(self):
- """Verify XTRA/LTO functionality after system server restart.
-
- Steps:
- 1. Disable SUPL mode.
- 2. Get location fixed within xtra_cs_criteria.
- 3. Restarts android runtime.
- 4. Get location fixed within xtra_cs_criteria.
-
- Expected Results:
- Location fixed within xtra_cs_criteria.
- """
- overall_test_result = []
- disable_supl_mode(self.ad)
- self.start_qxdm_and_tcpdump_log()
- for test_loop in range(1, 6):
- process_gnss_by_gtw_gpstool(self.ad, self.xtra_cs_criteria)
- start_gnss_by_gtw_gpstool(self.ad, False)
- self.ad.restart_runtime()
- self.ad.unlock_screen(password=None)
- test_result = process_gnss_by_gtw_gpstool(self.ad,
- self.xtra_cs_criteria)
- start_gnss_by_gtw_gpstool(self.ad, False)
- self.ad.log.info("Iteration %d => %s" % (test_loop, test_result))
- overall_test_result.append(test_result)
- asserts.assert_true(all(overall_test_result),
- "XTRA/LTO fail after system server restart.")
-
- @test_tracker_info(uuid="ab5ef9f7-0b28-48ed-a693-7f1d902ca3e1")
def test_gnss_init_after_reboot(self):
"""Verify SUPL and XTRA/LTO functionality after reboot.
@@ -1296,12 +607,14 @@
Location fixed within supl_hs_criteria.
"""
overall_test_result = []
- enable_supl_mode(self.ad)
+ # As b/252971345 requests, we need the log before reboot for debugging.
+ gutils.start_pixel_logger(self.ad)
process_gnss_by_gtw_gpstool(self.ad, self.supl_cs_criteria)
start_gnss_by_gtw_gpstool(self.ad, False)
+ gutils.stop_pixel_logger(self.ad)
for test_loop in range(1, 11):
reboot(self.ad)
- self.start_qxdm_and_tcpdump_log()
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
if is_device_wearable(self.ad):
test_result = process_gnss_by_gtw_gpstool(
self.ad, self.wearable_reboot_hs_criteria, clear_data=False)
@@ -1318,63 +631,184 @@
asserts.assert_true(all(overall_test_result),
"GNSS init fail after reboot.")
- @test_tracker_info(uuid="2c62183a-4354-4efc-92f2-84580cbd3398")
- def test_lto_download_after_reboot(self):
- """Verify LTO data could be downloaded and injected after device reboot.
+ def test_host_gnssstatus_validation(self):
+ """Verify GnssStatus integrity during host tracking for 1 minute.
Steps:
- 1. Reboot device.
- 2. Verify whether LTO is auto downloaded and injected without trigger GPS.
- 3. Repeat Step 1 to Step 2 for 5 times.
+ 1. Launch GTW_GPSTool.
+ 2. GNSS tracking for 1 minute with 1 second frequency.
+ 3. Validate all the GnssStatus raw data.(SV, SVID, Elev, Azim)
Expected Results:
- LTO data is properly downloaded and injected at the first time tether to phone.
+ GnssStatus obj should return no failures
"""
- reboot_lto_test_results_all = []
- disable_supl_mode(self.ad)
- for times in range(1, 6):
- delete_lto_file(self.ad)
- reboot(self.ad)
- self.start_qxdm_and_tcpdump_log()
- # Wait 20 seconds for boot busy and lto auto-download time
- time.sleep(20)
- begin_time = get_current_epoch_time()
- reboot_lto_test_result = gutils.check_xtra_download(self.ad, begin_time)
- self.ad.log.info("Iteration %d => %s" % (times, reboot_lto_test_result))
- reboot_lto_test_results_all.append(reboot_lto_test_result)
- gutils.stop_pixel_logger(self.ad)
- tutils.stop_adb_tcpdump(self.ad)
- asserts.assert_true(all(reboot_lto_test_results_all),
- "Fail to Download and Inject LTO File.")
+ gnss_tracking_via_gtw_gpstool(self.ad, self.standalone_cs_criteria,
+ api_type="gnss", testtime=1)
+ parse_gtw_gpstool_log(self.ad, self.pixel_lab_location, api_type="gnss",
+ validate_gnssstatus=True)
- @test_tracker_info(uuid="a7048a4f-8a40-40a4-bb6c-7fc90e8227bd")
- def test_ws_with_assist(self):
- """Verify Warm Start functionality with existed LTO data.
+ def test_onchip_gnssstatus_validation(self):
+ """Verify GnssStatus integrity during onchip tracking for 1 minute.
Steps:
- 1. Disable SUPL mode.
- 2. Make LTO is downloaded.
- 3. Turn on AirPlane mode to make sure there's no network connection.
- 4. TTFF Warm Start with Assist for 10 iteration.
+ 1. Launch GTW_GPSTool.
+ 2. GNSS tracking for 1 minute with 6 second frequency.
+ 3. Validate all the GnssStatus raw data.(SV, SVID, Elev, Azim)
Expected Results:
- All TTFF Warm Start with Assist results should be within
- xtra_ws_criteria.
+ GnssStatus obj should return no failures
"""
- self.ttff_with_assist("ws", self.xtra_ws_criteria)
+ if gutils.check_chipset_vendor_by_qualcomm(self.ad):
+ raise signals.TestSkip("Not BRCM chipset. Skip the test.")
+ gnss_tracking_via_gtw_gpstool(self.ad, self.standalone_cs_criteria,
+ api_type="gnss", testtime=1, freq=self.onchip_interval)
+ parse_gtw_gpstool_log(self.ad, self.pixel_lab_location, api_type="gnss",
+ validate_gnssstatus=True)
- @test_tracker_info(uuid="c5fb9519-63b0-42bd-bd79-fce7593604ea")
- def test_cs_with_assist(self):
- """Verify Cold Start functionality with existed LTO data.
-
- Steps:
- 1. Disable SUPL mode.
- 2. Make sure LTO is downloaded.
- 3. Turn on AirPlane mode to make sure there's no network connection.
- 4. TTFF Cold Start with Assist for 10 iteration.
-
- Expected Results:
- All TTFF Cold Start with Assist results should be within
- standalone_cs_criteria.
+ def test_location_update_after_resuming_from_deep_suspend(self):
+ """Verify the GPS location reported after resume from suspend mode
+ 1. Enable GPS location report for 1 min to make sure the GPS is working
+ 2. Force DUT into deep suspend mode for a while(3 times with 15s interval)
+ 3. Enable GPS location report for 5 mins
+ 4. Check the report frequency
+ 5. Check the location fix rate
"""
- self.ttff_with_assist("csa", self.standalone_cs_criteria)
+
+ gps_enable_minutes = 1
+ gnss_tracking_via_gtw_gpstool(self.ad, criteria=self.supl_cs_criteria, api_type="gnss",
+ testtime=gps_enable_minutes)
+ result = parse_gtw_gpstool_log(self.ad, self.pixel_lab_location, api_type="gnss")
+ self.ad.log.debug("Location report details before suspend")
+ self.ad.log.debug(result)
+ gutils.validate_location_fix_rate(self.ad, result, run_time=gps_enable_minutes,
+ fix_rate_criteria=0.95)
+
+ gutils.deep_suspend_device(self.ad)
+
+ gps_enable_minutes = 5
+ gnss_tracking_via_gtw_gpstool(self.ad, criteria=self.supl_cs_criteria, api_type="gnss",
+ testtime=gps_enable_minutes)
+ result = parse_gtw_gpstool_log(self.ad, self.pixel_lab_location, api_type="gnss")
+ self.ad.log.debug("Location report details after suspend")
+ self.ad.log.debug(result)
+
+ location_report_time = list(result.keys())
+ gutils.check_location_report_interval(self.ad, location_report_time,
+ gps_enable_minutes * 60, tolerance=0.01)
+ gutils.validate_location_fix_rate(self.ad, result, run_time=gps_enable_minutes,
+ fix_rate_criteria=0.99)
+
+ def test_location_mode_in_battery_saver_with_screen_off(self):
+ """Ensure location request with foreground permission can work
+ in battery saver mode (screen off)
+
+ 1. unplug power
+ 2. enter battery saver mode
+ 3. start tracking for 2 mins with screen off
+ 4. repest step 3 for 3 times
+ """
+ try:
+ gutils.set_battery_saver_mode(self.ad, state=True)
+ test_time = 2
+ for i in range(1, 4):
+ self.ad.log.info("Tracking attempt %s" % str(i))
+ gnss_tracking_via_gtw_gpstool(
+ self.ad, criteria=self.supl_cs_criteria, api_type="gnss", testtime=test_time,
+ is_screen_off=True)
+ result = parse_gtw_gpstool_log(self.ad, self.pixel_lab_location, api_type="gnss")
+ gutils.validate_location_fix_rate(self.ad, result, run_time=test_time,
+ fix_rate_criteria=0.99)
+ finally:
+ gutils.set_battery_saver_mode(self.ad, state=False)
+
+ def test_measure_adr_rate_after_10_mins_tracking(self):
+ """Verify ADR rate
+
+ 1. Enable "Force full gnss measurement"
+ 2. Start tracking with GnssMeasurement enabled for 10 mins
+ 3. Check ADR usable rate / valid rate
+ 4. Disable "Force full gnss measurement"
+ """
+ adr_threshold = self.adr_ratio_threshold.get(self.ad.model)
+ if not adr_threshold:
+ self.ad.log.warn((f"Can't get '{self.ad.model}' threshold from config "
+ f"{self.adr_ratio_threshold}, use default threshold 0.5"))
+ adr_threshold = 0.5
+ with gutils.full_gnss_measurement(self.ad):
+ gnss_tracking_via_gtw_gpstool(self.ad, criteria=self.supl_cs_criteria, api_type="gnss",
+ testtime=10, meas_flag=True)
+ gutils.validate_adr_rate(self.ad, pass_criteria=float(adr_threshold))
+
+
+ def test_hal_crashing_should_resume_tracking(self):
+ """Make sure location request can be resumed after HAL restart.
+
+ 1. Start GPS tool and get First Fixed
+ 2. Wait for 1 min for tracking
+ 3. Restart HAL service
+ 4. Wait for 1 min for tracking
+ 5. Check fix rate
+ """
+
+ first_fixed_time = process_gnss_by_gtw_gpstool(self.ad, criteria=self.supl_cs_criteria)
+ begin_time = int(first_fixed_time.timestamp() * 1000)
+
+ self.ad.log.info("Start 2 mins tracking")
+
+ gutils.wait_n_mins_for_gnss_tracking(self.ad, begin_time, testtime=1,
+ ignore_hal_crash=False)
+ gutils.restart_hal_service(self.ad)
+ # The test case is designed to run the tracking for 2 mins, so we assign testime to 2 to
+ # indicate the total run time is 2 mins (starting from begin_time).
+ gutils.wait_n_mins_for_gnss_tracking(self.ad, begin_time, testtime=2, ignore_hal_crash=True)
+
+ start_gnss_by_gtw_gpstool(self.ad, state=False)
+
+ result = parse_gtw_gpstool_log(self.ad, self.pixel_lab_location)
+ gutils.validate_location_fix_rate(self.ad, result, run_time=2,
+ fix_rate_criteria=0.95)
+
+
+ def test_power_save_mode_should_apply_latest_measurement_setting(self):
+ """Ensure power save mode will apply the GNSS measurement setting.
+
+ 1. Turn off full GNSS measurement.
+ 2. Run tracking for 2 mins
+ 3. Check the power save mode status
+ 4. Turn on full GNSS measurement and re-register measurement callback
+ 6. Run tracking for 30s
+ 7. Check the power save mode status
+ 8. Turn off full GNSS measurement and re-register measurement callback
+ 9. Run tracking for 2 mins
+ 10. Check the power save mode status
+ """
+ def wait_for_power_state_changes(wait_time):
+ gutils.re_register_measurement_callback(self.ad)
+ tracking_begin_time = get_current_epoch_time()
+ gutils.wait_n_mins_for_gnss_tracking(self.ad, tracking_begin_time, testtime=wait_time)
+ return tracking_begin_time
+
+ if self.ad.model.lower() == "sunfish":
+ raise signals.TestSkip(
+ "According to b/241049795, it's HW issue and won't be fixed.")
+
+ gutils.start_pixel_logger(self.ad)
+ with gutils.run_gnss_tracking(self.ad, criteria=self.supl_cs_criteria, meas_flag=True):
+ start_time = wait_for_power_state_changes(wait_time=2)
+ gutils.check_power_save_mode_status(
+ self.ad, full_power=False, begin_time=start_time,
+ brcm_error_allowlist=self.brcm_error_log_allowlist)
+
+ with gutils.full_gnss_measurement(self.ad):
+ start_time = wait_for_power_state_changes(wait_time=0.5)
+ gutils.check_power_save_mode_status(
+ self.ad, full_power=True, begin_time=start_time,
+ brcm_error_allowlist=self.brcm_error_log_allowlist)
+
+ start_time = wait_for_power_state_changes(wait_time=2)
+ gutils.check_power_save_mode_status(
+ self.ad, full_power=False, begin_time=start_time,
+ brcm_error_allowlist=self.brcm_error_log_allowlist)
+
+ gutils.stop_pixel_logger(self.ad)
+
diff --git a/acts_tests/tests/google/gnss/GnssHsSenTest.py b/acts_tests/tests/google/gnss/GnssHsSenTest.py
index 5269ae0..bd4e398 100644
--- a/acts_tests/tests/google/gnss/GnssHsSenTest.py
+++ b/acts_tests/tests/google/gnss/GnssHsSenTest.py
@@ -13,11 +13,12 @@
# 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.
+"""Lab GNSS Hot Start Sensitivity Test"""
import os
from acts_contrib.test_utils.gnss.GnssBlankingBase import GnssBlankingBase
from acts_contrib.test_utils.gnss.dut_log_test_utils import get_gpstool_logs
-from acts_contrib.test_utils.gnss.gnss_test_utils import excute_eecoexer_function
+from acts_contrib.test_utils.gnss.gnss_test_utils import execute_eecoexer_function
class GnssHsSenTest(GnssBlankingBase):
@@ -25,11 +26,19 @@
def __init__(self, controllers):
super().__init__(controllers)
- self.gnss_simulator_power_level = -130
- self.sa_sensitivity = -150
- self.gnss_pwr_lvl_offset = 5
+ self.cell_tx_ant = None
+ self.cell_pwr = None
+ self.eecoex_func = ''
+ self.coex_stop_cmd = ''
+ self.coex_params = {}
- def gnss_hot_start_sensitivity_search_base(self, cellular_enable=False):
+ def setup_class(self):
+ super().setup_class()
+ self.coex_params = self.user_params.get('coex_params', {})
+ self.cell_tx_ant = self.coex_params.get('cell_tx_ant', 'PRIMARY')
+ self.cell_pwr = self.coex_params.get('cell_pwr', 'Infinity')
+
+ def gnss_hot_start_sensitivity_search_base(self, coex_enable=False):
"""
Perform GNSS hot start sensitivity search.
@@ -41,49 +50,32 @@
# Get parameters from user_params.
first_wait = self.user_params.get('first_wait', 300)
wait_between_pwr = self.user_params.get('wait_between_pwr', 60)
- gnss_pwr_sweep = self.user_params.get('gnss_pwr_sweep')
- gnss_init_pwr = gnss_pwr_sweep.get('init')
- self.gnss_simulator_power_level = gnss_init_pwr[0]
- self.sa_sensitivity = gnss_init_pwr[1]
- self.gnss_pwr_lvl_offset = gnss_init_pwr[2]
- gnss_pwr_fine_sweep = gnss_pwr_sweep.get('fine_sweep')
ttft_iteration = self.user_params.get('ttff_iteration', 25)
# Start the test item with gnss_init_power_setting.
- if self.gnss_init_power_setting(first_wait):
- self.log.info('Successfully set the GNSS power level to %d' %
- self.sa_sensitivity)
+ ret, pwr_lvl = self.gnss_init_power_setting(first_wait)
+ if ret:
+ self.log.info(f'Successfully set the GNSS power level to {pwr_lvl}')
# Create gnss log folders for init and cellular sweep
gnss_init_log_dir = os.path.join(self.gnss_log_path, 'GNSS_init')
# Pull all exist GPStool logs into GNSS_init folder
get_gpstool_logs(self.dut, gnss_init_log_dir, False)
-
- if cellular_enable:
- self.log.info('Start cellular coexistence test.')
- # Set cellular Tx power level.
- eecoex_cmd = self.eecoex_func.format('Infinity')
- eecoex_cmd_file_str = eecoex_cmd.replace(',', '_')
- excute_eecoexer_function(self.dut, eecoex_cmd)
- else:
- self.log.info('Start stand alone test.')
- eecoex_cmd_file_str = 'Stand_alone'
-
- for i, gnss_pwr in enumerate(gnss_pwr_fine_sweep):
- self.log.info('Start fine GNSS power level sweep part %d' %
- (i + 1))
- sweep_start = gnss_pwr[0]
- sweep_stop = gnss_pwr[1]
- sweep_offset = gnss_pwr[2]
- self.log.info(
- 'The GNSS simulator (start, stop, offset): (%.1f, %.1f, %.1f)'
- % (sweep_start, sweep_stop, sweep_offset))
- result, sensitivity = self.hot_start_gnss_power_sweep(
- sweep_start, sweep_stop, sweep_offset, wait_between_pwr,
- ttft_iteration, True, eecoex_cmd_file_str)
- if not result:
- break
- self.log.info('The sensitivity level is: %.1f' % sensitivity)
+ if coex_enable:
+ self.log.info('Start coexistence test.')
+ eecoex_cmd_file_str = self.eecoex_func.replace(',', '_')
+ execute_eecoexer_function(self.dut, self.eecoex_func)
+ else:
+ self.log.info('Start stand alone test.')
+ eecoex_cmd_file_str = 'Stand_alone'
+ for i, gnss_pwr_swp in enumerate(self.gnss_pwr_sweep_fine_sweep_ls):
+ self.log.info(f'Start fine GNSS power level sweep part {i + 1}')
+ result, sensitivity = self.hot_start_gnss_power_sweep(
+ gnss_pwr_swp, wait_between_pwr, ttft_iteration, True,
+ eecoex_cmd_file_str)
+ if not result:
+ break
+ self.log.info(f'The sensitivity level is: {sensitivity}')
def test_hot_start_sensitivity_search(self):
"""
@@ -95,86 +87,127 @@
"""
GNSS hot start GSM850 Ch190 coexistence sensitivity search.
"""
- self.eecoex_func = 'CELLR,2,850,190,1,1,{}'
- self.log.info('Running GSM850 and GNSS coexistence sensitivity search.')
+ self.eecoex_func = f'CELLR,2,850,190,1,{self.cell_tx_ant},{self.cell_pwr}'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = f'Running GSM850 with {self.cell_tx_ant} antenna \
+ and GNSS coexistence sensitivity search.'
+
+ self.log.info(msg)
self.gnss_hot_start_sensitivity_search_base(True)
def test_hot_start_sensitivity_search_gsm900(self):
"""
GNSS hot start GSM900 Ch20 coexistence sensitivity search.
"""
- self.eecoex_func = 'CELLR,2,900,20,1,1,{}'
- self.log.info('Running GSM900 and GNSS coexistence sensitivity search.')
+ self.eecoex_func = f'CELLR,2,900,20,1,{self.cell_tx_ant},{self.cell_pwr}'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = f'Running GSM900 with {self.cell_tx_ant} \
+ antenna and GNSS coexistence sensitivity search.'
+
+ self.log.info(msg)
self.gnss_hot_start_sensitivity_search_base(True)
def test_hot_start_sensitivity_search_gsm1800(self):
"""
GNSS hot start GSM1800 Ch699 coexistence sensitivity search.
"""
- self.eecoex_func = 'CELLR,2,1800,699,1,1,{}'
- self.log.info(
- 'Running GSM1800 and GNSS coexistence sensitivity search.')
+ self.eecoex_func = f'CELLR,2,1800,699,1,{self.cell_tx_ant},{self.cell_pwr}'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = f'Running GSM1800 {self.cell_tx_ant} antenna and GNSS coexistence sensitivity search.'
+ self.log.info(msg)
self.gnss_hot_start_sensitivity_search_base(True)
def test_hot_start_sensitivity_search_gsm1900(self):
"""
GNSS hot start GSM1900 Ch661 coexistence sensitivity search.
"""
- self.eecoex_func = 'CELLR,2,1900,661,1,1,{}'
- self.log.info(
- 'Running GSM1900 and GNSS coexistence sensitivity search.')
+ self.eecoex_func = f'CELLR,2,1900,661,1,{self.cell_tx_ant},{self.cell_pwr}'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = f'Running GSM1900 {self.cell_tx_ant} antenna and GNSS coexistence sensitivity search.'
+ self.log.info(msg)
self.gnss_hot_start_sensitivity_search_base(True)
def test_hot_start_sensitivity_search_lte_b38(self):
"""
GNSS hot start LTE B38 Ch38000 coexistence sensitivity search.
"""
- self.eecoex_func = 'CELLR,5,38,38000,true,PRIMARY,{},10MHz,0,12'
- self.log.info(
- 'Running LTE B38 and GNSS coexistence sensitivity search.')
+ self.eecoex_func = f'CELLR,5,38,38000,true,{self.cell_tx_ant},{self.cell_pwr},10MHz,0,12'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = f'Running LTE B38 {self.cell_tx_ant} antenna and GNSS coexistence sensitivity search.'
+ self.log.info(msg)
self.gnss_hot_start_sensitivity_search_base(True)
def test_hot_start_sensitivity_search_lte_b39(self):
"""
GNSS hot start LTE B39 Ch38450 coexistence sensitivity search.
"""
- self.eecoex_func = 'CELLR,5,39,38450,true,PRIMARY,{},10MHz,0,12'
- self.log.info(
- 'Running LTE B38 and GNSS coexistence sensitivity search.')
+ self.eecoex_func = f'CELLR,5,39,38450,true,{self.cell_tx_ant},{self.cell_pwr},10MHz,0,12'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = f'Running LTE B38 {self.cell_tx_ant} antenna and GNSS coexistence sensitivity search.'
+ self.log.info(msg)
self.gnss_hot_start_sensitivity_search_base(True)
def test_hot_start_sensitivity_search_lte_b40(self):
"""
GNSS hot start LTE B40 Ch39150 coexistence sensitivity search.
"""
- self.eecoex_func = 'CELLR,5,40,39150,true,PRIMARY,{},10MHz,0,12'
- self.log.info(
- 'Running LTE B38 and GNSS coexistence sensitivity search.')
+ self.eecoex_func = f'CELLR,5,40,39150,true,{self.cell_tx_ant},{self.cell_pwr},10MHz,0,12'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = f'Running LTE B38 {self.cell_tx_ant} antenna and GNSS coexistence sensitivity search.'
+ self.log.info(msg)
self.gnss_hot_start_sensitivity_search_base(True)
def test_hot_start_sensitivity_search_lte_b41(self):
"""
GNSS hot start LTE B41 Ch40620 coexistence sensitivity search.
"""
- self.eecoex_func = 'CELLR,5,41,40620,true,PRIMARY,{},10MHz,0,12'
- self.log.info(
- 'Running LTE B41 and GNSS coexistence sensitivity search.')
+ self.eecoex_func = f'CELLR,5,41,40620,true,{self.cell_tx_ant},{self.cell_pwr},10MHz,0,12'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = f'Running LTE B41 {self.cell_tx_ant} antenna and GNSS coexistence sensitivity search.'
+ self.log.info(msg)
self.gnss_hot_start_sensitivity_search_base(True)
def test_hot_start_sensitivity_search_lte_b42(self):
"""
GNSS hot start LTE B42 Ch42590 coexistence sensitivity search.
"""
- self.eecoex_func = 'CELLR,5,42,42590,true,PRIMARY,{},10MHz,0,12'
- self.log.info(
- 'Running LTE B42 and GNSS coexistence sensitivity search.')
+ self.eecoex_func = f'CELLR,5,42,42590,true,{self.cell_tx_ant},{self.cell_pwr},10MHz,0,12'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = f'Running LTE B42 {self.cell_tx_ant} antenna and GNSS coexistence sensitivity search.'
+ self.log.info(msg)
self.gnss_hot_start_sensitivity_search_base(True)
def test_hot_start_sensitivity_search_lte_b48(self):
"""
GNSS hot start LTE B48 Ch55990 coexistence sensitivity search.
"""
- self.eecoex_func = 'CELLR,5,48,55990,true,PRIMARY,{},10MHz,0,12'
- self.log.info(
- 'Running LTE B48 and GNSS coexistence sensitivity search.')
+ self.eecoex_func = f'CELLR,5,48,55990,true,{self.cell_tx_ant},{self.cell_pwr},10MHz,0,12'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = f'Running LTE B48 {self.cell_tx_ant} antenna and GNSS coexistence sensitivity search.'
+ self.log.info(msg)
self.gnss_hot_start_sensitivity_search_base(True)
+
+ def test_hot_start_sensitivity_search_fr2_n2605(self):
+ """
+ GNSS hot start 5G NR B260 CH2234165 coexistence sensitivity search.
+ """
+ self.eecoex_func = f'CELLR,30,260,2234165,183,{self.cell_pwr}'
+ self.coex_stop_cmd = 'CELLR,19'
+ msg = 'Running 5G NR B260 CH2234165 and GNSS coexistence sensitivity search.'
+ self.log.info(msg)
+ self.gnss_hot_start_sensitivity_search_base(True)
+
+ def test_hot_start_sensitivity_custom_case(self):
+ """
+ GNSS hot start custom case coexistence sensitivity search.
+ """
+ cust_cmd = self.coex_params.get('custom_cmd', '')
+ cust_stop_cmd = self.coex_params.get('custom_stop_cmd', '')
+ if cust_cmd and cust_stop_cmd:
+ self.eecoex_func = cust_cmd
+ self.coex_stop_cmd = cust_stop_cmd
+ msg = f'Running custom {self.eecoex_func} and GNSS coexistence sensitivity search.'
+ self.log.info(msg)
+ self.gnss_hot_start_sensitivity_search_base(True)
+ else:
+ self.log.warning('No custom coex command is provided')
diff --git a/acts_tests/tests/google/gnss/GnssSuplTest.py b/acts_tests/tests/google/gnss/GnssSuplTest.py
new file mode 100644
index 0000000..853a228
--- /dev/null
+++ b/acts_tests/tests/google/gnss/GnssSuplTest.py
@@ -0,0 +1,294 @@
+from multiprocessing import Process
+import time
+
+from acts import asserts
+from acts import signals
+from acts.base_test import BaseTestClass
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.gnss import supl
+from acts_contrib.test_utils.gnss import gnss_defines
+from acts_contrib.test_utils.gnss.testtracker_util import log_testtracker_uuid
+from acts_contrib.test_utils.tel.tel_data_utils import http_file_download_by_sl4a
+from acts_contrib.test_utils.tel.tel_logging_utils import get_tcpdump_log
+from acts_contrib.test_utils.tel.tel_logging_utils import stop_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_logging_utils import get_tcpdump_log
+from acts_contrib.test_utils.tel.tel_test_utils import check_call_state_connected_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.tel_voice_utils import initiate_call
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts.utils import get_current_epoch_time
+
+
+class GnssSuplTest(BaseTestClass):
+ def setup_class(self):
+ super().setup_class()
+ self.ad = self.android_devices[0]
+ req_params = [
+ "pixel_lab_network", "standalone_cs_criteria", "supl_cs_criteria", "supl_ws_criteria",
+ "supl_hs_criteria", "default_gnss_signal_attenuation", "pixel_lab_location",
+ "qdsp6m_path", "collect_logs", "ttff_test_cycle",
+ "supl_capabilities", "no_gnss_signal_attenuation", "set_attenuator"
+ ]
+ self.unpack_userparams(req_param_names=req_params)
+ # create hashmap for SSID
+ self.ssid_map = {}
+ for network in self.pixel_lab_network:
+ SSID = network["SSID"]
+ self.ssid_map[SSID] = network
+ self.init_device()
+
+ def only_brcm_device_runs_wifi_case(self):
+ """SUPL over wifi is only supported by BRCM devices, for QUAL device, skip the test.
+ """
+ if gutils.check_chipset_vendor_by_qualcomm(self.ad):
+ raise signals.TestSkip("Qualcomm device doesn't support SUPL over wifi")
+
+ def wearable_btwifi_should_skip_mobile_data_case(self):
+ if gutils.is_wearable_btwifi(self.ad):
+ raise signals.TestSkip("Skip mobile data case for BtWiFi sku")
+
+ def init_device(self):
+ """Init GNSS test devices for SUPL suite."""
+ gutils._init_device(self.ad)
+ gutils.disable_vendor_orbit_assistance_data(self.ad)
+ gutils.enable_supl_mode(self.ad)
+ self.enable_supl_over_wifi()
+ gutils.reboot(self.ad)
+
+ def enable_supl_over_wifi(self):
+ if not gutils.check_chipset_vendor_by_qualcomm(self.ad):
+ supl.set_supl_over_wifi_state(self.ad, turn_on=True)
+
+ def setup_test(self):
+ gutils.log_current_epoch_time(self.ad, "test_start_time")
+ log_testtracker_uuid(self.ad, self.current_test_name)
+ gutils.clear_logd_gnss_qxdm_log(self.ad)
+ gutils.get_baseband_and_gms_version(self.ad)
+ toggle_airplane_mode(self.ad.log, self.ad, new_state=False)
+ if gutils.is_wearable_btwifi(self.ad):
+ wutils.wifi_toggle_state(self.ad, True)
+ gutils.connect_to_wifi_network(self.ad,
+ self.ssid_map[self.pixel_lab_network[0]["SSID"]])
+ else:
+ wutils.wifi_toggle_state(self.ad, False)
+ gutils.set_mobile_data(self.ad, state=True)
+ if not verify_internet_connection(self.ad.log, self.ad, retries=3,
+ expected_state=True):
+ raise signals.TestFailure("Fail to connect to LTE network.")
+ # Once the device is rebooted, the xtra service will be alive again
+ # In order not to affect the supl case, disable it in setup_test.
+ if gutils.check_chipset_vendor_by_qualcomm(self.ad):
+ gutils.disable_qualcomm_orbit_assistance_data(self.ad)
+
+ def teardown_test(self):
+ if self.collect_logs:
+ gutils.stop_pixel_logger(self.ad)
+ stop_adb_tcpdump(self.ad)
+ if self.set_attenuator:
+ gutils.set_attenuator_gnss_signal(self.ad, self.attenuators,
+ self.default_gnss_signal_attenuation)
+ gutils.log_current_epoch_time(self.ad, "test_end_time")
+
+ def on_fail(self, test_name, begin_time):
+ if self.collect_logs:
+ self.ad.take_bug_report(test_name, begin_time)
+ gutils.get_gnss_qxdm_log(self.ad, self.qdsp6m_path)
+ self.get_brcm_gps_xml_to_sponge()
+ get_tcpdump_log(self.ad, test_name, begin_time)
+
+ def get_brcm_gps_xml_to_sponge(self):
+ # request from b/250506003 - to check the SUPL setting
+ if not gutils.check_chipset_vendor_by_qualcomm(self.ad):
+ self.ad.pull_files(gnss_defines.BCM_GPS_XML_PATH, self.ad.device_log_path)
+
+ def run_ttff(self, mode, criteria):
+ """Triggers TTFF.
+
+ Args:
+ mode: "cs", "ws" or "hs"
+ criteria: Criteria for the test.
+ """
+ return gutils.run_ttff(self.ad, mode, criteria, self.ttff_test_cycle,
+ self.pixel_lab_location, self.collect_logs)
+
+ def supl_ttff_weak_gnss_signal(self, mode, criteria):
+ """Verify SUPL TTFF functionality under weak GNSS signal.
+
+ Args:
+ mode: "cs", "ws" or "hs"
+ criteria: Criteria for the test.
+ """
+ gutils.set_attenuator_gnss_signal(self.ad, self.attenuators,
+ self.weak_gnss_signal_attenuation)
+ self.run_ttff(mode, criteria)
+
+ def connect_to_wifi_with_mobile_data_off(self):
+ gutils.set_mobile_data(self.ad, False)
+ wutils.wifi_toggle_state(self.ad, True)
+ gutils.connect_to_wifi_network(self.ad, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
+
+ def connect_to_wifi_with_airplane_mode_on(self):
+ toggle_airplane_mode(self.ad.log, self.ad, new_state=True)
+ wutils.wifi_toggle_state(self.ad, True)
+ gutils.connect_to_wifi_network(self.ad, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
+
+ def check_position_mode(self, begin_time: int, mode: str):
+ logcat_results = self.ad.search_logcat(
+ matching_string="setting position_mode to", begin_time=begin_time)
+ return all([result["log_message"].split(" ")[-1] == mode for result in logcat_results])
+
+ def test_supl_capabilities(self):
+ """Verify SUPL capabilities.
+
+ Steps:
+ 1. Root DUT.
+ 2. Check SUPL capabilities.
+
+ Expected Results:
+ CAPABILITIES=0x37 which supports MSA + MSB.
+ CAPABILITIES=0x17 = ON_DEMAND_TIME | MSA | MSB | SCHEDULING
+ """
+ if not gutils.check_chipset_vendor_by_qualcomm(self.ad):
+ raise signals.TestSkip("Not Qualcomm chipset. Skip the test.")
+ capabilities_state = str(
+ self.ad.adb.shell(
+ "cat vendor/etc/gps.conf | grep CAPABILITIES")).split("=")[-1]
+ self.ad.log.info("SUPL capabilities - %s" % capabilities_state)
+
+ asserts.assert_true(capabilities_state in self.supl_capabilities,
+ "Wrong default SUPL capabilities is set. Found %s, "
+ "expected any of %r" % (capabilities_state,
+ self.supl_capabilities))
+
+
+ def test_supl_ttff_cs(self):
+ """Verify SUPL functionality of TTFF Cold Start.
+
+ Steps:
+ 1. Kill XTRA/LTO daemon to support SUPL only case.
+ 2. SUPL TTFF Cold Start for 10 iteration.
+
+ Expected Results:
+ All SUPL TTFF Cold Start results should be less than
+ supl_cs_criteria.
+ """
+ self.run_ttff("cs", self.supl_cs_criteria)
+
+ def test_supl_ttff_ws(self):
+ """Verify SUPL functionality of TTFF Warm Start.
+
+ Steps:
+ 1. Kill XTRA/LTO daemon to support SUPL only case.
+ 2. SUPL TTFF Warm Start for 10 iteration.
+
+ Expected Results:
+ All SUPL TTFF Warm Start results should be less than
+ supl_ws_criteria.
+ """
+ self.run_ttff("ws", self.supl_ws_criteria)
+
+ def test_supl_ttff_hs(self):
+ """Verify SUPL functionality of TTFF Hot Start.
+
+ Steps:
+ 1. Kill XTRA/LTO daemon to support SUPL only case.
+ 2. SUPL TTFF Hot Start for 10 iteration.
+
+ Expected Results:
+ All SUPL TTFF Hot Start results should be less than
+ supl_hs_criteria.
+ """
+ self.run_ttff("hs", self.supl_hs_criteria)
+
+ def test_cs_ttff_supl_over_wifi_with_airplane_mode_on(self):
+ """ Test supl can works through wifi with airplane mode on
+
+ Test steps are executed in the following sequence.
+ - Turn on airplane mode
+ - Connect to wifi
+ - Run SUPL CS TTFF
+ """
+ self.only_brcm_device_runs_wifi_case()
+
+ self.connect_to_wifi_with_airplane_mode_on()
+
+ self.run_ttff(mode="cs", criteria=self.supl_cs_criteria)
+
+ def test_ws_ttff_supl_over_wifi_with_airplane_mode_on(self):
+ """ Test supl can works through wifi with airplane mode on
+
+ Test steps are executed in the following sequence.
+ - Turn on airplane mode
+ - Connect to wifi
+ - Run SUPL WS TTFF
+ """
+ self.only_brcm_device_runs_wifi_case()
+
+ self.connect_to_wifi_with_airplane_mode_on()
+
+ self.run_ttff("ws", self.supl_ws_criteria)
+
+ def test_hs_ttff_supl_over_wifi_with_airplane_mode_on(self):
+ """ Test supl can works through wifi with airplane mode on
+
+ Test steps are executed in the following sequence.
+ - Turn on airplane mode
+ - Connect to wifi
+ - Run SUPL WS TTFF
+ """
+ self.only_brcm_device_runs_wifi_case()
+
+ self.connect_to_wifi_with_airplane_mode_on()
+
+ self.run_ttff("hs", self.supl_ws_criteria)
+
+ def test_ttff_gla_on(self):
+ """ Test the turn on "Google Location Accuracy" in settings work or not.
+
+ Test steps are executed in the following sequence.
+ - Turn off airplane mode
+ - Connect to Cellular
+ - Turn off LTO/RTO
+ - Turn on SUPL
+ - Turn on GLA
+ - Run CS TTFF
+
+ Expected Results:
+ - The position mode must be "MS_BASED"
+ - The TTFF time should be less than 10 seconds
+ """
+ begin_time = get_current_epoch_time()
+ gutils.gla_mode(self.ad, True)
+
+ self.run_ttff("cs", self.supl_cs_criteria)
+ asserts.assert_true(self.check_position_mode(begin_time, "MS_BASED"),
+ msg=f"Fail to enter the MS_BASED mode")
+
+ def test_ttff_gla_off(self):
+ """ Test the turn off "Google Location Accuracy" in settings work or not.
+
+ Test steps are executed in the following sequence.
+ - Turn off airplane mode
+ - Connect to Cellular
+ - Turn off LTO/RTO
+ - Turn on SUPL
+ - Turn off GLA
+ - Run CS TTFF
+
+ Expected Results:
+ - The position mode must be "standalone"
+ - The TTFF time must be between slower than supl_ws and faster than standalone_cs.
+ """
+ begin_time = get_current_epoch_time()
+ gutils.gla_mode(self.ad, False)
+
+ ttff_data = self.run_ttff("cs", self.standalone_cs_criteria)
+
+ asserts.assert_true(any(float(ttff_data[key].ttff_sec) > self.supl_ws_criteria
+ for key in ttff_data.keys()),
+ msg=f"One or more TTFF Cold Start are faster than \
+ test criteria {self.supl_ws_criteria} seconds")
+
+ asserts.assert_true(self.check_position_mode(begin_time, "standalone"),
+ msg=f"Fail to enter the standalone mode")
diff --git a/acts_tests/tests/google/gnss/GnssVendorFeaturesTest.py b/acts_tests/tests/google/gnss/GnssVendorFeaturesTest.py
new file mode 100644
index 0000000..e94626f
--- /dev/null
+++ b/acts_tests/tests/google/gnss/GnssVendorFeaturesTest.py
@@ -0,0 +1,290 @@
+import time
+
+from acts import asserts
+from acts import signals
+from acts.base_test import BaseTestClass
+from acts.utils import get_current_epoch_time
+from acts_contrib.test_utils.gnss import gnss_constant
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.gnss.testtracker_util import log_testtracker_uuid
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.tel import tel_logging_utils
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+
+
+class GnssVendorFeaturesTest(BaseTestClass):
+ """Validate vendor specific features."""
+ def setup_class(self):
+ super().setup_class()
+ self.ad = self.android_devices[0]
+ req_params = ["pixel_lab_network", "default_gnss_signal_attenuation", "pixel_lab_location",
+ "qdsp6m_path", "collect_logs", "ttff_test_cycle", "standalone_cs_criteria",
+ "xtra_cs_criteria", "xtra_ws_criteria", "xtra_hs_criteria",
+ "set_attenuator"]
+ self.unpack_userparams(req_param_names=req_params)
+ # create hashmap for SSID
+ self.ssid_map = {}
+ for network in self.pixel_lab_network:
+ SSID = network["SSID"]
+ self.ssid_map[SSID] = network
+ self.init_device()
+
+ def init_device(self):
+ """Init GNSS test devices for vendor features suite."""
+ gutils._init_device(self.ad)
+ gutils.disable_supl_mode(self.ad)
+ gutils.enable_vendor_orbit_assistance_data(self.ad)
+
+ def setup_test(self):
+ gutils.log_current_epoch_time(self.ad, "test_start_time")
+ log_testtracker_uuid(self.ad, self.current_test_name)
+ gutils.clear_logd_gnss_qxdm_log(self.ad)
+ gutils.get_baseband_and_gms_version(self.ad)
+ toggle_airplane_mode(self.ad.log, self.ad, new_state=False)
+ if gutils.is_wearable_btwifi(self.ad):
+ wutils.wifi_toggle_state(self.ad, True)
+ gutils.connect_to_wifi_network(self.ad,
+ self.ssid_map[self.pixel_lab_network[0]["SSID"]])
+ else:
+ wutils.wifi_toggle_state(self.ad, False)
+ gutils.set_mobile_data(self.ad, state=True)
+ if not verify_internet_connection(self.ad.log, self.ad, retries=3,
+ expected_state=True):
+ raise signals.TestFailure("Fail to connect to internet.")
+
+ def teardown_test(self):
+ if self.collect_logs:
+ gutils.stop_pixel_logger(self.ad)
+ tel_logging_utils.stop_adb_tcpdump(self.ad)
+ if self.set_attenuator:
+ gutils.set_attenuator_gnss_signal(self.ad, self.attenuators,
+ self.default_gnss_signal_attenuation)
+ gutils.log_current_epoch_time(self.ad, "test_end_time")
+
+ def on_fail(self, test_name, begin_time):
+ if self.collect_logs:
+ self.ad.take_bug_report(test_name, begin_time)
+ gutils.get_gnss_qxdm_log(self.ad, self.qdsp6m_path)
+ tel_logging_utils.get_tcpdump_log(self.ad, test_name, begin_time)
+
+ def connect_to_wifi_with_airplane_mode_on(self):
+ self.ad.log.info("Turn airplane mode on")
+ toggle_airplane_mode(self.ad.log, self.ad, new_state=True)
+ wutils.wifi_toggle_state(self.ad, True)
+ gutils.connect_to_wifi_network(self.ad, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
+
+ def ttff_with_assist(self, mode, criteria):
+ """Verify CS/WS TTFF functionality with Assist data.
+
+ Args:
+ mode: "csa" or "ws"
+ criteria: Criteria for the test.
+ """
+ begin_time = get_current_epoch_time()
+ gutils.process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
+ gutils.check_xtra_download(self.ad, begin_time)
+ self.ad.log.info("Turn airplane mode on")
+ toggle_airplane_mode(self.ad.log, self.ad, new_state=True)
+ gutils.start_gnss_by_gtw_gpstool(self.ad, True)
+ gutils.start_ttff_by_gtw_gpstool(self.ad, mode, iteration=self.ttff_test_cycle)
+ ttff_data = gutils.process_ttff_by_gtw_gpstool(self.ad, begin_time, self.pixel_lab_location)
+ result = gutils.check_ttff_data(self.ad, ttff_data, mode, criteria)
+ asserts.assert_true(
+ result, "TTFF %s fails to reach designated criteria of %d "
+ "seconds." % (gnss_constant.TTFF_MODE.get(mode), criteria))
+
+ def test_xtra_ttff_cs_mobile_data(self):
+ """Verify XTRA/LTO functionality of TTFF Cold Start with mobile data.
+
+ Steps:
+ 1. TTFF Cold Start for 10 iteration.
+
+ Expected Results:
+ XTRA/LTO TTFF Cold Start results should be within xtra_cs_criteria.
+ """
+ gutils.run_ttff(self.ad, mode="cs", criteria=self.xtra_cs_criteria,
+ test_cycle=self.ttff_test_cycle, base_lat_long=self.pixel_lab_location,
+ collect_logs=self.collect_logs)
+
+ def test_xtra_ttff_ws_mobile_data(self):
+ """Verify XTRA/LTO functionality of TTFF Warm Start with mobile data.
+
+ Steps:
+ 1. TTFF Warm Start for 10 iteration.
+
+ Expected Results:
+ XTRA/LTO TTFF Warm Start results should be within xtra_ws_criteria.
+ """
+ gutils.run_ttff(self.ad, mode="ws", criteria=self.xtra_ws_criteria,
+ test_cycle=self.ttff_test_cycle, base_lat_long=self.pixel_lab_location,
+ collect_logs=self.collect_logs)
+
+ def test_xtra_ttff_hs_mobile_data(self):
+ """Verify XTRA/LTO functionality of TTFF Hot Start with mobile data.
+
+ Steps:
+ 1. TTFF Hot Start for 10 iteration.
+
+ Expected Results:
+ XTRA/LTO TTFF Hot Start results should be within xtra_hs_criteria.
+ """
+ gutils.run_ttff(self.ad, mode="hs", criteria=self.xtra_hs_criteria,
+ test_cycle=self.ttff_test_cycle, base_lat_long=self.pixel_lab_location,
+ collect_logs=self.collect_logs)
+
+ def test_xtra_ttff_cs_wifi(self):
+ """Verify XTRA/LTO functionality of TTFF Cold Start with WiFi.
+
+ Steps:
+ 1. Turn airplane mode on.
+ 2. Connect to WiFi.
+ 3. TTFF Cold Start for 10 iteration.
+
+ Expected Results:
+ XTRA/LTO TTFF Cold Start results should be within
+ xtra_cs_criteria.
+ """
+ self.connect_to_wifi_with_airplane_mode_on()
+ gutils.run_ttff(self.ad, mode="cs", criteria=self.xtra_cs_criteria,
+ test_cycle=self.ttff_test_cycle, base_lat_long=self.pixel_lab_location,
+ collect_logs=self.collect_logs)
+
+ def test_xtra_ttff_ws_wifi(self):
+ """Verify XTRA/LTO functionality of TTFF Warm Start with WiFi.
+
+ Steps:
+ 1. Turn airplane mode on.
+ 2. Connect to WiFi.
+ 3. TTFF Warm Start for 10 iteration.
+
+ Expected Results:
+ XTRA/LTO TTFF Warm Start results should be within xtra_ws_criteria.
+ """
+ self.connect_to_wifi_with_airplane_mode_on()
+ gutils.run_ttff(self.ad, mode="ws", criteria=self.xtra_ws_criteria,
+ test_cycle=self.ttff_test_cycle, base_lat_long=self.pixel_lab_location,
+ collect_logs=self.collect_logs)
+
+ def test_xtra_ttff_hs_wifi(self):
+ """Verify XTRA/LTO functionality of TTFF Hot Start with WiFi.
+
+ Steps:
+ 1. Turn airplane mode on.
+ 2. Connect to WiFi.
+ 3. TTFF Hot Start for 10 iteration.
+
+ Expected Results:
+ XTRA/LTO TTFF Hot Start results should be within xtra_hs_criteria.
+ """
+ self.connect_to_wifi_with_airplane_mode_on()
+ gutils.run_ttff(self.ad, mode="hs", criteria=self.xtra_hs_criteria,
+ test_cycle=self.ttff_test_cycle, base_lat_long=self.pixel_lab_location,
+ collect_logs=self.collect_logs)
+
+ def test_xtra_download_mobile_data(self):
+ """Verify XTRA/LTO data could be downloaded via mobile data.
+
+ Steps:
+ 1. Delete all GNSS aiding data.
+ 2. Get location fixed.
+ 3. Verify whether XTRA/LTO is downloaded and injected.
+ 4. Repeat Step 1. to Step 3. for 5 times.
+
+ Expected Results:
+ XTRA/LTO data is properly downloaded and injected via mobile data.
+ """
+ mobile_xtra_result_all = []
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
+ for i in range(1, 6):
+ begin_time = get_current_epoch_time()
+ gutils.process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
+ time.sleep(5)
+ gutils.start_gnss_by_gtw_gpstool(self.ad, False)
+ mobile_xtra_result = gutils.check_xtra_download(self.ad, begin_time)
+ self.ad.log.info("Iteration %d => %s" % (i, mobile_xtra_result))
+ mobile_xtra_result_all.append(mobile_xtra_result)
+ asserts.assert_true(all(mobile_xtra_result_all),
+ "Fail to Download and Inject XTRA/LTO File.")
+
+ def test_xtra_download_wifi(self):
+ """Verify XTRA/LTO data could be downloaded via WiFi.
+
+ Steps:
+ 1. Connect to WiFi.
+ 2. Delete all GNSS aiding data.
+ 3. Get location fixed.
+ 4. Verify whether XTRA/LTO is downloaded and injected.
+ 5. Repeat Step 2. to Step 4. for 5 times.
+
+ Expected Results:
+ XTRA data is properly downloaded and injected via WiFi.
+ """
+ wifi_xtra_result_all = []
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
+ self.connect_to_wifi_with_airplane_mode_on()
+ for i in range(1, 6):
+ begin_time = get_current_epoch_time()
+ gutils.process_gnss_by_gtw_gpstool(self.ad, self.standalone_cs_criteria)
+ time.sleep(5)
+ gutils.start_gnss_by_gtw_gpstool(self.ad, False)
+ wifi_xtra_result = gutils.check_xtra_download(self.ad, begin_time)
+ wifi_xtra_result_all.append(wifi_xtra_result)
+ self.ad.log.info("Iteration %d => %s" % (i, wifi_xtra_result))
+ asserts.assert_true(all(wifi_xtra_result_all),
+ "Fail to Download and Inject XTRA/LTO File.")
+
+ def test_lto_download_after_reboot(self):
+ """Verify LTO data could be downloaded and injected after device reboot.
+
+ Steps:
+ 1. Reboot device.
+ 2. Verify whether LTO is auto downloaded and injected without trigger GPS.
+ 3. Repeat Step 1 to Step 2 for 5 times.
+
+ Expected Results:
+ LTO data is properly downloaded and injected at the first time tether to phone.
+ """
+ reboot_lto_test_results_all = []
+ for times in range(1, 6):
+ gutils.delete_lto_file(self.ad)
+ gutils.reboot(self.ad)
+ gutils.start_qxdm_and_tcpdump_log(self.ad, self.collect_logs)
+ # Wait 20 seconds for boot busy and lto auto-download time
+ time.sleep(20)
+ begin_time = get_current_epoch_time()
+ reboot_lto_test_result = gutils.check_xtra_download(self.ad, begin_time)
+ self.ad.log.info("Iteration %d => %s" % (times, reboot_lto_test_result))
+ reboot_lto_test_results_all.append(reboot_lto_test_result)
+ gutils.stop_pixel_logger(self.ad)
+ tel_logging_utils.stop_adb_tcpdump(self.ad)
+ asserts.assert_true(all(reboot_lto_test_results_all),
+ "Fail to Download and Inject LTO File.")
+
+ def test_ws_with_assist(self):
+ """Verify Warm Start functionality with existed LTO data.
+
+ Steps:
+ 2. Make LTO is downloaded.
+ 3. Turn on AirPlane mode to make sure there's no network connection.
+ 4. TTFF Warm Start with Assist for 10 iteration.
+
+ Expected Results:
+ All TTFF Warm Start with Assist results should be within
+ xtra_ws_criteria.
+ """
+ self.ttff_with_assist("ws", self.xtra_ws_criteria)
+
+ def test_cs_with_assist(self):
+ """Verify Cold Start functionality with existed LTO data.
+
+ Steps:
+ 2. Make sure LTO is downloaded.
+ 3. Turn on AirPlane mode to make sure there's no network connection.
+ 4. TTFF Cold Start with Assist for 10 iteration.
+
+ Expected Results:
+ All TTFF Cold Start with Assist results should be within
+ standalone_cs_criteria.
+ """
+ self.ttff_with_assist("csa", self.standalone_cs_criteria)
diff --git a/acts_tests/tests/google/gnss/GnssWearableTetherFunctionTest.py b/acts_tests/tests/google/gnss/GnssWearableTetherFunctionTest.py
index 0604c65..a90a01f 100644
--- a/acts_tests/tests/google/gnss/GnssWearableTetherFunctionTest.py
+++ b/acts_tests/tests/google/gnss/GnssWearableTetherFunctionTest.py
@@ -14,15 +14,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import time
+import statistics
from acts import asserts
from acts import signals
from acts.base_test import BaseTestClass
-from acts.test_decorators import test_tracker_info
+from acts_contrib.test_utils.gnss.testtracker_util import log_testtracker_uuid
from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
-from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.tel import tel_logging_utils as tutils
from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
from acts.utils import get_current_epoch_time
from acts_contrib.test_utils.gnss.gnss_test_utils import delete_lto_file, pair_to_wearable
from acts_contrib.test_utils.gnss.gnss_test_utils import process_gnss_by_gtw_gpstool
@@ -33,57 +34,56 @@
class GnssWearableTetherFunctionTest(BaseTestClass):
""" GNSS Wearable Tether Function Tests"""
-
def setup_class(self):
super().setup_class()
self.watch = self.android_devices[0]
self.phone = self.android_devices[1]
self.phone.uia = Device(self.phone.serial)
- req_params = [
- "pixel_lab_network", "standalone_cs_criteria",
- "flp_ttff_max_threshold", "pixel_lab_location", "flp_ttff_cycle",
- "default_gnss_signal_attenuation", "flp_waiting_time",
- "tracking_test_time", "fast_start_criteria"
- ]
+ req_params = ["pixel_lab_network", "standalone_cs_criteria",
+ "flp_ttff_max_threshold", "pixel_lab_location",
+ "flp_ttff_cycle", "default_gnss_signal_attenuation",
+ "flp_waiting_time", "tracking_test_time",
+ "far_start_criteria", "ttff_test_cycle"]
self.unpack_userparams(req_param_names=req_params)
# create hashmap for SSID
self.ssid_map = {}
for network in self.pixel_lab_network:
SSID = network["SSID"]
self.ssid_map[SSID] = network
- self.ttff_mode = {
- "cs": "Cold Start",
- "ws": "Warm Start",
- "hs": "Hot Start"
- }
+ self.ttff_mode = {"cs": "Cold Start",
+ "ws": "Warm Start",
+ "hs": "Hot Start"}
gutils._init_device(self.watch)
pair_to_wearable(self.watch, self.phone)
+ def teardown_class(self):
+ super().teardown_class()
+ gutils.reboot(self.phone)
+
def setup_test(self):
+ gutils.log_current_epoch_time(self.watch, "test_start_time")
+ log_testtracker_uuid(self.watch, self.current_test_name)
gutils.get_baseband_and_gms_version(self.watch)
gutils.clear_logd_gnss_qxdm_log(self.watch)
gutils.clear_logd_gnss_qxdm_log(self.phone)
gutils.set_attenuator_gnss_signal(self.watch, self.attenuators,
self.default_gnss_signal_attenuation)
- if not gutils.is_mobile_data_on(self.watch):
- gutils.set_mobile_data(self.watch, True)
- # TODO (b/202101058:chenstanley): Need to double check how to disable wifi successfully in wearable projects.
- if gutils.is_wearable_btwifi(self.watch):
- wutils.wifi_toggle_state(self.watch, True)
- gutils.connect_to_wifi_network(
- self.watch, self.ssid_map[self.pixel_lab_network[0]["SSID"]])
- if not verify_internet_connection(
- self.watch.log, self.watch, retries=3, expected_state=True):
- raise signals.TestFailure(
- "Fail to connect to LTE or WiFi network.")
- if not gutils.is_bluetooth_connected(self.watch, self.phone):
- gutils.pair_to_wearable(self.phone, self.watch)
+ if not verify_internet_connection(self.watch.log, self.watch, retries=5,
+ expected_state=True):
+ time.sleep(60)
+ if not verify_internet_connection(self.watch.log, self.watch, retries=3,
+ expected_state=True):
+ raise signals.TestFailure("Fail to connect to LTE network.")
def teardown_test(self):
gutils.stop_pixel_logger(self.watch)
tutils.stop_adb_tcpdump(self.watch)
gutils.set_attenuator_gnss_signal(self.watch, self.attenuators,
- self.default_gnss_signal_attenuation)
+ self.default_gnss_signal_attenuation)
+ if self.watch.droid.connectivityCheckAirplaneMode():
+ self.watch.log.info("Force airplane mode off")
+ toggle_airplane_mode(self.watch.log, self.watch, new_state=False)
+ gutils.log_current_epoch_time(self.watch, "test_end_time")
def on_fail(self, test_name, begin_time):
self.watch.take_bug_report(test_name, begin_time)
@@ -97,64 +97,44 @@
def flp_ttff(self, mode, criteria, location):
self.start_qxdm_and_tcpdump_log()
- start_gnss_by_gtw_gpstool(self.phone, True, type="FLP")
+ start_gnss_by_gtw_gpstool(self.phone, True, api_type="FLP")
time.sleep(self.flp_waiting_time)
self.watch.unlock_screen(password=None)
begin_time = get_current_epoch_time()
- process_gnss_by_gtw_gpstool(self.watch,
- self.standalone_cs_criteria,
- type="flp")
- gutils.start_ttff_by_gtw_gpstool(self.watch,
- mode,
- iteration=self.flp_ttff_cycle)
- results = gutils.process_ttff_by_gtw_gpstool(self.watch,
- begin_time,
- location,
- type="flp")
+ process_gnss_by_gtw_gpstool(
+ self.watch, self.standalone_cs_criteria, api_type="flp")
+ gutils.start_ttff_by_gtw_gpstool(
+ self.watch, mode, iteration=self.flp_ttff_cycle)
+ results = gutils.process_ttff_by_gtw_gpstool(
+ self.watch, begin_time, location, api_type="flp")
gutils.check_ttff_data(self.watch, results, mode, criteria)
self.check_location_from_phone()
- start_gnss_by_gtw_gpstool(self.phone, False, type="FLP")
+ start_gnss_by_gtw_gpstool(self.phone, False, api_type="FLP")
def check_location_from_phone(self):
watch_file = check_tracking_file(self.watch)
phone_file = check_tracking_file(self.phone)
- return gutils.compare_watch_phone_location(self, watch_file,
- phone_file)
+ return gutils.compare_watch_phone_location(self, watch_file, phone_file)
+
+ def run_ttff_via_gtw_gpstool(self, mode, criteria):
+ """Run GNSS TTFF test with selected mode and parse the results.
+
+ Args:
+ mode: "cs", "ws" or "hs"
+ criteria: Criteria for the TTFF.
+ """
+ begin_time = get_current_epoch_time()
+ gutils.process_gnss_by_gtw_gpstool(self.watch, self.standalone_cs_criteria, clear_data=False)
+ gutils.start_ttff_by_gtw_gpstool(self.watch, mode, self.ttff_test_cycle)
+ ttff_data = gutils.process_ttff_by_gtw_gpstool(
+ self.watch, begin_time, self.pixel_lab_location)
+ result = gutils.check_ttff_data(
+ self.watch, ttff_data, self.ttff_mode.get(mode), criteria)
+ asserts.assert_true(
+ result, "TTFF %s fails to reach designated criteria of %d "
+ "seconds." % (self.ttff_mode.get(mode), criteria))
""" Test Cases """
-
- @test_tracker_info(uuid="2c62183a-4354-4efc-92f2-84580cbd3398")
- def test_lto_download_after_reboot(self):
- """Verify LTO data could be downloaded and injected after device reboot.
-
- Steps:
- 1. Reboot device.
- 2. Verify whether LTO is auto downloaded and injected without trigger GPS.
- 3. Repeat Step 1 to Step 2 for 5 times.
-
- Expected Results:
- LTO data is properly downloaded and injected at the first time tether to phone.
- """
- reboot_lto_test_results_all = []
- gutils.disable_supl_mode(self.watch)
- for times in range(1, 6):
- delete_lto_file(self.watch)
- gutils.reboot(self.watch)
- self.start_qxdm_and_tcpdump_log()
- # Wait 20 seconds for boot busy and lto auto-download time
- time.sleep(20)
- begin_time = get_current_epoch_time()
- reboot_lto_test_result = gutils.check_xtra_download(
- self.watch, begin_time)
- self.watch.log.info("Iteration %d => %s" %
- (times, reboot_lto_test_result))
- reboot_lto_test_results_all.append(reboot_lto_test_result)
- gutils.stop_pixel_logger(self.watch)
- tutils.stop_adb_tcpdump(self.watch)
- asserts.assert_true(all(reboot_lto_test_results_all),
- "Fail to Download and Inject LTO File.")
-
- @test_tracker_info(uuid="7ed596df-df71-42ca-bdb3-69a3cad81963")
def test_flp_ttff_cs(self):
"""Verify FLP TTFF Cold Start while tether with phone.
@@ -168,10 +148,8 @@
flp_ttff_max_threshold.
2. Watch uses phone's FLP location.
"""
- self.flp_ttff("cs", self.flp_ttff_max_threshold,
- self.pixel_lab_location)
+ self.flp_ttff("cs", self.flp_ttff_max_threshold, self.pixel_lab_location)
- @test_tracker_info(uuid="de19617c-1f03-4077-99af-542b300ab4ed")
def test_flp_ttff_ws(self):
"""Verify FLP TTFF Warm Start while tether with phone.
@@ -185,10 +163,8 @@
flp_ttff_max_threshold.
2. Watch uses phone's FLP location.
"""
- self.flp_ttff("ws", self.flp_ttff_max_threshold,
- self.pixel_lab_location)
+ self.flp_ttff("ws", self.flp_ttff_max_threshold, self.pixel_lab_location)
- @test_tracker_info(uuid="c58c90ae-9f4a-4619-a9f8-f2f98c930008")
def test_flp_ttff_hs(self):
"""Verify FLP TTFF Hot Start while tether with phone.
@@ -202,10 +178,8 @@
flp_ttff_max_threshold.
2. Watch uses phone's FLP location.
"""
- self.flp_ttff("hs", self.flp_ttff_max_threshold,
- self.pixel_lab_location)
+ self.flp_ttff("hs", self.flp_ttff_max_threshold, self.pixel_lab_location)
- @test_tracker_info(uuid="ca955ad3-e2eb-4fde-af2b-3e19abe47792")
def test_tracking_during_bt_disconnect_resume(self):
"""Verify tracking is correct during Bluetooth disconnect and resume.
@@ -222,18 +196,17 @@
2. Tracking results should be within pixel_lab_location criteria.
"""
self.start_qxdm_and_tcpdump_log()
- for i in range(1, 6):
+ for i in range(1, 4):
if not self.watch.droid.bluetoothCheckState():
self.watch.droid.bluetoothToggleState(True)
self.watch.log.info("Turn Bluetooth on")
- self.watch.log.info("Wait 1 min for Bluetooth auto re-connect")
- time.sleep(60)
- if not gutils.is_bluetooth_connect(self.watch, self.phone):
- raise signals.TestFailure(
- "Fail to connect to device via Bluetooth.")
- start_gnss_by_gtw_gpstool(self.phone, True, type="FLP")
+ self.watch.log.info("Wait 40s for Bluetooth auto re-connect")
+ time.sleep(40)
+ if not gutils.is_bluetooth_connected(self.watch, self.phone):
+ raise signals.TestFailure("Fail to connect to device via Bluetooth.")
+ start_gnss_by_gtw_gpstool(self.phone, True, api_type="FLP")
time.sleep(self.flp_waiting_time)
- start_gnss_by_gtw_gpstool(self.watch, True, type="FLP")
+ start_gnss_by_gtw_gpstool(self.watch, True, api_type="FLP")
time.sleep(self.flp_waiting_time)
self.watch.log.info("Wait 1 min for tracking")
time.sleep(self.tracking_test_time)
@@ -245,14 +218,11 @@
time.sleep(self.tracking_test_time)
if self.check_location_from_phone():
raise signals.TestError("Watch should not use phone location")
- gutils.parse_gtw_gpstool_log(self.watch,
- self.pixel_lab_location,
- type="FLP")
- start_gnss_by_gtw_gpstool(self.phone, False, type="FLP")
+ gutils.parse_gtw_gpstool_log(self.watch, self.pixel_lab_location, api_type="FLP")
+ start_gnss_by_gtw_gpstool(self.phone, False, api_type="FLP")
- @test_tracker_info(uuid="654a8f1b-f9c6-433e-a21f-59224cce822e")
- def test_fast_start_first_fix_and_ttff(self):
- """Verify first fix and TTFF of Fast Start (Warm Start v4) within the criteria
+ def test_oobe_first_fix(self):
+ """Verify first fix after OOBE pairing within the criteria
Steps:
1. Pair watch to phone during OOBE.
@@ -261,33 +231,78 @@
4. Enable AirPlane mode to untether to phone.
5. Open GPSTool to get first fix in LTO and UTC time injected.
6. Repeat Step1 ~ Step5 for 5 times.
- 7. After Step6, Warm Start TTFF for 10 iterations.
Expected Results:
- 1. First fix should be within fast_start_threshold.
- 2. TTFF should be within fast_start_threshold.
+ 1. First fix should be within far_start_threshold.
"""
- for i in range(1, 6):
- self.watch.log.info("First fix of Fast Start - attempts %s" % i)
+ oobe_results_all = []
+ for i in range(1,4):
+ self.watch.log.info("First fix after OOBE pairing - attempts %s" % i)
pair_to_wearable(self.watch, self.phone)
- gutils.enable_framework_log(self.watch)
self.start_qxdm_and_tcpdump_log()
begin_time = get_current_epoch_time()
gutils.check_xtra_download(self.watch, begin_time)
gutils.check_inject_time(self.watch)
self.watch.log.info("Turn airplane mode on")
- self.watch.droid.connectivityToggleAirplaneMode(True)
- self.watch.unlock_screen(password=None)
- gutils.process_gnss_by_gtw_gpstool(self.watch,
- self.fast_start_criteria,
- clear_data=False)
- gutils.start_ttff_by_gtw_gpstool(self.watch,
- ttff_mode="ws",
- iteration=self.ttff_test_cycle)
- ttff_data = gutils.process_ttff_by_gtw_gpstool(self.watch, begin_time,
- self.pixel_lab_location)
- result = gutils.check_ttff_data(self.watch,
- ttff_data,
- self.ttff_mode.get("ws"),
- criteria=self.fast_start_criteria)
- asserts.assert_true(result, "TTFF fails to reach designated criteria")
+ # self.watch.droid.connectivityToggleAirplaneMode(True)
+ toggle_airplane_mode(self.watch.log, self.watch, new_state=True)
+ oobe_results = gutils.process_gnss(
+ self.watch, self.far_start_criteria, clear_data=False)
+ oobe_results_all.append(oobe_results)
+ self.watch.log.info(f"TestResult Max_OOBE_First_Fix {max(oobe_results_all)}")
+ self.watch.log.info(f"TestResult Avg_OOBE_First_Fix {statistics.mean(oobe_results_all)}")
+ # self.watch.droid.connectivityToggleAirplaneMode(False)
+ toggle_airplane_mode(self.watch.log, self.watch, new_state=False)
+ self.watch.log.info("Turn airplane mode off")
+
+ def test_oobe_first_fix_with_network_connection(self):
+ """Verify first fix after OOBE pairing within the criteria
+
+ Steps:
+ 1. Pair watch to phone during OOBE.
+ 2. Ensure LTO file download in watch.
+ 3. Ensure UTC time inject in watch.
+ 4. Turn off Bluetooth to untether to phone.
+ 5. Open GPSTool to get first fix in LTO and UTC time injected.
+ 6. Repeat Step1 ~ Step5 for 5 times.
+
+ Expected Results:
+ 1. First fix should be within far_start_threshold.
+ """
+ oobe_results_all = []
+ for i in range(1,4):
+ self.watch.log.info("First fix after OOBE pairing - attempts %s" % i)
+ pair_to_wearable(self.watch, self.phone)
+ self.start_qxdm_and_tcpdump_log()
+ begin_time = get_current_epoch_time()
+ gutils.check_xtra_download(self.watch, begin_time)
+ gutils.check_inject_time(self.watch)
+ self.watch.log.info("Turn off Bluetooth to disconnect to phone")
+ self.watch.droid.bluetoothToggleState(False)
+ oobe_results = gutils.process_gnss(
+ self.watch, self.far_start_criteria, clear_data=False)
+ oobe_results_all.append(oobe_results)
+ self.watch.log.info(f"TestResult Max_OOBE_First_Fix {max(oobe_results_all)}")
+ self.watch.log.info(f"TestResult Avg_OOBE_First_Fix {statistics.mean(oobe_results_all)}")
+
+ def test_far_start_ttff(self):
+ """Verify Far Start (Warm Start v4) TTFF within the criteria
+
+ Steps:
+ 1. Pair watch to phone during OOBE.
+ 2. Ensure LTO file download in watch.
+ 3. Ensure UTC time inject in watch.
+ 4. Enable AirPlane mode to untether to phone.
+ 5. TTFF Warm Start for 10 iteration.
+
+ Expected Results:
+ 1. TTFF should be within far_start_threshold.
+ """
+ pair_to_wearable(self.watch, self.phone)
+ self.start_qxdm_and_tcpdump_log()
+ begin_time = get_current_epoch_time()
+ gutils.check_xtra_download(self.watch, begin_time)
+ gutils.check_inject_time(self.watch)
+ self.watch.log.info("Turn airplane mode on")
+ toggle_airplane_mode(self.watch.log, self.watch, new_state=True)
+ self.run_ttff_via_gtw_gpstool("ws", self.far_start_criteria)
diff --git a/acts_tests/tests/google/gnss/LabGnssPowerSweepTest.py b/acts_tests/tests/google/gnss/LabGnssPowerSweepTest.py
new file mode 100644
index 0000000..f641cc4
--- /dev/null
+++ b/acts_tests/tests/google/gnss/LabGnssPowerSweepTest.py
@@ -0,0 +1,324 @@
+#!/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.
+import os
+from acts_contrib.test_utils.gnss.GnssBlankingBase import GnssBlankingBase
+from collections import namedtuple
+from acts_contrib.test_utils.gnss.LabTtffTestBase import LabTtffTestBase
+from acts_contrib.test_utils.gnss.gnss_test_utils import detect_crash_during_tracking, gnss_tracking_via_gtw_gpstool, \
+ start_gnss_by_gtw_gpstool, process_ttff_by_gtw_gpstool, calculate_position_error
+from acts.context import get_current_context
+from acts.utils import get_current_epoch_time
+from time import sleep
+import csv
+import matplotlib.pyplot as plt
+from mpl_toolkits.mplot3d.axes3d import Axes3D
+import statistics
+
+
+class LabGnssPowerSweepTest(GnssBlankingBase):
+
+ def gnss_plot_2D_result(self, position_error):
+ """Plot 2D position error result
+ """
+ x_axis = []
+ y_axis = []
+ z_axis = []
+ for key in position_error:
+ tmp = key.split('_')
+ l1_pwr = float(tmp[1])
+ l5_pwr = float(tmp[3])
+ position_error_value = position_error[key]
+ x_axis.append(l1_pwr)
+ y_axis.append(l5_pwr)
+ z_axis.append(position_error_value)
+
+ fig = plt.figure(figsize=(12, 7))
+ ax = plt.axes(projection='3d')
+ ax.scatter(x_axis, y_axis, z_axis)
+ plt.title("Z axis Position Error", fontsize=12)
+ plt.xlabel("L1 PWR (dBm)", fontsize=12)
+ plt.ylabel("L5 PWR (dBm)", fontsize=12)
+ plt.show()
+ path_name = os.path.join(self.gnss_log_path, 'result.png')
+ plt.savefig(path_name)
+
+ def gnss_wait_for_ephemeris_download(self):
+ """Launch GTW GPSTool and Clear all GNSS aiding data
+ Start GNSS tracking on GTW_GPSTool.
+ Wait for "first_wait" at simulator power = "power_level" to download Ephemeris
+ """
+ first_wait = self.user_params.get('first_wait', 300)
+ LabTtffTestBase.start_set_gnss_power(self)
+ self.start_gnss_and_wait(first_wait)
+
+ def gnss_check_fix(self, json_tag):
+ """Launch GTW GPSTool and check position fix or not
+ Returns:
+ True : Can fix within 120 sec
+ False
+ """
+ # Check Latitude for fix
+ self.dut.log.info("Restart GTW GPSTool in gnss_check_fix")
+ start_gnss_by_gtw_gpstool(self.dut, state=True)
+ begin_time = get_current_epoch_time()
+ if not self.dut.is_adb_logcat_on:
+ self.dut.start_adb_logcat()
+ while True:
+ if get_current_epoch_time() - begin_time >= 120000:
+ self.dut.log.info("Location fix timeout in gnss_check_fix")
+ start_gnss_by_gtw_gpstool(self.dut, state=False)
+ json_tag = json_tag + '_gnss_check_fix_timeout'
+ self.dut.cat_adb_log(tag=json_tag,
+ begin_time=begin_time,
+ end_time=None,
+ dest_path=self.gnss_log_path)
+ return False
+ sleep(1)
+ logcat_results = self.dut.search_logcat("Latitude", begin_time)
+ if logcat_results:
+ self.dut.log.info("Location fix successfully in gnss_check_fix")
+ json_tag = json_tag + '_gnss_check_fix_success'
+ self.dut.cat_adb_log(tag=json_tag,
+ begin_time=begin_time,
+ end_time=None,
+ dest_path=self.gnss_log_path)
+ return True
+
+ def gnss_check_l5_engaging(self, json_tag):
+ """check L5 engaging
+ Returns:
+ True : L5 engaged
+ False
+ """
+ # Check L5 engaging rate
+ begin_time = get_current_epoch_time()
+ if not self.dut.is_adb_logcat_on:
+ self.dut.start_adb_logcat()
+ while True:
+ if get_current_epoch_time() - begin_time >= 120000:
+ self.dut.log.info(
+ "L5 engaging timeout in gnss_check_l5_engaging")
+ start_gnss_by_gtw_gpstool(self.dut, state=False)
+ json_tag = json_tag + '_gnss_check_l5_engaging_timeout'
+ self.dut.cat_adb_log(tag=json_tag,
+ begin_time=begin_time,
+ end_time=None,
+ dest_path=self.gnss_log_path)
+ return False
+ sleep(1)
+ logcat_results = self.dut.search_logcat("L5 engaging rate:",
+ begin_time)
+ if logcat_results:
+ start_idx = logcat_results[-1]['log_message'].find(
+ "L5 engaging rate:")
+ tmp = logcat_results[-1]['log_message'][(start_idx + 18):]
+ l5_engaging_rate = float(tmp.strip('%'))
+
+ if l5_engaging_rate != 0:
+ self.dut.log.info("L5 engaged")
+ json_tag = json_tag + '_gnss_check_l5_engaging_success'
+ self.dut.cat_adb_log(tag=json_tag,
+ begin_time=begin_time,
+ end_time=None,
+ dest_path=self.gnss_log_path)
+ return True
+
+ def gnss_check_position_error(self, json_tag):
+ """check position error
+ Returns:
+ position error average value
+ """
+ average_position_error_count = 60
+ position_error_all = []
+ hacc_all = []
+ default_position_error_mean = 6666
+ default_position_error_std = 6666
+ default_hacc_mean = 6666
+ default_hacc_std = 6666
+ idx = 0
+ begin_time = get_current_epoch_time()
+ if not self.dut.is_adb_logcat_on:
+ self.dut.start_adb_logcat()
+ while True:
+ if get_current_epoch_time() - begin_time >= 120000:
+ self.dut.log.info(
+ "Position error calculation timeout in gnss_check_position_error"
+ )
+ start_gnss_by_gtw_gpstool(self.dut, state=False)
+ json_tag = json_tag + '_gnss_check_position_error_timeout'
+ self.dut.cat_adb_log(tag=json_tag,
+ begin_time=begin_time,
+ end_time=None,
+ dest_path=self.gnss_log_path)
+ return default_position_error_mean, default_position_error_std, default_hacc_mean, default_hacc_std
+ sleep(1)
+ gnss_results = self.dut.search_logcat("GPSService: Check item",
+ begin_time)
+ if gnss_results:
+ self.dut.log.info(gnss_results[-1]["log_message"])
+ gnss_location_log = \
+ gnss_results[-1]["log_message"].split()
+ ttff_lat = float(gnss_location_log[8].split("=")[-1].strip(","))
+ ttff_lon = float(gnss_location_log[9].split("=")[-1].strip(","))
+ loc_time = int(gnss_location_log[10].split("=")[-1].strip(","))
+ ttff_haccu = float(
+ gnss_location_log[11].split("=")[-1].strip(","))
+ hacc_all.append(ttff_haccu)
+ position_error = calculate_position_error(
+ ttff_lat, ttff_lon, self.simulator_location)
+ position_error_all.append(abs(position_error))
+ idx = idx + 1
+ if idx >= average_position_error_count:
+ position_error_mean = statistics.mean(position_error_all)
+ position_error_std = statistics.stdev(position_error_all)
+ hacc_mean = statistics.mean(hacc_all)
+ hacc_std = statistics.stdev(hacc_all)
+ json_tag = json_tag + '_gnss_check_position_error_success'
+ self.dut.cat_adb_log(tag=json_tag,
+ begin_time=begin_time,
+ end_time=None,
+ dest_path=self.gnss_log_path)
+ return position_error_mean, position_error_std, hacc_mean, hacc_std
+
+ def gnss_tracking_L5_position_error_capture(self, json_tag):
+ """Capture position error after L5 engaged
+ Args:
+ Returns:
+ Position error with L5
+ """
+ self.dut.log.info('Start gnss_tracking_L5_position_error_capture')
+ fixed = self.gnss_check_fix(json_tag)
+ if fixed:
+ l5_engaged = self.gnss_check_l5_engaging(json_tag)
+ if l5_engaged:
+ position_error_mean, position_error_std, hacc_mean, hacc_std = self.gnss_check_position_error(
+ json_tag)
+ start_gnss_by_gtw_gpstool(self.dut, state=False)
+ else:
+ position_error_mean = 8888
+ position_error_std = 8888
+ hacc_mean = 8888
+ hacc_std = 8888
+ else:
+ position_error_mean = 9999
+ position_error_std = 9999
+ hacc_mean = 9999
+ hacc_std = 9999
+ self.position_fix_timeout_cnt = self.position_fix_timeout_cnt + 1
+
+ if self.position_fix_timeout_cnt > (self.l1_sweep_cnt / 2):
+ self.l1_sensitivity_point = self.current_l1_pwr
+
+ return position_error_mean, position_error_std, hacc_mean, hacc_std
+
+ def gnss_power_tracking_loop(self):
+ """Launch GTW GPSTool and Clear all GNSS aiding data
+ Start GNSS tracking on GTW_GPSTool.
+
+ Args:
+
+ Returns:
+ True: First fix TTFF are within criteria.
+ False: First fix TTFF exceed criteria.
+ """
+ test_period = 60
+ type = 'gnss'
+ start_time = get_current_epoch_time()
+ start_gnss_by_gtw_gpstool(self.dut, state=True, type=type)
+ while get_current_epoch_time() - start_time < test_period * 1000:
+ detect_crash_during_tracking(self.dut, start_time, type)
+ stop_time = get_current_epoch_time()
+
+ return start_time, stop_time
+
+ def parse_tracking_log_cat(self, log_dir):
+ self.log.warning(f'Parsing log cat {log_dir} results into dataframe!')
+
+ def check_l5_points(self, gnss_pwr_swp):
+ cnt = 0
+ for kk in range(len(gnss_pwr_swp[1])):
+ if gnss_pwr_swp[1][kk][0] == gnss_pwr_swp[1][kk + 1][0]:
+ cnt = cnt + 1
+ else:
+ return cnt
+
+ def test_tracking_power_sweep(self):
+ # Create log file path
+ full_output_path = get_current_context().get_full_output_path()
+ self.gnss_log_path = os.path.join(full_output_path, '')
+ os.makedirs(self.gnss_log_path, exist_ok=True)
+ self.log.debug(f'Create log path: {self.gnss_log_path}')
+ csv_path = self.gnss_log_path + 'L1_L5_2D_search_result.csv'
+ csvfile = open(csv_path, 'w')
+ writer = csv.writer(csvfile)
+ writer.writerow([
+ "csv_result_tag", "position_error_mean", "position_error_std",
+ "hacc_mean", "hacc_std"
+ ])
+ # for L1 position fix early termination
+ self.l1_sensitivity_point = -999
+ self.enable_early_terminate = 1
+ self.position_fix_timeout_cnt = 0
+ self.current_l1_pwr = 0
+ self.l1_sweep_cnt = 0
+
+ self.gnss_wait_for_ephemeris_download()
+ l1_cable_loss = self.gnss_sim_params.get('L1_cable_loss')
+ l5_cable_loss = self.gnss_sim_params.get('L5_cable_loss')
+
+ for i, gnss_pwr_swp in enumerate(self.gnss_pwr_sweep_fine_sweep_ls):
+ self.log.info(f'Start fine GNSS power level sweep part {i + 1}')
+ self.l1_sweep_cnt = self.check_l5_points(gnss_pwr_swp)
+ for gnss_pwr_params in gnss_pwr_swp[1]:
+ json_tag = f'test_'
+ csv_result_tag = ''
+ for ii, pwr in enumerate(
+ gnss_pwr_params): # Setup L1 and L5 power
+ sat_sys = gnss_pwr_swp[0][ii].get('sat').upper()
+ band = gnss_pwr_swp[0][ii].get('band').upper()
+ if band == "L1":
+ pwr_biased = pwr + l1_cable_loss
+ if pwr != self.current_l1_pwr:
+ self.position_fix_timeout_cnt = 0
+ self.current_l1_pwr = pwr
+ elif band == "L5":
+ pwr_biased = pwr + l5_cable_loss
+ else:
+ pwr_biased = pwr
+ # Set GNSS Simulator power level
+ self.gnss_simulator.ping_inst()
+ self.gnss_simulator.set_scenario_power(
+ power_level=pwr_biased,
+ sat_system=sat_sys,
+ freq_band=band)
+ self.log.info(f'Set {sat_sys} {band} with power {pwr}')
+ json_tag = json_tag + f'{sat_sys}_{band}_{pwr}'
+ csv_result_tag = csv_result_tag + f'{band}_{pwr}_'
+
+ if self.current_l1_pwr < self.l1_sensitivity_point and self.enable_early_terminate == 1:
+ position_error_mean = -1
+ position_error_std = -1
+ hacc_mean = -1
+ hacc_std = -1
+ else:
+ position_error_mean, position_error_std, hacc_mean, hacc_std = self.gnss_tracking_L5_position_error_capture(
+ json_tag)
+ writer = csv.writer(csvfile)
+ writer.writerow([
+ csv_result_tag, position_error_mean, position_error_std,
+ hacc_mean, hacc_std
+ ])
+ csvfile.close()
diff --git a/acts_tests/tests/google/gnss/LabTtffGeneralCoexTest.py b/acts_tests/tests/google/gnss/LabTtffGeneralCoexTest.py
index 24da4d3..664c5b6 100644
--- a/acts_tests/tests/google/gnss/LabTtffGeneralCoexTest.py
+++ b/acts_tests/tests/google/gnss/LabTtffGeneralCoexTest.py
@@ -16,7 +16,8 @@
from acts_contrib.test_utils.gnss import LabTtffTestBase as lttb
from acts_contrib.test_utils.gnss.gnss_test_utils import launch_eecoexer
-from acts_contrib.test_utils.gnss.gnss_test_utils import excute_eecoexer_function
+from acts_contrib.test_utils.gnss.gnss_test_utils import execute_eecoexer_function
+
class LabTtffGeneralCoexTest(lttb.LabTtffTestBase):
@@ -26,6 +27,8 @@
super().setup_class()
req_params = ['coex_testcase_ls']
self.unpack_userparams(req_param_names=req_params)
+ self.test_cmd = ''
+ self.stop_cmd = ''
def setup_test(self):
super().setup_test()
@@ -34,16 +37,9 @@
self.dut.adb.shell(
'setprop persist.com.google.eecoexer.cellular.temperature_limit 60')
- def exe_eecoexer_loop_cmd(self, cmd_list=list()):
- """
- Function for execute EECoexer command list
- Args:
- cmd_list: a list of EECoexer function command.
- Type, list.
- """
- for cmd in cmd_list:
- self.log.info('Execute EEcoexer Command: {}'.format(cmd))
- excute_eecoexer_function(self.dut, cmd)
+ def teardown_test(self):
+ super().teardown_test()
+ self.exe_eecoexer_loop_cmd(self.stop_cmd)
def gnss_ttff_ffpe_coex_base(self, mode):
"""
@@ -54,29 +50,35 @@
cs(cold start), ws(warm start), hs(hot start)
"""
# Loop all test case in coex_testcase_ls
- for test_item in self.coex_testcase_ls:
+ for i, test_item in enumerate(self.coex_testcase_ls):
+
+ if i > 0:
+ self.setup_test()
# get test_log_path from coex_testcase_ls['test_name']
test_log_path = test_item['test_name']
# get test_cmd from coex_testcase_ls['test_cmd']
- test_cmd = test_item['test_cmd']
+ self.test_cmd = test_item['test_cmd']
# get stop_cmd from coex_testcase_ls['stop_cmd']
- stop_cmd = test_item['stop_cmd']
+ self.stop_cmd = test_item['stop_cmd']
# Start aggressor Tx by EEcoexer
- self.exe_eecoexer_loop_cmd(test_cmd)
+ # self.exe_eecoexer_loop_cmd(test_cmd)
# Start GNSS TTFF FFPE testing
- self.gnss_ttff_ffpe(mode, test_log_path)
+ self.gnss_ttff_ffpe(mode, test_log_path, self.test_cmd, self.stop_cmd)
# Stop aggressor Tx by EEcoexer
- self.exe_eecoexer_loop_cmd(stop_cmd)
+ # self.exe_eecoexer_loop_cmd(stop_cmd)
# Clear GTW GPSTool log. Need to clean the log every round of the test.
self.clear_gps_log()
+ if i < len(self.coex_testcase_ls) - 1:
+ self.teardown_test()
+
def test_gnss_cold_ttff_ffpe_coex(self):
"""
Cold start TTFF and FFPE GNSS general coex testing
diff --git a/acts_tests/tests/google/gnss/LocationPlatinumTest.py b/acts_tests/tests/google/gnss/LocationPlatinumTest.py
index 6f4c253..a50acb4 100644
--- a/acts_tests/tests/google/gnss/LocationPlatinumTest.py
+++ b/acts_tests/tests/google/gnss/LocationPlatinumTest.py
@@ -67,7 +67,7 @@
gutils.check_location_service(self.ad)
if not self.ad.droid.wifiCheckState():
wutils.wifi_toggle_state(self.ad, True)
- gutils.connect_to_wifi_network(self.ad, self.wifi_network)
+ gutils.connect_to_wifi_network(self.ad, self.wifi_network)
def get_and_verify_ttff(self, mode):
"""Retrieve ttff with designate mode.
diff --git a/acts_tests/tests/google/power/bt/PowerBLEadvertiseTest.py b/acts_tests/tests/google/power/bt/PowerBLEadvertiseTest.py
index 4f42ae0..27fca6b 100644
--- a/acts_tests/tests/google/power/bt/PowerBLEadvertiseTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBLEadvertiseTest.py
@@ -25,6 +25,7 @@
class PowerBLEadvertiseTest(PBtBT.PowerBTBaseTest):
+
def __init__(self, configs):
super().__init__(configs)
req_params = ['adv_modes', 'adv_power_levels']
@@ -32,7 +33,9 @@
# Loop all advertise modes and power levels
for adv_mode in self.adv_modes:
for adv_power_level in self.adv_power_levels:
- self.generate_test_case(adv_mode, adv_power_level)
+ # As a temporary fix, set high power tests directly
+ if adv_power_level != 3:
+ self.generate_test_case(adv_mode, adv_power_level)
def setup_class(self):
@@ -58,3 +61,12 @@
self.adv_duration)
time.sleep(EXTRA_ADV_TIME)
self.measure_power_and_validate()
+
+ def test_BLE_ADVERTISE_MODE_LOW_POWER_TX_POWER_HIGH(self):
+ self.measure_ble_advertise_power(0, 3)
+
+ def test_BLE_ADVERTISE_MODE_BALANCED_TX_POWER_HIGH(self):
+ self.measure_ble_advertise_power(1, 3)
+
+ def test_BLE_ADVERTISE_MODE_LOW_LATENCY_TX_POWER_HIGH(self):
+ self.measure_ble_advertise_power(2, 3)
diff --git a/acts_tests/tests/google/power/bt/PowerBLEscanTest.py b/acts_tests/tests/google/power/bt/PowerBLEscanTest.py
index 0aa5ad9..f76f796 100644
--- a/acts_tests/tests/google/power/bt/PowerBLEscanTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBLEscanTest.py
@@ -30,9 +30,6 @@
req_params = ['scan_modes']
self.unpack_userparams(req_params)
- for scan_mode in self.scan_modes:
- self.generate_test_case_no_devices_around(scan_mode)
-
def setup_class(self):
super().setup_class()
@@ -54,3 +51,12 @@
btputils.start_apk_ble_scan(self.dut, scan_mode, self.scan_duration)
time.sleep(EXTRA_SCAN_TIME)
self.measure_power_and_validate()
+
+ def test_BLE_SCAN_MODE_LOW_POWER_no_advertisers(self):
+ self.measure_ble_scan_power(0)
+
+ def test_BLE_SCAN_MODE_BALANCED_no_advertisers(self):
+ self.measure_ble_scan_power(1)
+
+ def test_BLE_SCAN_MODE_LOW_LATENCY_no_advertisers(self):
+ self.measure_ble_scan_power(2)
diff --git a/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py b/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py
index 419c418..7c882bd 100644
--- a/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py
@@ -39,7 +39,9 @@
# Loop all codecs and tx power levels
for codec_config in self.codecs:
for tpl in self.tx_power_levels:
- self.generate_test_case(codec_config, tpl)
+ # As a temporary fix, directly set the test with tpl 10
+ if tpl != 10:
+ self.generate_test_case(codec_config, tpl)
def setup_test(self):
super().setup_test()
@@ -107,3 +109,9 @@
codec_config['codec_type'], tpl))
self.dut.droid.goToSleepNow()
self.measure_power_and_validate()
+
+ def test_BTa2dp_AAC_codec_at_EPA_BF(self):
+ self.measure_a2dp_power(self.codecs[0], 10)
+
+ def test_BTa2dp_SBC_codec_at_EPA_BF(self):
+ self.measure_a2dp_power(self.codecs[1], 10)
\ No newline at end of file
diff --git a/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py b/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py
index c0bac23..db92276 100644
--- a/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py
@@ -73,3 +73,9 @@
with open(self.log_file, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerows(self.cal_matrix)
+
+ def teardown_test(self):
+ """
+ Clean up in teardown_test : Disable BQR
+ """
+ btutils.disable_bqr(self.dut)
diff --git a/acts_tests/tests/google/power/tel/PowerTelAirplaneMode_Test.py b/acts_tests/tests/google/power/tel/PowerTelAirplaneMode_Test.py
new file mode 100644
index 0000000..44fab02
--- /dev/null
+++ b/acts_tests/tests/google/power/tel/PowerTelAirplaneMode_Test.py
@@ -0,0 +1,36 @@
+# Copyright 2022 - 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 time
+
+import acts_contrib.test_utils.power.cellular.cellular_power_preset_base_test as PB
+
+
+class PowerTelAirplaneModeTest(PB.PowerCellularPresetLabBaseTest):
+
+ def power_tel_airplane_mode_test(self):
+ """Measure power while airplane mode is on. """
+ # Start airplane mode
+ self.cellular_dut.toggle_airplane_mode(True)
+
+ # Allow airplane mode to propagate
+ time.sleep(3)
+
+ # Measure power
+ self.collect_power_data()
+ # Check if power measurement is within the required values
+ self.pass_fail_check(self.avg_current)
+
+class PowerTelAirplaneMode_Test(PowerTelAirplaneModeTest):
+ def test_airplane_mode(self):
+ self.power_tel_airplane_mode_test()
\ No newline at end of file
diff --git a/acts_tests/tests/google/power/tel/PowerTelIdle_Preset_Test.py b/acts_tests/tests/google/power/tel/PowerTelIdle_Preset_Test.py
new file mode 100644
index 0000000..8efbc76
--- /dev/null
+++ b/acts_tests/tests/google/power/tel/PowerTelIdle_Preset_Test.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+#
+# Copyright 2022 - 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 acts_contrib.test_utils.power.cellular.cellular_power_preset_base_test as PB
+
+
+class PowerTelIdle_Preset_Test(PB.PowerCellularPresetLabBaseTest):
+ def power_tel_idle_test(self):
+ """ Measures power when the device is on RRC idle state."""
+ idle_wait_time = self.simulation.rrc_sc_timer + 30
+ # Wait for RRC status change to trigger
+ self.cellular_simulator.wait_until_idle_state(idle_wait_time)
+
+ # Measure power
+ self.collect_power_data()
+
+ # Check if power measurement is below the required value
+ self.pass_fail_check(self.avg_current)
+
+ def test_preset_LTE_idle(self):
+ self.power_tel_idle_test()
+
+ def test_preset_sa_idle_fr1(self):
+ self.power_tel_idle_test()
diff --git a/acts_tests/tests/google/power/tel/PowerTelIms_Preset_Test.py b/acts_tests/tests/google/power/tel/PowerTelIms_Preset_Test.py
new file mode 100644
index 0000000..a18653d
--- /dev/null
+++ b/acts_tests/tests/google/power/tel/PowerTelIms_Preset_Test.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+#
+# Copyright 20022 - 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 time
+
+from acts_contrib.test_utils.power.cellular.ims_api_connector_utils import ImsApiConnector
+import acts_contrib.test_utils.power.cellular.cellular_power_base_test as PWCEL
+from acts_contrib.test_utils.tel.tel_test_utils import set_phone_silent_mode
+from acts_contrib.test_utils.tel.tel_voice_utils import hangup_call
+import acts_contrib.test_utils.power.cellular.cellular_power_preset_base_test as PB
+
+
+class PowerTelImsPresetTest(PB.PowerCellularPresetLabBaseTest):
+ ADB_CMD_ENABLE_IMS = ('am broadcast '
+ '-a com.google.android.carrier.action.LOCAL_OVERRIDE '
+ '-n com.google.android.carrier/.ConfigOverridingReceiver '
+ '--ez carrier_volte_available_bool true '
+ '--ez carrier_wfc_ims_available_bool true '
+ '--ez carrier_vt_available_bool true '
+ '--ez carrier_supports_ss_over_ut_bool true '
+ '--ez vonr_setting_visibility_bool true '
+ '--ez vonr_enabled_bool true')
+
+ ADB_CMD_DISABLE_IMS = ('am broadcast '
+ '-a com.google.android.carrier.action.LOCAL_OVERRIDE '
+ '-n com.google.android.carrier/.ConfigOverridingReceiver '
+ '--ez carrier_volte_available_bool false '
+ '--ez carrier_wfc_ims_available_bool false '
+ '--ez carrier_vt_available_bool false '
+ '--ez carrier_supports_ss_over_ut_bool false '
+ '--ez vonr_setting_visibility_bool false '
+ '--ez vonr_enabled_bool false')
+
+ # set NV command
+ # !NRCAPA.Gen.VoiceOverNr, 0, 01
+ ADB_SET_GOOG_NV = 'echo at+googsetnv="{nv_name}",{index},"{value}" > /dev/umts_router'
+
+ # Key IMS simulator default value
+ IMS_CLIENT_DEFAULT_IP = '127.0.0.1'
+ IMS_CLIENT_DEFAULT_PORT = 8250
+ IMS_CLIENT_DEFAULT_API_TOKEN = 'myclient'
+ IMS_API_CONNECTOR_DEFAULT_PORT = 5050
+
+ # IMS available app
+ IMS_CLIENT = 'client'
+ IMS_SERVER = 'server'
+
+ def setup_class(self):
+ """ Executed only once when initializing the class. """
+ super().setup_class()
+
+ # disable mobile data
+ self.log.info('Disable mobile data.')
+ self.dut.adb.shell('svc data disable')
+
+ # Enable IMS on UE
+ self.log.info('Enable VoLTE using adb command.')
+ self.dut.adb.shell(self.ADB_CMD_ENABLE_IMS)
+
+ # reboot device for settings to update
+ self.log.info('Reboot for VoLTE settings to update.')
+ self.dut.reboot()
+
+ # Set voice call volume to minimum
+ set_phone_silent_mode(self.log, self.dut)
+
+ # initialize ims simulator connector wrapper
+ self.unpack_userparams(api_connector_port=self.IMS_API_CONNECTOR_DEFAULT_PORT,
+ api_token=self.IMS_CLIENT_DEFAULT_API_TOKEN,
+ ims_client_ip=self.IMS_CLIENT_DEFAULT_IP,
+ ims_client_port=self.IMS_CLIENT_DEFAULT_PORT)
+ self.ims_client = ImsApiConnector(
+ self.uxm_ip,
+ self.api_connector_port,
+ self.IMS_CLIENT,
+ self.api_token,
+ self.ims_client_ip,
+ self.ims_client_port,
+ self.log
+ )
+
+ def setup_test(self):
+ # Enable NR if it is VoNR test case
+ self.log.info(f'test name: {self.test_name}')
+ if 'NR' in self.test_name:
+ self.log.info('Enable VoNR for UE.')
+ self.enable_ims_nr()
+ super().setup_test()
+
+ def power_ims_call_test(self):
+ """ Measures power during a VoLTE call.
+
+ Measurement step in this test. Starts the voice call and
+ initiates power measurement. Pass or fail is decided with a
+ threshold value. """
+ # create dedicated bearer
+ self.log.info('create dedicated bearer.')
+ if 'LTE' in self.test_name:
+ self.cellular_simulator.create_dedicated_bearer()
+
+ time.sleep(5)
+
+ # Initiate the voice call
+ self.log.info('Callbox initiates call to UE.')
+ self.ims_client.initiate_call('001010123456789')
+
+ time.sleep(5)
+
+ # pick up call
+ self.log.info('UE pick up call.')
+ self.dut.adb.shell('input keyevent KEYCODE_CALL')
+
+ # Mute the call
+ self.dut.droid.telecomCallMute()
+
+ # Turn of screen
+ self.dut.droid.goToSleepNow()
+
+ # Measure power
+ self.collect_power_data()
+
+ # End the call
+ hangup_call(self.log, self.dut)
+
+ # Check if power measurement is within the required values
+ self.pass_fail_check()
+
+ def teardown_test(self):
+ super().teardown_test()
+ #self.cellular_simulator.deregister_ue_ims()
+ self.ims_client.remove_ims_app_link()
+
+ def teardown_class(self):
+ super().teardown_class()
+ self.log.info('Disable IMS.')
+ self.dut.adb.shell(self.ADB_CMD_DISABLE_IMS)
+
+
+class PowerTelIms_Preset_Test(PowerTelImsPresetTest):
+ def test_preset_LTE_voice(self):
+ self.power_ims_call_test()
+
+ def test_preset_NR_voice(self):
+ self.power_ims_call_test()
diff --git a/acts_tests/tests/google/power/tel/PowerTelPdcchFr2_Preset_Test.py b/acts_tests/tests/google/power/tel/PowerTelPdcchFr2_Preset_Test.py
new file mode 100644
index 0000000..3f023f3
--- /dev/null
+++ b/acts_tests/tests/google/power/tel/PowerTelPdcchFr2_Preset_Test.py
@@ -0,0 +1,36 @@
+# Copyright 2022 - 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 acts_contrib.test_utils.power.cellular.cellular_power_preset_base_test as PB
+
+
+class PowerTelPdcchFr2_Preset_Test(PB.PowerCellularPresetLabBaseTest):
+ def power_pdcch_test(self):
+ """ Measures power during PDCCH only.
+
+ There's nothing to do here other than starting the power measurement
+ and deciding for pass or fail, as the base class will handle attaching.
+ Requirements for this test are that mac padding is off and that the
+ inactivity timer is not enabled. """
+
+ # Measure power
+ self.collect_power_data()
+
+ # Check if power measurement is within the required values
+ self.pass_fail_check(self.avg_current)
+
+ def test_preset_nsa_cdrx_fr2(self):
+ self.power_pdcch_test()
+
+ def test_preset_nsa_pdcch_fr2(self):
+ self.power_pdcch_test()
diff --git a/acts_tests/tests/google/power/tel/PowerTelPdcch_Preset_Test.py b/acts_tests/tests/google/power/tel/PowerTelPdcch_Preset_Test.py
index 1ee069a..2704638 100644
--- a/acts_tests/tests/google/power/tel/PowerTelPdcch_Preset_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelPdcch_Preset_Test.py
@@ -11,26 +11,38 @@
# 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 acts_contrib.test_utils.power.cellular.cellular_power_preset_base_test as PB
-from acts import context
-import acts_contrib.test_utils.power.cellular.cellular_pdcch_power_test as cppt
+class PowerTelPdcch_Preset_Test(PB.PowerCellularPresetLabBaseTest):
+ def power_pdcch_test(self):
+ """ Measures power during PDCCH only.
+ There's nothing to do here other than starting the power measurement
+ and deciding for pass or fail, as the base class will handle attaching.
+ Requirements for this test are that mac padding is off and that the
+ inactivity timer is not enabled. """
-class PowerTelPdcch_Preset_Test(cppt.PowerTelPDCCHTest):
- def test_preset_sa_pdcch(self):
+ # Measure power
+ self.collect_power_data()
+
+ # Check if power measurement is within the required values
+ self.pass_fail_check(self.avg_current)
+
+ def test_preset_sa_pdcch_fr1(self):
self.power_pdcch_test()
- def test_preset_nsa_pdcch(self):
+ def test_preset_nsa_pdcch_fr1(self):
self.power_pdcch_test()
def test_preset_LTE_pdcch(self):
self.power_pdcch_test()
- def test_preset_sa_cdrx(self):
+ def test_preset_sa_cdrx_fr1(self):
self.power_pdcch_test()
- def test_preset_nsa_cdrx(self):
+ def test_preset_nsa_cdrx_fr1(self):
self.power_pdcch_test()
def test_preset_LTE_cdrx(self):
self.power_pdcch_test()
+
diff --git a/acts_tests/tests/google/power/tel/PowerTelTraffic_Preset_Test.py b/acts_tests/tests/google/power/tel/PowerTelTraffic_Preset_Test.py
index ee30986..d05c801 100644
--- a/acts_tests/tests/google/power/tel/PowerTelTraffic_Preset_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelTraffic_Preset_Test.py
@@ -11,22 +11,24 @@
# 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 os
+import time
-import paramiko
-import acts_contrib.test_utils.power.cellular.cellular_power_base_test as PWCEL
+import acts_contrib.test_utils.power.cellular.cellular_power_preset_base_test as PB
-class PowerTelTrafficPresetTest(PWCEL.PowerCellularLabBaseTest):
+class PowerTelTrafficPresetTest(PB.PowerCellularPresetLabBaseTest):
+ # command to enable mobile data
+ ADB_CMD_ENABLE_MOBILE_DATA = 'svc data enable'
+
# command to start iperf server on UE
START_IPERF_SV_UE_CMD = 'nohup > /dev/null 2>&1 sh -c "iperf3 -s -i1 -p5201 > /dev/null &"'
# command to start iperf server on UE
# (require: 1.path to iperf exe 2.hostname/hostIP)
- START_IPERF_CLIENT_UE_CMD = (
- 'nohup > /dev/null 2>&1 sh -c '
- '"iperf3 -c {iperf_host_ip} -i1 -p5202 -w8m -t2000 > /dev/null &"')
+ START_IPERF_CLIENT_UE_CMD = 'nohup > /dev/null 2>&1 sh -c "iperf3 -c {iperf_host_ip} -i1 -p5202 -w8m -t2000 > /dev/null &"'
- #command to start iperf server on host()
+ # command to start iperf server on host()
START_IPERF_SV_HOST_CMD = '{exe_path}\\iperf3 -s -p5202'
# command to start iperf client on host
@@ -34,58 +36,56 @@
START_IPERF_CLIENT_HOST_CMD = (
'{exe_path}\\iperf3 -c {ue_ip} -w16M -t1000 -p5201')
+ START_IPERF_CLIENT_HOST_CMD_FR2 = (
+ '{exe_path}\\iperf3 -c {ue_ip} -w16M -t1000 -p5201 -P32')
+
def __init__(self, controllers):
super().__init__(controllers)
self.ssh_iperf_client = None
self.ssh_iperf_server = None
+ self.iperf_out_err = {}
def setup_class(self):
super().setup_class()
# Unpack test parameters used in this class
- self.unpack_userparams(ssh_host_ip=None,
- host_username=None,
- client_ssh_private_key_file=None,
- iperf_exe_path=None,
+ self.unpack_userparams(iperf_exe_path=None,
ue_ip=None,
iperf_host_ip=None)
# Verify required config
- for param in ('ssh_host_ip', 'host_username', 'client_ssh_private_key_file',
- 'iperf_exe_path', 'ue_ip', 'iperf_host_ip'):
+ for param in ('iperf_exe_path', 'ue_ip', 'iperf_host_ip'):
if getattr(self, param) is None:
raise RuntimeError(
f'Parameter "{param}" is required to run this type of test')
def setup_test(self):
# Call parent method first to setup simulation
- if not super().setup_test():
- return False
+ super().setup_test()
# setup ssh client
- self.ssh_iperf_client = self._create_ssh_client()
- self.ssh_iperf_server = self._create_ssh_client()
+ self.ssh_iperf_client = self.cellular_simulator.create_ssh_client()
+ self.ssh_iperf_server = self.cellular_simulator.create_ssh_client()
+
+ self.turn_on_mobile_data()
def power_tel_traffic_test(self):
"""Measure power while data is transferring."""
# Start data traffic
- self.start_downlink_process()
self.start_uplink_process()
+ time.sleep(5)
+ self.start_downlink_process()
# Measure power
self.collect_power_data()
- def _create_ssh_client(self):
- """Create a ssh client to host."""
- ssh = paramiko.SSHClient()
- ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- mykey = paramiko.Ed25519Key.from_private_key_file(
- self.client_ssh_private_key_file)
- ssh.connect(hostname=self.ssh_host_ip,
- username=self.host_username,
- pkey=mykey)
- self.log.info('SSH client to %s is connected' % self.ssh_host_ip)
- return ssh
+ # Write iperf log
+ self.ssh_iperf_server.close()
+ uplink_log_name = self.test_name + '_uplink.txt'
+ self._write_iperf_log(uplink_log_name, self.ssh_iperf_server)
+ self.ssh_iperf_client.close()
+ downlink_log_name = self.test_name + '_downlink.txt'
+ self._write_iperf_log(downlink_log_name, self.ssh_iperf_client)
def _exec_ssh_cmd(self, ssh_client, cmd):
"""Execute command on given ssh client.
@@ -95,24 +95,34 @@
cmd: command to execute via ssh.
"""
self.log.info('Sending cmd to ssh host: ' + cmd)
- stdin, _, _ = ssh_client.exec_command(cmd, get_pty=True)
+ stdin, stdout, stderr = ssh_client.exec_command(cmd, get_pty=True)
stdin.close()
- # TODO: stdout.readline cause program to hang
- # implement a workaround to getting response
- # from executed command
+ self.iperf_out_err[ssh_client] = (stdout, stderr)
def start_downlink_process(self):
"""UE transfer data to host."""
self.log.info('Start downlink process')
# start UE iperf server
self.cellular_dut.ad.adb.shell(self.START_IPERF_SV_UE_CMD)
+ self.log.info('cmd sent to UE: ' + self.START_IPERF_SV_UE_CMD)
self.log.info('UE iperf server started')
+ time.sleep(5)
# start host iperf client
- cmd = self.START_IPERF_CLIENT_HOST_CMD.format(
- exe_path=self.iperf_exe_path,
- ue_ip=self.ue_ip)
+ cmd = None
+ if 'fr2' in self.test_name:
+ cmd = self.START_IPERF_CLIENT_HOST_CMD_FR2.format(
+ exe_path=self.iperf_exe_path,
+ ue_ip=self.ue_ip)
+ else:
+ cmd = self.START_IPERF_CLIENT_HOST_CMD.format(
+ exe_path=self.iperf_exe_path,
+ ue_ip=self.ue_ip)
+
+ if not cmd:
+ raise RuntimeError('Cannot format command to start iperf client.')
self._exec_ssh_cmd(self.ssh_iperf_client, cmd)
self.log.info('Host iperf client started')
+ time.sleep(5)
def start_uplink_process(self):
"""Host transfer data to UE."""
@@ -121,13 +131,48 @@
cmd = self.START_IPERF_SV_HOST_CMD.format(exe_path=self.iperf_exe_path)
self._exec_ssh_cmd(self.ssh_iperf_server, cmd)
self.log.info('Host iperf server started')
+ time.sleep(5)
# start UE iperf
- self.cellular_dut.ad.adb.shell(
- self.START_IPERF_CLIENT_UE_CMD.format(iperf_host_ip=self.iperf_host_ip))
+ adb_cmd = self.START_IPERF_CLIENT_UE_CMD.format(
+ iperf_host_ip=self.iperf_host_ip)
+ self.cellular_dut.ad.adb.shell(adb_cmd)
+ self.log.info('cmd sent to UE: ' + adb_cmd)
self.log.info('UE iperf client started')
+ time.sleep(5)
+
+ def _write_iperf_log(self, file_name, ssh):
+ """ Writing ssh stdout and stdin to log file.
+
+ Args:
+ file_name: log file name to write log to.
+ ssh: paramiko client object.
+ """
+ iperf_log_dir = os.path.join(self.root_output_path, 'iperf')
+ os.makedirs(iperf_log_dir, exist_ok=True)
+ iperf_log_file_path = os.path.join(iperf_log_dir, file_name)
+ with open(iperf_log_file_path, 'w') as f:
+ out, err = self.iperf_out_err[ssh]
+ out_content = ''.join(out.readlines())
+ err_content = ''.join(err.readlines())
+ f.write(out_content)
+ f.write('\nErrors:\n')
+ f.write(err_content)
+
+ def turn_on_mobile_data(self):
+ self.dut.adb.shell(self.ADB_CMD_ENABLE_MOBILE_DATA)
class PowerTelTraffic_Preset_Test(PowerTelTrafficPresetTest):
-
def test_preset_LTE_traffic(self):
self.power_tel_traffic_test()
+
+ def test_preset_nsa_traffic_fr1(self):
+ self.power_tel_traffic_test()
+
+ def test_preset_sa_traffic_fr1(self):
+ self.power_tel_traffic_test()
+
+
+class PowerTelTrafficFr2_Preset_Test(PowerTelTrafficPresetTest):
+ def test_preset_nsa_traffic_fr2(self):
+ self.power_tel_traffic_test()
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py b/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py
index 7e7c3b8..e0013b1 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py
@@ -23,6 +23,7 @@
from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils
from acts_contrib.test_utils.power.IperfHelper import IperfHelper
+WAIT_TIME_BEFORE_CONNECT = 10
class PowerWiFiHotspotTest(PWBT.PowerWiFiBaseTest):
""" WiFi Tethering HotSpot Power test.
@@ -60,6 +61,7 @@
Configures the Hotspot SSID
"""
super().setup_class()
+ self.set_attenuation([0, 0, 0, 0])
# If an SSID and password are indicated in the configuration parameters,
# use those. If not, use default parameters and warn the user.
@@ -101,6 +103,15 @@
super().setup_test()
wutils.reset_wifi(self.android_devices[1])
+ def teardown_test(self):
+ # Tearing down tethering on dut
+ if self.dut.droid.isSdkAtLeastS():
+ hotspot_cmd = "cmd wifi stop-softap " + str(self.network[wutils.WifiEnums.SSID_KEY]) + \
+ " wpa2 " + str(self.network[wutils.WifiEnums.PWD_KEY]) + " -b " + \
+ str(list(self.test_configs.band)[0])
+ self.dut.adb.shell(hotspot_cmd)
+ wutils.reset_wifi(self.android_devices[1])
+
def setup_hotspot(self, connect_client=False):
"""Configure Hotspot and connects client device.
@@ -134,12 +145,21 @@
self.main_network[self.test_configs.wifi_sharing])
# Setup tethering on dut
- wutils.start_wifi_tethering(
- self.dut, self.network[wutils.WifiEnums.SSID_KEY],
- self.network[wutils.WifiEnums.PWD_KEY], wifi_band_id)
+ if self.dut.droid.isSdkAtLeastS():
+ # Setup tethering on dut with adb command.
+ hotspot_cmd = "cmd wifi start-softap " + str(self.network[wutils.WifiEnums.SSID_KEY]) + \
+ " wpa2 " + str(self.network[wutils.WifiEnums.PWD_KEY]) + " -b " + \
+ str(list(self.test_configs.band)[0])
+ self.log.info(str(hotspot_cmd))
+ self.dut.adb.shell(hotspot_cmd)
+ else:
+ wutils.start_wifi_tethering(
+ self.dut, self.network[wutils.WifiEnums.SSID_KEY],
+ self.network[wutils.WifiEnums.PWD_KEY], wifi_band_id)
# Connect client device to Hotspot
if connect_client:
+ time.sleep(WAIT_TIME_BEFORE_CONNECT)
wutils.wifi_connect(
self.android_devices[1],
self.network,
@@ -168,7 +188,14 @@
self.client_iperf_helper.process_iperf_results(
self.dut, self.log, self.iperf_servers, self.test_name)
- self.pass_fail_check(self.avg_current)
+ if hasattr(self, 'bitses'):
+ """
+ If measurement is taken through BITS, metric value is avg_power,
+ else metric value is avg_current.
+ """
+ self.pass_fail_check(self.power_result.metric_value)
+ else:
+ self.pass_fail_check(self.avg_current)
def power_idle_tethering_test(self):
""" Start power test when Hotspot is idle
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFiscanTest.py b/acts_tests/tests/google/power/wifi/PowerWiFiscanTest.py
index 72f733f..da1c67d 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFiscanTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFiscanTest.py
@@ -18,10 +18,20 @@
from acts.test_decorators import test_tracker_info
from acts_contrib.test_utils.power import PowerWiFiBaseTest as PWBT
from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.wifi_power_test_utils import CHRE_WIFI_SCAN_TYPE
+from acts_contrib.test_utils.wifi.wifi_power_test_utils import CHRE_WIFI_RADIO_CHAIN
+from acts_contrib.test_utils.wifi.wifi_power_test_utils import CHRE_WIFI_CHANNEL_SET
UNLOCK_SCREEN = 'input keyevent 82'
LOCATION_ON = 'settings put secure location_mode 3'
-
+ENABLE_WIFI_SCANNING = 'cmd wifi enable-scanning enabled'
+DISABLE_WIFI_SCANNING = 'cmd wifi enable-scanning disabled'
+CHRE_POWER_TEST_CLIENT = 'chre_power_test_client'
+UNLOAD_ALL_CHRE_PRODUCTION_APP = CHRE_POWER_TEST_CLIENT + ' unloadall'
+LOAD_CHRE_TEST_NANOAPP = CHRE_POWER_TEST_CLIENT + ' load'
+DISABLE_CHRE_WIFI_SCAN = CHRE_POWER_TEST_CLIENT + ' wifi disable'
+CHRE_SCAN_INTERVAL = 5
+NS = 1000000000
class PowerWiFiscanTest(PWBT.PowerWiFiBaseTest):
def setup_class(self):
@@ -71,6 +81,25 @@
atten_setting = self.test_configs.wifi_band + '_roaming'
self.set_attenuation(self.atten_level[atten_setting])
+ def chre_scan_setup(self):
+ self.dut.adb.shell("cmd wifi force-country-code enabled US")
+ time.sleep(1)
+ country_code = self.dut.adb.shell("cmd wifi get-country-code")
+ if "US" in country_code:
+ self.log.info("Country-Code is set to US")
+ else:
+ self.log.warning("Country Code is : " + str(country_code))
+ self.dut.adb.shell(DISABLE_WIFI_SCANNING)
+ self.dut.adb.shell(UNLOAD_ALL_CHRE_PRODUCTION_APP)
+ self.dut.adb.shell(LOAD_CHRE_TEST_NANOAPP)
+ chre_wifi_scan_trigger_cmd = CHRE_POWER_TEST_CLIENT + ' wifi enable ' + \
+ str(CHRE_SCAN_INTERVAL*NS) + \
+ " " + CHRE_WIFI_SCAN_TYPE[self.test_configs.wifi_scan_type] + " " + \
+ CHRE_WIFI_RADIO_CHAIN[self.test_configs.wifi_radio_chain] + " " + \
+ CHRE_WIFI_CHANNEL_SET[self.test_configs.wifi_channel_set]
+ self.dut.log.info(chre_wifi_scan_trigger_cmd)
+ self.dut.adb.shell(chre_wifi_scan_trigger_cmd)
+
def wifi_scan_test_func(self):
attrs = [
@@ -94,6 +123,22 @@
self.scan_setup()
self.measure_power_and_validate()
+ def chre_wifi_scan_test_func(self):
+ attrs = [
+ 'screen_status', 'wifi_scan_type', 'wifi_radio_chain', 'wifi_channel_set'
+ ]
+ indices = [2, 6, 7, 8]
+ self.decode_test_configs(attrs, indices)
+ wutils.wifi_toggle_state(self.dut, True)
+ if self.test_configs.screen_status == 'OFF':
+ self.dut.droid.goToSleepNow()
+ self.dut.log.info('Screen is OFF')
+ time.sleep(5)
+ self.chre_scan_setup()
+ self.measure_power_and_validate()
+ self.dut.adb.shell(DISABLE_CHRE_WIFI_SCAN)
+ self.dut.adb.shell(ENABLE_WIFI_SCANNING)
+
# Test cases
# Power.apk triggered singleshot scans
@test_tracker_info(uuid='e5539b01-e208-43c6-bebf-6f1e73d8d8cb')
@@ -157,3 +202,52 @@
"""
self.wifi_scan_test_func()
+
+ def test_screen_OFF_CHRE_wifi_scan_activePassiveDfs_highAccuracy_all(self):
+ """
+ Trigger CHRE based scan for the following parameters :
+ wifi_scan_type : activePassiveDfs
+ wifi_radio_chain : highAccuracy
+ wifi_channel_set : all
+ """
+ self.chre_wifi_scan_test_func()
+
+
+ def test_screen_OFF_CHRE_wifi_scan_activePassiveDfs_lowLatency_all(self):
+ """
+ Trigger CHRE based scan for the following parameters :
+ wifi_scan_type : activePassiveDfs
+ wifi_radio_chain : lowLatency
+ wifi_channel_set : all
+ """
+ self.chre_wifi_scan_test_func()
+
+
+ def test_screen_OFF_CHRE_wifi_scan_noPreference_lowPower_all(self):
+ """
+ Trigger CHRE based scan for the following parameters :
+ wifi_scan_type : noPreference
+ wifi_radio_chain : lowPower
+ wifi_channel_set : all
+ """
+ self.chre_wifi_scan_test_func()
+
+
+ def test_screen_OFF_CHRE_wifi_scan_passive_lowLatency_all(self):
+ """
+ Trigger CHRE based scan for the following parameters :
+ wifi_scan_type : passive
+ wifi_radio_chain : lowLatency
+ wifi_channel_set : all
+ """
+ self.chre_wifi_scan_test_func()
+
+
+ def test_screen_OFF_CHRE_wifi_scan_passive_highAccuracy_all(self):
+ """
+ Trigger CHRE based scan for the following parameters :
+ wifi_scan_type : passive
+ wifi_radio_chain : highAccuracy
+ wifi_channel_set : all
+ """
+ self.chre_wifi_scan_test_func()
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/WifiBridgedApTest.py b/acts_tests/tests/google/wifi/WifiBridgedApTest.py
index 01cb744..d98a7cb 100644
--- a/acts_tests/tests/google/wifi/WifiBridgedApTest.py
+++ b/acts_tests/tests/google/wifi/WifiBridgedApTest.py
@@ -57,7 +57,10 @@
self.client1 = self.android_devices[1]
self.client2 = self.android_devices[2]
else:
- raise signals.TestFailure("WifiBridgedApTest requires 3 DUTs")
+ raise signals.TestAbortClass("WifiBridgedApTest requires 3 DUTs")
+
+ if not self.dut.droid.wifiIsBridgedApConcurrencySupported():
+ raise signals.TestAbortClass("Legacy phone is not supported")
req_params = ["dbs_supported_models"]
opt_param = []
@@ -1329,4 +1332,4 @@
self.dut, [WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G], False)
# Restore config
- wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
+ wutils.save_wifi_soft_ap_config(self.dut, original_softap_config)
\ No newline at end of file
diff --git a/acts_tests/tests/google/wifi/WifiPingTest.py b/acts_tests/tests/google/wifi/WifiPingTest.py
index 096a935..ca1ccd7 100644
--- a/acts_tests/tests/google/wifi/WifiPingTest.py
+++ b/acts_tests/tests/google/wifi/WifiPingTest.py
@@ -504,26 +504,56 @@
"""
# Configure AP
self.setup_ap(testcase_params)
- # Set attenuator to 0 dB
+ # Set attenuator to starting attenuation
+ band = wputils.CHANNEL_TO_BAND_MAP[testcase_params['channel']]
for attenuator in self.attenuators:
- attenuator.set_atten(testcase_params['atten_range'][0],
- strict=False,
- retry=True)
+ attenuator.set_atten(
+ self.testclass_params['range_atten_start'].get(band, 0),
+ strict=False,
+ retry=True)
# Reset, configure, and connect DUT
self.setup_dut(testcase_params)
def get_range_start_atten(self, testcase_params):
"""Gets the starting attenuation for this ping test.
- This function is used to get the starting attenuation for ping range
- tests. This implementation returns the default starting attenuation,
- however, defining this function enables a more involved configuration
- for over-the-air test classes.
+ The function gets the starting attenuation by checking whether a test
+ at the same configuration has executed. If so it sets the starting
+ point a configurable number of dBs below the reference test.
Args:
- testcase_params: dict containing all test params
+ testcase_params: dict containing all test parameters
+ Returns:
+ start_atten: starting attenuation for current test
"""
- return self.testclass_params['range_atten_start']
+ band = wputils.CHANNEL_TO_BAND_MAP[testcase_params['channel']]
+ # If the test is being retried, start from the beginning
+ if self.retry_flag:
+ self.log.info('Retry flag set. Setting attenuation to minimum.')
+ return self.testclass_params['range_atten_start'].get(band, 0)
+ # Get the current and reference test config. The reference test is the
+ # one performed at the current MCS+1
+ ref_test_params = wputils.extract_sub_dict(
+ testcase_params, testcase_params['reference_params'])
+ # Check if reference test has been run and set attenuation accordingly
+ previous_params = [
+ wputils.extract_sub_dict(result['testcase_params'],
+ testcase_params['reference_params'])
+ for result in self.testclass_results
+ ]
+ try:
+ ref_index = previous_params[::-1].index(ref_test_params)
+ ref_index = len(previous_params) - 1 - ref_index
+ start_atten = self.testclass_results[ref_index][
+ 'atten_at_range'] - (
+ self.testclass_params['adjacent_range_test_gap'])
+ except ValueError:
+ start_atten = self.testclass_params['range_atten_start'].get(
+ band, 0)
+ self.log.info(
+ 'Reference test not found. Starting from {} dB'.format(
+ start_atten))
+ return start_atten
def compile_test_params(self, testcase_params):
# Check if test should be skipped.
@@ -534,6 +564,7 @@
band = self.access_point.band_lookup_by_channel(
testcase_params['channel'])
testcase_params['test_network'] = self.main_network[band]
+ testcase_params['band'] = band
if testcase_params['chain_mask'] in ['0', '1']:
testcase_params['attenuated_chain'] = 'DUT-Chain-{}'.format(
1 if testcase_params['chain_mask'] == '0' else 0)
@@ -595,7 +626,7 @@
self.pass_fail_check(ping_result)
def generate_test_cases(self, ap_power, channels, modes, chain_mask,
- test_types):
+ test_types, **kwargs):
"""Function that auto-generates test cases for a test class."""
test_cases = []
allowed_configs = {
@@ -620,7 +651,8 @@
channel=channel,
mode=mode,
bandwidth=bandwidth,
- chain_mask=chain)
+ chain_mask=chain,
+ **kwargs)
setattr(self, testcase_name,
partial(self._test_ping, testcase_params))
test_cases.append(testcase_name)
@@ -628,46 +660,50 @@
class WifiPing_TwoChain_Test(WifiPingTest):
+
def __init__(self, controllers):
super().__init__(controllers)
- self.tests = self.generate_test_cases(ap_power='standard',
- channels=[
- 1, 6, 11, 36, 40, 44, 48,
- 149, 153, 157, 161, '6g37',
- '6g117', '6g213'
- ],
- modes=['bw20', 'bw40', 'bw80'],
- test_types=[
- 'test_ping_range',
- 'test_fast_ping_rtt',
- 'test_slow_ping_rtt'
- ],
- chain_mask=['2x2'])
+ self.tests = self.generate_test_cases(
+ ap_power='standard',
+ channels=[
+ 1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161, '6g37', '6g117',
+ '6g213'
+ ],
+ modes=['bw20', 'bw80', 'bw160'],
+ test_types=[
+ 'test_ping_range', 'test_fast_ping_rtt', 'test_slow_ping_rtt'
+ ],
+ chain_mask=['2x2'],
+ reference_params=['band', 'chain_mask'])
class WifiPing_PerChainRange_Test(WifiPingTest):
+
def __init__(self, controllers):
super().__init__(controllers)
- self.tests = self.generate_test_cases(ap_power='standard',
- chain_mask=['0', '1', '2x2'],
- channels=[
- 1, 6, 11, 36, 40, 44, 48,
- 149, 153, 157, 161, '6g37',
- '6g117', '6g213'
- ],
- modes=['bw20', 'bw40', 'bw80'],
- test_types=['test_ping_range'])
+ self.tests = self.generate_test_cases(
+ ap_power='standard',
+ chain_mask=['0', '1', '2x2'],
+ channels=[
+ 1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161, '6g37', '6g117',
+ '6g213'
+ ],
+ modes=['bw20', 'bw80', 'bw160'],
+ test_types=['test_ping_range'],
+ reference_params=['band', 'chain_mask'])
class WifiPing_LowPowerAP_Test(WifiPingTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
ap_power='low_power',
chain_mask=['0', '1', '2x2'],
channels=[1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
- modes=['bw20', 'bw40', 'bw80'],
- test_types=['test_ping_range'])
+ modes=['bw20', 'bw80'],
+ test_types=['test_ping_range'],
+ reference_params=['band', 'chain_mask'])
# Over-the air version of ping tests
@@ -678,6 +714,7 @@
setting turntable orientation and other chamber parameters to study
performance in varying channel conditions
"""
+
def __init__(self, controllers):
base_test.BaseTestClass.__init__(self, controllers)
self.testcase_metric_logger = (
@@ -788,45 +825,8 @@
# Continue setting up ping test
WifiPingTest.setup_ping_test(self, testcase_params)
- def get_range_start_atten(self, testcase_params):
- """Gets the starting attenuation for this ping test.
-
- The function gets the starting attenuation by checking whether a test
- at the same configuration has executed. If so it sets the starting
- point a configurable number of dBs below the reference test.
-
- Returns:
- start_atten: starting attenuation for current test
- """
- # If the test is being retried, start from the beginning
- if self.retry_flag:
- self.log.info('Retry flag set. Setting attenuation to minimum.')
- return self.testclass_params['range_atten_start']
- # Get the current and reference test config. The reference test is the
- # one performed at the current MCS+1
- ref_test_params = wputils.extract_sub_dict(
- testcase_params, ['channel', 'mode', 'chain_mask'])
- # Check if reference test has been run and set attenuation accordingly
- previous_params = [
- wputils.extract_sub_dict(result['testcase_params'],
- ['channel', 'mode', 'chain_mask'])
- for result in self.testclass_results
- ]
- try:
- ref_index = previous_params[::-1].index(ref_test_params)
- ref_index = len(previous_params) - 1 - ref_index
- start_atten = self.testclass_results[ref_index][
- 'atten_at_range'] - (
- self.testclass_params['adjacent_range_test_gap'])
- except ValueError:
- self.log.info(
- 'Reference test not found. Starting from {} dB'.format(
- self.testclass_params['range_atten_start']))
- start_atten = self.testclass_params['range_atten_start']
- return start_atten
-
def generate_test_cases(self, ap_power, channels, modes, chain_masks,
- chamber_mode, positions):
+ chamber_mode, positions, **kwargs):
test_cases = []
allowed_configs = {
20: [
@@ -853,7 +853,8 @@
chain_mask=chain_mask,
chamber_mode=chamber_mode,
total_positions=len(positions),
- position=position)
+ position=position,
+ **kwargs)
setattr(self, testcase_name,
partial(self._test_ping, testcase_params))
test_cases.append(testcase_name)
@@ -861,6 +862,7 @@
class WifiOtaPing_TenDegree_Test(WifiOtaPingTest):
+
def __init__(self, controllers):
WifiOtaPingTest.__init__(self, controllers)
self.tests = self.generate_test_cases(
@@ -869,49 +871,57 @@
modes=['bw20'],
chain_masks=['2x2'],
chamber_mode='orientation',
- positions=list(range(0, 360, 10)))
+ positions=list(range(0, 360, 10)),
+ reference_params=['channel', 'mode', 'chain_mask'])
class WifiOtaPing_45Degree_Test(WifiOtaPingTest):
+
def __init__(self, controllers):
WifiOtaPingTest.__init__(self, controllers)
- self.tests = self.generate_test_cases(ap_power='standard',
- channels=[
- 1, 6, 11, 36, 40, 44, 48,
- 149, 153, 157, 161, '6g37',
- '6g117', '6g213'
- ],
- modes=['bw20'],
- chain_masks=['2x2'],
- chamber_mode='orientation',
- positions=list(range(0, 360,
- 45)))
+ self.tests = self.generate_test_cases(
+ ap_power='standard',
+ channels=[
+ 1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161, '6g37', '6g117',
+ '6g213'
+ ],
+ modes=['bw20'],
+ chain_masks=['2x2'],
+ chamber_mode='orientation',
+ positions=list(range(0, 360, 45)),
+ reference_params=['channel', 'mode', 'chain_mask'])
class WifiOtaPing_SteppedStirrers_Test(WifiOtaPingTest):
+
def __init__(self, controllers):
WifiOtaPingTest.__init__(self, controllers)
- self.tests = self.generate_test_cases(ap_power='standard',
- channels=[6, 36, 149],
- modes=['bw20'],
- chain_masks=['2x2'],
- chamber_mode='stepped stirrers',
- positions=list(range(100)))
+ self.tests = self.generate_test_cases(
+ ap_power='standard',
+ channels=[6, 36, 149],
+ modes=['bw20'],
+ chain_masks=['2x2'],
+ chamber_mode='stepped stirrers',
+ positions=list(range(100)),
+ reference_params=['channel', 'mode', 'chain_mask'])
class WifiOtaPing_LowPowerAP_TenDegree_Test(WifiOtaPingTest):
+
def __init__(self, controllers):
WifiOtaPingTest.__init__(self, controllers)
- self.tests = self.generate_test_cases(ap_power='low_power',
- channels=[6, 36, 149],
- modes=['bw20'],
- chain_masks=['2x2'],
- chamber_mode='orientation',
- positions=list(range(0, 360,
- 10)))
+ self.tests = self.generate_test_cases(
+ ap_power='low_power',
+ channels=[6, 36, 149],
+ modes=['bw20'],
+ chain_masks=['2x2'],
+ chamber_mode='orientation',
+ positions=list(range(0, 360, 10)),
+ reference_params=['channel', 'mode', 'chain_mask'])
class WifiOtaPing_LowPowerAP_45Degree_Test(WifiOtaPingTest):
+
def __init__(self, controllers):
WifiOtaPingTest.__init__(self, controllers)
self.tests = self.generate_test_cases(
@@ -920,33 +930,40 @@
modes=['bw20'],
chain_masks=['2x2'],
chamber_mode='orientation',
- positions=list(range(0, 360, 45)))
+ positions=list(range(0, 360, 45)),
+ reference_params=['channel', 'mode', 'chain_mask'])
class WifiOtaPing_LowPowerAP_SteppedStirrers_Test(WifiOtaPingTest):
+
def __init__(self, controllers):
WifiOtaPingTest.__init__(self, controllers)
- self.tests = self.generate_test_cases(ap_power='low_power',
- channels=[6, 36, 149],
- modes=['bw20'],
- chain_masks=['2x2'],
- chamber_mode='stepped stirrers',
- positions=list(range(100)))
+ self.tests = self.generate_test_cases(
+ ap_power='low_power',
+ channels=[6, 36, 149],
+ modes=['bw20'],
+ chain_masks=['2x2'],
+ chamber_mode='stepped stirrers',
+ positions=list(range(100)),
+ reference_params=['channel', 'mode', 'chain_mask'])
class WifiOtaPing_LowPowerAP_PerChain_TenDegree_Test(WifiOtaPingTest):
+
def __init__(self, controllers):
WifiOtaPingTest.__init__(self, controllers)
- self.tests = self.generate_test_cases(ap_power='low_power',
- channels=[6, 36, 149],
- modes=['bw20'],
- chain_masks=[0, 1, '2x2'],
- chamber_mode='orientation',
- positions=list(range(0, 360,
- 10)))
+ self.tests = self.generate_test_cases(
+ ap_power='low_power',
+ channels=[6, 36, 149],
+ modes=['bw20'],
+ chain_masks=[0, 1, '2x2'],
+ chamber_mode='orientation',
+ positions=list(range(0, 360, 10)),
+ reference_params=['channel', 'mode', 'chain_mask'])
class WifiOtaPing_PerChain_TenDegree_Test(WifiOtaPingTest):
+
def __init__(self, controllers):
WifiOtaPingTest.__init__(self, controllers)
self.tests = self.generate_test_cases(
@@ -955,4 +972,5 @@
modes=['bw20'],
chain_masks=[0, 1, '2x2'],
chamber_mode='orientation',
- positions=list(range(0, 360, 10)))
+ positions=list(range(0, 360, 10)),
+ reference_params=['channel', 'mode', 'chain_mask'])
diff --git a/acts_tests/tests/google/wifi/WifiRssiTest.py b/acts_tests/tests/google/wifi/WifiRssiTest.py
index 06eed43..452591f 100644
--- a/acts_tests/tests/google/wifi/WifiRssiTest.py
+++ b/acts_tests/tests/google/wifi/WifiRssiTest.py
@@ -53,6 +53,7 @@
configurable attenuation waveforms.For an example config file to run this
test class see example_connectivity_performance_ap_sta.json.
"""
+
def __init__(self, controllers):
base_test.BaseTestClass.__init__(self, controllers)
self.testcase_metric_logger = (
@@ -494,12 +495,12 @@
thread_future = wputils.get_ping_stats_nb(
self.remote_server, self.dut_ip,
testcase_params['traffic_timeout'], 0.5, 64)
+ llstats_obj.update_stats()
for atten in testcase_params['rssi_atten_range']:
# Set Attenuation
self.log.info('Setting attenuation to {} dB'.format(atten))
for attenuator in self.attenuators:
attenuator.set_atten(atten)
- llstats_obj.update_stats()
current_rssi = collections.OrderedDict()
current_rssi = wputils.get_connected_rssi(
self.dut, testcase_params['connected_measurements'],
@@ -633,9 +634,13 @@
testclass_params['rssi_vs_atten_connected_measurements'],
scan_measurements=self.
testclass_params['rssi_vs_atten_scan_measurements'],
- first_measurement_delay=MED_SLEEP,
- rssi_under_test=self.testclass_params['rssi_vs_atten_metrics'],
+ first_measurement_delay=SHORT_SLEEP,
absolute_accuracy=1)
+ rssi_under_test = self.testclass_params['rssi_vs_atten_metrics']
+ if self.testclass_params[
+ 'rssi_vs_atten_scan_measurements'] == 0 and 'scan_rssi' in rssi_under_test:
+ rssi_under_test.remove('scan_rssi')
+ testcase_params['rssi_under_test'] = rssi_under_test
testcase_params['band'] = self.access_point.band_lookup_by_channel(
testcase_params['channel'])
@@ -677,7 +682,7 @@
self.testclass_params['rssi_stability_duration'] /
self.testclass_params['polling_frequency']),
scan_measurements=0,
- first_measurement_delay=MED_SLEEP,
+ first_measurement_delay=SHORT_SLEEP,
rssi_atten_range=self.testclass_params['rssi_stability_atten'])
testcase_params['band'] = self.access_point.band_lookup_by_channel(
testcase_params['channel'])
@@ -846,6 +851,7 @@
class WifiRssi_2GHz_ActiveTraffic_Test(WifiRssiTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -854,6 +860,7 @@
class WifiRssi_5GHz_ActiveTraffic_Test(WifiRssiTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -863,6 +870,7 @@
class WifiRssi_AllChannels_ActiveTraffic_Test(WifiRssiTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -873,6 +881,7 @@
class WifiRssi_SampleChannels_NoTraffic_Test(WifiRssiTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -881,6 +890,7 @@
class WifiRssiTrackingTest(WifiRssiTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(['test_rssi_tracking'],
@@ -897,6 +907,7 @@
It allows setting orientation and other chamber parameters to study
performance in varying channel conditions
"""
+
def __init__(self, controllers):
base_test.BaseTestClass.__init__(self, controllers)
self.testcase_metric_logger = (
@@ -1027,7 +1038,7 @@
testcase_params.update(connected_measurements=int(
rssi_test_duration / self.testclass_params['polling_frequency']),
scan_measurements=0,
- first_measurement_delay=MED_SLEEP,
+ first_measurement_delay=SHORT_SLEEP,
rssi_atten_range=rssi_ota_test_attenuation)
testcase_params['band'] = self.access_point.band_lookup_by_channel(
testcase_params['channel'])
@@ -1100,6 +1111,7 @@
class WifiOtaRssi_Accuracy_Test(WifiOtaRssiTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(['test_rssi_vs_atten'],
@@ -1110,6 +1122,7 @@
class WifiOtaRssi_StirrerVariation_Test(WifiOtaRssiTest):
+
def __init__(self, controllers):
WifiRssiTest.__init__(self, controllers)
self.tests = self.generate_test_cases(['test_rssi_variation'],
@@ -1119,6 +1132,7 @@
class WifiOtaRssi_TenDegree_Test(WifiOtaRssiTest):
+
def __init__(self, controllers):
WifiRssiTest.__init__(self, controllers)
self.tests = self.generate_test_cases(['test_rssi_over_orientation'],
diff --git a/acts_tests/tests/google/wifi/WifiRvrTest.py b/acts_tests/tests/google/wifi/WifiRvrTest.py
index ffa52d5..4a64552 100644
--- a/acts_tests/tests/google/wifi/WifiRvrTest.py
+++ b/acts_tests/tests/google/wifi/WifiRvrTest.py
@@ -137,14 +137,23 @@
primary_y_label='Throughput (Mbps)')
plots[plot_id].add_line(result['total_attenuation'],
result['throughput_receive'],
- result['test_name'],
+ result['test_name'].strip('test_rvr_'),
hover_text=result['hover_text'],
marker='circle')
plots[plot_id].add_line(result['total_attenuation'],
- result['avg_phy_rate'],
- result['test_name'] + ' (PHY)',
+ result['rx_phy_rate'],
+ result['test_name'].strip('test_rvr_') +
+ ' (Rx PHY)',
hover_text=result['hover_text'],
- marker='circle')
+ style='dashed',
+ marker='inverted_triangle')
+ plots[plot_id].add_line(result['total_attenuation'],
+ result['tx_phy_rate'],
+ result['test_name'].strip('test_rvr_') +
+ ' (Tx PHY)',
+ hover_text=result['hover_text'],
+ style='dashed',
+ marker='triangle')
figure_list = []
for plot_id, plot in plots.items():
@@ -311,39 +320,36 @@
) for rssi in rvr_result['rssi']
]
}
- if 'DL' in self.current_test_name:
- rvr_result['avg_phy_rate'] = [
- curr_llstats['summary'].get('mean_rx_phy_rate', 0)
- for curr_llstats in rvr_result['llstats']
- ]
- else:
- rvr_result['avg_phy_rate'] = [
- curr_llstats['summary'].get('mean_tx_phy_rate', 0)
- for curr_llstats in rvr_result['llstats']
- ]
+
figure.add_line(rvr_result['total_attenuation'],
rvr_result['throughput_receive'],
'Measured Throughput',
hover_text=rvr_result['hover_text'],
- color='red',
+ color='black',
marker='circle')
- rvr_result['avg_phy_rate'].extend(
- [0] * (len(rvr_result['total_attenuation']) -
- len(rvr_result['avg_phy_rate'])))
- figure.add_line(rvr_result['total_attenuation'],
- rvr_result['avg_phy_rate'],
- 'Average PHY Rate',
- hover_text=rvr_result['hover_text'],
- color='red',
- style='dashed',
- marker='square')
+ figure.add_line(
+ rvr_result['total_attenuation'][0:len(rvr_result['rx_phy_rate'])],
+ rvr_result['rx_phy_rate'],
+ 'Rx PHY Rate',
+ hover_text=rvr_result['hover_text'],
+ color='blue',
+ style='dashed',
+ marker='inverted_triangle')
+ figure.add_line(
+ rvr_result['total_attenuation'][0:len(rvr_result['rx_phy_rate'])],
+ rvr_result['tx_phy_rate'],
+ 'Tx PHY Rate',
+ hover_text=rvr_result['hover_text'],
+ color='red',
+ style='dashed',
+ marker='triangle')
output_file_path = os.path.join(
self.log_path, '{}.html'.format(self.current_test_name))
figure.generate_figure(output_file_path)
def compute_test_metrics(self, rvr_result):
- #Set test metrics
+ # Set test metrics
rvr_result['metrics'] = {}
rvr_result['metrics']['peak_tput'] = max(
rvr_result['throughput_receive'])
@@ -362,7 +368,7 @@
for idx in range(len(tput_below_limit)):
if all(tput_below_limit[idx:]):
if idx == 0:
- #Throughput was never above limit
+ # Throughput was never above limit
rvr_result['metrics']['high_tput_range'] = -1
else:
rvr_result['metrics']['high_tput_range'] = rvr_result[
@@ -411,6 +417,8 @@
self.testclass_params.get('monitor_llstats', 1))
zero_counter = 0
throughput = []
+ rx_phy_rate = []
+ tx_phy_rate = []
llstats = []
rssi = []
for atten in testcase_params['atten_range']:
@@ -479,6 +487,10 @@
llstats_obj.update_stats()
curr_llstats = llstats_obj.llstats_incremental.copy()
llstats.append(curr_llstats)
+ rx_phy_rate.append(curr_llstats['summary'].get(
+ 'mean_rx_phy_rate', 0))
+ tx_phy_rate.append(curr_llstats['summary'].get(
+ 'mean_tx_phy_rate', 0))
self.log.info(
('Throughput at {0:.2f} dB is {1:.2f} Mbps. '
'RSSI = {2:.2f} [{3:.2f}, {4:.2f}].').format(
@@ -492,9 +504,11 @@
if zero_counter == self.MAX_CONSECUTIVE_ZEROS:
self.log.info(
'Throughput stable at 0 Mbps. Stopping test now.')
- throughput.extend(
- [0] *
- (len(testcase_params['atten_range']) - len(throughput)))
+ zero_padding = len(
+ testcase_params['atten_range']) - len(throughput)
+ throughput.extend([0] * zero_padding)
+ rx_phy_rate.extend([0] * zero_padding)
+ tx_phy_rate.extend([0] * zero_padding)
break
for attenuator in self.attenuators:
attenuator.set_atten(0, strict=False, retry=True)
@@ -512,6 +526,8 @@
]
rvr_result['rssi'] = rssi
rvr_result['throughput_receive'] = throughput
+ rvr_result['rx_phy_rate'] = rx_phy_rate
+ rvr_result['tx_phy_rate'] = tx_phy_rate
rvr_result['llstats'] = llstats
return rvr_result
@@ -556,8 +572,9 @@
self.sta_dut.droid.wakeLockAcquireDim()
else:
self.sta_dut.go_to_sleep()
- if wputils.validate_network(self.sta_dut,
- testcase_params['test_network']['SSID']):
+ if (wputils.validate_network(self.sta_dut,
+ testcase_params['test_network']['SSID'])
+ and not self.testclass_params.get('force_reconnect', 0)):
self.log.info('Already connected to desired network')
else:
wutils.wifi_toggle_state(self.sta_dut, False)
@@ -733,6 +750,7 @@
class WifiRvr_TCP_Test(WifiRvrTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -746,6 +764,7 @@
class WifiRvr_VHT_TCP_Test(WifiRvrTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -756,6 +775,7 @@
class WifiRvr_HE_TCP_Test(WifiRvrTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -769,6 +789,7 @@
class WifiRvr_SampleUDP_Test(WifiRvrTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -779,6 +800,7 @@
class WifiRvr_VHT_SampleUDP_Test(WifiRvrTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -789,6 +811,7 @@
class WifiRvr_HE_SampleUDP_Test(WifiRvrTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -799,6 +822,7 @@
class WifiRvr_SampleDFS_Test(WifiRvrTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -809,6 +833,7 @@
class WifiRvr_SingleChain_TCP_Test(WifiRvrTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -867,6 +892,7 @@
setting turntable orientation and other chamber parameters to study
performance in varying channel conditions
"""
+
def __init__(self, controllers):
base_test.BaseTestClass.__init__(self, controllers)
self.testcase_metric_logger = (
@@ -902,7 +928,12 @@
]).items())
if test_id not in plots:
# Initialize test id data when not present
- compiled_data[test_id] = {'throughput': [], 'metrics': {}}
+ compiled_data[test_id] = {
+ 'throughput': [],
+ 'rx_phy_rate': [],
+ 'tx_phy_rate': [],
+ 'metrics': {}
+ }
compiled_data[test_id]['metrics'] = {
key: []
for key in result['metrics'].keys()
@@ -927,6 +958,8 @@
# Compile test id data and metrics
compiled_data[test_id]['throughput'].append(
result['throughput_receive'])
+ compiled_data[test_id]['rx_phy_rate'].append(result['rx_phy_rate'])
+ compiled_data[test_id]['tx_phy_rate'].append(result['tx_phy_rate'])
compiled_data[test_id]['total_attenuation'] = result[
'total_attenuation']
for metric_key, metric_value in result['metrics'].items():
@@ -935,18 +968,27 @@
# Add test id to plots
plots[test_id].add_line(result['total_attenuation'],
result['throughput_receive'],
- result['test_name'],
+ result['test_name'].strip('test_rvr_'),
hover_text=result['hover_text'],
width=1,
style='dashed',
marker='circle')
- plots[test_id_phy].add_line(result['total_attenuation'],
- result['avg_phy_rate'],
- result['test_name'] + ' PHY',
- hover_text=result['hover_text'],
- width=1,
- style='dashed',
- marker='circle')
+ plots[test_id_phy].add_line(
+ result['total_attenuation'],
+ result['rx_phy_rate'],
+ result['test_name'].strip('test_rvr_') + ' Rx PHY Rate',
+ hover_text=result['hover_text'],
+ width=1,
+ style='dashed',
+ marker='inverted_triangle')
+ plots[test_id_phy].add_line(
+ result['total_attenuation'],
+ result['tx_phy_rate'],
+ result['test_name'].strip('test_rvr_') + ' Tx PHY Rate',
+ hover_text=result['hover_text'],
+ width=1,
+ style='dashed',
+ marker='triangle')
# Compute average RvRs and compute metrics over orientations
for test_id, test_data in compiled_data.items():
@@ -966,6 +1008,10 @@
metric_key, metric_value)
test_data['avg_rvr'] = numpy.mean(test_data['throughput'], 0)
test_data['median_rvr'] = numpy.median(test_data['throughput'], 0)
+ test_data['avg_rx_phy_rate'] = numpy.mean(test_data['rx_phy_rate'],
+ 0)
+ test_data['avg_tx_phy_rate'] = numpy.mean(test_data['tx_phy_rate'],
+ 0)
plots[test_id].add_line(test_data['total_attenuation'],
test_data['avg_rvr'],
legend='Average Throughput',
@@ -974,6 +1020,15 @@
test_data['median_rvr'],
legend='Median Throughput',
marker='square')
+ test_id_phy = test_id + tuple('PHY')
+ plots[test_id_phy].add_line(test_data['total_attenuation'],
+ test_data['avg_rx_phy_rate'],
+ legend='Average Rx Rate',
+ marker='inverted_triangle')
+ plots[test_id_phy].add_line(test_data['total_attenuation'],
+ test_data['avg_tx_phy_rate'],
+ legend='Average Tx Rate',
+ marker='triangle')
figure_list = []
for plot_id, plot in plots.items():
@@ -1019,6 +1074,7 @@
class WifiOtaRvr_StandardOrientation_Test(WifiOtaRvrTest):
+
def __init__(self, controllers):
WifiOtaRvrTest.__init__(self, controllers)
self.tests = self.generate_test_cases(
@@ -1028,6 +1084,7 @@
class WifiOtaRvr_SampleChannel_Test(WifiOtaRvrTest):
+
def __init__(self, controllers):
WifiOtaRvrTest.__init__(self, controllers)
self.tests = self.generate_test_cases([6], ['bw20'],
@@ -1042,6 +1099,7 @@
class WifiOtaRvr_SingleOrientation_Test(WifiOtaRvrTest):
+
def __init__(self, controllers):
WifiOtaRvrTest.__init__(self, controllers)
self.tests = self.generate_test_cases(
@@ -1050,6 +1108,7 @@
class WifiOtaRvr_SingleChain_Test(WifiOtaRvrTest):
+
def __init__(self, controllers):
WifiOtaRvrTest.__init__(self, controllers)
self.tests = self.generate_test_cases([6], ['bw20'],
diff --git a/acts_tests/tests/google/wifi/WifiRvrTwTest.py b/acts_tests/tests/google/wifi/WifiRvrTwTest.py
index e732b83..6e2babe 100644
--- a/acts_tests/tests/google/wifi/WifiRvrTwTest.py
+++ b/acts_tests/tests/google/wifi/WifiRvrTwTest.py
@@ -25,6 +25,8 @@
from acts.test_decorators import test_tracker_info
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
from acts.controllers import iperf_server as ipf
+from acts.controllers import attenuator
+from acts.controllers.sl4a_lib import rpc_client
import json
import logging
@@ -35,446 +37,688 @@
import serial
import sys
+import urllib.request
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils_RSSI as wperfutils
WifiEnums = wutils.WifiEnums
-class WifiRvrTWTest(WifiBaseTest):
- """ Tests for wifi RVR performance
+class WifiRvrTwTest(WifiBaseTest):
+ """ Tests for wifi RVR performance.
Test Bed Requirement:
* One Android device
* Wi-Fi networks visible to the device
+ """
+ TEST_TIMEOUT = 10
+ IPERF_SETUP_TIME = 5
+ TURN_TABLE_SETUP_TIME = 5
+
+ def __init__(self, controllers):
+ WifiBaseTest.__init__(self, controllers)
+
+ def setup_class(self):
+ self.dut = self.android_devices[0]
+
+ req_params = ["rvr_networks", "rvr_test_params", "attenuators"]
+ opt_params = ["angle_params", "usb_port"]
+ self.unpack_userparams(
+ req_param_names=req_params, opt_param_names=opt_params)
+ asserts.assert_true(
+ len(self.rvr_networks) > 0, "Need at least one network.")
+
+ if "rvr_test_params" in self.user_params:
+ self.iperf_server = self.iperf_servers[0]
+ self.maxdb = self.rvr_test_params["rvr_atten_maxdb"]
+ self.mindb = self.rvr_test_params["rvr_atten_mindb"]
+ self.stepdb = self.rvr_test_params["rvr_atten_step"]
+ self.country_code = self.rvr_test_params["country_code"]
+ if "angle_params" in self.user_params:
+ self.angle_list = self.angle_params
+ if "usb_port" in self.user_params:
+ self.turntable_port = self.read_comport(self.usb_port["turntable"])
+
+ # Init DUT
+ wutils.wifi_test_device_init(self.dut, self.country_code)
+ self.dut.droid.bluetoothToggleState(False)
+ utils.set_location_service(self.dut, False)
+ wutils.wifi_toggle_state(self.dut, True)
+ utils.subprocess.check_output(
+ "adb root", shell=True, timeout=self.TEST_TIMEOUT)
+ utils.subprocess.check_output(
+ "adb shell settings put system screen_off_timeout 18000000",
+ shell=True,
+ timeout=self.TEST_TIMEOUT)
+ utils.subprocess.check_output(
+ "adb shell svc power stayon true",
+ shell=True,
+ timeout=self.TEST_TIMEOUT)
+
+ # create folder for rvr test result
+ self.log_path = os.path.join(logging.log_path, "rvr_results")
+ utils.create_dir(self.log_path)
+
+ Header = ("Test_date", "Project", "Device_SN", "ROM", "HW_Stage",
+ "test_SSID", "Frequency", "Turn_table_orientation",
+ "Attenuate_dB", "Signal_poll_avg_rssi", "Chain_0_rssi",
+ "Chain_1_rssi", "Link_speed", "TX_throughput_Mbps",
+ "RX_throughput_Mbps", "HE_Capable", "Country_code", "Channel",
+ "WiFi_chip", "Type", "Host_name", "AP_model",
+ "Incremental_build_id", "Build_type", "TCP_UDP_Protocol",
+ "Security_type", "Test_tool", "Airplane_mode_status", "BT_status",
+ "Bug_ID", "Comment")
+ self.csv_write(Header)
+
+ def setup_test(self):
+ self.dut.droid.wakeLockAcquireBright()
+ self.dut.droid.wakeUpNow()
+ rom_info = self.get_rominfo()
+ self.testdate = time.strftime("%Y-%m-%d", time.localtime())
+ self.rom = rom_info[0]
+ self.build_id = rom_info[1]
+ self.build_type = rom_info[2]
+ self.project = rom_info[3]
+ self.ret_country_code = self.get_country_code()
+ self.ret_hw_stage = self.get_hw_stage()
+ self.ret_platform = wperfutils.detect_wifi_platform(self.dut)
+
+ def teardown_test(self):
+ self.dut.droid.wakeLockRelease()
+ self.dut.droid.goToSleepNow()
+ wutils.set_attns(self.attenuators, "default")
+
+ def teardown_class(self):
+ if "rvr_test_params" in self.user_params:
+ self.iperf_server.stop()
+
+ def on_fail(self, test_name, begin_time):
+ self.dut.take_bug_report(test_name, begin_time)
+ self.dut.cat_adb_log(test_name, begin_time)
+
+ """Helper Functions"""
+
+ def csv_write(self, data):
+ """Output .CSV file for test result.
+
+ Args:
+ data: Dict containing attenuation, throughput and other meta data.
"""
- TEST_TIMEOUT = 10
+ with open(
+ "{}/Result.csv".format(self.log_path), "a", newline="") as csv_file:
+ csv_writer = csv.writer(csv_file, delimiter=",")
+ csv_writer.writerow(data)
+ csv_file.close()
- def setup_class(self):
- super().setup_class()
+ def set_atten(self, db):
+ """Setup attenuator dB for current test.
- self.dut = self.android_devices[0]
- wutils.wifi_test_device_init(self.dut)
+ Args:
+ db: Attenuator setup dB.
+ """
+ if db < 0:
+ db = 0
+ elif db > 95:
+ db = 95
+ self.log.info("[Attenuation] %s", "Set dB = " + str(db) + "dB")
+ for atten in self.attenuators:
+ atten.set_atten(db)
+ self.log.info("[Attenuation] %s",
+ "Current dB = " + str(atten.get_atten()) + "dB")
+ retry = 0
+ while atten.get_atten() != db and retry < 11:
+ retry = retry + 1
+ self.log.info(
+ "[Attenuation] %s", "Fail to set Attenuator to " + str(db) + ", " +
+ str(retry) + " times try to reset")
+ self.set_atten(db)
+ if retry == 11:
+ self.log.info("Attenuation] %s",
+ "Retry Attenuator fail for 10 cycles, end test!")
+ sys.exit()
- req_params = [ "iot_networks","rvr_test_params"]
- opt_params = [ "angle_params","usb_port"]
- self.unpack_userparams(req_param_names=req_params,
- opt_param_names=opt_params)
+ def read_comport(self, com):
+ """Read com port for current test.
- asserts.assert_true(
- len(self.iot_networks) > 0,
- "Need at least one iot network with psk.")
+ Args:
+ com: Serial port.
- wutils.wifi_toggle_state(self.dut, True)
- if "rvr_test_params" in self.user_params:
- self.iperf_server = self.iperf_servers[0]
- self.MaxdB= self.rvr_test_params ["rvr_atten_MaxDB"]
- self.MindB= self.rvr_test_params ["rvr_atten_MinDB"]
- self.stepdB= self.rvr_test_params ["rvr_atten_step"]
+ Returns:
+ port: Serial port with baud rate.
+ """
+ port = serial.Serial(com, 9600, timeout=1)
+ time.sleep(1)
+ return port
- if "angle_params" in self.user_params:
- self.angle = self.angle_params
+ def get_angle(self, port):
+ """Get turn table angle for current test.
- if "usb_port" in self.user_params:
- self.T1=self.readport(self.usb_port["turntable"])
- self.ATT1=self.readport(self.usb_port["atten1"])
- self.ATT2=self.readport(self.usb_port["atten2"])
- self.ATT3=self.readport(self.usb_port["atten3"])
+ Args:
+ port: Turn table com port.
- # create hashmap for testcase name and SSIDs
- self.iot_test_prefix = "test_iot_connection_to_"
- self.ssid_map = {}
- for network in self.iot_networks:
- SSID = network['SSID'].replace('-','_')
- self.ssid_map[SSID] = network
+ Returns:
+ angle: Angle from turn table.
+ """
+ angle = ""
+ port.write("DG?;".encode())
+ time.sleep(0.1)
+ degree_data = port.readline().decode("utf-8")
+ for data in range(len(degree_data)):
+ if (degree_data[data].isdigit()) is True:
+ angle = angle + degree_data[data]
+ if angle == "":
+ return -1
+ return int(angle)
- # create folder for rvr test result
- self.log_path = os.path.join(logging.log_path, "rvr_results")
- os.makedirs(self.log_path, exist_ok=True)
+ def set_angle(self, port, angle):
+ """Setup turn table angle for current test.
- Header=("test_SSID","Turn table (angle)","Attenuator(dBm)",
- "TX throughput (Mbps)","RX throughput (Mbps)",
- "RSSI","Link speed","Frequency")
- self.csv_write(Header)
+ Args:
+ port: Turn table com port
+ angle: Turn table setup angle
+ """
+ if angle > 359:
+ angle = 359
+ elif angle < 0:
+ angle = 0
+ self.log.info("Set angle to " + str(angle))
+ input_angle = str("DG") + str(angle) + str(";")
+ port.write(input_angle.encode())
+ time.sleep(self.TURN_TABLE_SETUP_TIME)
- def setup_test(self):
- self.dut.droid.wakeLockAcquireBright()
- self.dut.droid.wakeUpNow()
+ def check_angle(self, port, angle):
+ """Check turn table angle for current test.
- def teardown_test(self):
- self.dut.droid.wakeLockRelease()
- self.dut.droid.goToSleepNow()
+ Args:
+ port: Turn table com port
+ angle: Turn table setup angle
+ """
+ retrytime = self.TEST_TIMEOUT
+ retry = 0
+ while self.get_angle(port) != angle and retry < retrytime:
+ retry = retry + 1
+ self.log.info("Turntable] %s",
+ "Current angle = " + str(self.get_angle(port)))
+ self.log.info(
+ "Turntable] %s", "Fail set angle to " + str(angle) + ", " +
+ str(retry) + " times try to reset")
+ self.set_angle(port, angle)
+ time.sleep(self.TURN_TABLE_SETUP_TIME)
+ if retry == retrytime:
+ self.log.info(
+ "Turntable] %s",
+ "Retry turntable fail for " + str(retry) + " cycles, end test!")
+ sys.exit()
- def teardown_class(self):
- if "rvr_test_params" in self.user_params:
- self.iperf_server.stop()
+ def get_wifiinfo(self):
+ """Get WiFi RSSI/ link speed/ frequency for current test.
- def on_fail(self, test_name, begin_time):
- self.dut.take_bug_report(test_name, begin_time)
- self.dut.cat_adb_log(test_name, begin_time)
+ Returns:
+ [rssi,link_speed,frequency]: DUT WiFi RSSI,Link speed and Frequency.
+ """
+ def is_number(string):
+ for i in string:
+ if i.isdigit() is False:
+ if (i == "-" or i == "."):
+ continue
+ return str(-1)
+ return string
- """Helper Functions"""
+ try:
+ cmd = "adb shell iw wlan0 link"
+ wifiinfo = utils.subprocess.check_output(
+ cmd, shell=True, timeout=self.TEST_TIMEOUT)
- def csv_write(self,data):
- """Output .CSV file for test result.
+ # Check RSSI Enhance
+ rssi = self.get_rssi_func()
- Args:
- data: Dict containing attenuation, throughput and other meta data.
- """
- with open("{}/Result.csv".format(self.log_path), "a", newline="") as csv_file:
- csv_writer = csv.writer(csv_file,delimiter=',')
- csv_writer.writerow(data)
- csv_file.close()
+ # Check link speed
+ link_speed = wifiinfo.decode(
+ "utf-8")[wifiinfo.decode("utf-8").find("bitrate:") +
+ 8:wifiinfo.decode("utf-8").find("Bit/s") - 2]
+ link_speed = link_speed.strip(" ")
+ link_speed = is_number(link_speed)
+ # Check frequency
+ frequency = wifiinfo.decode(
+ "utf-8")[wifiinfo.decode("utf-8").find("freq:") +
+ 6:wifiinfo.decode("utf-8").find("freq:") + 10]
+ frequency = frequency.strip(" ")
+ frequency = is_number(frequency)
+ except:
+ return -1, -1, -1
+ return [rssi, link_speed, frequency]
- def readport(self,com):
- """Read com port for current test.
+ def get_rssi_func(self):
+ """Get RSSI from brcm/qcom wifi chip.
- Args:
- com: Attenuator or turn table com port
- """
- port=serial.Serial(com,9600,timeout=1)
- time.sleep(1)
- return port
+ Returns:
+ current_rssi: DUT WiFi RSSI.
+ """
+ if self.ret_platform == "brcm":
+ rssi_future = wperfutils.get_connected_rssi_brcm(self.dut)
+ signal_poll_avg_rssi_tmp = rssi_future.pop("signal_poll_avg_rssi").pop(
+ "mean")
+ chain_0_rssi_tmp = rssi_future.pop("chain_0_rssi").pop("mean")
+ chain_1_rssi_tmp = rssi_future.pop("chain_1_rssi").pop("mean")
+ current_rssi = {
+ "signal_poll_avg_rssi": signal_poll_avg_rssi_tmp,
+ "chain_0_rssi": chain_0_rssi_tmp,
+ "chain_1_rssi": chain_1_rssi_tmp
+ }
+ elif self.ret_platform == "qcom":
+ rssi_future = wperfutils.get_connected_rssi_qcom(
+ self.dut, interface="wlan0")
+ signal_poll_avg_rssi_tmp = rssi_future.pop("signal_poll_avg_rssi").pop(
+ "mean")
+ chain_0_rssi_tmp = rssi_future.pop("chain_0_rssi").pop("mean")
+ chain_1_rssi_tmp = rssi_future.pop("chain_1_rssi").pop("mean")
+ if math.isnan(signal_poll_avg_rssi_tmp):
+ signal_poll_avg_rssi_tmp = -1
+ if math.isnan(chain_0_rssi_tmp):
+ chain_0_rssi_tmp = -1
+ if math.isnan(chain_1_rssi_tmp):
+ chain_1_rssi_tmp = -1
- def getdB(self,port):
- """Get attenuator dB for current test.
+ if signal_poll_avg_rssi_tmp == -1 & chain_0_rssi_tmp == -1 & chain_1_rssi_tmp == -1:
+ current_rssi = -1
+ else:
+ current_rssi = {
+ "signal_poll_avg_rssi": signal_poll_avg_rssi_tmp,
+ "chain_0_rssi": chain_0_rssi_tmp,
+ "chain_1_rssi": chain_1_rssi_tmp
+ }
+ else:
+ current_rssi = {
+ "signal_poll_avg_rssi": float("nan"),
+ "chain_0_rssi": float("nan"),
+ "chain_1_rssi": float("nan")
+ }
+ return current_rssi
- Args:
- port: Attenuator com port
- """
- port.write('V?;'.encode())
- dB=port.readline().decode()
- dB=dB.strip(';')
- dB=dB[dB.find('V')+1:]
- return int(dB)
+ def get_rominfo(self):
+ """Get DUT ROM build info.
- def setdB(self,port,dB):
- """Setup attenuator dB for current test.
+ Returns:
+ rom, build_id, build_type, project: DUT Build info,Build ID,
+ Build type, and Project name
+ """
+ rom = "NA"
+ build_id = "NA"
+ build_type = "NA"
+ project = "NA"
+ rominfo = self.dut.adb.shell("getprop ro.build.display.id").split()
- Args:
- port: Attenuator com port
- dB: Attenuator setup dB
- """
- if dB<0:
- dB=0
- elif dB>101:
- dB=101
- self.log.info("Set dB to "+str(dB))
- InputdB=str('V')+str(dB)+str(';')
- port.write(InputdB.encode())
- time.sleep(0.1)
+ if rominfo:
+ rom = rominfo[2]
+ build_id = rominfo[3]
+ project, build_type = rominfo[0].split("-")
- def set_Three_Att_dB(self,port1,port2,port3,dB):
- """Setup 3 attenuator dB for current test.
+ return rom, build_id, build_type, project
- Args:
- port1: Attenuator1 com port
- port1: Attenuator2 com port
- port1: Attenuator com port
- dB: Attenuator setup dB
- """
- self.setdB(port1,dB)
- self.setdB(port2,dB)
- self.setdB(port3,dB)
- self.checkdB(port1,dB)
- self.checkdB(port2,dB)
- self.checkdB(port3,dB)
+ def get_hw_stage(self):
+ """Get DUT HW stage.
- def checkdB(self,port,dB):
- """Check attenuator dB for current test.
+ Returns:
+ hw_stage: DUT HW stage e.g. EVT/DVT/PVT..etc.
+ """
+ cmd = "adb shell getprop ro.boot.hardware.revision"
+ hw_stage_temp = utils.subprocess.check_output(
+ cmd, shell=True, timeout=self.TEST_TIMEOUT)
+ hw_stage = hw_stage_temp.decode("utf-8").split("\n")[0]
+ return hw_stage
- Args:
- port: Attenuator com port
- dB: Attenuator setup dB
- """
- retry=0
- while self.getdB(port)!=dB and retry<10:
- retry=retry+1
- self.log.info("Current dB = "+str(self.getdB(port)))
- self.log.info("Fail to set Attenuator to "+str(dB)+", "
- +str(retry)+" times try to reset")
- self.setdB(port,dB)
- if retry ==10:
- self.log.info("Retry Attenuator fail for 9 cycles, end test!")
- sys.exit()
- return 0
+ def get_country_code(self):
+ """Get DUT country code.
- def getDG(self,port):
- """Get turn table angle for current test.
+ Returns:
+ country_code: DUT country code e.g. US/JP/GE..etc.
+ """
+ cmd = "adb shell cmd wifi get-country-code"
+ country_code_temp = utils.subprocess.check_output(
+ cmd, shell=True, timeout=self.TEST_TIMEOUT)
+ country_code = country_code_temp.decode("utf-8").split(" ")[4].split(
+ "\n")[0]
+ return country_code
- Args:
- port: Turn table com port
- """
- DG = ""
- port.write('DG?;'.encode())
- time.sleep(0.1)
- data = port.readline().decode('utf-8')
- for i in range(len(data)):
- if (data[i].isdigit()) == True:
- DG = DG + data[i]
- if DG == "":
- return -1
- return int(DG)
+ def get_channel(self):
+ """Get DUT WiFi channel.
- def setDG(self,port,DG):
- """Setup turn table angle for current test.
+ Returns:
+ country_code: DUT channel e.g. 6/36/37..etc.
+ """
+ if self.ret_platform == "brcm":
+ cmd = 'adb shell wl assoc | grep "Primary channel:"'
+ channel_temp = utils.subprocess.check_output(
+ cmd, shell=True, timeout=self.TEST_TIMEOUT)
+ channel = channel_temp.decode("utf-8").split(": ")[1].split("\n")[0]
+ elif self.ret_platform == "qcom":
+ cmd = "adb shell iw wlan0 info | grep channel"
+ channel_temp = utils.subprocess.check_output(
+ cmd, shell=True, timeout=self.TEST_TIMEOUT)
+ channel = channel_temp.decode("utf-8").split(" ")[1].split("\n")[0]
+ return channel
- Args:
- port: Turn table com port
- DG: Turn table setup angle
- """
- if DG>359:
- DG=359
- elif DG<0:
- DG=0
- self.log.info("Set angle to "+str(DG))
- InputDG=str('DG')+str(DG)+str(';')
- port.write(InputDG.encode())
+ def get_he_capable(self):
+ """Get DUT WiFi high efficiency capable status .
- def checkDG(self,port,DG):
- """Check turn table angle for current test.
+ Returns:
+ he_capable: DUT high efficiency capable status.
+ """
+ if self.ret_platform == "brcm":
+ cmd = 'adb shell wl assoc | grep "Chanspec:"'
+ he_temp = utils.subprocess.check_output(
+ cmd, shell=True, timeout=self.TEST_TIMEOUT)
+ he_capable = he_temp.decode("utf-8").split(": ")[1].split("\n")[0].split(
+ "MHz")[0].split(" ")[3]
+ elif self.ret_platform == "qcom":
+ cmd = "adb shell iw wlan0 info | grep channel"
+ he_temp = utils.subprocess.check_output(
+ cmd, shell=True, timeout=self.TEST_TIMEOUT)
+ he_capable = he_temp.decode("utf-8").split("width: ")[1].split(" ")[0]
+ return he_capable
- Args:
- port: Turn table com port
- DG: Turn table setup angle
- """
- retrytime = self.TEST_TIMEOUT
- retry = 0
- while self.getDG(port)!=DG and retry<retrytime:
- retry=retry+1
- self.log.info('Current angle = '+str(self.getDG(port)))
- self.log.info('Fail set angle to '+str(DG)+', '+str(retry)+' times try to reset')
- self.setDG(port,DG)
- time.sleep(10)
- if retry == retrytime:
- self.log.info('Retry turntable fail for '+str(retry)+' cycles, end test!')
- sys.exit()
- return 0
+ def post_process_results(self, rvr_result):
+ """Saves JSON formatted results.
- def getwifiinfo(self):
- """Get WiFi RSSI/ link speed/ frequency for current test.
+ Args:
+ rvr_result: Dict containing attenuation, throughput and other meta data
+ Returns:
+ wifiinfo[0]: To check WiFi connection by RSSI value
+ """
+ # Save output as text file
+ wifiinfo = self.get_wifiinfo()
+ if wifiinfo[0] != -1:
+ rvr_result["signal_poll_avg_rssi"] = wifiinfo[0]["signal_poll_avg_rssi"]
+ rvr_result["chain_0_rssi"] = wifiinfo[0]["chain_0_rssi"]
+ rvr_result["chain_1_rssi"] = wifiinfo[0]["chain_1_rssi"]
+ else:
+ rvr_result["signal_poll_avg_rssi"] = wifiinfo[0]
+ rvr_result["chain_0_rssi"] = wifiinfo[0]
+ rvr_result["chain_1_rssi"] = wifiinfo[0]
+ if rvr_result["signal_poll_avg_rssi"] == -1:
+ rvr_result["channel"] = "NA"
+ else:
+ rvr_result["channel"] = self.ret_channel
+ rvr_result["country_code"] = self.ret_country_code
+ rvr_result["hw_stage"] = self.ret_hw_stage
+ rvr_result["wifi_chip"] = self.ret_platform
+ rvr_result["test_ssid"] = self.ssid
+ rvr_result["test_angle"] = self.angle_list[self.angle]
+ rvr_result["test_dB"] = self.db
+ rvr_result["test_link_speed"] = wifiinfo[1]
+ rvr_result["test_frequency"] = wifiinfo[2]
- Returns:
- [RSSI,LS,FR]: WiFi RSSI/ link speed/ frequency
- """
- def is_number(string):
- for i in string:
- if i.isdigit() == False:
- if (i=="-" or i=="."):
- continue
- return str(-1)
- return string
+ data = (
+ self.testdate,
+ self.project,
+ self.dut.serial,
+ self.rom,
+ rvr_result["hw_stage"],
+ rvr_result["test_ssid"],
+ rvr_result["test_frequency"],
+ rvr_result["test_angle"],
+ rvr_result["test_dB"],
+ rvr_result["signal_poll_avg_rssi"],
+ rvr_result["chain_0_rssi"],
+ rvr_result["chain_1_rssi"],
+ rvr_result["test_link_speed"],
+ rvr_result["throughput_TX"][0],
+ rvr_result["throughput_RX"][0],
+ "HE" + self.he_capable,
+ rvr_result["country_code"],
+ rvr_result["channel"],
+ rvr_result["wifi_chip"],
+ "OTA_RvR",
+ "OTA_Testbed2",
+ "RAXE500",
+ self.build_id,
+ self.build_type,
+ "TCP",
+ "WPA3",
+ "iperf3",
+ "OFF",
+ "OFF",
+ )
+ self.csv_write(data)
- try:
- cmd = "adb shell iw wlan0 link"
- wifiinfo = utils.subprocess.check_output(cmd,shell=True,
- timeout=self.TEST_TIMEOUT)
- # Check RSSI
- RSSI = wifiinfo.decode("utf-8")[wifiinfo.decode("utf-8").find("signal:") +
- 7:wifiinfo.decode("utf-8").find("dBm") - 1]
- RSSI = RSSI.strip(' ')
- RSSI = is_number(RSSI)
- # Check link speed
- LS = wifiinfo.decode("utf-8")[wifiinfo.decode("utf-8").find("bitrate:") +
- 8:wifiinfo.decode("utf-8").find("Bit/s") - 2]
- LS = LS.strip(' ')
- LS = is_number(LS)
- # Check frequency
- FR = wifiinfo.decode("utf-8")[wifiinfo.decode("utf-8").find("freq:") +
- 6:wifiinfo.decode("utf-8").find("freq:") + 10]
- FR = FR.strip(' ')
- FR = is_number(FR)
- except:
- return -1, -1, -1
- return [RSSI,LS,FR]
+ results_file_path = "{}/{}_angle{}_{}dB.json".format(
+ self.log_path, self.ssid, self.angle_list[self.angle], self.db)
+ with open(results_file_path, "w") as results_file:
+ json.dump(rvr_result, results_file, indent=4)
+ return wifiinfo[0]
- def post_process_results(self, rvr_result):
- """Saves JSON formatted results.
+ def connect_to_wifi_network(self, network):
+ """Connection logic for wifi networks.
- Args:
- rvr_result: Dict containing attenuation, throughput and other meta
- data
- """
- # Save output as text file
- data=(rvr_result["test_name"],rvr_result["test_angle"],rvr_result["test_dB"],
- rvr_result["throughput_TX"][0],rvr_result["throughput_RX"][0],
- rvr_result["test_RSSI"],rvr_result["test_LS"],rvr_result["test_FR"])
- self.csv_write(data)
+ Args:
+ params: Dictionary with network info.
+ """
+ ssid = network[WifiEnums.SSID_KEY]
+ self.dut.ed.clear_all_events()
+ wutils.start_wifi_connection_scan(self.dut)
+ scan_results = self.dut.droid.wifiGetScanResults()
+ wutils.assert_network_in_list({WifiEnums.SSID_KEY: ssid}, scan_results)
+ wutils.wifi_connect(self.dut, network, num_of_tries=3)
- results_file_path = "{}/{}_angle{}_{}dB.json".format(self.log_path,
- self.ssid,
- self.angle[self.ag],self.DB)
- with open(results_file_path, 'w') as results_file:
- json.dump(rvr_result, results_file, indent=4)
+ def run_iperf_init(self, network):
+ self.iperf_server.start(tag="init")
+ self.log.info("[Iperf] %s", "Starting iperf traffic init.")
+ time.sleep(self.IPERF_SETUP_TIME)
+ try:
+ port_arg = "-p {} -J -R -t10".format(self.iperf_server.port)
+ self.dut.run_iperf_client(
+ self.rvr_test_params["iperf_server_address"],
+ port_arg,
+ timeout=self.rvr_test_params["iperf_duration"] + self.TEST_TIMEOUT)
+ self.iperf_server.stop()
+ self.log.info("[Iperf] %s", "iperf traffic init Pass")
+ except:
+ self.log.warning("ValueError: iperf init ERROR.")
- def connect_to_wifi_network(self, network):
- """Connection logic for psk wifi networks.
+ def run_iperf_client(self, network):
+ """Run iperf TX throughput after connection.
- Args:
- params: Dictionary with network info.
- """
- SSID = network[WifiEnums.SSID_KEY]
- self.dut.ed.clear_all_events()
- wutils.start_wifi_connection_scan(self.dut)
- scan_results = self.dut.droid.wifiGetScanResults()
- wutils.assert_network_in_list({WifiEnums.SSID_KEY: SSID}, scan_results)
- wutils.wifi_connect(self.dut, network, num_of_tries=3)
+ Args:
+ network: Dictionary with network info.
- def run_iperf_client(self, network):
- """Run iperf TX throughput after connection.
+ Returns:
+ rvr_result: Dict containing TX rvr_results.
+ """
+ rvr_result = []
+ try:
+ self.iperf_server.start(tag="TX_server_{}_angle{}_{}dB".format(
+ self.ssid, self.angle_list[self.angle], self.db))
+ ssid = network[WifiEnums.SSID_KEY]
+ self.log.info("[Iperf] %s",
+ "Starting iperf traffic TX through {}".format(ssid))
+ time.sleep(self.IPERF_SETUP_TIME)
+ port_arg = "-p {} -J {}".format(self.iperf_server.port,
+ self.rvr_test_params["iperf_port_arg"])
+ success, data = self.dut.run_iperf_client(
+ self.rvr_test_params["iperf_server_address"],
+ port_arg,
+ timeout=self.rvr_test_params["iperf_duration"] + self.TEST_TIMEOUT)
+ # Parse and log result
+ client_output_path = os.path.join(
+ self.iperf_server.log_path,
+ "IperfDUT,{},TX_client_{}_angle{}_{}dB".format(
+ self.iperf_server.port, self.ssid, self.angle_list[self.angle],
+ self.db))
+ with open(client_output_path, "w") as out_file:
+ out_file.write("\n".join(data))
+ self.iperf_server.stop()
- Args:
- params: Dictionary with network info.
+ iperf_file = self.iperf_server.log_files[-1]
+ iperf_result = ipf.IPerfResult(iperf_file)
+ curr_throughput = (math.fsum(iperf_result.instantaneous_rates[
+ self.rvr_test_params["iperf_ignored_interval"]:-1]) /
+ len(iperf_result.instantaneous_rates[
+ self.rvr_test_params["iperf_ignored_interval"]:-1])
+ ) * 8 * (1.024**2)
+ rvr_result.append(curr_throughput)
+ self.log.info(
+ "[Iperf] %s", "TX Throughput at {0:.2f} dB is {1:.2f} Mbps".format(
+ self.db, curr_throughput))
+ self.log.debug(pprint.pformat(data))
+ asserts.assert_true(success, "Error occurred in iPerf traffic.")
+ return rvr_result
+ except:
+ rvr_result = ["NA"]
+ self.log.warning("ValueError: TX iperf ERROR.")
+ self.iperf_server.stop()
+ return rvr_result
- Returns:
- rvr_result: Dict containing rvr_results
- """
- rvr_result = []
- self.iperf_server.start(tag="TX_server_{}_angle{}_{}dB".format(
- self.ssid,self.angle[self.ag],self.DB))
- wait_time = 5
- SSID = network[WifiEnums.SSID_KEY]
- self.log.info("Starting iperf traffic TX through {}".format(SSID))
- time.sleep(wait_time)
- port_arg = "-p {} -J {}".format(self.iperf_server.port,
- self.rvr_test_params["iperf_port_arg"])
- success, data = self.dut.run_iperf_client(
- self.rvr_test_params["iperf_server_address"],
- port_arg,
- timeout=self.rvr_test_params["iperf_duration"] + self.TEST_TIMEOUT)
- # Parse and log result
- client_output_path = os.path.join(
- self.iperf_server.log_path, "IperfDUT,{},TX_client_{}_angle{}_{}dB".format(
- self.iperf_server.port,self.ssid,self.angle[self.ag],self.DB))
- with open(client_output_path, 'w') as out_file:
- out_file.write("\n".join(data))
- self.iperf_server.stop()
+ def run_iperf_server(self, network):
+ """Run iperf RX throughput after connection.
- iperf_file = self.iperf_server.log_files[-1]
- try:
- iperf_result = ipf.IPerfResult(iperf_file)
- curr_throughput = (math.fsum(iperf_result.instantaneous_rates[
- self.rvr_test_params["iperf_ignored_interval"]:-1]) / len(
- iperf_result.instantaneous_rates[self.rvr_test_params[
- "iperf_ignored_interval"]:-1])) * 8 * (1.024**2)
- except:
- self.log.warning(
- "ValueError: Cannot get iperf result. Setting to 0")
- curr_throughput = 0
- rvr_result.append(curr_throughput)
- self.log.info("TX Throughput at {0:.2f} dB is {1:.2f} Mbps".format(
- self.DB, curr_throughput))
+ Args:
+ network: Dictionary with network info.
- self.log.debug(pprint.pformat(data))
- asserts.assert_true(success, "Error occurred in iPerf traffic.")
- return rvr_result
+ Returns:
+ rvr_result: Dict containing RX rvr_results.
+ """
- def run_iperf_server(self, network):
- """Run iperf RX throughput after connection.
+ rvr_result = []
+ try:
+ self.iperf_server.start(tag="RX_client_{}_angle{}_{}dB".format(
+ self.ssid, self.angle_list[self.angle], self.db))
+ ssid = network[WifiEnums.SSID_KEY]
+ self.log.info("[Iperf] %s",
+ "Starting iperf traffic RX through {}".format(ssid))
+ time.sleep(self.IPERF_SETUP_TIME)
+ port_arg = "-p {} -J -R {}".format(self.iperf_server.port,
+ self.rvr_test_params["iperf_port_arg"])
+ success, data = self.dut.run_iperf_client(
+ self.rvr_test_params["iperf_server_address"],
+ port_arg,
+ timeout=self.rvr_test_params["iperf_duration"] + self.TEST_TIMEOUT)
+ # Parse and log result
+ client_output_path = os.path.join(
+ self.iperf_server.log_path,
+ "IperfDUT,{},RX_server_{}_angle{}_{}dB".format(
+ self.iperf_server.port, self.ssid, self.angle_list[self.angle],
+ self.db))
+ with open(client_output_path, "w") as out_file:
+ out_file.write("\n".join(data))
+ self.iperf_server.stop()
- Args:
- params: Dictionary with network info.
+ iperf_file = client_output_path
+ iperf_result = ipf.IPerfResult(iperf_file)
+ curr_throughput = (math.fsum(iperf_result.instantaneous_rates[
+ self.rvr_test_params["iperf_ignored_interval"]:-1]) /
+ len(iperf_result.instantaneous_rates[
+ self.rvr_test_params["iperf_ignored_interval"]:-1])
+ ) * 8 * (1.024**2)
+ rvr_result.append(curr_throughput)
+ self.log.info(
+ "[Iperf] %s", "RX Throughput at {0:.2f} dB is {1:.2f} Mbps".format(
+ self.db, curr_throughput))
- Returns:
- rvr_result: Dict containing rvr_results
- """
- rvr_result = []
- self.iperf_server.start(tag="RX_client_{}_angle{}_{}dB".format(
- self.ssid,self.angle[self.ag],self.DB))
- wait_time = 5
- SSID = network[WifiEnums.SSID_KEY]
- self.log.info("Starting iperf traffic RX through {}".format(SSID))
- time.sleep(wait_time)
- port_arg = "-p {} -J -R {}".format(self.iperf_server.port,
- self.rvr_test_params["iperf_port_arg"])
- success, data = self.dut.run_iperf_client(
- self.rvr_test_params["iperf_server_address"],
- port_arg,
- timeout=self.rvr_test_params["iperf_duration"] + self.TEST_TIMEOUT)
- # Parse and log result
- client_output_path = os.path.join(
- self.iperf_server.log_path, "IperfDUT,{},RX_server_{}_angle{}_{}dB".format(
- self.iperf_server.port,self.ssid,self.angle[self.ag],self.DB))
- with open(client_output_path, 'w') as out_file:
- out_file.write("\n".join(data))
- self.iperf_server.stop()
+ self.log.debug(pprint.pformat(data))
+ asserts.assert_true(success, "Error occurred in iPerf traffic.")
+ return rvr_result
+ except:
+ rvr_result = ["NA"]
+ self.log.warning("ValueError: RX iperf ERROR.")
+ self.iperf_server.stop()
+ return rvr_result
- iperf_file = client_output_path
- try:
- iperf_result = ipf.IPerfResult(iperf_file)
- curr_throughput = (math.fsum(iperf_result.instantaneous_rates[
- self.rvr_test_params["iperf_ignored_interval"]:-1]) / len(
- iperf_result.instantaneous_rates[self.rvr_test_params[
- "iperf_ignored_interval"]:-1])) * 8 * (1.024**2)
- except:
- self.log.warning(
- "ValueError: Cannot get iperf result. Setting to 0")
- curr_throughput = 0
- rvr_result.append(curr_throughput)
- self.log.info("RX Throughput at {0:.2f} dB is {1:.2f} Mbps".format(
- self.DB, curr_throughput))
+ def iperf_test_func(self, network):
+ """Main function to test iperf TX/RX.
- self.log.debug(pprint.pformat(data))
- asserts.assert_true(success, "Error occurred in iPerf traffic.")
- return rvr_result
+ Args:
+ network: Dictionary with network info.
+ """
+ # Initialize
+ rvr_result = {}
+ # Run RvR and log result
+ rvr_result["throughput_RX"] = self.run_iperf_server(network)
+ retry_time = 2
+ for retry in range(retry_time):
+ if rvr_result["throughput_RX"] == ["NA"]:
+ if not self.iperf_retry():
+ time.sleep(self.IPERF_SETUP_TIME)
+ rvr_result["throughput_RX"] = self.run_iperf_server(network)
+ else:
+ break
+ else:
+ break
+ rvr_result["throughput_TX"] = self.run_iperf_client(network)
+ retry_time = 2
+ for retry in range(retry_time):
+ if rvr_result["throughput_TX"] == ["NA"]:
+ if not self.iperf_retry():
+ time.sleep(self.IPERF_SETUP_TIME)
+ rvr_result["throughput_TX"] = self.run_iperf_client(network)
+ else:
+ break
+ else:
+ break
+ self.post_process_results(rvr_result)
+ self.rssi = wifiinfo[0]
+ return self.rssi
- def iperf_test_func(self,network):
- """Main function to test iperf TX/RX.
+ def iperf_retry(self):
+ """Check iperf TX/RX status and retry."""
+ try:
+ cmd = "adb -s {} shell pidof iperf3| xargs adb shell kill -9".format(
+ self.dut.serial)
+ utils.subprocess.call(cmd, shell=True, timeout=self.TEST_TIMEOUT)
+ self.log.warning("ValueError: Killed DUT iperf process, keep test")
+ except:
+ self.log.info("[Iperf] %s", "No iperf DUT process found, keep test")
- Args:
- params: Dictionary with network info
- """
- if "rvr_test_params" in self.user_params:
- # Initialize
- rvr_result = {}
- # Run RvR and log result
- wifiinfo = self.getwifiinfo()
- rvr_result["throughput_TX"] = self.run_iperf_client(network)
- rvr_result["throughput_RX"] = self.run_iperf_server(network)
- rvr_result["test_name"] = self.ssid
- rvr_result["test_angle"] = self.angle[self.ag]
- rvr_result["test_dB"] = self.DB
- rvr_result["test_RSSI"] = wifiinfo[0]
- rvr_result["test_LS"] = wifiinfo[1]
- rvr_result["test_FR"] = wifiinfo[2]
- self.post_process_results(rvr_result)
+ wifiinfo = self.get_wifiinfo()
+ print("--[iperf_retry]--", wifiinfo[0])
+ self.log.info("[WiFiinfo] %s", "Current RSSI = " + str(wifiinfo[0]) + "dBm")
+ if wifiinfo[0] == -1:
+ self.log.warning("ValueError: Cannot get RSSI, stop throughput test")
+ return True
+ else:
+ return False
- def rvr_test(self,network):
- """Test function to run RvR.
+ def rvr_test(self, network):
+ """Test function to run RvR.
- The function runs an RvR test in the current device/AP configuration.
- Function is called from another wrapper function that sets up the
- testbed for the RvR test
+ The function runs an RvR test in the current device/AP configuration.
+ Function is called from another wrapper function that sets up the
+ testbed for the RvR test
- Args:
- params: Dictionary with network info
- """
- wait_time = 5
- utils.subprocess.check_output('adb root', shell=True, timeout=20)
- self.ssid = network[WifiEnums.SSID_KEY]
- self.log.info("Start rvr test")
- for i in range(len(self.angle)):
- self.setDG(self.T1,self.angle[i])
- time.sleep(wait_time)
- self.checkDG(self.T1,self.angle[i])
- self.set_Three_Att_dB(self.ATT1,self.ATT2,self.ATT3,0)
- time.sleep(wait_time)
- self.connect_to_wifi_network(network)
- self.set_Three_Att_dB(self.ATT1,self.ATT2,self.ATT3,self.MindB)
- for j in range(self.MindB,self.MaxdB+self.stepdB,self.stepdB):
- self.DB=j
- self.ag=i
- self.set_Three_Att_dB(self.ATT1,self.ATT2,self.ATT3,self.DB)
- self.iperf_test_func(network)
- wutils.reset_wifi(self.dut)
+ Args:
+ params: Dictionary with network info
+ """
+ self.ssid = network[WifiEnums.SSID_KEY]
+ self.log.info("Start rvr test")
- """Tests"""
+ for angle in range(len(self.angle_list)):
+ self.angle = angle
+ self.set_angle(self.turntable_port, self.angle_list[angle])
+ self.check_angle(self.turntable_port, self.angle_list[angle])
+ self.set_atten(0)
+ self.connect_to_wifi_network(network)
+ self.ret_channel = self.get_channel()
+ self.he_capable = self.get_he_capable()
+ self.run_iperf_init(network)
+ for db in range(self.mindb, self.maxdb + self.stepdb, self.stepdb):
+ self.db = db
+ self.set_atten(self.db)
+ self.iperf_test_func(network)
+ if self.rssi == -1:
+ self.log.warning("ValueError: Cannot get RSSI. Run next angle")
+ break
+ else:
+ continue
+ wutils.reset_wifi(self.dut)
- @test_tracker_info(uuid="93816af8-4c63-45f8-b296-cb49fae0b158")
- def test_iot_connection_to_RVR_2G(self):
- ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
- self.rvr_test(self.ssid_map[ssid_key])
+ """Tests"""
+ def test_rvr_2g(self):
+ network = self.rvr_networks[0]
+ self.rvr_test(network)
- @test_tracker_info(uuid="e1a67e13-946f-4d91-aa73-3f945438a1ac")
- def test_iot_connection_to_RVR_5G(self):
- ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
- self.rvr_test(self.ssid_map[ssid_key])
\ No newline at end of file
+ def test_rvr_5g(self):
+ network = self.rvr_networks[1]
+ self.rvr_test(network)
+
+ def test_rvr_6g(self):
+ network = self.rvr_networks[2]
+ self.rvr_test(network)
diff --git a/acts_tests/tests/google/wifi/WifiSensitivityTest.py b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
index 954bc90..535572d 100644
--- a/acts_tests/tests/google/wifi/WifiSensitivityTest.py
+++ b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
@@ -375,18 +375,21 @@
self.testbed_params['ap_tx_power_offset'][str(
testcase_params['channel'])] - ping_result['range'])
- def setup_sensitivity_test(self, testcase_params):
- # Setup test
- if testcase_params['traffic_type'].lower() == 'ping':
- self.setup_ping_test(testcase_params)
- self.run_sensitivity_test = self.run_ping_test
- self.process_sensitivity_test_results = (
- self.process_ping_test_results)
- else:
- self.setup_rvr_test(testcase_params)
- self.run_sensitivity_test = self.run_rvr_test
- self.process_sensitivity_test_results = (
- self.process_rvr_test_results)
+ def setup_ping_test(self, testcase_params):
+ """Function that gets devices ready for the test.
+
+ Args:
+ testcase_params: dict containing test-specific parameters
+ """
+ # Configure AP
+ self.setup_ap(testcase_params)
+ # Set attenuator to starting attenuation
+ for attenuator in self.attenuators:
+ attenuator.set_atten(testcase_params['atten_start'],
+ strict=False,
+ retry=True)
+ # Reset, configure, and connect DUT
+ self.setup_dut(testcase_params)
def setup_ap(self, testcase_params):
"""Sets up the AP and attenuator to compensate for AP chain imbalance.
@@ -586,9 +589,14 @@
]
# Prepare devices and run test
- self.setup_sensitivity_test(testcase_params)
- result = self.run_sensitivity_test(testcase_params)
- self.process_sensitivity_test_results(testcase_params, result)
+ if testcase_params['traffic_type'].lower() == 'ping':
+ self.setup_ping_test(testcase_params)
+ result = self.run_ping_test(testcase_params)
+ self.process_ping_test_results(testcase_params, result)
+ else:
+ self.setup_rvr_test(testcase_params)
+ result = self.run_rvr_test(testcase_params)
+ self.process_rvr_test_results(testcase_params, result)
# Post-process results
self.testclass_results.append(result)
@@ -644,6 +652,7 @@
class WifiSensitivity_AllChannels_Test(WifiSensitivityTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -652,6 +661,7 @@
class WifiSensitivity_SampleChannels_Test(WifiSensitivityTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases([6, 36, 149],
@@ -660,6 +670,7 @@
class WifiSensitivity_2GHz_Test(WifiSensitivityTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases([1, 2, 6, 10, 11], ['VHT20'],
@@ -667,6 +678,7 @@
class WifiSensitivity_5GHz_Test(WifiSensitivityTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases(
@@ -675,6 +687,7 @@
class WifiSensitivity_UNII1_Test(WifiSensitivityTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases([36, 40, 44, 48],
@@ -683,6 +696,7 @@
class WifiSensitivity_UNII3_Test(WifiSensitivityTest):
+
def __init__(self, controllers):
super().__init__(controllers)
self.tests = self.generate_test_cases([149, 153, 157, 161],
@@ -698,6 +712,7 @@
It allows setting orientation and other chamber parameters to study
performance in varying channel conditions
"""
+
def __init__(self, controllers):
base_test.BaseTestClass.__init__(self, controllers)
self.testcase_metric_logger = (
@@ -911,6 +926,7 @@
class WifiOtaSensitivity_TenDegree_Test(WifiOtaSensitivityTest):
+
def __init__(self, controllers):
WifiOtaSensitivityTest.__init__(self, controllers)
requested_channels = [6, 36, 149]
@@ -929,6 +945,7 @@
class WifiOtaSensitivity_PerChain_TenDegree_Test(WifiOtaSensitivityTest):
+
def __init__(self, controllers):
WifiOtaSensitivityTest.__init__(self, controllers)
requested_channels = [6, 36, 149]
@@ -947,6 +964,7 @@
class WifiOtaSensitivity_ThirtyDegree_Test(WifiOtaSensitivityTest):
+
def __init__(self, controllers):
WifiOtaSensitivityTest.__init__(self, controllers)
requested_channels = [6, 36, 149]
@@ -971,6 +989,7 @@
class WifiOtaSensitivity_45Degree_Test(WifiOtaSensitivityTest):
+
def __init__(self, controllers):
WifiOtaSensitivityTest.__init__(self, controllers)
requested_rates = [
diff --git a/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py b/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
index a8d9628..ddd25f7 100644
--- a/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
+++ b/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
@@ -50,6 +50,7 @@
example config file to run this test class see
example_connectivity_performance_ap_sta.json.
"""
+
def __init__(self, controllers):
base_test.BaseTestClass.__init__(self, controllers)
# Define metrics to be uploaded to BlackBox
@@ -125,6 +126,7 @@
except:
self.log.warning('Could not start sniffer. Disabling sniffs.')
self.testbed_params['sniffer_enable'] = 0
+ self.sniffer_subsampling = 1
self.log_path = os.path.join(logging.log_path, 'test_results')
os.makedirs(self.log_path, exist_ok=True)
self.log.info('Access Point Configuration: {}'.format(
@@ -159,44 +161,18 @@
test_result_dict: dict containing attenuation, throughput and other
meta data
"""
- avg_throughput = test_result['iperf_summary']['avg_throughput']
- min_throughput = test_result['iperf_summary']['min_throughput']
- std_dev_percent = (
- test_result['iperf_summary']['std_deviation'] /
- test_result['iperf_summary']['avg_throughput']) * 100
- # Set blackbox metrics
- if self.publish_testcase_metrics:
- self.testcase_metric_logger.add_metric('avg_throughput',
- avg_throughput)
- self.testcase_metric_logger.add_metric('min_throughput',
- min_throughput)
- self.testcase_metric_logger.add_metric('std_dev_percent',
- std_dev_percent)
# Evaluate pass/fail
min_throughput_check = (
- (min_throughput / avg_throughput) *
+ (test_result['iperf_summary']['min_throughput'] /
+ test_result['iperf_summary']['avg_throughput']) *
100) > self.testclass_params['min_throughput_threshold']
- std_deviation_check = std_dev_percent < self.testclass_params[
- 'std_deviation_threshold']
+ std_deviation_check = test_result['iperf_summary'][
+ 'std_dev_percent'] < self.testclass_params[
+ 'std_deviation_threshold']
- llstats = (
- 'TX MCS = {0} ({1:.1f}%). '
- 'RX MCS = {2} ({3:.1f}%)'.format(
- test_result['llstats']['summary']['common_tx_mcs'],
- test_result['llstats']['summary']['common_tx_mcs_freq'] * 100,
- test_result['llstats']['summary']['common_rx_mcs'],
- test_result['llstats']['summary']['common_rx_mcs_freq'] * 100))
-
- test_message = (
- 'Atten: {0:.2f}dB, RSSI: {1:.2f}dB. '
- 'Throughput (Mean: {2:.2f}, Std. Dev:{3:.2f}%, Min: {4:.2f} Mbps).'
- 'LLStats : {5}'.format(
- test_result['attenuation'],
- test_result['rssi_result']['signal_poll_rssi']['mean'],
- avg_throughput, std_dev_percent, min_throughput, llstats))
if min_throughput_check and std_deviation_check:
- asserts.explicit_pass('Test Passed.' + test_message)
- asserts.fail('Test Failed. ' + test_message)
+ asserts.explicit_pass('Test Passed.')
+ asserts.fail('Test Failed.')
def post_process_results(self, test_result):
"""Extracts results and saves plots and JSON formatted results.
@@ -209,41 +185,37 @@
avg throughput, other metrics, and other meta data
"""
# Save output as text file
- test_name = self.current_test_name
- results_file_path = os.path.join(self.log_path,
- '{}.txt'.format(test_name))
- if test_result['iperf_result'].instantaneous_rates:
- instantaneous_rates_Mbps = [
- rate * 8 * (1.024**2)
- for rate in test_result['iperf_result'].instantaneous_rates[
- self.testclass_params['iperf_ignored_interval']:-1]
- ]
- tput_standard_deviation = test_result[
- 'iperf_result'].get_std_deviation(
- self.testclass_params['iperf_ignored_interval']) * 8
- else:
- instantaneous_rates_Mbps = [float('nan')]
- tput_standard_deviation = float('nan')
- test_result['iperf_summary'] = {
- 'instantaneous_rates': instantaneous_rates_Mbps,
- 'avg_throughput': numpy.mean(instantaneous_rates_Mbps),
- 'std_deviation': tput_standard_deviation,
- 'min_throughput': min(instantaneous_rates_Mbps)
- }
+ results_file_path = os.path.join(
+ self.log_path, '{}.txt'.format(self.current_test_name))
with open(results_file_path, 'w') as results_file:
json.dump(wputils.serialize_dict(test_result), results_file)
# Plot and save
- figure = BokehFigure(test_name,
- x_label='Time (s)',
- primary_y_label='Throughput (Mbps)')
- time_data = list(range(0, len(instantaneous_rates_Mbps)))
- figure.add_line(time_data,
- instantaneous_rates_Mbps,
- legend=self.current_test_name,
- marker='circle')
- output_file_path = os.path.join(self.log_path,
- '{}.html'.format(test_name))
- figure.generate_figure(output_file_path)
+ # Set blackbox metrics
+ if self.publish_testcase_metrics:
+ self.testcase_metric_logger.add_metric(
+ 'avg_throughput',
+ test_result['iperf_summary']['avg_throughput'])
+ self.testcase_metric_logger.add_metric(
+ 'min_throughput',
+ test_result['iperf_summary']['min_throughput'])
+ self.testcase_metric_logger.add_metric(
+ 'std_dev_percent',
+ test_result['iperf_summary']['std_dev_percent'])
+ figure = BokehFigure(self.current_test_name,
+ x_label='Time (s)',
+ primary_y_label='Throughput (Mbps)')
+ time_data = list(
+ range(
+ 0,
+ len(test_result['iperf_summary']['instantaneous_rates'])))
+ figure.add_line(
+ time_data,
+ test_result['iperf_summary']['instantaneous_rates'],
+ legend=self.current_test_name,
+ marker='circle')
+ output_file_path = os.path.join(
+ self.log_path, '{}.html'.format(self.current_test_name))
+ figure.generate_figure(output_file_path)
return test_result
def setup_ap(self, testcase_params):
@@ -352,9 +324,11 @@
# Run test and log result
# Start iperf session
self.log.info('Starting iperf test.')
+ test_result = collections.OrderedDict()
llstats_obj = wputils.LinkLayerStats(self.dut)
llstats_obj.update_stats()
- if self.testbed_params['sniffer_enable']:
+ if self.testbed_params['sniffer_enable'] and len(
+ self.testclass_results) % self.sniffer_subsampling == 0:
self.sniffer.start_capture(
network=testcase_params['test_network'],
chan=testcase_params['channel'],
@@ -375,7 +349,8 @@
current_rssi = current_rssi.result()
server_output_path = self.iperf_server.stop()
# Stop sniffer
- if self.testbed_params['sniffer_enable']:
+ if self.testbed_params['sniffer_enable'] and len(
+ self.testclass_results) % self.sniffer_subsampling == 0:
self.sniffer.stop_capture()
# Set attenuator to 0 dB
for attenuator in self.attenuators:
@@ -388,16 +363,61 @@
try:
iperf_result = ipf.IPerfResult(iperf_file)
except:
- asserts.fail('Cannot get iperf result.')
+ iperf_result = ipf.IPerfResult('{}') #empty iperf result
+ self.log.warning('Cannot get iperf result.')
+ if iperf_result.instantaneous_rates:
+ instantaneous_rates_Mbps = [
+ rate * 8 * (1.024**2)
+ for rate in iperf_result.instantaneous_rates[
+ self.testclass_params['iperf_ignored_interval']:-1]
+ ]
+ tput_standard_deviation = iperf_result.get_std_deviation(
+ self.testclass_params['iperf_ignored_interval']) * 8
+ else:
+ instantaneous_rates_Mbps = [float('nan')]
+ tput_standard_deviation = float('nan')
+ test_result['iperf_summary'] = {
+ 'instantaneous_rates':
+ instantaneous_rates_Mbps,
+ 'avg_throughput':
+ numpy.mean(instantaneous_rates_Mbps),
+ 'std_deviation':
+ tput_standard_deviation,
+ 'min_throughput':
+ min(instantaneous_rates_Mbps),
+ 'std_dev_percent':
+ (tput_standard_deviation / numpy.mean(instantaneous_rates_Mbps)) *
+ 100
+ }
llstats_obj.update_stats()
curr_llstats = llstats_obj.llstats_incremental.copy()
- test_result = collections.OrderedDict()
test_result['testcase_params'] = testcase_params.copy()
test_result['ap_settings'] = self.access_point.ap_settings.copy()
test_result['attenuation'] = testcase_params['atten_level']
test_result['iperf_result'] = iperf_result
test_result['rssi_result'] = current_rssi
test_result['llstats'] = curr_llstats
+
+ llstats = (
+ 'TX MCS = {0} ({1:.1f}%). '
+ 'RX MCS = {2} ({3:.1f}%)'.format(
+ test_result['llstats']['summary']['common_tx_mcs'],
+ test_result['llstats']['summary']['common_tx_mcs_freq'] * 100,
+ test_result['llstats']['summary']['common_rx_mcs'],
+ test_result['llstats']['summary']['common_rx_mcs_freq'] * 100))
+
+ test_message = (
+ 'Atten: {0:.2f}dB, RSSI: {1:.2f}dB. '
+ 'Throughput (Mean: {2:.2f}, Std. Dev:{3:.2f}%, Min: {4:.2f} Mbps).'
+ 'LLStats : {5}'.format(
+ test_result['attenuation'],
+ test_result['rssi_result']['signal_poll_rssi']['mean'],
+ test_result['iperf_summary']['avg_throughput'],
+ test_result['iperf_summary']['std_dev_percent'],
+ test_result['iperf_summary']['min_throughput'], llstats))
+
+ self.log.info(test_message)
+
self.testclass_results.append(test_result)
return test_result
@@ -501,6 +521,7 @@
setting turntable orientation and other chamber parameters to study
performance in varying channel conditions
"""
+
def __init__(self, controllers):
base_test.BaseTestClass.__init__(self, controllers)
# Define metrics to be uploaded to BlackBox
@@ -605,6 +626,22 @@
test_atten = self.testclass_params['ota_atten_levels'][band][1]
return test_atten
+ def _test_throughput_stability_over_orientation(self, testcase_params):
+ """ Function that gets called for each test case
+
+ The function gets called in each test case. The function customizes
+ the test based on the test name of the test that called it
+
+ Args:
+ testcase_params: dict containing test specific parameters
+ """
+ testcase_params = self.compile_test_params(testcase_params)
+ for position in testcase_params['positions']:
+ testcase_params['position'] = position
+ self.setup_throughput_stability_test(testcase_params)
+ test_result = self.run_throughput_stability_test(testcase_params)
+ self.post_process_results(test_result)
+
def generate_test_cases(self, channels, modes, traffic_types,
traffic_directions, signal_levels, chamber_mode,
positions):
@@ -619,8 +656,8 @@
}
test_cases = []
- for channel, mode, signal_level, position, traffic_type, traffic_direction in itertools.product(
- channels, modes, signal_levels, positions, traffic_types,
+ for channel, mode, signal_level, traffic_type, traffic_direction in itertools.product(
+ channels, modes, signal_levels, traffic_types,
traffic_directions):
bandwidth = int(''.join([x for x in mode if x.isdigit()]))
if channel not in allowed_configs[bandwidth]:
@@ -634,19 +671,22 @@
signal_level=signal_level,
chamber_mode=chamber_mode,
total_positions=len(positions),
- position=position)
+ positions=positions)
testcase_name = ('test_tput_stability'
- '_{}_{}_{}_ch{}_{}_pos{}'.format(
+ '_{}_{}_{}_ch{}_{}'.format(
signal_level, traffic_type, traffic_direction,
- channel, mode, position))
- setattr(self, testcase_name,
- partial(self._test_throughput_stability, testcase_params))
+ channel, mode))
+ setattr(
+ self, testcase_name,
+ partial(self._test_throughput_stability_over_orientation,
+ testcase_params))
test_cases.append(testcase_name)
return test_cases
class WifiOtaThroughputStability_TenDegree_Test(WifiOtaThroughputStabilityTest
):
+
def __init__(self, controllers):
WifiOtaThroughputStabilityTest.__init__(self, controllers)
self.tests = self.generate_test_cases([6, 36, 149, '6g37'],
@@ -655,8 +695,13 @@
['high', 'low'], 'orientation',
list(range(0, 360, 10)))
+ def setup_class(self):
+ WifiOtaThroughputStabilityTest.setup_class(self)
+ self.sniffer_subsampling = 6
+
class WifiOtaThroughputStability_45Degree_Test(WifiOtaThroughputStabilityTest):
+
def __init__(self, controllers):
WifiOtaThroughputStabilityTest.__init__(self, controllers)
self.tests = self.generate_test_cases([6, 36, 149, '6g37'],
@@ -668,6 +713,7 @@
class WifiOtaThroughputStability_SteppedStirrers_Test(
WifiOtaThroughputStabilityTest):
+
def __init__(self, controllers):
WifiOtaThroughputStabilityTest.__init__(self, controllers)
self.tests = self.generate_test_cases([6, 36, 149, '6g37'],
@@ -676,3 +722,7 @@
['high', 'low'],
'stepped stirrers',
list(range(100)))
+
+ def setup_class(self):
+ WifiOtaThroughputStabilityTest.setup_class(self)
+ self.sniffer_subsampling = 10
diff --git a/acts_tests/tests/google/wifi/WifiTxPowerCheckTest.py b/acts_tests/tests/google/wifi/WifiTxPowerCheckTest.py
index 706903c..d0a3334 100644
--- a/acts_tests/tests/google/wifi/WifiTxPowerCheckTest.py
+++ b/acts_tests/tests/google/wifi/WifiTxPowerCheckTest.py
@@ -95,8 +95,8 @@
test_types=[
'test_tx_power',
],
- country_codes=['US', 'GB', 'JP'],
- sar_states=range(0, 13))
+ country_codes=['US', 'GB', 'JP', 'CA', 'AU'],
+ sar_states=range(-1, 13))
def setup_class(self):
self.dut = self.android_devices[-1]
@@ -139,6 +139,9 @@
self.nvram_sar_data = self.read_nvram_sar_data()
self.csv_sar_data = self.read_sar_csv(self.testclass_params['sar_csv'])
+ # Configure test retries
+ self.user_params['retry_tests'] = [self.__class__.__name__]
+
def teardown_class(self):
# Turn WiFi OFF and reset AP
self.access_point.teardown()
@@ -246,11 +249,13 @@
of SAR scenarios to NVRAM data tables.
"""
- self.sar_state_mapping = collections.OrderedDict([(-1, {
+ self.sar_state_mapping = collections.OrderedDict([(-2, {
"google_name":
- 'WIFI_POWER_SCENARIO_DISABLE'
- }), (0, {
+ 'WIFI_POWER_SCENARIO_INVALID'
+ }), (-1, {
"google_name": 'WIFI_POWER_SCENARIO_DISABLE'
+ }), (0, {
+ "google_name": 'WIFI_POWER_SCENARIO_VOICE_CALL'
}), (1, {
"google_name": 'WIFI_POWER_SCENARIO_ON_HEAD_CELL_OFF'
}), (2, {
@@ -303,24 +308,24 @@
"""
sar_config = collections.OrderedDict()
- list_of_countries = ['fcc', 'jp']
+ list_of_countries = ['fcc', 'jp', 'ca']
try:
sar_config['country'] = next(country
for country in list_of_countries
- if country in sar_line)
+ if country in sar_line.split('=')[0])
except:
sar_config['country'] = 'row'
list_of_sar_states = ['grip', 'bt', 'hotspot']
try:
sar_config['state'] = next(state for state in list_of_sar_states
- if state in sar_line)
+ if state in sar_line.split('=')[0])
except:
sar_config['state'] = 'head'
list_of_bands = ['2g', '5g', '6g']
sar_config['band'] = next(band for band in list_of_bands
- if band in sar_line)
+ if band in sar_line.split('=')[0])
sar_config['rsdb'] = 'rsdb' if 'rsdb' in sar_line else 'mimo'
sar_config['airplane_mode'] = '_2=' in sar_line
@@ -328,9 +333,10 @@
sar_powers = sar_line.split('=')[1].split(',')
decoded_powers = []
for sar_power in sar_powers:
+ # Note that core 0 and 1 are flipped in the NVRAM entries
decoded_powers.append([
- (int(sar_power[2:4], 16) & int('7f', 16)) / 4,
- (int(sar_power[4:], 16) & int('7f', 16)) / 4
+ (int(sar_power[4:], 16) & int('7f', 16)) / 4,
+ (int(sar_power[2:4], 16) & int('7f', 16)) / 4
])
return tuple(sar_config.values()), decoded_powers
@@ -353,6 +359,8 @@
reg_domain = 'fcc'
elif testcase_params['country_code'] == 'JP':
reg_domain = 'jp'
+ elif testcase_params['country_code'] == 'CA':
+ reg_domain = 'ca'
else:
reg_domain = 'row'
for band, channels in self.BAND_TO_CHANNEL_MAP.items():
@@ -385,6 +393,8 @@
reg_domain = 'fcc'
elif testcase_params['country_code'] == 'JP':
reg_domain = 'jp'
+ elif testcase_params['country_code'] == 'CA':
+ reg_domain = 'ca'
else:
reg_domain = 'row'
for band, channels in self.BAND_TO_CHANNEL_MAP.items():
@@ -597,19 +607,22 @@
str) and '6g' in result['testcase_params']['channel']:
mode = 'HE' + str(result['testcase_params']['bandwidth'])
else:
- mode = 'VHT' + str(result['testcase_params']['bandwidth'])
+ mode = 'HE' + str(result['testcase_params']['bandwidth'])
regulatory_power = result['wl_curpower']['regulatory_limits'][(mode, 0,
2)]
- if result['testcase_params']['sar_state'] == 0:
- #get from wl_curpower
- csv_powers = [30, 30]
- nvram_powers = [30, 30]
- sar_config = 'SAR DISABLED'
- else:
- sar_config, nvram_powers = self.get_sar_power_from_nvram(
- result['testcase_params'])
+ board_power = result['wl_curpower']['board_limits'][(mode, str(0), 2)]
+ # try:
+ sar_config, nvram_powers = self.get_sar_power_from_nvram(
+ result['testcase_params'])
+ # except:
+ # nvram_powers = [99, 99]
+ # sar_config = 'SAR DISABLED'
+ try:
csv_config, csv_powers = self.get_sar_power_from_csv(
result['testcase_params'])
+ except:
+ #get from wl_curpower
+ csv_powers = [99, 99]
self.log.info("SAR state: {} ({})".format(
result['testcase_params']['sar_state'],
self.sar_state_mapping[result['testcase_params']['sar_state']],
@@ -618,12 +631,12 @@
result['testcase_params']['country_code']))
self.log.info('BRCM SAR Table: {}'.format(sar_config))
expected_power = [
- min([csv_powers[0], regulatory_power]) - 1.5,
- min([csv_powers[1], regulatory_power]) - 1.5
+ min([csv_powers[0], regulatory_power, board_power]) - 1.5,
+ min([csv_powers[1], regulatory_power, board_power]) - 1.5
]
- power_str = "NVRAM Powers: {}, CSV Powers: {}, Reg Powers: {}, Expected Powers: {}, Reported Powers: {}".format(
- nvram_powers, csv_powers, [regulatory_power] * 2, expected_power,
- result['tx_powers'])
+ power_str = "NVRAM Powers: {}, CSV Powers: {}, Reg Powers: {}, Board Power: {}, Expected Powers: {}, Reported Powers: {}".format(
+ nvram_powers, csv_powers, [regulatory_power] * 2,
+ [board_power] * 2, expected_power, result['tx_powers'])
max_error = max([
abs(expected_power[idx] - result['tx_powers'][idx])
for idx in [0, 1]
@@ -668,6 +681,12 @@
bw=testcase_params['bandwidth'],
duration=testcase_params['ping_duration'] *
len(testcase_params['atten_range']) + self.TEST_TIMEOUT)
+ # Set sar state
+ if testcase_params['sar_state'] == -1:
+ self.dut.adb.shell('halutil -sar disable')
+ else:
+ self.dut.adb.shell('halutil -sar enable {}'.format(
+ testcase_params['sar_state']))
# Run ping and sweep attenuation as needed
self.log.info('Starting ping.')
thread_future = wputils.get_ping_stats_nb(self.ping_server,
@@ -679,18 +698,15 @@
# Set mcs
if isinstance(testcase_params['channel'],
int) and testcase_params['channel'] < 13:
- self.dut.adb.shell('wl 2g_rate -v 0x2 -b {}'.format(
+ self.dut.adb.shell('wl 2g_rate -e 0 -s 2 -b {}'.format(
testcase_params['bandwidth']))
elif isinstance(testcase_params['channel'],
int) and testcase_params['channel'] > 13:
- self.dut.adb.shell('wl 5g_rate -v 0x2 -b {}'.format(
+ self.dut.adb.shell('wl 5g_rate -e 0 -s 2 -b {}'.format(
testcase_params['bandwidth']))
else:
self.dut.adb.shell('wl 6g_rate -e 0 -s 2 -b {}'.format(
testcase_params['bandwidth']))
- # Set sar state
- self.dut.adb.shell('halutil -sar enable {}'.format(
- testcase_params['sar_state']))
# Refresh link layer stats
llstats_obj.update_stats()
# Check sar state
@@ -703,11 +719,16 @@
last_est_out = self.dut.adb.shell(
"wl curpower | grep 'Last est. power'", ignore_status=True)
if "Last est. power" in last_est_out:
- per_chain_powers = last_est_out.split(
- ':')[1].strip().split(' ')
- per_chain_powers = [
- float(power) for power in per_chain_powers
- ]
+ try:
+ per_chain_powers = last_est_out.split(
+ ':')[1].strip().split(' ')
+ per_chain_powers = [
+ float(power) for power in per_chain_powers
+ ]
+ except:
+ per_chain_powers = [0, 0]
+ self.log.warning(
+ 'Could not parse output: {}'.format(last_est_out))
self.log.info(
'Current Tx Powers = {}'.format(per_chain_powers))
if per_chain_powers[0] > 0:
@@ -786,8 +807,11 @@
self.access_point.set_region(self.testbed_params['DFS_region'])
else:
self.access_point.set_region(self.testbed_params['default_region'])
- self.access_point.set_channel(band, testcase_params['channel'])
- self.access_point.set_bandwidth(band, testcase_params['mode'])
+ self.access_point.set_channel_and_bandwidth(band,
+ testcase_params['channel'],
+ testcase_params['mode'])
+ #self.access_point.set_channel(band, testcase_params['channel'])
+ #self.access_point.set_bandwidth(band, testcase_params['mode'])
if 'low' in testcase_params['ap_power']:
self.log.info('Setting low AP power.')
self.access_point.set_power(
@@ -825,9 +849,17 @@
if self.testbed_params.get('txbf_off', False):
wputils.disable_beamforming(self.dut)
wutils.set_wifi_country_code(self.dut, testcase_params['country_code'])
+ current_country = self.dut.adb.shell('wl country')
+ self.log.info('Current country code: {}'.format(current_country))
+ if testcase_params['country_code'] not in current_country:
+ asserts.fail('Country code not correct.')
+ chan_list = self.dut.adb.shell('wl chan_info_list')
+ if str(testcase_params['channel']) not in chan_list:
+ asserts.skip('Channel {} not supported in {}'.format(
+ testcase_params['channel'], testcase_params['country_code']))
wutils.wifi_connect(self.dut,
testcase_params['test_network'],
- num_of_tries=1,
+ num_of_tries=5,
check_connectivity=True)
self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
@@ -925,3 +957,23 @@
partial(self._test_ping, testcase_params))
test_cases.append(testcase_name)
return test_cases
+
+
+class WifiTxPowerCheck_BasicSAR_Test(WifiTxPowerCheckTest):
+
+ def __init__(self, controllers):
+ base_test.BaseTestClass.__init__(self, controllers)
+ self.testcase_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_case())
+ self.testclass_metric_logger = (
+ BlackboxMappedMetricLogger.for_test_class())
+ self.publish_testcase_metrics = True
+ self.tests = self.generate_test_cases(
+ ap_power='standard',
+ channels=[6, 36, 52, 100, 149, '6g37'],
+ modes=['bw20', 'bw160'],
+ test_types=[
+ 'test_tx_power',
+ ],
+ country_codes=['US', 'GB', 'JP', 'CA'],
+ sar_states=[-1, 0, 1, 2, 3, 4])
diff --git a/acts_tests/tests/google/wifi/aware/functional/AttachTest.py b/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
index eee0f9d..8b16dc7 100644
--- a/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
@@ -166,3 +166,18 @@
# try enabling Aware again (attach)
dut.droid.wifiAwareAttach()
autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ @test_tracker_info(uuid="")
+ def test_attach_detach_attach_again(self):
+ """Validated there is no delay between Disable Aware and re-enable Aware
+ """
+ dut = self.android_devices[0]
+
+ # enable Aware (attach)
+ dut.droid.wifiAwareAttach()
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ dut.droid.wifiAwareDestroyAll()
+ # Restart Aware immediately
+ dut.droid.wifiAwareAttach()
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED, timeout=1)
diff --git a/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py b/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
index 39f009d..69dc42c 100644
--- a/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
@@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import queue
import string
import time
@@ -32,6 +33,7 @@
PAYLOAD_SIZE_MIN = 0
PAYLOAD_SIZE_TYPICAL = 1
PAYLOAD_SIZE_MAX = 2
+ EVENT_TIMEOUT = 3
# message strings
query_msg = "How are you doing? 你好嗎?"
@@ -1111,6 +1113,18 @@
event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], s_to_p_msg,
"Message on service %s from Subscriber to Publisher "
"not received correctly" % session_name["pub"][p_disc_id])
+ try:
+ event = p_dut.ed.pop_event(autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+ p_disc_id), self.EVENT_TIMEOUT)
+ p_dut.log.info("re-transmit message received: "
+ + event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING])
+ asserts.assert_equal(
+ event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], s_to_p_msg,
+ "Message on service %s from Subscriber to Publisher "
+ "not received correctly" % session_name["pub"][p_disc_id])
+ except queue.Empty:
+ p_dut.log.info("no re-transmit message")
+
peer_id_on_pub = event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
# Message send from Publisher to Subscriber
@@ -1129,6 +1143,17 @@
event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], p_to_s_msg,
"Message on service %s from Publisher to Subscriber"
"not received correctly" % session_name["sub"][s_disc_id])
+ try:
+ event = s_dut.ed.pop_event(autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+ s_disc_id), self.EVENT_TIMEOUT)
+ s_dut.log.info("re-transmit message received: "
+ + event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING])
+ asserts.assert_equal(
+ event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], p_to_s_msg,
+ "Message on service %s from Publisher to Subscriber"
+ "not received correctly" % session_name["sub"][s_disc_id])
+ except queue.Empty:
+ s_dut.log.info("no re-transmit message")
def run_multiple_concurrent_services_same_name_diff_ssi(self, type_x, type_y):
"""Validate same service name with multiple service specific info on publisher
diff --git a/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py b/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py
index 871602b..9c44ca6 100644
--- a/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py
+++ b/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py
@@ -33,7 +33,7 @@
# take some time
WAIT_FOR_CLUSTER = 5
- def start_discovery_session(self, dut, session_id, is_publish, dtype):
+ def start_discovery_session(self, dut, session_id, is_publish, dtype, instant_mode = None):
"""Start a discovery session
Args:
@@ -41,6 +41,7 @@
session_id: ID of the Aware session in which to start discovery
is_publish: True for a publish session, False for subscribe session
dtype: Type of the discovery session
+ instant_mode: set the channel to use instant communication mode.
Returns:
Discovery session started event.
@@ -48,6 +49,9 @@
config = {}
config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceXY"
+ if instant_mode is not None:
+ config[aconsts.DISCOVERY_KEY_INSTANT_COMMUNICATION_MODE] = instant_mode
+
if is_publish:
disc_id = dut.droid.wifiAwarePublish(session_id, config)
@@ -74,6 +78,7 @@
solicited/active.
dw_24ghz: DW interval in the 2.4GHz band.
dw_5ghz: DW interval in the 5GHz band.
+ num_iterations: number of the iterations.
startup_offset: The start-up gap (in seconds) between the two devices
timeout_period: Time period over which to measure synchronization
"""
@@ -160,6 +165,7 @@
solicited/active.
dw_24ghz: DW interval in the 2.4GHz band.
dw_5ghz: DW interval in the 5GHz band.
+ num_iterations: number of the iterations.
"""
key = "%s_dw24_%d_dw5_%d" % ("unsolicited_passive"
if do_unsolicited_passive else
@@ -237,13 +243,15 @@
p_dut.droid.wifiAwareDestroyAll()
s_dut.droid.wifiAwareDestroyAll()
- def run_message_latency(self, results, dw_24ghz, dw_5ghz, num_iterations):
+ def run_message_latency(self, results, dw_24ghz, dw_5ghz, num_iterations, instant_mode=None):
"""Run the message tx latency test with the specified DW intervals.
Args:
results: Result array to be populated - will add results (not erase it)
dw_24ghz: DW interval in the 2.4GHz band.
dw_5ghz: DW interval in the 5GHz band.
+ num_iterations: number of the iterations.
+ instant_mode: set the band to use instant communication mode, 2G or 5G
"""
key = "dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
results[key] = {}
@@ -262,9 +270,9 @@
p_dut,
s_dut,
p_config=autils.create_discovery_config(
- self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+ self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED, instant_mode=instant_mode),
s_config=autils.create_discovery_config(
- self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+ self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE, instant_mode=instant_mode),
device_startup_offset=self.device_startup_offset)
latencies = []
@@ -330,6 +338,7 @@
results: Result array to be populated - will add results (not erase it)
dw_24ghz: DW interval in the 2.4GHz band.
dw_5ghz: DW interval in the 5GHz band.
+ num_iterations: number of the iterations.
"""
key_avail = "on_avail_dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
key_link_props = "link_props_dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
@@ -435,21 +444,21 @@
results[key_avail]["ndp_setup_failures"] = ndp_setup_failures
def run_end_to_end_latency(self, results, dw_24ghz, dw_5ghz,
- num_iterations, startup_offset, include_setup):
+ num_iterations, startup_offset, include_setup, instant_mode = None):
"""Measure the latency for end-to-end communication link setup:
- Start Aware
- Discovery
- - Message from Sub -> Pub
- - Message from Pub -> Sub
- NDP setup
Args:
results: Result array to be populated - will add results (not erase it)
dw_24ghz: DW interval in the 2.4GHz band.
dw_5ghz: DW interval in the 5GHz band.
+ num_iterations: number of the iterations.
startup_offset: The start-up gap (in seconds) between the two devices
include_setup: True to include the cluster setup in the latency
measurements.
+ instant_mode: set the band to use instant communication mode, 2G or 5G
"""
key = "dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
results[key] = {}
@@ -492,11 +501,19 @@
# start publish
p_disc_id, p_disc_event = self.start_discovery_session(
- p_dut, p_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED)
+ p_dut, p_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED, instant_mode)
# start subscribe
s_disc_id, s_session_event = self.start_discovery_session(
- s_dut, s_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE)
+ s_dut, s_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE, instant_mode)
+
+ # create NDP
+
+ # Publisher: request network
+ p_req_key = autils.request_network(
+ p_dut,
+ p_dut.droid.wifiAwareCreateNetworkSpecifier(
+ p_disc_id, None, None))
# wait for discovery (allow for failures here since running lots of
# samples and would like to get the partial data even in the presence of
@@ -516,82 +533,6 @@
failures = failures + 1
break
- # message from Sub -> Pub
- msg_s2p = "Message Subscriber -> Publisher #%d" % i
- next_msg_id = self.get_next_msg_id()
- s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
- next_msg_id, msg_s2p, 0)
-
- # wait for Tx confirmation
- try:
- s_dut.ed.pop_event(aconsts.SESSION_CB_ON_MESSAGE_SENT,
- autils.EVENT_TIMEOUT)
- except queue.Empty:
- s_dut.log.info("[Subscriber] Timed out while waiting for "
- "SESSION_CB_ON_MESSAGE_SENT")
- failures = failures + 1
- break
-
- # wait for Rx confirmation (and validate contents)
- try:
- event = p_dut.ed.pop_event(
- aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
- autils.EVENT_TIMEOUT)
- peer_id_on_pub = event['data'][
- aconsts.SESSION_CB_KEY_PEER_ID]
- if (event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
- != msg_s2p):
- p_dut.log.info(
- "[Publisher] Corrupted input message - %s", event)
- failures = failures + 1
- break
- except queue.Empty:
- p_dut.log.info("[Publisher] Timed out while waiting for "
- "SESSION_CB_ON_MESSAGE_RECEIVED")
- failures = failures + 1
- break
-
- # message from Pub -> Sub
- msg_p2s = "Message Publisher -> Subscriber #%d" % i
- next_msg_id = self.get_next_msg_id()
- p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub,
- next_msg_id, msg_p2s, 0)
-
- # wait for Tx confirmation
- try:
- p_dut.ed.pop_event(aconsts.SESSION_CB_ON_MESSAGE_SENT,
- autils.EVENT_TIMEOUT)
- except queue.Empty:
- p_dut.log.info("[Publisher] Timed out while waiting for "
- "SESSION_CB_ON_MESSAGE_SENT")
- failures = failures + 1
- break
-
- # wait for Rx confirmation (and validate contents)
- try:
- event = s_dut.ed.pop_event(
- aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
- autils.EVENT_TIMEOUT)
- if (event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
- != msg_p2s):
- s_dut.log.info(
- "[Subscriber] Corrupted input message - %s", event)
- failures = failures + 1
- break
- except queue.Empty:
- s_dut.log.info("[Subscriber] Timed out while waiting for "
- "SESSION_CB_ON_MESSAGE_RECEIVED")
- failures = failures + 1
- break
-
- # create NDP
-
- # Publisher: request network
- p_req_key = autils.request_network(
- p_dut,
- p_dut.droid.wifiAwareCreateNetworkSpecifier(
- p_disc_id, peer_id_on_pub, None))
-
# Subscriber: request network
s_req_key = autils.request_network(
s_dut,
@@ -613,6 +554,8 @@
cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
(cconsts.NETWORK_CB_KEY_ID, s_req_key))
except:
+ s_dut.log.info("[Subscriber] Timed out while waiting for "
+ "EVENT_NETWORK_CALLBACK")
failures = failures + 1
break
@@ -742,6 +685,34 @@
asserts.explicit_pass(
"test_message_latency_default_dws finished", extras=results)
+ def test_message_latency_default_dws_instant_mode_2g(self):
+ """Measure the send message latency with the default DW configuration. Test
+ performed on non-queued message transmission - i.e. waiting for confirmation
+ of reception (ACK) before sending the next message."""
+ results = {}
+ self.run_message_latency(
+ results=results,
+ dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+ dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+ num_iterations=100,
+ instant_mode="2G")
+ asserts.explicit_pass(
+ "test_message_latency_default_dws finished", extras=results)
+
+ def test_message_latency_default_dws_instant_mode_5g(self):
+ """Measure the send message latency with the default DW configuration. Test
+ performed on non-queued message transmission - i.e. waiting for confirmation
+ of reception (ACK) before sending the next message."""
+ results = {}
+ self.run_message_latency(
+ results=results,
+ dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+ dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+ num_iterations=100,
+ instant_mode="5G")
+ asserts.explicit_pass(
+ "test_message_latency_default_dws finished", extras=results)
+
def test_message_latency_non_interactive_dws(self):
"""Measure the send message latency with the DW configuration for
non-interactive mode. Test performed on non-queued message transmission -
@@ -787,8 +758,6 @@
"""Measure the latency for end-to-end communication link setup:
- Start Aware
- Discovery
- - Message from Sub -> Pub
- - Message from Pub -> Sub
- NDP setup
"""
results = {}
@@ -802,14 +771,48 @@
asserts.explicit_pass(
"test_end_to_end_latency_default_dws finished", extras=results)
+ def test_end_to_end_latency_default_dws_instant_mode_2g(self):
+ """Measure the latency for end-to-end communication link setup:
+ - Start Aware
+ - Discovery
+ - NDP setup
+ """
+ results = {}
+ self.run_end_to_end_latency(
+ results,
+ dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+ dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+ num_iterations=10,
+ startup_offset=0,
+ include_setup=True,
+ instant_mode="2G")
+ asserts.explicit_pass(
+ "test_end_to_end_latency_default_dws finished", extras=results)
+
+ def test_end_to_end_latency_default_dws_instant_mode_5g(self):
+ """Measure the latency for end-to-end communication link setup:
+ - Start Aware
+ - Discovery
+ - NDP setup
+ """
+ results = {}
+ self.run_end_to_end_latency(
+ results,
+ dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+ dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+ num_iterations=10,
+ startup_offset=0,
+ include_setup=True,
+ instant_mode="5G")
+ asserts.explicit_pass(
+ "test_end_to_end_latency_default_dws finished", extras=results)
+
def test_end_to_end_latency_post_attach_default_dws(self):
"""Measure the latency for end-to-end communication link setup without
the initial synchronization:
- Start Aware & synchronize initially
- Loop:
- Discovery
- - Message from Sub -> Pub
- - Message from Pub -> Sub
- NDP setup
"""
results = {}
@@ -823,3 +826,59 @@
asserts.explicit_pass(
"test_end_to_end_latency_post_attach_default_dws finished",
extras=results)
+
+ def test_end_to_end_latency_post_attach_default_dws_instant_mode_2g(self):
+ """Measure the latency for end-to-end communication link setup without
+ the initial synchronization:
+ - Start Aware & synchronize initially
+ - Loop:
+ - Discovery
+ - NDP setup
+ """
+ asserts.skip_if(not self.android_devices[0].droid.isSdkAtLeastT(),
+ "instant communication mode is only supported on T+")
+ asserts.skip_if(not (self.android_devices[0].aware_capabilities[aconsts
+ .CAP_SUPPORTED_INSTANT_COMMUNICATION_MODE]
+ and self.android_devices[0].aware_capabilities[aconsts
+ .CAP_SUPPORTED_INSTANT_COMMUNICATION_MODE]),
+ "Device doesn't support instant communication mode")
+ results = {}
+ self.run_end_to_end_latency(
+ results,
+ dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+ dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+ num_iterations=10,
+ startup_offset=0,
+ include_setup=False,
+ instant_mode="2G")
+ asserts.explicit_pass(
+ "test_end_to_end_latency_post_attach_default_dws_instant_mode finished",
+ extras=results)
+
+ def test_end_to_end_latency_post_attach_default_dws_instant_mode_5g(self):
+ """Measure the latency for end-to-end communication link setup without
+ the initial synchronization:
+ - Start Aware & synchronize initially
+ - Loop:
+ - Discovery
+ - NDP setup
+ """
+ asserts.skip_if(not self.android_devices[0].droid.isSdkAtLeastT(),
+ "instant communication mode is only supported on T+")
+ asserts.skip_if(not (self.android_devices[0].aware_capabilities[aconsts
+ .CAP_SUPPORTED_INSTANT_COMMUNICATION_MODE]
+ and self.android_devices[0].aware_capabilities[aconsts
+ .CAP_SUPPORTED_INSTANT_COMMUNICATION_MODE]),
+ "Device doesn't support instant communication mode")
+ results = {}
+ self.run_end_to_end_latency(
+ results,
+ dw_24ghz=aconsts.POWER_DW_24_INTERACTIVE,
+ dw_5ghz=aconsts.POWER_DW_5_INTERACTIVE,
+ num_iterations=10,
+ startup_offset=0,
+ include_setup=False,
+ instant_mode="5G")
+ asserts.explicit_pass(
+ "test_end_to_end_latency_post_attach_default_dws_instant_mode finished",
+ extras=results)
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
index 1051fc4..84ad88e 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
@@ -339,19 +339,6 @@
#############################################################################
- @test_tracker_info(uuid="9e4e7ab4-2254-498c-9788-21e15ed9a370")
- def test_rtt_oob_discovery_one_way(self):
- """Perform RTT between 2 Wi-Fi Aware devices. Use out-of-band discovery
- to communicate the MAC addresses to the peer. Test one-direction RTT only.
- Functionality test: Only evaluate success rate.
- """
- rtt_results = self.run_rtt_oob_discovery_set(
- do_both_directions=False,
- iter_count=self.NUM_ITER,
- time_between_iterations=self.TIME_BETWEEN_ITERATIONS,
- time_between_roles=self.TIME_BETWEEN_ROLES)
- self.verify_results(rtt_results)
-
@test_tracker_info(uuid="22edba77-eeb2-43ee-875a-84437550ad84")
def test_rtt_oob_discovery_both_ways(self):
"""Perform RTT between 2 Wi-Fi Aware devices. Use out-of-band discovery
@@ -393,19 +380,6 @@
time_between_roles=self.TIME_BETWEEN_ROLES)
self.verify_results(rtt_results1, rtt_results2)
- @test_tracker_info(uuid="3a1d19a2-7241-49e0-aaf2-0a1da4c29783")
- def test_rtt_oob_discovery_one_way_with_accuracy_evaluation(self):
- """Perform RTT between 2 Wi-Fi Aware devices. Use out-of-band discovery
- to communicate the MAC addresses to the peer. Test one-direction RTT only.
- Performance test: evaluate success rate and accuracy.
- """
- rtt_results = self.run_rtt_oob_discovery_set(
- do_both_directions=False,
- iter_count=self.NUM_ITER,
- time_between_iterations=self.TIME_BETWEEN_ITERATIONS,
- time_between_roles=self.TIME_BETWEEN_ROLES)
- self.verify_results(rtt_results, accuracy_evaluation=True)
-
@test_tracker_info(uuid="82f954a5-c0ec-4bc6-8940-3b72199328ac")
def test_rtt_oob_discovery_both_ways_with_accuracy_evaluation(self):
"""Perform RTT between 2 Wi-Fi Aware devices. Use out-of-band discovery
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiEnterprise11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiEnterprise11axTest.py
index 7f0f121..94313b2 100644
--- a/acts_tests/tests/google/wifi/wifi6/WifiEnterprise11axTest.py
+++ b/acts_tests/tests/google/wifi/wifi6/WifiEnterprise11axTest.py
@@ -15,7 +15,7 @@
from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiEnterpriseTest import WifiEnterpriseTest
+from ..WifiEnterpriseTest import WifiEnterpriseTest
WifiEnums = wutils.WifiEnums
# EAP Macros
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiEnterpriseRoaming11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiEnterpriseRoaming11axTest.py
index ec70da9..781e5af 100644
--- a/acts_tests/tests/google/wifi/wifi6/WifiEnterpriseRoaming11axTest.py
+++ b/acts_tests/tests/google/wifi/wifi6/WifiEnterpriseRoaming11axTest.py
@@ -15,7 +15,7 @@
from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiEnterpriseRoamingTest import WifiEnterpriseRoamingTest
+from ..WifiEnterpriseRoamingTest import WifiEnterpriseRoamingTest
WifiEnums = wutils.WifiEnums
# EAP Macros
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiManager11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiManager11axTest.py
index d2fb981..fd906a0 100644
--- a/acts_tests/tests/google/wifi/wifi6/WifiManager11axTest.py
+++ b/acts_tests/tests/google/wifi/wifi6/WifiManager11axTest.py
@@ -14,7 +14,7 @@
# limitations under the License.
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiManagerTest import WifiManagerTest
+from ..WifiManagerTest import WifiManagerTest
class WifiManager11axTest(WifiManagerTest):
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiNetworkSuggestion11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiNetworkSuggestion11axTest.py
index 2c45e8c..ab08cce 100644
--- a/acts_tests/tests/google/wifi/wifi6/WifiNetworkSuggestion11axTest.py
+++ b/acts_tests/tests/google/wifi/wifi6/WifiNetworkSuggestion11axTest.py
@@ -15,7 +15,7 @@
from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiNetworkSuggestionTest import WifiNetworkSuggestionTest
+from ..WifiNetworkSuggestionTest import WifiNetworkSuggestionTest
WifiEnums = wutils.WifiEnums
# EAP Macros
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiPno11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiPno11axTest.py
index 7509d28..63b43ef 100644
--- a/acts_tests/tests/google/wifi/wifi6/WifiPno11axTest.py
+++ b/acts_tests/tests/google/wifi/wifi6/WifiPno11axTest.py
@@ -15,7 +15,7 @@
import time
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiPnoTest import WifiPnoTest
+from ..WifiPnoTest import WifiPnoTest
MAX_ATTN = 95
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiSoftApAcs11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiSoftApAcs11axTest.py
index 0155be4..1a4a2c8 100644
--- a/acts_tests/tests/google/wifi/wifi6/WifiSoftApAcs11axTest.py
+++ b/acts_tests/tests/google/wifi/wifi6/WifiSoftApAcs11axTest.py
@@ -17,7 +17,7 @@
from acts.controllers.ap_lib import hostapd_constants
import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiSoftApAcsTest import WifiSoftApAcsTest
+from ..WifiSoftApAcsTest import WifiSoftApAcsTest
class WifiSoftApAcs11axTest(WifiSoftApAcsTest):
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiStaApConcurrency11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiStaApConcurrency11axTest.py
index bdb9dc1..88b1e90 100644
--- a/acts_tests/tests/google/wifi/wifi6/WifiStaApConcurrency11axTest.py
+++ b/acts_tests/tests/google/wifi/wifi6/WifiStaApConcurrency11axTest.py
@@ -17,7 +17,7 @@
import acts.signals as signals
import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiStaApConcurrencyTest import WifiStaApConcurrencyTest
+from ..WifiStaApConcurrencyTest import WifiStaApConcurrencyTest
class WifiStaApConcurrency11axTest(WifiStaApConcurrencyTest):
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiStaConcurrencyNetworkRequest11axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiStaConcurrencyNetworkRequest11axTest.py
index 215a6e6..8dd76627b 100644
--- a/acts_tests/tests/google/wifi/wifi6/WifiStaConcurrencyNetworkRequest11axTest.py
+++ b/acts_tests/tests/google/wifi/wifi6/WifiStaConcurrencyNetworkRequest11axTest.py
@@ -16,7 +16,7 @@
import acts.utils as utils
import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiStaConcurrencyNetworkRequestTest import WifiStaConcurrencyNetworkRequestTest
+from ..WifiStaConcurrencyNetworkRequestTest import WifiStaConcurrencyNetworkRequestTest
class WifiStaConcurrencyNetworkRequest11axTest(
diff --git a/acts_tests/tests/google/wifi/wifi6/WifiWpa311axTest.py b/acts_tests/tests/google/wifi/wifi6/WifiWpa311axTest.py
index 57ab366..cfe7987 100644
--- a/acts_tests/tests/google/wifi/wifi6/WifiWpa311axTest.py
+++ b/acts_tests/tests/google/wifi/wifi6/WifiWpa311axTest.py
@@ -14,7 +14,7 @@
# limitations under the License.
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiWpa3OweTest import WifiWpa3OweTest
+from ..WifiWpa3OweTest import WifiWpa3OweTest
class WifiWpa311axTest(WifiWpa3OweTest):
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiNetworkSelector6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiNetworkSelector6eTest.py
index 4a0ecf3..fa51247 100644
--- a/acts_tests/tests/google/wifi/wifi6e/WifiNetworkSelector6eTest.py
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiNetworkSelector6eTest.py
@@ -17,7 +17,7 @@
from acts.test_decorators import test_tracker_info
from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiNetworkSelectorTest import WifiNetworkSelectorTest
+from ..WifiNetworkSelectorTest import WifiNetworkSelectorTest
# WifiNetworkSelector imposes a 10 seconds gap between two selections
NETWORK_SELECTION_TIME_GAP = 12
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiPno6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiPno6eTest.py
index 0bfc3da..fbccc88 100644
--- a/acts_tests/tests/google/wifi/wifi6e/WifiPno6eTest.py
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiPno6eTest.py
@@ -17,7 +17,7 @@
from acts.test_decorators import test_tracker_info
from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiPnoTest import WifiPnoTest
+from ..WifiPnoTest import WifiPnoTest
MAX_ATTN = 95
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiStaApConcurrency6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiStaApConcurrency6eTest.py
index 15a3264..5ba6dec 100644
--- a/acts_tests/tests/google/wifi/wifi6e/WifiStaApConcurrency6eTest.py
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiStaApConcurrency6eTest.py
@@ -18,7 +18,7 @@
from acts.test_decorators import test_tracker_info
from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from WifiStaApConcurrencyTest import WifiStaApConcurrencyTest
+from ..WifiStaApConcurrencyTest import WifiStaApConcurrencyTest
WifiEnums = wutils.WifiEnums
WIFI_CONFIG_SOFTAP_BAND_2G = WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G
diff --git a/acts_tests/tests/google/wifi/wifi6e/WifiTeleCoex6eTest.py b/acts_tests/tests/google/wifi/wifi6e/WifiTeleCoex6eTest.py
index 066abdd..9a35f4e 100644
--- a/acts_tests/tests/google/wifi/wifi6e/WifiTeleCoex6eTest.py
+++ b/acts_tests/tests/google/wifi/wifi6e/WifiTeleCoex6eTest.py
@@ -16,7 +16,7 @@
from acts.test_decorators import test_tracker_info
from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from WifiTeleCoexTest import WifiTeleCoexTest
+from ..WifiTeleCoexTest import WifiTeleCoexTest
class WifiTeleCoex6eTest(WifiTeleCoexTest):