Merge "Modify basic coex tests to be inline with new coexbasetest."
diff --git a/acts/framework/acts/controllers/android_device.py b/acts/framework/acts/controllers/android_device.py
index f66d6ce..1f8bbce 100755
--- a/acts/framework/acts/controllers/android_device.py
+++ b/acts/framework/acts/controllers/android_device.py
@@ -473,7 +473,8 @@
'serial': self.serial,
'model': self.model,
'build_info': self.build_info,
- 'user_added_info': self._user_added_device_info
+ 'user_added_info': self._user_added_device_info,
+ 'flavor': self.flavor
}
return info
@@ -526,6 +527,11 @@
return self.adb.getprop("ro.product.name").lower()
@property
+ def flavor(self):
+ """Returns the specific flavor of Android build the device is using."""
+ return self.adb.getprop("ro.build.flavor").lower()
+
+ @property
def droid(self):
"""Returns the RPC Service of the first Sl4aSession created."""
if len(self._sl4a_manager.sessions) > 0:
diff --git a/acts/framework/acts/controllers/sl4a_lib/rpc_client.py b/acts/framework/acts/controllers/sl4a_lib/rpc_client.py
index e08ffb0..2f40bee 100644
--- a/acts/framework/acts/controllers/sl4a_lib/rpc_client.py
+++ b/acts/framework/acts/controllers/sl4a_lib/rpc_client.py
@@ -276,8 +276,9 @@
break
except BrokenPipeError as e:
if self.is_alive:
- self._log.exception('Exception %s happened for sl4a call %s',
- e, method)
+ self._log.exception('The device disconnected during RPC call '
+ '%s. Please check the logcat for a crash '
+ 'or disconnect.', method)
self.on_error(connection)
else:
self._log.warning('The connection was killed during cleanup:')
diff --git a/acts/framework/acts/test_utils/coex/audio_capture.py b/acts/framework/acts/test_utils/coex/audio_capture.py
deleted file mode 100644
index bb29dcc..0000000
--- a/acts/framework/acts/test_utils/coex/audio_capture.py
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-import argparse
-import json
-import logging
-import os
-import pyaudio
-import wave
-
-RECORD_FILE_TEMPLATE = 'recorded_audio_%s.wav'
-
-
-class DeviceNotFound(Exception):
- """Raises exception if audio capture device is not found."""
-
-# TODO: (@sairamganesh) This class will be deprecated for
-# ../acts/test_utils/coex/audio_capture_device.py
-
-
-class AudioCapture:
-
- def __init__(self, test_params, path):
- """Creates object to pyaudio and defines audio parameters.
-
- Args:
- test_params: Audio parameters fetched from config.
- path: Result path.
- """
- self.audio = pyaudio.PyAudio()
- self.audio_format = pyaudio.paInt16
- self.audio_params = test_params
- self.channels = self.audio_params["channel"]
- self.chunk = self.audio_params["chunk"]
- self.sample_rate = self.audio_params["sample_rate"]
- self.file_counter = 0
- self.__input_device = None
- self.record_file_template = os.path.join(path, RECORD_FILE_TEMPLATE)
- if not self.audio_params["ssh_config"]:
- self.__input_device = self.__get_input_device()
-
- @property
- def name(self):
- try:
- return self.audio_params["ssh_config"]["host"]
- except KeyError:
- return self.__input_device["name"]
-
- def __get_input_device(self):
- """Checks for the audio capture device."""
- if self.__input_device is None:
- for i in range(self.audio.get_device_count()):
- device_info = self.audio.get_device_info_by_index(i)
- logging.info("Device Information {}".format(device_info))
- if self.audio_params['input_device'] in device_info['name']:
- self.__input_device = device_info
- break
- else:
- logging.error("Audio Capture device {} not found.".format(
- self.audio_params["input_device"]))
- raise DeviceNotFound("Audio Capture Input device not found")
- return self.__input_device
-
- def capture_and_store_audio(self, trim_beginning=0, trim_end=0):
- """Records the A2DP streaming.
-
- Args:
- trim_beginning: how many seconds to trim from the beginning
- trim_end: how many seconds to trim from the end
- """
- if self.audio_params['ssh_config']:
- self.__input_device = self.__get_input_device()
- stream = self.audio.open(
- format=self.audio_format,
- channels=self.channels,
- rate=self.sample_rate,
- input=True,
- frames_per_buffer=self.chunk,
- input_device_index=self.__input_device['index'])
- frames = []
- b_chunks = trim_beginning * (self.sample_rate // self.chunk)
- e_chunks = trim_end * (self.sample_rate // self.chunk)
- total_chunks = self.sample_rate // self.chunk * self.audio_params[
- 'record_duration']
- for i in range(total_chunks):
- try:
- data = stream.read(self.chunk, exception_on_overflow=False)
- except IOError as ex:
- logging.error("Cannot record audio :{}".format(ex))
- return False
- if b_chunks <= i < total_chunks - e_chunks:
- frames.append(data)
-
- stream.stop_stream()
- stream.close()
- status = self.write_record_file(frames)
- return status
-
- def last_fileno(self):
- return self.next_fileno() - 1
-
- def next_fileno(self):
- counter = 0
- while os.path.exists(self.record_file_template % counter):
- counter += 1
- return counter
-
- def write_record_file(self, frames):
- """Writes the recorded audio into the file.
-
- Args:
- frames: Recorded audio frames.
- """
- file_name = self.record_file_template % self.next_fileno()
- logging.info('writing to %s' % file_name)
- wf = wave.open(file_name, 'wb')
- wf.setnchannels(self.channels)
- wf.setsampwidth(self.audio.get_sample_size(self.audio_format))
- wf.setframerate(self.sample_rate)
- wf.writeframes(b''.join(frames))
- wf.close()
- return True
-
- def terminate_audio(self):
- """Terminates the pulse audio instance."""
- self.audio.terminate()
-
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser()
- parser.add_argument(
- '-p',
- '--path',
- type=str,
- help="Contains path where the recorded files to be stored")
- parser.add_argument(
- '-t',
- '--test_params',
- type=json.loads,
- help="Contains sample rate, channels,"
- " chunk and device index for recording.")
- args = parser.parse_args()
- audio = AudioCapture(args.test_params, args.path)
- audio.capture_and_store_audio(args.test_params['trim_beginning'],
- args.test_params['trim_end'])
- audio.terminate_audio()
diff --git a/acts/framework/acts/test_utils/coex/audio_capture_device.py b/acts/framework/acts/test_utils/coex/audio_capture_device.py
index 924bf4a..f99f6a8 100644
--- a/acts/framework/acts/test_utils/coex/audio_capture_device.py
+++ b/acts/framework/acts/test_utils/coex/audio_capture_device.py
@@ -57,6 +57,21 @@
def last_fileno(self):
return self.next_fileno - 1
+ @property
+ def get_last_record_duration_millis(self):
+ """Get duration of most recently recorded file.
+
+ Returns:
+ duration (float): duration of recorded file in milliseconds.
+ """
+ latest_file_path = self.wave_file % self.last_fileno
+ print (latest_file_path)
+ with wave.open(latest_file_path, 'r') as f:
+ frames = f.getnframes()
+ rate = f.getframerate()
+ duration = (frames / float(rate)) * 1000
+ return duration
+
def write_record_file(self, audio_params, frames):
"""Writes the recorded audio into the file.
diff --git a/acts/framework/acts/test_utils/coex/audio_test_utils.py b/acts/framework/acts/test_utils/coex/audio_test_utils.py
index b514712..02b99ca 100644
--- a/acts/framework/acts/test_utils/coex/audio_test_utils.py
+++ b/acts/framework/acts/test_utils/coex/audio_test_utils.py
@@ -16,16 +16,12 @@
import logging
import os
-import wave
+from acts.test_utils.coex.audio_capture_device import AudioCaptureBase
from acts.test_utils.coex.audio_capture_device import CaptureAudioOverAdb
from acts.test_utils.coex.audio_capture_device import CaptureAudioOverLocal
-from acts.controllers.utils_lib.ssh import connection
-from acts.controllers.utils_lib.ssh import settings
from acts.test_utils.audio_analysis_lib import audio_analysis
from acts.test_utils.audio_analysis_lib.check_quality import quality_analysis
-from acts.test_utils.coex.audio_capture import AudioCapture
-from acts.test_utils.coex.audio_capture import RECORD_FILE_TEMPLATE
ANOMALY_DETECTION_BLOCK_SIZE = audio_analysis.ANOMALY_DETECTION_BLOCK_SIZE
ANOMALY_GROUPING_TOLERANCE = audio_analysis.ANOMALY_GROUPING_TOLERANCE
@@ -67,58 +63,18 @@
class FileNotFound(Exception):
"""Raises Exception if file is not present"""
-# TODO @sairamganesh Rename this class to AudioCaptureResult and
-# remove duplicates which are in ../test_utils/coex/audio_capture_device.py.
+class AudioCaptureResult(AudioCaptureBase):
-class SshAudioCapture(AudioCapture):
-
- def __init__(self, test_params, path):
- super(SshAudioCapture, self).__init__(test_params, path)
- self.remote_path = path
- self.ssh_session = None
-
- def capture_audio(self, trim_beginning=0, trim_end=0):
- """Captures audio and store results.
+ def __init__(self, path):
+ """Initializes Audio Capture Result class.
Args:
- trim_beginning: To trim audio at the start in seconds.
- trim_end: To trim audio at the end in seconds.
-
- Returns:
- Returns exit status of ssh connection.
+ path: Path of audio capture result.
"""
- if not trim_beginning:
- trim_beginning = self.audio_params.get('trim_beginning', 0)
- if not trim_end:
- trim_end = self.audio_params.get('trim_end', 0)
- if self.audio_params["ssh_config"]:
- ssh_settings = settings.from_config(
- self.audio_params["ssh_config"])
- self.ssh_session = connection.SshConnection(ssh_settings)
- cur_path = os.path.dirname(os.path.realpath(__file__))
- local_path = os.path.join(cur_path, "audio_capture.py")
- self.ssh_session.send_file(local_path,
- self.audio_params["dest_path"])
- path = self.audio_params["dest_path"]
- test_params = str(self.audio_params).replace("\'", "\"")
- self.cmd = "python3 audio_capture.py -p '{}' -t '{}'".format(
- path, test_params)
- job_result = self.ssh_session.run(self.cmd)
- logging.debug("Job Result {}".format(job_result.stdout))
- self.ssh_session.pull_file(
- self.remote_path, os.path.join(
- self.audio_params["dest_path"], "*.wav"))
- return bool(not job_result.exit_status)
- else:
- return self.capture_and_store_audio(trim_beginning, trim_end)
-
- def terminate_and_store_audio_results(self):
- """Terminates audio and stores audio files."""
- if self.audio_params["ssh_config"]:
- self.ssh_session.run('rm *.wav', ignore_status=True)
- else:
- self.terminate_audio()
+ super().__init__()
+ self.path = path
+ self.analysis_path = os.path.join(self.log_path, ANALYSIS_FILE_TEMPLATE)
def THDN(self, win_size=None, step_size=None, q=1, freq=None):
"""Calculate THD+N value for most recently recorded file.
@@ -137,13 +93,12 @@
channel_results (list): THD+N value for each channel's signal.
List index corresponds to channel index.
"""
- latest_file_path = self.record_file_template % self.last_fileno()
if not (win_size and step_size):
- return audio_analysis.get_file_THDN(filename=latest_file_path,
+ return audio_analysis.get_file_THDN(filename=self.path,
q=q,
freq=freq)
else:
- return audio_analysis.get_file_max_THDN(filename=latest_file_path,
+ return audio_analysis.get_file_max_THDN(filename=self.path,
step_size=step_size,
window_size=win_size,
q=q,
@@ -172,28 +127,22 @@
channel_results (list): anomaly durations for each channel's signal.
List index corresponds to channel index.
"""
- latest_file_path = self.record_file_template % self.last_fileno()
return audio_analysis.get_file_anomaly_durations(
- filename=latest_file_path,
+ filename=self.path,
freq=freq,
block_size=block_size,
threshold=threshold,
tolerance=tolerance)
- def get_last_record_duration_millis(self):
- """Get duration of most recently recorded file.
+ @property
+ def analysis_fileno(self):
+ """Returns the file number to dump audio analysis results."""
+ counter = 0
+ while os.path.exists(self.analysis_path % counter):
+ counter += 1
+ return counter
- Returns:
- duration (float): duration of recorded file in milliseconds.
- """
- latest_file_path = self.record_file_template % self.last_fileno()
- with wave.open(latest_file_path, 'r') as f:
- frames = f.getnframes()
- rate = f.getframerate()
- duration = (frames / float(rate)) * 1000
- return duration
-
- def audio_quality_analysis(self, path):
+ def audio_quality_analysis(self, audio_params):
"""Measures audio quality based on the audio file given as input.
Args:
@@ -202,19 +151,16 @@
Returns:
analysis_path on success.
"""
- dest_file_path = os.path.join(path,
- RECORD_FILE_TEMPLATE % self.last_fileno())
- analysis_path = os.path.join(path,
- ANALYSIS_FILE_TEMPLATE % self.last_fileno())
- if not os.path.exists(dest_file_path):
+ analysis_path = self.analysis_path % self.analysis_fileno
+ if not os.path.exists(self.path):
raise FileNotFound("Recorded file not found")
try:
quality_analysis(
- filename=dest_file_path,
+ filename=self.path,
output_file=analysis_path,
bit_width=bits_per_sample,
- rate=self.audio_params["sample_rate"],
- channel=self.audio_params["channel"],
+ rate=audio_params["sample_rate"],
+ channel=audio_params["channel"],
spectral_only=False)
except Exception as err:
logging.exception("Failed to analyze raw audio: %s" % err)
diff --git a/acts/framework/acts/test_utils/power/PowerBaseTest.py b/acts/framework/acts/test_utils/power/PowerBaseTest.py
index 2cd8509..53fbc7a 100644
--- a/acts/framework/acts/test_utils/power/PowerBaseTest.py
+++ b/acts/framework/acts/test_utils/power/PowerBaseTest.py
@@ -189,6 +189,14 @@
self.power_logger.set_avg_power(self.power_result.metric_value)
self.power_logger.set_testbed(self.testbed_name)
+ build_id = self.dut.build_info.get('incremental_build_id')
+ branch = self.user_params.get('branch')
+ target = self.dut.device_info.get('flavor')
+
+ self.power_logger.set_branch(branch)
+ self.power_logger.set_build_id(build_id)
+ self.power_logger.set_target(target)
+
# Take Bugreport
if self.bug_report:
begin_time = utils.get_current_epoch_time()
diff --git a/acts/framework/acts/test_utils/power/loggers/power_metric_logger.py b/acts/framework/acts/test_utils/power/loggers/power_metric_logger.py
index 6738e09..09cf399 100644
--- a/acts/framework/acts/test_utils/power/loggers/power_metric_logger.py
+++ b/acts/framework/acts/test_utils/power/loggers/power_metric_logger.py
@@ -48,6 +48,15 @@
def set_testbed(self, testbed):
self.proto.testbed = testbed
+ def set_branch(self, branch):
+ self.proto.branch = branch
+
+ def set_build_id(self, build_id):
+ self.proto.build_id = build_id
+
+ def set_target(self, target):
+ self.proto.target = target
+
def end(self, event):
metric = ProtoMetric(name='spanner_power_metric', data=self.proto)
return self.publisher.publish(metric)
diff --git a/acts/framework/acts/test_utils/power/loggers/protos/power_metric.proto b/acts/framework/acts/test_utils/power/loggers/protos/power_metric.proto
index a3c81ba..5cd9bf9 100644
--- a/acts/framework/acts/test_utils/power/loggers/protos/power_metric.proto
+++ b/acts/framework/acts/test_utils/power/loggers/protos/power_metric.proto
@@ -19,6 +19,9 @@
optional float avg_power = 1; // Required
optional string testbed = 2; // Required
optional PowerCellularMetric cellular_metric = 3;
+ optional string branch = 4;
+ optional string build_id = 5;
+ optional string target = 6;
}
message PowerCellularMetric {
diff --git a/acts/framework/acts/test_utils/power/loggers/protos/power_metric_pb2.py b/acts/framework/acts/test_utils/power/loggers/protos/power_metric_pb2.py
index 0d205dd..6f29e20 100644
--- a/acts/framework/acts/test_utils/power/loggers/protos/power_metric_pb2.py
+++ b/acts/framework/acts/test_utils/power/loggers/protos/power_metric_pb2.py
@@ -19,7 +19,7 @@
name='power_metric.proto',
package='wireless.android.platform.testing.power.metrics',
syntax='proto2',
- serialized_pb=_b('\n\x12power_metric.proto\x12/wireless.android.platform.testing.power.metrics\"\x90\x01\n\x0bPowerMetric\x12\x11\n\tavg_power\x18\x01 \x01(\x02\x12\x0f\n\x07testbed\x18\x02 \x01(\t\x12]\n\x0f\x63\x65llular_metric\x18\x03 \x01(\x0b\x32\x44.wireless.android.platform.testing.power.metrics.PowerCellularMetric\"?\n\x13PowerCellularMetric\x12\x13\n\x0b\x61vg_dl_tput\x18\x01 \x01(\x02\x12\x13\n\x0b\x61vg_ul_tput\x18\x02 \x01(\x02')
+ serialized_pb=_b('\n\x12power_metric.proto\x12/wireless.android.platform.testing.power.metrics\"\xc2\x01\n\x0bPowerMetric\x12\x11\n\tavg_power\x18\x01 \x01(\x02\x12\x0f\n\x07testbed\x18\x02 \x01(\t\x12]\n\x0f\x63\x65llular_metric\x18\x03 \x01(\x0b\x32\x44.wireless.android.platform.testing.power.metrics.PowerCellularMetric\x12\x0e\n\x06\x62ranch\x18\x04 \x01(\t\x12\x10\n\x08\x62uild_id\x18\x05 \x01(\t\x12\x0e\n\x06target\x18\x06 \x01(\t\"?\n\x13PowerCellularMetric\x12\x13\n\x0b\x61vg_dl_tput\x18\x01 \x01(\x02\x12\x13\n\x0b\x61vg_ul_tput\x18\x02 \x01(\x02')
)
@@ -53,6 +53,27 @@
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
+ _descriptor.FieldDescriptor(
+ name='branch', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.branch', index=3,
+ number=4, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='build_id', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.build_id', index=4,
+ number=5, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='target', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.target', index=5,
+ number=6, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
],
extensions=[
],
@@ -66,7 +87,7 @@
oneofs=[
],
serialized_start=72,
- serialized_end=216,
+ serialized_end=266,
)
@@ -103,8 +124,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=218,
- serialized_end=281,
+ serialized_start=268,
+ serialized_end=331,
)
_POWERMETRIC.fields_by_name['cellular_metric'].message_type = _POWERCELLULARMETRIC
diff --git a/acts/tests/google/net/DataCostTest.py b/acts/tests/google/net/DataCostTest.py
index 2b7bd50..617626f 100644
--- a/acts/tests/google/net/DataCostTest.py
+++ b/acts/tests/google/net/DataCostTest.py
@@ -65,6 +65,34 @@
""" Helper functions """
+ def _clear_netstats(self, ad):
+ """ Clear netstats stored on device
+
+ Args:
+ ad: Android device object
+ """
+ ad.log.info("Clear netstats record.")
+ ad.adb.shell("rm /data/system/netstats/*")
+ asserts.assert_equal("", ad.adb.shell("ls /data/system/netstats/"),
+ "Fail to clear netstats.")
+ ad.reboot()
+ time.sleep(10)
+ self._check_multipath_preference_from_dumpsys(ad)
+
+ def _check_multipath_preference_from_dumpsys(self, ad):
+ """ Check cell multipath_preference from dumpsys
+
+ Args:
+ ad: Android device object
+ """
+ out = ad.adb.shell("dumpsys connectivity | grep budget")
+ asserts.assert_true(out, "Fail to get status from dumpsys.")
+ ad.log.info("MultipathPolicyTracker: %s" % out)
+ asserts.assert_true(
+ "HANDOVER|RELIABILITY" in out,
+ "Cell multipath preference should be HANDOVER|RELIABILITY."
+ )
+
def _get_total_data_usage_for_device(self, ad, conn_type, sub_id):
""" Get total data usage in MB for device
@@ -138,6 +166,8 @@
"""
# set vars
ad = self.android_devices[0]
+ self._clear_netstats(ad)
+
sub_id = str(ad.droid.telephonyGetSubscriberId())
cell_network = ad.droid.connectivityGetActiveNetwork()
self.log.info("cell network %s" % cell_network)
@@ -182,6 +212,8 @@
"""
# set vars
ad = self.android_devices[1]
+ self._clear_netstats(ad)
+
cell_network = ad.droid.connectivityGetActiveNetwork()
self.log.info("cell network %s" % cell_network)
wutils.wifi_connect(ad, self.wifi_network)
diff --git a/acts/tests/google/tel/live/TelLiveStressTest.py b/acts/tests/google/tel/live/TelLiveStressTest.py
index ec364ce..5d55883 100644
--- a/acts/tests/google/tel/live/TelLiveStressTest.py
+++ b/acts/tests/google/tel/live/TelLiveStressTest.py
@@ -96,6 +96,7 @@
from acts.test_utils.tel.tel_subscription_utils import set_subid_for_data
from acts.test_utils.tel.tel_subscription_utils import set_subid_for_message
from acts.test_utils.tel.tel_subscription_utils import set_subid_for_outgoing_call
+from acts.test_utils.tel.tel_subscription_utils import set_slways_allow_mms_data
from acts.utils import get_current_epoch_time
from acts.utils import rand_ascii_str
@@ -960,6 +961,7 @@
if not call_verification_func:
call_verification_func = is_phone_in_call
self.finishing_time = time.time() + self.max_run_time
+ # CBRS setup
if self.cbrs_esim:
cbrs_sub_count = 0
for ad in self.android_devices:
@@ -976,6 +978,15 @@
if cbrs_sub_count != 2:
self.log.error("Expecting - 2 CBRS subs, found - %d", cbrs_sub_count)
raise signals.TestAbortClass("Cannot find all expected CBRS subs")
+ # DSDS setup
+ if self.dsds_esim:
+ for ad in self.android_devices:
+ for i in range(0, 2):
+ sub_id = get_subid_from_slot_index(ad.log, ad, i)
+ set_slways_allow_mms_data(ad, sub_id)
+ operator = get_operatorname_from_slot_index(ad, i)
+ ad.log.info("Slot %d - Sub %s - %s", i, sub_id, operator)
+ # Actual test trigger
if not self.dsds_esim and self.check_incall_data():
self.log.info(
"==== Start parallel voice/message/data stress test ====")