Merge "Merge "Add blackbox metrics to ping tests." am: e1204d1851" into pi-dev
diff --git a/acts/framework/acts/controllers/attenuator_lib/minicircuits/http.py b/acts/framework/acts/controllers/attenuator_lib/minicircuits/http.py
new file mode 100644
index 0000000..b68d79d
--- /dev/null
+++ b/acts/framework/acts/controllers/attenuator_lib/minicircuits/http.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+
+# Copyright 2016- The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Class for HTTP control of Mini-Circuits RCDAT series attenuators
+
+This class provides a wrapper to the MC-RCDAT attenuator modules for purposes
+of simplifying and abstracting control down to the basic necessities. It is
+not the intention of the module to expose all functionality, but to allow
+interchangeable HW to be used.
+
+See http://www.minicircuits.com/softwaredownload/Prog_Manual-6-Programmable_Attenuator.pdf
+"""
+
+import urllib
+from acts.controllers import attenuator
+
+
+class AttenuatorInstrument(attenuator.AttenuatorInstrument):
+ """A specific HTTP-controlled implementation of AttenuatorInstrument for
+ Mini-Circuits RC-DAT attenuators.
+
+ With the exception of HTTP-specific commands, all functionality is defined
+ by the AttenuatorInstrument class.
+ """
+
+ def __init__(self, num_atten=1):
+ super(AttenuatorInstrument, self).__init__(num_atten)
+ self._ip_address = None
+ self._port = None
+ self._timeout = None
+
+ def open(self, host, port=80, timeout=2):
+ """Initializes the AttenuatorInstrument and queries basic information.
+
+ Args:
+ host: A valid hostname (IP address or DNS-resolvable name) to an
+ MC-DAT attenuator instrument.
+ port: An optional port number (defaults to http default 80)
+ timeout: An optional timeout for http requests
+ """
+ self._ip_address = host
+ self._port = port
+ self._timeout = timeout
+
+ att_req = urllib.request.urlopen('http://{}:{}/MN?'.format(
+ self._ip_address, self._port))
+ config_str = att_req.read().decode('utf-8')
+ if not config_str.startswith('MN='):
+ raise attenuator.InvalidDataError(
+ 'Attenuator returned invalid data. Attenuator returned: {}'.
+ format(config_str))
+
+ config_str = config_str[len('MN='):]
+ self.properties = dict(
+ zip(['model', 'max_freq', 'max_atten'], config_str.split('-', 2)))
+ self.max_atten = float(self.properties['max_atten'])
+
+ def is_open(self):
+ """Returns True if the AttenuatorInstrument has an open connection.
+
+ Since this controller is based on HTTP requests, there is no connection
+ required and the attenuator is always ready to accept requests.
+ """
+ return True
+
+ def close(self):
+ """Closes the connection to the attenuator.
+
+ Since this controller is based on HTTP requests, there is no connection
+ teardowns required.
+ """
+ pass
+
+ def set_atten(self, idx, value):
+ """This function sets the attenuation of an attenuator given its index
+ in the instrument.
+
+ Args:
+ idx: A zero-based index that identifies a particular attenuator in
+ an instrument. For instruments that only have one channel, this
+ is ignored by the device.
+ value: A floating point value for nominal attenuation to be set.
+
+ Raises:
+ InvalidDataError if the attenuator does not respond with the
+ expected output.
+ """
+ if not (0 <= idx < self.num_atten):
+ raise IndexError('Attenuator index out of range!', self.num_atten,
+ idx)
+
+ if value > self.max_atten:
+ raise ValueError('Attenuator value out of range!', self.max_atten,
+ value)
+ # The actual device uses one-based index for channel numbers.
+ att_req = urllib.request.urlopen(
+ 'http://{}:{}/CHAN:{}:SETATT:{}'.format(
+ self._ip_address, self._port, idx + 1, value),
+ timeout=self._timeout)
+ att_resp = att_req.read().decode('utf-8')
+ if att_resp != '1':
+ raise attenuator.InvalidDataError(
+ 'Attenuator returned invalid data. Attenuator returned: {}'.
+ format(att_resp))
+
+ def get_atten(self, idx):
+ """Returns the current attenuation of the attenuator at the given index.
+
+ Args:
+ idx: The index of the attenuator.
+
+ Raises:
+ InvalidDataError if the attenuator does not respond with the
+ expected outpu
+
+ Returns:
+ the current attenuation value as a float
+ """
+ if not (0 <= idx < self.num_atten):
+ raise IndexError('Attenuator index out of range!', self.num_atten,
+ idx)
+ att_req = urllib.request.urlopen(
+ 'http://{}:{}/CHAN:{}:ATT?'.format(self._ip_address, self.port,
+ idx + 1),
+ timeout=self._timeout)
+ att_resp = att_req.read().decode('utf-8')
+ try:
+ atten_val = float(att_resp)
+ except:
+ raise attenuator.InvalidDataError(
+ 'Attenuator returned invalid data. Attenuator returned: {}'.
+ format(att_resp))
+ return atten_val
diff --git a/acts/framework/acts/test_utils/wifi/wifi_retail_ap.py b/acts/framework/acts/test_utils/wifi/wifi_retail_ap.py
index 8949bb1..6efa6af 100644
--- a/acts/framework/acts/test_utils/wifi/wifi_retail_ap.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_retail_ap.py
@@ -126,7 +126,11 @@
self.quit()
self.__enter__()
- def visit_persistent(self, url, page_load_timeout, num_tries):
+ def visit_persistent(self,
+ url,
+ page_load_timeout,
+ num_tries,
+ backup_url="about:blank"):
"""Method to visit webpages and retry upon failure.
The function visits a web page and checks the the resulting URL matches
@@ -136,15 +140,20 @@
url: the intended url
page_load_timeout: timeout for page visits
num_tries: number of tries before url is declared unreachable
+ backup_url: url to visit if first url is not reachable. This can be
+ use to simply refresh the browser and try again or to re-login to
+ the AP
"""
self.driver.set_page_load_timeout(page_load_timeout)
for idx in range(num_tries):
try:
self.visit(url)
+ if self.url.split("/")[-1] == url.split("/")[-1]:
+ break
+ else:
+ self.visit(backup_url)
except:
self.restart()
- if self.url.split("/")[-1] == url.split("/")[-1]:
- break
if idx == num_tries - 1:
self.log.error("URL unreachable. Current URL: {}".format(
self.url))
@@ -479,7 +488,7 @@
# Visit URL
browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
browser.visit_persistent(self.config_page_nologin,
- BROWSER_WAIT_MED, 10)
+ BROWSER_WAIT_MED, 10, self.config_page)
# Update region, and power/bandwidth for each network
for key, value in self.config_page_fields.items():
@@ -815,7 +824,7 @@
browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
browser.visit_persistent(self.config_page_advanced,
BROWSER_WAIT_MED, 10)
- time.sleep(BROWSER_WAIT_SHORT)
+ time.sleep(BROWSER_WAIT_MED)
wireless_button = browser.find_by_id("advanced_bt").first
wireless_button.click()
time.sleep(BROWSER_WAIT_SHORT)
@@ -853,6 +862,7 @@
browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
browser.visit_persistent(self.config_page_advanced,
BROWSER_WAIT_MED, 10)
+ time.sleep(BROWSER_WAIT_MED)
wireless_button = browser.find_by_id("advanced_bt").first
wireless_button.click()
time.sleep(BROWSER_WAIT_SHORT)
diff --git a/acts/tests/google/wifi/WifiPingTest.py b/acts/tests/google/wifi/WifiPingTest.py
index 32eaf83..3153c2f 100644
--- a/acts/tests/google/wifi/WifiPingTest.py
+++ b/acts/tests/google/wifi/WifiPingTest.py
@@ -466,6 +466,18 @@
def test_slow_ping_rtt_ch1_VHT20(self):
self._test_ping_rtt()
+ def test_fast_ping_rtt_ch6_VHT20(self):
+ self._test_ping_rtt()
+
+ def test_slow_ping_rtt_ch6_VHT20(self):
+ self._test_ping_rtt()
+
+ def test_fast_ping_rtt_ch11_VHT20(self):
+ self._test_ping_rtt()
+
+ def test_slow_ping_rtt_ch11_VHT20(self):
+ self._test_ping_rtt()
+
def test_fast_ping_rtt_ch36_VHT20(self):
self._test_ping_rtt()
diff --git a/acts/tests/google/wifi/WifiRssiTest.py b/acts/tests/google/wifi/WifiRssiTest.py
index 08bc325..8dd0b52 100644
--- a/acts/tests/google/wifi/WifiRssiTest.py
+++ b/acts/tests/google/wifi/WifiRssiTest.py
@@ -25,6 +25,7 @@
from acts import asserts
from acts import base_test
from acts import utils
+from acts.metrics.loggers.blackbox import BlackboxMetricLogger
from acts.test_decorators import test_tracker_info
from acts.test_utils.wifi import wifi_power_test_utils as wputils
from acts.test_utils.wifi import wifi_retail_ap as retail_ap
@@ -53,6 +54,19 @@
def __init__(self, controllers):
base_test.BaseTestClass.__init__(self, controllers)
+ test_metrics = [
+ "signal_poll_rssi_shift", "signal_poll_avg_rssi_shift",
+ "scan_rssi_shift", "chain_0_rssi_shift", "chain_1_rssi_shift",
+ "signal_poll_rssi_error", "signal_poll_avg_rssi_error",
+ "scan_rssi_error", "chain_0_rssi_error", "chain_1_rssi_error",
+ "signal_poll_rssi_stdev", "chain_0_rssi_stdev",
+ "chain_1_rssi_stdev"
+ ]
+ for metric in test_metrics:
+ setattr(
+ self,
+ "{}_metric".format(metric),
+ BlackboxMetricLogger.for_test_case(metric_name=metric))
def setup_class(self):
self.dut = self.android_devices[0]
@@ -85,6 +99,14 @@
Args:
postprocessed_results: compiled arrays of RSSI measurements
"""
+ # Set Blackbox metric values
+ self.signal_poll_rssi_stdev_metric.metric_value = max(
+ postprocessed_results["signal_poll_rssi"]["stdev"])
+ self.chain_0_rssi_stdev_metric.metric_value = max(
+ postprocessed_results["chain_0_rssi"]["stdev"])
+ self.chain_1_rssi_stdev_metric.metric_value = max(
+ postprocessed_results["chain_1_rssi"]["stdev"])
+ # Evaluate test pass/fail
test_failed = any([
stdev > self.test_params["stdev_tolerance"]
for stdev in postprocessed_results["signal_poll_rssi"]["stdev"]
@@ -147,6 +169,14 @@
else:
avg_error = RSSI_ERROR_VAL
avg_shift = RSSI_ERROR_VAL
+ # Set Blackbox metric values
+ setattr(
+ getattr(self, "{}_error_metric".format(key)),
+ "metric_value", avg_error)
+ setattr(
+ getattr(self, "{}_shift_metric".format(key)),
+ "metric_value", avg_shift)
+ # Evaluate test pass/fail
rssi_failure = (avg_error > self.test_params["abs_tolerance"]
) or math.isnan(avg_error)
if rssi_failure and key in rssi_under_test:
@@ -165,7 +195,6 @@
"{} passed ({} error = {:.2f} dB, "
"shift = {:.2f} dB)\n").format(key, error_type,
avg_error, avg_shift)
-
if test_failed:
asserts.fail(test_message)
asserts.explicit_pass(test_message)
@@ -909,7 +938,7 @@
class WifiRssi_2GHz_ActiveTraffic_Test(WifiRssiTest):
def __init__(self, controllers):
- base_test.BaseTestClass.__init__(self, controllers)
+ super().__init__(controllers)
self.tests = ("test_rssi_stability_ch1_VHT20_ActiveTraffic",
"test_rssi_vs_atten_ch1_VHT20_ActiveTraffic",
"test_rssi_stability_ch2_VHT20_ActiveTraffic",
@@ -924,7 +953,7 @@
class WifiRssi_5GHz_ActiveTraffic_Test(WifiRssiTest):
def __init__(self, controllers):
- base_test.BaseTestClass.__init__(self, controllers)
+ super().__init__(controllers)
self.tests = ("test_rssi_stability_ch36_VHT20_ActiveTraffic",
"test_rssi_vs_atten_ch36_VHT20_ActiveTraffic",
"test_rssi_stability_ch36_VHT40_ActiveTraffic",
diff --git a/acts/tests/google/wifi/WifiThroughputStabilityTest.py b/acts/tests/google/wifi/WifiThroughputStabilityTest.py
index f856f18..6d98fe9 100644
--- a/acts/tests/google/wifi/WifiThroughputStabilityTest.py
+++ b/acts/tests/google/wifi/WifiThroughputStabilityTest.py
@@ -46,12 +46,12 @@
def __init__(self, controllers):
base_test.BaseTestClass.__init__(self, controllers)
# Define metrics to be uploaded to BlackBox
- BlackboxMetricLogger.for_test_case(
- metric_name='min_throughput', result_attr='min_throughput')
- BlackboxMetricLogger.for_test_case(
- metric_name='avg_throughput', result_attr='avg_throughput')
- BlackboxMetricLogger.for_test_case(
- metric_name='std_dev_percent', result_attr='std_dev_percent')
+ self.min_throughput_metric = BlackboxMetricLogger.for_test_case(
+ metric_name='min_throughput')
+ self.avg_throughput_metric = BlackboxMetricLogger.for_test_case(
+ metric_name='avg_throughput')
+ self.std_dev_percent_metric = BlackboxMetricLogger.for_test_case(
+ metric_name='std_dev_percent')
# Generate test cases
modes = [(6, "VHT20"), (36, "VHT20"), (36, "VHT40"), (36, "VHT80"),
@@ -99,31 +99,34 @@
meta data
"""
#TODO(@oelayach): Check throughput vs RvR golden file
- self.avg_throughput = test_result_dict["iperf_results"][
- "avg_throughput"]
- self.min_throughput = test_result_dict["iperf_results"][
- "min_throughput"]
- self.std_dev_percent = (
+ avg_throughput = test_result_dict["iperf_results"]["avg_throughput"]
+ min_throughput = test_result_dict["iperf_results"]["min_throughput"]
+ std_dev_percent = (
test_result_dict["iperf_results"]["std_deviation"] /
test_result_dict["iperf_results"]["avg_throughput"]) * 100
+ # Set blackbox metrics
+ self.avg_throughput_metric.metric_value = avg_throughput
+ self.min_throughput_metric.metric_value = min_throughput
+ self.std_dev_percent_metric.metric_value = std_dev_percent
+ # Evaluate pass/fail
min_throughput_check = (
- (self.min_throughput / self.avg_throughput) *
+ (min_throughput / avg_throughput) *
100) > self.test_params["min_throughput_threshold"]
- std_deviation_check = self.std_dev_percent < self.test_params["std_deviation_threshold"]
+ std_deviation_check = std_dev_percent < self.test_params["std_deviation_threshold"]
if min_throughput_check and std_deviation_check:
asserts.explicit_pass(
"Test Passed. Throughput at {0:.2f}dB attenuation is stable. "
"Mean throughput is {1:.2f} Mbps with a standard deviation of "
"{2:.2f}% and dips down to {3:.2f} Mbps.".format(
- self.atten_level, self.avg_throughput,
- self.std_dev_percent, self.min_throughput))
+ self.atten_level, avg_throughput, std_dev_percent,
+ min_throughput))
asserts.fail(
"Test Failed. Throughput at {0:.2f}dB attenuation is unstable. "
"Mean throughput is {1:.2f} Mbps with a standard deviation of "
"{2:.2f}% and dips down to {3:.2f} Mbps.".format(
- self.atten_level, self.avg_throughput, self.std_dev_percent,
- self.min_throughput))
+ self.atten_level, avg_throughput, std_dev_percent,
+ min_throughput))
def post_process_results(self, test_result):
"""Extracts results and saves plots and JSON formatted results.