Fix import path and clean up log messages. am: 8fb1219bf3
am: 71cdfc639e
Change-Id: Iccffa857fa66e1b8134c371becdb6e46011ba175
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 8429cf6..5ad47bf 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,6 @@
[Hook Scripts]
acts_base_class_test = ./acts/framework/tests/acts_base_class_test.py
+acts_adb_test = ./acts/framework/tests/acts_adb_test.py
acts_android_device_test = ./acts/framework/tests/acts_android_device_test.py
acts_asserts_test = ./acts/framework/tests/acts_asserts_test.py
acts_base_class_test = ./acts/framework/tests/acts_base_class_test.py
@@ -14,7 +15,10 @@
acts_import_test_utils_test = ./acts/framework/tests/acts_import_test_utils_test.py
acts_import_unit_test = ./acts/framework/tests/acts_import_unit_test.py
acts_relay_controller_test = ./acts/framework/tests/acts_relay_controller_test.py
+commit_message_hook = ./tools/commit_message_check.py
yapf_hook = ./tools/yapf_checker.py
+commit_message_check = ./tools/commit_message_check.py
+lab_test = ./tools/lab/test_main.py
[Builtin Hooks]
commit_msg_bug_field = true
diff --git a/acts/README.md b/acts/README.md
index bf0ec48..caa2f0c 100644
--- a/acts/README.md
+++ b/acts/README.md
@@ -20,6 +20,19 @@
[Python Style Guide](https://google.github.io/styleguide/pyguide.html), and
it is recommended for all new test cases.
+System dependencies:
+ - adb
+ - python3.4+
+ - python3.4-setuptools
+
+Python dependencies (installed automatically by setup.py):
+ - future
+ - pyserial
+
+To run unit tests:
+$ python3 setup.py test
+$ python setup.py test
+
## ACTS Execution Flow Overview
Below is a high level view of the ACTS flow:
@@ -57,9 +70,12 @@
displayed. Check the "Always" box and click "Yes".
## ACTS Setup
-From the ACTS directory, run setup
-- `$ sudo python setup.py develop`
+1. Install the system dependencies.
+ On Ubuntu, sudo apt-get install python3.4 python3-setuptools
+2. Run "python3.4 setup.py install" with elevated permissions
+3. To verify ACTS is ready to go, at the location for README, and run:
+ cd tests/ && act.py -c acts_sanity_test_config.json -tc IntegrationTest
After installation, `act.py` will be in usr/bin and can be called as command
line utilities. Components in ACTS are importable under the package "acts."
@@ -71,34 +87,15 @@
>>> device_list = android_device.get_all_instances()
```
-## Verifying Setup
-To verify the host and device are ready, from the frameworks folder run:
+## Breaking Down a Sample Command
-- `$ act.py -c sample_config.json -tb SampleTestBed -tc SampleTest`
-
-If the above command executed successfully, the ouput should look something
-similar to following:
-
-```
-[SampleTestBed] 07-22 15:23:50.323 INFO ==========> SampleTest <==========
-[SampleTestBed] 07-22 15:23:50.327 INFO [Test Case] test_make_toast
-[SampleTestBed] 07-22 15:23:50.334 INFO [Test Case] test_make_toast PASS
-[SampleTestBed] 07-22 15:23:50.338 INFO Summary for test class SampleTest:
-Requested 1, Executed 1, Passed 1, Failed 0, Skipped 0
-[SampleTestBed] 07-22 15:23:50.338 INFO Summary for test run
-SampleTestBed@07-22-2015_1-23-44-096: Requested 1, Executed 1, Passed 1,
-Failed 0, Skipped 0
-```
-
-By default, all logs are saved in `/tmp/logs`
-
-## Breaking Down the Example
-Below are the components of the command run for the SampleTest:
+Above, the command `act.py -c acts_sanity_test_config.json -tc IntegrationTest`
+was run to verify ACTS was properly set up.
+Below are the components of that command:
- `acts.py`: is the script that runs the test
-- -c sample_config: is the flag and name of the configuration file to be used
-in the test
-- -tb StampleTestBed: is the flag and name of the test bed to be used
-- -tc SampleTest: is the name of the test case
+- -c acts_sanity_test_config: is the flag and name of the configuration file
+to be used in the test
+- -tc IntegrationTest: is the name of the test case
### Configuration Files
To run tests, required information must be provided via a json-formatted
@@ -133,8 +130,8 @@
and are created based on the provided configuration file.
Test classes must also contain an iterable member self.tests that lists the
-test case names within the class. More on this is discussed after the following
-code snippet.
+test case names within the class. More on this is discussed after the
+following code snippet.
```
from acts.base_test import BaseTestClass
diff --git a/acts/framework/README b/acts/framework/README
deleted file mode 100644
index 43c98aa..0000000
--- a/acts/framework/README
+++ /dev/null
@@ -1,21 +0,0 @@
-This package includes the Android Comms Testing Suite (ACTS) alpha release
-
-System dependencies:
- - adb
- - python3.4+
- - python3.4-setuptools
-
-Python dependencies (installed automatically by setup.py):
- - future
- - pyserial
-
-To run unit tests:
-$ python3 setup.py test
-$ python setup.py test
-
-Setup:
- 1. Install the system dependencies.
- On Ubuntu, sudo apt-get install python3.4 python3-setuptools
- 2. Run "python3.4 setup.py install" with elevated permissions
- 3. To verify ACTS is ready to go, at the location for README, and run:
- cd tests/ && act.py -c acts_sanity_test_config.json -tc IntegrationTest
diff --git a/acts/framework/acts/controllers/adb.py b/acts/framework/acts/controllers/adb.py
index 2a1b69d..68c96de 100644
--- a/acts/framework/acts/controllers/adb.py
+++ b/acts/framework/acts/controllers/adb.py
@@ -29,6 +29,9 @@
DEFAULT_ADB_TIMEOUT = 60
DEFAULT_ADB_PULL_TIMEOUT = 180
+# Uses a regex to be backwards compatible with previous versions of ADB
+# (N and above add the serial to the error msg).
+DEVICE_NOT_FOUND_REGEX = re.compile('^error: device (?:\'.*?\' )?not found')
def parsing_parcel_output(output):
@@ -107,31 +110,28 @@
def _exec_cmd(self, cmd, ignore_status=False, timeout=DEFAULT_ADB_TIMEOUT):
"""Executes adb commands in a new shell.
- This is specific to executing adb binary because stderr is not a good
- indicator of cmd execution status.
+ This is specific to executing adb commands.
Args:
- cmds: A string that is the adb command to execute.
+ cmd: A string that is the adb command to execute.
Returns:
- The output of the adb command run if exit code is 0.
+ The stdout of the adb command.
Raises:
- AdbError is raised if the adb command exit code is not 0.
+ AdbError is raised if adb cannot find the device.
"""
result = job.run(cmd, ignore_status=True, timeout=timeout)
ret, out, err = result.exit_status, result.stdout, result.stderr
logging.debug("cmd: %s, stdout: %s, stderr: %s, ret: %s", cmd, out,
err, ret)
-
- if ret == 0 or ignore_status:
- if "Result: Parcel" in out:
- return parsing_parcel_output(out)
- else:
- return out
- else:
+ if not ignore_status and ret == 1 and DEVICE_NOT_FOUND_REGEX.match(err):
raise AdbError(cmd=cmd, stdout=out, stderr=err, ret_code=ret)
+ elif "Result: Parcel" in out:
+ return parsing_parcel_output(out)
+ else:
+ return out
def _exec_adb_cmd(self, name, arg_str, **kwargs):
return self._exec_cmd(' '.join((self.adb_str, name, arg_str)),
diff --git a/acts/framework/acts/controllers/android_device.py b/acts/framework/acts/controllers/android_device.py
index 1dfac75..7f508ca 100755
--- a/acts/framework/acts/controllers/android_device.py
+++ b/acts/framework/acts/controllers/android_device.py
@@ -776,18 +776,15 @@
Returns:
True if package is installed. False otherwise.
"""
+
try:
- out = self.adb.shell(
- 'pm list packages | grep -w "package:%s"' % package_name,
- ignore_status=False)
- if package_name in out:
- self.log.debug("apk %s is installed", package_name)
- return True
- else:
- self.log.debug("apk %s is not installed, query returns %s",
- package_name, out)
- return False
- except:
+ return bool(
+ self.adb.shell('pm list packages | grep -w "package:%s"' %
+ package_name))
+
+ except Exception as err:
+ self.log.error('Could not determine if %s is installed. '
+ 'Received error:\n%s', package_name, err)
return False
def is_sl4a_installed(self):
diff --git a/acts/framework/acts/controllers/native.py b/acts/framework/acts/controllers/native.py
index 7c9df2b..aa987b1 100644
--- a/acts/framework/acts/controllers/native.py
+++ b/acts/framework/acts/controllers/native.py
@@ -14,7 +14,7 @@
# License for the specific language governing permissions and limitations under
# the License.
-from acts.controllers.android import Android
+from acts.controllers.sl4a_client import Sl4aClient
import json
import os
import socket
@@ -46,13 +46,12 @@
i += 1
-class NativeAndroid(Android):
+class NativeAndroid(Sl4aClient):
COUNTER = IDCounter()
def _rpc(self, method, *args):
- self.lock.acquire()
- apiid = next(NativeAndroid.COUNTER)
- self.lock.release()
+ with self._lock:
+ apiid = next(self._counter)
data = {'id': apiid, 'method': method, 'params': args}
request = json.dumps(data)
self.client.write(request.encode("utf8") + b'\n')
@@ -61,9 +60,8 @@
if not response:
raise SL4NProtocolError(SL4NProtocolError.NO_RESPONSE_FROM_SERVER)
#TODO: (tturney) fix the C side from sending \x00 char over the socket.
- result = json.loads(str(response,
- encoding="utf8").rstrip().replace("\x00", ""))
-
+ result = json.loads(
+ str(response, encoding="utf8").rstrip().replace("\x00", ""))
if result['error']:
raise SL4NAPIError(result['error'])
if result['id'] != apiid:
diff --git a/acts/framework/acts/controllers/native_android_device.py b/acts/framework/acts/controllers/native_android_device.py
index 00662b3..cbe3237 100644
--- a/acts/framework/acts/controllers/native_android_device.py
+++ b/acts/framework/acts/controllers/native_android_device.py
@@ -15,7 +15,7 @@
# limitations under the License.
from acts.controllers.android_device import AndroidDevice
-from acts.controllers.utils_lib import host_utils
+from acts.controllers.utils_lib import host_utils
import acts.controllers.native as native
from subprocess import call
@@ -29,8 +29,8 @@
def create(configs):
- logger = logging.getLogger()
- ads = get_instances(configs, logger)
+ logger = logging
+ ads = get_instances(configs)
for ad in ads:
try:
ad.get_droid()
@@ -43,7 +43,7 @@
pass
-def get_instances(serials, logger=None):
+def get_instances(serials, ):
"""Create AndroidDevice instances from a list of serials.
Args:
@@ -55,7 +55,7 @@
"""
results = []
for s in serials:
- results.append(NativeAndroidDevice(s, logger=logger))
+ results.append(NativeAndroidDevice(s))
return results
@@ -97,12 +97,10 @@
if not self.h_port or not host_utils.is_port_available(self.h_port):
self.h_port = host_utils.get_available_host_port()
self.adb.tcp_forward(self.h_port, self.d_port)
- pid = self.adb.shell("ps | grep sl4n | awk '{print $2}'").decode(
- 'ascii')
+ pid = self.adb.shell("pidof -s sl4n", ignore_status=True)
while (pid):
self.adb.shell("kill {}".format(pid))
- pid = self.adb.shell("ps | grep sl4n | awk '{print $2}'").decode(
- 'ascii')
+ pid = self.adb.shell("pidof -s sl4n", ignore_status=True)
call(
["adb -s " + self.serial + " shell sh -c \"/system/bin/sl4n\" &"],
shell=True)
@@ -127,6 +125,7 @@
existing uid to a new session.
"""
droid = native.NativeAndroid(port=self.h_port)
+ droid.open()
if droid.uid in self._droid_sessions:
raise bt.SL4NException(("SL4N returned an existing uid for a "
"new session. Abort."))
diff --git a/acts/framework/acts/test_runner.py b/acts/framework/acts/test_runner.py
index 69e8dd2..8324650 100644
--- a/acts/framework/acts/test_runner.py
+++ b/acts/framework/acts/test_runner.py
@@ -284,8 +284,8 @@
for ctrl_name in keys.Config.builtin_controller_names.value:
if ctrl_name in self.testbed_configs:
module_name = keys.get_module_name(ctrl_name)
- module = importlib.import_module("acts.controllers.%s" %
- module_name)
+ module = importlib.import_module(
+ "acts.controllers.%s" % module_name)
builtin_controllers.append(module)
return builtin_controllers
@@ -312,8 +312,8 @@
attr))
if not getattr(module, attr):
raise signals.ControllerError(
- "Controller interface %s in %s cannot be null." % (
- attr, module.__name__))
+ "Controller interface %s in %s cannot be null." %
+ (attr, module.__name__))
def register_controller(self, module, required=True):
"""Registers an ACTS controller module for a test run.
@@ -431,8 +431,8 @@
# tracking controller objs in test_run_info.
if builtin:
self.test_run_info[module_ref_name] = objects
- self.log.debug("Found %d objects for controller %s", len(objects),
- module_config_name)
+ self.log.debug("Found %d objects for controller %s",
+ len(objects), module_config_name)
destroy_func = module.destroy
self.controller_destructors[module_ref_name] = destroy_func
return objects
diff --git a/acts/framework/acts/test_utils/bt/bt_gatt_utils.py b/acts/framework/acts/test_utils/bt/bt_gatt_utils.py
index da0eb6f..f6ae247 100644
--- a/acts/framework/acts/test_utils/bt/bt_gatt_utils.py
+++ b/acts/framework/acts/test_utils/bt/bt_gatt_utils.py
@@ -40,36 +40,43 @@
def setup_gatt_connection(cen_ad,
mac_address,
autoconnect,
- transport=GattTransport.TRANSPORT_AUTO.value):
+ transport=GattTransport.TRANSPORT_AUTO.value,
+ opportunistic=False):
gatt_callback = cen_ad.droid.gattCreateGattCallback()
log.info("Gatt Connect to mac address {}.".format(mac_address))
bluetooth_gatt = cen_ad.droid.gattClientConnectGatt(
- gatt_callback, mac_address, autoconnect, transport,
+ gatt_callback, mac_address, autoconnect, transport, opportunistic,
GattPhyMask.PHY_LE_1M_MASK)
expected_event = GattCbStrings.GATT_CONN_CHANGE.value.format(gatt_callback)
try:
event = cen_ad.ed.pop_event(expected_event, default_timeout)
except Empty:
- try:
- cen_ad.droid.gattClientClose(bluetooth_gatt)
- except Exception:
- self.log.debug("Failed to close gatt client.")
+ close_gatt_client(cen_ad, bluetooth_gatt)
raise GattTestUtilsError(
"Could not establish a connection to "
"peripheral. Expected event: {}".format(expected_event))
if event['data']['State'] != GattConnectionState.STATE_CONNECTED.value:
- try:
- cen_ad.droid.gattClientClose(bluetooth_gatt)
- except Exception:
- self.log.debug("Failed to close gatt client.")
+ close_gatt_client(cen_ad, bluetooth_gatt)
raise GattTestUtilsError(
"Could not establish a connection to "
"peripheral. Event Details: {}".format(pprint.pformat(event)))
return bluetooth_gatt, gatt_callback
+def close_gatt_client(cen_ad, bluetooth_gatt):
+ try:
+ cen_ad.droid.gattClientClose(bluetooth_gatt)
+ except Exception:
+ log.debug("Failed to close gatt client.")
+
+
def disconnect_gatt_connection(cen_ad, bluetooth_gatt, gatt_callback):
cen_ad.droid.gattClientDisconnect(bluetooth_gatt)
+ wait_for_gatt_disconnect_event(cen_ad, gatt_callback)
+ return
+
+
+def wait_for_gatt_disconnect_event(cen_ad, gatt_callback):
expected_event = GattCbStrings.GATT_CONN_CHANGE.value.format(gatt_callback)
try:
event = cen_ad.ed.pop_event(expected_event, default_timeout)
@@ -89,7 +96,8 @@
per_ad,
transport=GattTransport.TRANSPORT_LE.value,
mac_address=None,
- autoconnect=False):
+ autoconnect=False,
+ opportunistic=False):
adv_callback = None
if mac_address is None:
if transport == GattTransport.TRANSPORT_LE.value:
@@ -103,7 +111,7 @@
mac_address = per_ad.droid.bluetoothGetLocalAddress()
adv_callback = None
bluetooth_gatt, gatt_callback = setup_gatt_connection(
- cen_ad, mac_address, autoconnect, transport)
+ cen_ad, mac_address, autoconnect, transport, opportunistic)
return bluetooth_gatt, gatt_callback, adv_callback
@@ -173,44 +181,31 @@
def setup_characteristics_and_descriptors(droid):
characteristic_input = [
{
- 'uuid':
- "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
- 'property':
- GattCharacteristic.PROPERTY_WRITE.value |
+ 'uuid': "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
+ 'property': GattCharacteristic.PROPERTY_WRITE.value |
GattCharacteristic.PROPERTY_WRITE_NO_RESPONSE.value,
- 'permission':
- GattCharacteristic.PROPERTY_WRITE.value
+ 'permission': GattCharacteristic.PROPERTY_WRITE.value
},
{
- 'uuid':
- "21c0a0bf-ad51-4a2d-8124-b74003e4e8c8",
- 'property':
- GattCharacteristic.PROPERTY_NOTIFY.value |
+ 'uuid': "21c0a0bf-ad51-4a2d-8124-b74003e4e8c8",
+ 'property': GattCharacteristic.PROPERTY_NOTIFY.value |
GattCharacteristic.PROPERTY_READ.value,
- 'permission':
- GattCharacteristic.PERMISSION_READ.value
+ 'permission': GattCharacteristic.PERMISSION_READ.value
},
{
- 'uuid':
- "6774191f-6ec3-4aa2-b8a8-cf830e41fda6",
- 'property':
- GattCharacteristic.PROPERTY_NOTIFY.value |
+ 'uuid': "6774191f-6ec3-4aa2-b8a8-cf830e41fda6",
+ 'property': GattCharacteristic.PROPERTY_NOTIFY.value |
GattCharacteristic.PROPERTY_READ.value,
- 'permission':
- GattCharacteristic.PERMISSION_READ.value
+ 'permission': GattCharacteristic.PERMISSION_READ.value
},
]
descriptor_input = [{
- 'uuid':
- "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
- 'property':
- GattDescriptor.PERMISSION_READ.value |
+ 'uuid': "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
+ 'property': GattDescriptor.PERMISSION_READ.value |
GattDescriptor.PERMISSION_WRITE.value,
}, {
- 'uuid':
- "76d5ed92-ca81-4edb-bb6b-9f019665fb32",
- 'property':
- GattDescriptor.PERMISSION_READ.value |
+ 'uuid': "76d5ed92-ca81-4edb-bb6b-9f019665fb32",
+ 'property': GattDescriptor.PERMISSION_READ.value |
GattCharacteristic.PERMISSION_WRITE.value,
}]
characteristic_list = setup_gatt_characteristics(droid,
@@ -276,44 +271,31 @@
def setup_characteristics_and_descriptors(droid):
characteristic_input = [
{
- 'uuid':
- "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
- 'property':
- GattCharacteristic.PROPERTY_WRITE.value |
+ 'uuid': "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
+ 'property': GattCharacteristic.PROPERTY_WRITE.value |
GattCharacteristic.PROPERTY_WRITE_NO_RESPONSE.value,
- 'permission':
- GattCharacteristic.PROPERTY_WRITE.value
+ 'permission': GattCharacteristic.PROPERTY_WRITE.value
},
{
- 'uuid':
- "21c0a0bf-ad51-4a2d-8124-b74003e4e8c8",
- 'property':
- GattCharacteristic.PROPERTY_NOTIFY.value |
+ 'uuid': "21c0a0bf-ad51-4a2d-8124-b74003e4e8c8",
+ 'property': GattCharacteristic.PROPERTY_NOTIFY.value |
GattCharacteristic.PROPERTY_READ.value,
- 'permission':
- GattCharacteristic.PERMISSION_READ.value
+ 'permission': GattCharacteristic.PERMISSION_READ.value
},
{
- 'uuid':
- "6774191f-6ec3-4aa2-b8a8-cf830e41fda6",
- 'property':
- GattCharacteristic.PROPERTY_NOTIFY.value |
+ 'uuid': "6774191f-6ec3-4aa2-b8a8-cf830e41fda6",
+ 'property': GattCharacteristic.PROPERTY_NOTIFY.value |
GattCharacteristic.PROPERTY_READ.value,
- 'permission':
- GattCharacteristic.PERMISSION_READ.value
+ 'permission': GattCharacteristic.PERMISSION_READ.value
},
]
descriptor_input = [{
- 'uuid':
- "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
- 'property':
- GattDescriptor.PERMISSION_READ.value |
+ 'uuid': "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
+ 'property': GattDescriptor.PERMISSION_READ.value |
GattDescriptor.PERMISSION_WRITE.value,
}, {
- 'uuid':
- "76d5ed92-ca81-4edb-bb6b-9f019665fb32",
- 'property':
- GattDescriptor.PERMISSION_READ.value |
+ 'uuid': "76d5ed92-ca81-4edb-bb6b-9f019665fb32",
+ 'property': GattDescriptor.PERMISSION_READ.value |
GattCharacteristic.PERMISSION_WRITE.value,
}]
characteristic_list = setup_gatt_characteristics(droid,
diff --git a/acts/framework/acts/test_utils/bt/native_bt_test_utils.py b/acts/framework/acts/test_utils/bt/native_bt_test_utils.py
index d070f13..9be24f0 100644
--- a/acts/framework/acts/test_utils/bt/native_bt_test_utils.py
+++ b/acts/framework/acts/test_utils/bt/native_bt_test_utils.py
@@ -25,15 +25,14 @@
def setup_native_bluetooth(native_devices):
for n in native_devices:
droid = n.droid
- pid = n.adb.shell("ps | grep bluetoothtbd | awk '{print $2}'").decode(
- 'ascii')
+ pid = n.adb.shell("pidof -s bluetoothtbd")
if not pid:
call(
["adb -s " + n.serial + " shell sh -c \"bluetoothtbd\" &"],
shell=True)
- droid.BluetoothBinderInitInterface()
+ droid.BtBinderInitInterface()
time.sleep(5) #temporary sleep statement
- droid.BluetoothBinderEnable()
+ droid.BtBinderEnable()
time.sleep(5) #temporary sleep statement
- droid.BluetoothBinderRegisterBLE()
+ droid.BtBinderRegisterBLE()
time.sleep(5) #temporary sleep statement
diff --git a/acts/framework/acts/test_utils/net/connectivity_const.py b/acts/framework/acts/test_utils/net/connectivity_const.py
index de8de47..89bdb2c 100644
--- a/acts/framework/acts/test_utils/net/connectivity_const.py
+++ b/acts/framework/acts/test_utils/net/connectivity_const.py
@@ -39,6 +39,8 @@
NETWORK_CB_KEY_MAX_MS_TO_LIVE = "maxMsToLive"
NETWORK_CB_KEY_RSSI = "rssi"
NETWORK_CB_KEY_INTERFACE_NAME = "interfaceName"
+NETWORK_CB_KEY_CREATE_TS = "creation_timestamp"
+NETWORK_CB_KEY_CURRENT_TS = "current_timestamp"
# Constants for VPN connection status
VPN_STATE_DISCONNECTED = 0
diff --git a/acts/framework/acts/test_utils/tel/tel_test_utils.py b/acts/framework/acts/test_utils/tel/tel_test_utils.py
index 8219ce4..abe23cb 100644
--- a/acts/framework/acts/test_utils/tel/tel_test_utils.py
+++ b/acts/framework/acts/test_utils/tel/tel_test_utils.py
@@ -3748,10 +3748,12 @@
Phone not in airplane mode.
"""
result = True
+
if not toggle_airplane_mode(log, ad, False, False):
ad.log.error("Fail to turn off airplane mode")
result = False
set_wifi_to_default(log, ad)
+
try:
if ad.droid.telecomIsInCall():
ad.droid.telecomEndCall()
diff --git a/acts/framework/acts/test_utils/wifi/aware/AwareBaseTest.py b/acts/framework/acts/test_utils/wifi/aware/AwareBaseTest.py
new file mode 100644
index 0000000..ec60bf1
--- /dev/null
+++ b/acts/framework/acts/test_utils/wifi/aware/AwareBaseTest.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2017 - Google
+#
+# 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.
+
+from acts import asserts
+from acts.base_test import BaseTestClass
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+
+
+class AwareBaseTest(BaseTestClass):
+ def __init__(self, controllers):
+ BaseTestClass.__init__(self, controllers)
+
+ # message ID counter to make sure all uses are unique
+ msg_id = 0
+
+ # offset (in seconds) to separate the start-up of multiple devices.
+ # De-synchronizes the start-up time so that they don't start and stop scanning
+ # at the same time - which can lead to very long clustering times.
+ device_startup_offset = 2
+
+ def setup_test(self):
+ required_params = ("default_power_mode", )
+ self.unpack_userparams(required_params)
+
+ for ad in self.android_devices:
+ wutils.wifi_toggle_state(ad, True)
+ ad.droid.wifiP2pClose()
+ aware_avail = ad.droid.wifiIsAwareAvailable()
+ if not aware_avail:
+ self.log.info('Aware not available. Waiting ...')
+ autils.wait_for_event(ad, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+ ad.ed.pop_all(aconsts.BROADCAST_WIFI_AWARE_AVAILABLE) # clear-out extras
+ ad.aware_capabilities = autils.get_aware_capabilities(ad)
+ self.reset_device_parameters(ad)
+ self.reset_device_statistics(ad)
+ self.set_power_mode_parameters(ad)
+
+ def teardown_test(self):
+ for ad in self.android_devices:
+ ad.droid.wifiP2pClose()
+ ad.droid.wifiAwareDestroyAll()
+ self.reset_device_parameters(ad)
+ autils.validate_forbidden_callbacks(ad)
+
+ def reset_device_parameters(self, ad):
+ """Reset device configurations which may have been set by tests. Should be
+ done before tests start (in case previous one was killed without tearing
+ down) and after they end (to leave device in usable state).
+
+ Args:
+ ad: device to be reset
+ """
+ ad.adb.shell("cmd wifiaware reset")
+
+ def reset_device_statistics(self, ad):
+ """Reset device statistics.
+
+ Args:
+ ad: device to be reset
+ """
+ ad.adb.shell("cmd wifiaware native_cb get_cb_count --reset")
+
+ def set_power_mode_parameters(self, ad):
+ """Set the power configuration DW parameters for the device based on any
+ configuration overrides (if provided)"""
+ if self.default_power_mode == "INTERACTIVE":
+ autils.config_dw_high_power(ad)
+ elif self.default_power_mode == "NON_INTERACTIVE":
+ autils.config_dw_low_power(ad)
+ else:
+ asserts.assert_false(
+ "The 'default_power_mode' configuration must be INTERACTIVE or "
+ "NON_INTERACTIVE"
+ )
+
+ def get_next_msg_id(self):
+ """Increment the message ID and returns the new value. Guarantees that
+ each call to the method returns a unique value.
+
+ Returns: a new message id value.
+ """
+ self.msg_id = self.msg_id + 1
+ return self.msg_id
diff --git a/acts/framework/acts/test_utils/wifi/aware/__init__.py b/acts/framework/acts/test_utils/wifi/aware/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/acts/test_utils/wifi/aware/__init__.py
diff --git a/acts/framework/acts/test_utils/wifi/wifi_aware_const.py b/acts/framework/acts/test_utils/wifi/aware/aware_const.py
similarity index 66%
rename from acts/framework/acts/test_utils/wifi/wifi_aware_const.py
rename to acts/framework/acts/test_utils/wifi/aware/aware_const.py
index 7e495f5..a7574a4 100644
--- a/acts/framework/acts/test_utils/wifi/wifi_aware_const.py
+++ b/acts/framework/acts/test_utils/wifi/aware/aware_const.py
@@ -15,6 +15,16 @@
# limitations under the License.
######################################################
+# Aware DW (Discovery Window) power mode values
+######################################################
+
+DW_24_INTERACTIVE = 1
+DW_5_INTERACTIVE = 1
+
+DW_24_NON_INTERACTIVE = 4
+DW_5_NON_INTERACTIVE = 0
+
+######################################################
# Broadcast events
######################################################
BROADCAST_WIFI_AWARE_AVAILABLE = "WifiAwareAvailable"
@@ -31,29 +41,22 @@
CONFIG_KEY_ENABLE_IDEN_CB = "EnableIdentityChangeCallback"
######################################################
-# PublishConfig keys
+# Publish & Subscribe Config keys
######################################################
-PUBLISH_KEY_SERVICE_NAME = "ServiceName"
-PUBLISH_KEY_SSI = "ServiceSpecificInfo"
-PUBLISH_KEY_MATCH_FILTER = "MatchFilter"
-PUBLISH_KEY_TYPE = "PublishType"
-PUBLISH_KEY_COUNT = "PublishCount"
-PUBLISH_KEY_TTL = "TtlSec"
-PUBLISH_KEY_TERM_CB_ENABLED = "TerminateNotificationEnabled"
+DISCOVERY_KEY_SERVICE_NAME = "ServiceName"
+DISCOVERY_KEY_SSI = "ServiceSpecificInfo"
+DISCOVERY_KEY_MATCH_FILTER = "MatchFilter"
+DISCOVERY_KEY_MATCH_FILTER_LIST = "MatchFilterList"
+DISCOVERY_KEY_DISCOVERY_TYPE = "DiscoveryType"
+DISCOVERY_KEY_TTL = "TtlSec"
+DISCOVERY_KEY_TERM_CB_ENABLED = "TerminateNotificationEnabled"
-######################################################
-# SubscribeConfig keys
-######################################################
+PUBLISH_TYPE_UNSOLICITED = 0
+PUBLISH_TYPE_SOLICITED = 1
-SUBSCRIBE_KEY_SERVICE_NAME = "ServiceName"
-SUBSCRIBE_KEY_SSI = "ServiceSpecificInfo"
-SUBSCRIBE_KEY_MATCH_FILTER = "MatchFilter"
-SUBSCRIBE_KEY_TYPE = "SubscribeType"
-SUBSCRIBE_KEY_COUNT = "SubscribeCount"
-SUBSCRIBE_KEY_TTL = "TtlSec"
-SUBSCRIBE_KEY_STYLE = "MatchStyle"
-SUBSCRIBE_KEY_ENABLE_TERM_CB = "EnableTerminateNotification"
+SUBSCRIBE_TYPE_PASSIVE = 0
+SUBSCRIBE_TYPE_ACTIVE = 1
######################################################
# WifiAwareAttachCallback events
@@ -92,6 +95,7 @@
SESSION_CB_KEY_PEER_ID = "peerId"
SESSION_CB_KEY_SERVICE_SPECIFIC_INFO = "serviceSpecificInfo"
SESSION_CB_KEY_MATCH_FILTER = "matchFilter"
+SESSION_CB_KEY_MATCH_FILTER_LIST = "matchFilterList"
SESSION_CB_KEY_MESSAGE = "message"
SESSION_CB_KEY_MESSAGE_ID = "messageId"
SESSION_CB_KEY_MESSAGE_AS_STRING = "messageAsString"
@@ -113,6 +117,32 @@
RTT_LISTENER_CB_KEY_DESCRIPTION = "description"
######################################################
+# Capabilities keys
+######################################################
+
+CAP_MAX_CONCURRENT_AWARE_CLUSTERS = "maxConcurrentAwareClusters"
+CAP_MAX_PUBLISHES = "maxPublishes"
+CAP_MAX_SUBSCRIBES = "maxSubscribes"
+CAP_MAX_SERVICE_NAME_LEN = "maxServiceNameLen"
+CAP_MAX_MATCH_FILTER_LEN = "maxMatchFilterLen"
+CAP_MAX_TOTAL_MATCH_FILTER_LEN = "maxTotalMatchFilterLen"
+CAP_MAX_SERVICE_SPECIFIC_INFO_LEN = "maxServiceSpecificInfoLen"
+CAP_MAX_EXTENDED_SERVICE_SPECIFIC_INFO_LEN = "maxExtendedServiceSpecificInfoLen"
+CAP_MAX_NDI_INTERFACES = "maxNdiInterfaces"
+CAP_MAX_NDP_SESSIONS = "maxNdpSessions"
+CAP_MAX_APP_INFO_LEN = "maxAppInfoLen"
+CAP_MAX_QUEUED_TRANSMIT_MESSAGES = "maxQueuedTransmitMessages"
+CAP_MAX_SUBSCRIBE_INTERFACE_ADDRESSES = "maxSubscribeInterfaceAddresses"
+CAP_SUPPORTED_CIPHER_SUITES = "supportedCipherSuites"
+
+######################################################
+
+# Aware NDI (NAN data-interface) name prefix
+AWARE_NDI_PREFIX = "aware_data"
+
+# Aware discovery channels
+AWARE_DISCOVERY_CHANNEL_24_BAND = 6
+AWARE_DISCOVERY_CHANNEL_5_BAND = 149
# Aware Data-Path Constants
DATA_PATH_INITIATOR = 0
@@ -120,3 +150,16 @@
# Maximum send retry
MAX_TX_RETRIES = 5
+
+# Callback keys (for 'adb shell cmd wifiaware native_cb get_cb_count')
+CB_EV_CLUSTER = "0"
+CB_EV_DISABLED = "1"
+CB_EV_PUBLISH_TERMINATED = "2"
+CB_EV_SUBSCRIBE_TERMINATED = "3"
+CB_EV_MATCH = "4"
+CB_EV_MATCH_EXPIRED = "5"
+CB_EV_FOLLOWUP_RECEIVED = "6"
+CB_EV_TRANSMIT_FOLLOWUP = "7"
+CB_EV_DATA_PATH_REQUEST = "8"
+CB_EV_DATA_PATH_CONFIRM = "9"
+CB_EV_DATA_PATH_TERMINATED = "10"
diff --git a/acts/framework/acts/test_utils/wifi/aware/aware_test_utils.py b/acts/framework/acts/test_utils/wifi/aware/aware_test_utils.py
new file mode 100644
index 0000000..9ed704f
--- /dev/null
+++ b/acts/framework/acts/test_utils/wifi/aware/aware_test_utils.py
@@ -0,0 +1,672 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2017 - Google
+#
+# 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 base64
+import json
+import queue
+import re
+import statistics
+import time
+from acts import asserts
+
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+
+# arbitrary timeout for events
+EVENT_TIMEOUT = 10
+
+# semi-arbitrary timeout for network formation events. Based on framework
+# timeout for NDP (NAN data-path) negotiation to be completed.
+EVENT_NDP_TIMEOUT = 20
+
+# number of second to 'reasonably' wait to make sure that devices synchronize
+# with each other - useful for OOB test cases, where the OOB discovery would
+# take some time
+WAIT_FOR_CLUSTER = 5
+
+
+def decorate_event(event_name, id):
+ return '%s_%d' % (event_name, id)
+
+
+def wait_for_event(ad, event_name, timeout=EVENT_TIMEOUT):
+ """Wait for the specified event or timeout.
+
+ Args:
+ ad: The android device
+ event_name: The event to wait on
+ timeout: Number of seconds to wait
+ Returns:
+ The event (if available)
+ """
+ prefix = ''
+ if hasattr(ad, 'pretty_name'):
+ prefix = '[%s] ' % ad.pretty_name
+ try:
+ event = ad.ed.pop_event(event_name, timeout)
+ ad.log.info('%s%s: %s', prefix, event_name, event['data'])
+ return event
+ except queue.Empty:
+ ad.log.info('%sTimed out while waiting for %s', prefix, event_name)
+ asserts.fail(event_name)
+
+def wait_for_event_with_keys(ad, event_name, timeout=EVENT_TIMEOUT, *keyvalues):
+ """Wait for the specified event contain the key/value pairs or timeout
+
+ Args:
+ ad: The android device
+ event_name: The event to wait on
+ timeout: Number of seconds to wait
+ keyvalues: (kay, value) pairs
+ Returns:
+ The event (if available)
+ """
+ def filter_callbacks(event, keyvalues):
+ for keyvalue in keyvalues:
+ key, value = keyvalue
+ if event['data'][key] != value:
+ return False
+ return True
+
+ prefix = ''
+ if hasattr(ad, 'pretty_name'):
+ prefix = '[%s] ' % ad.pretty_name
+ try:
+ event = ad.ed.wait_for_event(event_name, filter_callbacks, timeout,
+ keyvalues)
+ ad.log.info('%s%s: %s', prefix, event_name, event['data'])
+ return event
+ except queue.Empty:
+ ad.log.info('%sTimed out while waiting for %s (%s)', prefix, event_name,
+ keyvalues)
+ asserts.fail(event_name)
+
+def fail_on_event(ad, event_name, timeout=EVENT_TIMEOUT):
+ """Wait for a timeout period and looks for the specified event - fails if it
+ is observed.
+
+ Args:
+ ad: The android device
+ event_name: The event to wait for (and fail on its appearance)
+ """
+ prefix = ''
+ if hasattr(ad, 'pretty_name'):
+ prefix = '[%s] ' % ad.pretty_name
+ try:
+ event = ad.ed.pop_event(event_name, timeout)
+ ad.log.info('%sReceived unwanted %s: %s', prefix, event_name, event['data'])
+ asserts.fail(event_name, extras=event)
+ except queue.Empty:
+ ad.log.info('%s%s not seen (as expected)', prefix, event_name)
+ return
+
+def fail_on_event_with_keys(ad, event_name, timeout=EVENT_TIMEOUT, *keyvalues):
+ """Wait for a timeout period and looks for the specified event which contains
+ the key/value pairs - fails if it is observed.
+
+ Args:
+ ad: The android device
+ event_name: The event to wait on
+ timeout: Number of seconds to wait
+ keyvalues: (kay, value) pairs
+ """
+ def filter_callbacks(event, keyvalues):
+ for keyvalue in keyvalues:
+ key, value = keyvalue
+ if event['data'][key] != value:
+ return False
+ return True
+
+ prefix = ''
+ if hasattr(ad, 'pretty_name'):
+ prefix = '[%s] ' % ad.pretty_name
+ try:
+ event = ad.ed.wait_for_event(event_name, filter_callbacks, timeout,
+ keyvalues)
+ ad.log.info('%sReceived unwanted %s: %s', prefix, event_name, event['data'])
+ asserts.fail(event_name, extras=event)
+ except queue.Empty:
+ ad.log.info('%s%s (%s) not seen (as expected)', prefix, event_name,
+ keyvalues)
+ return
+
+def verify_no_more_events(ad, timeout=EVENT_TIMEOUT):
+ """Verify that there are no more events in the queue.
+ """
+ prefix = ''
+ if hasattr(ad, 'pretty_name'):
+ prefix = '[%s] ' % ad.pretty_name
+ should_fail = False
+ try:
+ while True:
+ event = ad.ed.pop_events('.*', timeout, freq=0)
+ ad.log.info('%sQueue contains %s', prefix, event)
+ should_fail = True
+ except queue.Empty:
+ if should_fail:
+ asserts.fail('%sEvent queue not empty' % prefix)
+ ad.log.info('%sNo events in the queue (as expected)', prefix)
+ return
+
+
+def encode_list(list_of_objects):
+ """Converts the list of strings or bytearrays to a list of b64 encoded
+ bytearrays.
+
+ A None object is treated as a zero-length bytearray.
+
+ Args:
+ list_of_objects: A list of strings or bytearray objects
+ Returns: A list of the same objects, converted to bytes and b64 encoded.
+ """
+ encoded_list = []
+ for obj in list_of_objects:
+ if obj is None:
+ obj = bytes()
+ if isinstance(obj, str):
+ encoded_list.append(base64.b64encode(bytes(obj, 'utf-8')).decode('utf-8'))
+ else:
+ encoded_list.append(base64.b64encode(obj).decode('utf-8'))
+ return encoded_list
+
+
+def decode_list(list_of_b64_strings):
+ """Converts the list of b64 encoded strings to a list of bytearray.
+
+ Args:
+ list_of_b64_strings: list of strings, each of which is b64 encoded array
+ Returns: a list of bytearrays.
+ """
+ decoded_list = []
+ for str in list_of_b64_strings:
+ decoded_list.append(base64.b64decode(str))
+ return decoded_list
+
+
+def construct_max_match_filter(max_size):
+ """Constructs a maximum size match filter that fits into the 'max_size' bytes.
+
+ Match filters are a set of LVs (Length, Value pairs) where L is 1 byte. The
+ maximum size match filter will contain max_size/2 LVs with all Vs (except
+ possibly the last one) of 1 byte, the last V may be 2 bytes for odd max_size.
+
+ Args:
+ max_size: Maximum size of the match filter.
+ Returns: an array of bytearrays.
+ """
+ mf_list = []
+ num_lvs = max_size // 2
+ for i in range(num_lvs - 1):
+ mf_list.append(bytes([i]))
+ if (max_size % 2 == 0):
+ mf_list.append(bytes([255]))
+ else:
+ mf_list.append(bytes([254, 255]))
+ return mf_list
+
+
+def assert_equal_strings(first, second, msg=None, extras=None):
+ """Assert equality of the string operands - where None is treated as equal to
+ an empty string (''), otherwise fail the test.
+
+ Error message is "first != second" by default. Additional explanation can
+ be supplied in the message.
+
+ Args:
+ first, seconds: The strings that are evaluated for equality.
+ msg: A string that adds additional info about the failure.
+ extras: An optional field for extra information to be included in
+ test result.
+ """
+ if first == None:
+ first = ''
+ if second == None:
+ second = ''
+ asserts.assert_equal(first, second, msg, extras)
+
+
+def get_aware_capabilities(ad):
+ """Get the Wi-Fi Aware capabilities from the specified device. The
+ capabilities are a dictionary keyed by aware_const.CAP_* keys.
+
+ Args:
+ ad: the Android device
+ Returns: the capability dictionary.
+ """
+ return json.loads(ad.adb.shell('cmd wifiaware state_mgr get_capabilities'))
+
+def get_wifi_mac_address(ad):
+ """Get the Wi-Fi interface MAC address as a upper-case string of hex digits
+ without any separators (e.g. ':').
+
+ Args:
+ ad: Device on which to run.
+ """
+ return ad.droid.wifiGetConnectionInfo()['mac_address'].upper().replace(
+ ':', '')
+
+def validate_forbidden_callbacks(ad, limited_cb=None):
+ """Validate that the specified callbacks have not been called more then permitted.
+
+ In addition to the input configuration also validates that forbidden callbacks
+ have never been called.
+
+ Args:
+ ad: Device on which to run.
+ limited_cb: Dictionary of CB_EV_* ids and maximum permitted calls (0
+ meaning never).
+ """
+ cb_data = json.loads(ad.adb.shell('cmd wifiaware native_cb get_cb_count'))
+
+ if limited_cb is None:
+ limited_cb = {}
+ # add callbacks which should never be called
+ limited_cb[aconsts.CB_EV_MATCH_EXPIRED] = 0
+
+ fail = False
+ for cb_event in limited_cb.keys():
+ if cb_event in cb_data:
+ if cb_data[cb_event] > limited_cb[cb_event]:
+ fail = True
+ ad.log.info(
+ 'Callback %s observed %d times: more then permitted %d times',
+ cb_event, cb_data[cb_event], limited_cb[cb_event])
+
+ asserts.assert_false(fail, 'Forbidden callbacks observed', extras=cb_data)
+
+def extract_stats(ad, data, results, key_prefix, log_prefix):
+ """Extract statistics from the data, store in the results dictionary, and
+ output to the info log.
+
+ Args:
+ ad: Android device (for logging)
+ data: A list containing the data to be analyzed.
+ results: A dictionary into which to place the statistics.
+ key_prefix: A string prefix to use for the dict keys storing the
+ extracted stats.
+ log_prefix: A string prefix to use for the info log.
+ include_data: If True includes the raw data in the dictionary,
+ otherwise just the stats.
+ """
+ num_samples = len(data)
+ results['%snum_samples' % key_prefix] = num_samples
+
+ if not data:
+ return
+
+ data_min = min(data)
+ data_max = max(data)
+ data_mean = statistics.mean(data)
+
+ results['%smin' % key_prefix] = data_min
+ results['%smax' % key_prefix] = data_max
+ results['%smean' % key_prefix] = data_mean
+ results['%sraw_data' % key_prefix] = data
+
+ if num_samples > 1:
+ data_stdev = statistics.stdev(data)
+ results['%sstdev' % key_prefix] = data_stdev
+ ad.log.info('%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f, stdev=%.2f',
+ log_prefix, num_samples, data_min, data_max, data_mean,
+ data_stdev)
+ else:
+ ad.log.info('%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f', log_prefix,
+ num_samples, data_min, data_max, data_mean)
+
+def get_mac_addr(device, interface):
+ """Get the MAC address of the specified interface. Uses ifconfig and parses
+ its output. Normalizes string to remove ':' and upper case.
+
+ Args:
+ device: Device on which to query the interface MAC address.
+ interface: Name of the interface for which to obtain the MAC address.
+ """
+ out = device.adb.shell("ifconfig %s" % interface)
+ res = re.match(".* HWaddr (\S+).*", out , re.S)
+ asserts.assert_true(
+ res,
+ 'Unable to obtain MAC address for interface %s' % interface,
+ extras=out)
+ return res.group(1).upper().replace(':', '')
+
+#########################################################
+# Aware primitives
+#########################################################
+
+def request_network(dut, ns):
+ """Request a Wi-Fi Aware network.
+
+ Args:
+ dut: Device
+ ns: Network specifier
+ Returns: the request key
+ """
+ network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+ return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+def configure_dw(device, is_default, is_24_band, value):
+ """Use the command-line API to configure the DW (discovery window) setting
+
+ Args:
+ device: Device on which to perform configuration
+ is_default: True for the default setting, False for the non-interactive
+ setting
+ is_24_band: True for 2.4GHz band, False for 5GHz band
+ value: An integer 0 to 5
+ """
+ variable = 'dw_%s_%sghz' % ('default' if is_default else 'on_inactive', '24'
+ if is_24_band else '5')
+ device.adb.shell("cmd wifiaware native_api set %s %d" % (variable, value))
+
+def config_dw_high_power(device):
+ """Configure device's discovery window (DW) values to high power mode -
+ whether device is in interactive or non-interactive modes"""
+ configure_dw(
+ device, is_default=True, is_24_band=True, value=aconsts.DW_24_INTERACTIVE)
+ configure_dw(
+ device, is_default=True, is_24_band=False, value=aconsts.DW_5_INTERACTIVE)
+ configure_dw(
+ device,
+ is_default=False,
+ is_24_band=True,
+ value=aconsts.DW_24_INTERACTIVE)
+ configure_dw(
+ device,
+ is_default=False,
+ is_24_band=False,
+ value=aconsts.DW_5_INTERACTIVE)
+
+def config_dw_low_power(device):
+ """Configure device's discovery window (DW) values to low power mode - whether
+ device is in interactive or non-interactive modes"""
+ configure_dw(
+ device,
+ is_default=True,
+ is_24_band=True,
+ value=aconsts.DW_24_NON_INTERACTIVE)
+ configure_dw(
+ device,
+ is_default=True,
+ is_24_band=False,
+ value=aconsts.DW_5_NON_INTERACTIVE)
+ configure_dw(
+ device,
+ is_default=False,
+ is_24_band=True,
+ value=aconsts.DW_24_NON_INTERACTIVE)
+ configure_dw(
+ device,
+ is_default=False,
+ is_24_band=False,
+ value=aconsts.DW_5_NON_INTERACTIVE)
+
+def config_dw_all_modes(device, dw_24ghz, dw_5ghz):
+ """Configure device's discovery window (DW) values to the specified values -
+ whether the device is in interactive or non-interactive mode.
+
+ Args:
+ dw_24ghz: DW interval in the 2.4GHz band.
+ dw_5ghz: DW interval in the 5GHz band.
+ """
+ configure_dw(device, is_default=True, is_24_band=True, value=dw_24ghz)
+ configure_dw(device, is_default=True, is_24_band=False, value=dw_5ghz)
+ configure_dw(device, is_default=False, is_24_band=True, value=dw_24ghz)
+ configure_dw(device, is_default=False, is_24_band=False, value=dw_5ghz)
+
+def create_discovery_config(service_name,
+ d_type,
+ ssi=None,
+ match_filter=None,
+ match_filter_list=None,
+ ttl=0,
+ term_cb_enable=True):
+ """Create a publish discovery configuration based on input parameters.
+
+ Args:
+ service_name: Service name - required
+ d_type: Discovery type (publish or subscribe constants)
+ ssi: Supplemental information - defaults to None
+ match_filter, match_filter_list: The match_filter, only one mechanism can
+ be used to specify. Defaults to None.
+ 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.
+ Returns:
+ publish discovery configuration object.
+ """
+ config = {}
+ config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+ config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = d_type
+ if ssi is not None:
+ config[aconsts.DISCOVERY_KEY_SSI] = ssi
+ if match_filter is not None:
+ 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
+ config[aconsts.DISCOVERY_KEY_TTL] = ttl
+ config[aconsts.DISCOVERY_KEY_TERM_CB_ENABLED] = term_cb_enable
+ return config
+
+def create_discovery_pair(p_dut,
+ s_dut,
+ p_config,
+ s_config,
+ device_startup_offset,
+ msg_id=None):
+ """Creates a discovery session (publish and subscribe), and waits for
+ service discovery - at that point the sessions are connected and ready for
+ further messaging of data-path setup.
+
+ Args:
+ p_dut: Device to use as publisher.
+ s_dut: Device to use as subscriber.
+ p_config: Publish configuration.
+ s_config: Subscribe configuration.
+ device_startup_offset: Number of seconds to offset the enabling of NAN on
+ the two devices.
+ msg_id: Controls whether a message is sent from Subscriber to Publisher
+ (so that publisher has the sub's peer ID). If None then not sent,
+ otherwise should be an int for the message id.
+ Returns: variable size list of:
+ p_id: Publisher attach session id
+ s_id: Subscriber attach session id
+ p_disc_id: Publisher discovery session id
+ s_disc_id: Subscriber discovery session id
+ peer_id_on_sub: Peer ID of the Publisher as seen on the Subscriber
+ peer_id_on_pub: Peer ID of the Subscriber as seen on the Publisher. Only
+ included if |msg_id| is not None.
+ """
+ p_dut.pretty_name = 'Publisher'
+ s_dut.pretty_name = 'Subscriber'
+
+ # Publisher+Subscriber: attach and wait for confirmation
+ p_id = p_dut.droid.wifiAwareAttach()
+ wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ time.sleep(device_startup_offset)
+ s_id = s_dut.droid.wifiAwareAttach()
+ wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # Publisher: start publish and wait for confirmation
+ p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
+ wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+ # Subscriber: start subscribe and wait for confirmation
+ s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
+ wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+ # Subscriber: wait for service discovery
+ discovery_event = wait_for_event(s_dut,
+ aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+ peer_id_on_sub = discovery_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+
+ # Optionally send a message from Subscriber to Publisher
+ if msg_id is not None:
+ ping_msg = 'PING'
+
+ # Subscriber: send message to peer (Publisher)
+ s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id,
+ ping_msg, aconsts.MAX_TX_RETRIES)
+ sub_tx_msg_event = wait_for_event(s_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+ asserts.assert_equal(
+ msg_id, sub_tx_msg_event['data'][aconsts.SESSION_CB_KEY_MESSAGE_ID],
+ 'Subscriber -> Publisher message ID corrupted')
+
+ # Publisher: wait for received message
+ pub_rx_msg_event = wait_for_event(p_dut,
+ aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+ peer_id_on_pub = pub_rx_msg_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+ asserts.assert_equal(
+ ping_msg,
+ pub_rx_msg_event['data'][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+ 'Subscriber -> Publisher message corrupted')
+ return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub, peer_id_on_pub
+
+ return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub
+
+def create_ib_ndp(p_dut, s_dut, p_config, s_config, device_startup_offset):
+ """Create an NDP (using in-band discovery)
+
+ Args:
+ p_dut: Device to use as publisher.
+ s_dut: Device to use as subscriber.
+ p_config: Publish configuration.
+ s_config: Subscribe configuration.
+ device_startup_offset: Number of seconds to offset the enabling of NAN on
+ the two devices.
+ """
+ (p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+ peer_id_on_pub) = create_discovery_pair(
+ p_dut, s_dut, p_config, s_config, device_startup_offset, msg_id=9999)
+
+ # Publisher: request network
+ p_req_key = request_network(p_dut,
+ p_dut.droid.wifiAwareCreateNetworkSpecifier(
+ p_disc_id, peer_id_on_pub, None))
+
+ # Subscriber: request network
+ s_req_key = request_network(s_dut,
+ s_dut.droid.wifiAwareCreateNetworkSpecifier(
+ s_disc_id, peer_id_on_sub, None))
+
+ # Publisher & Subscriber: wait for network formation
+ p_net_event = wait_for_event_with_keys(
+ p_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED), (cconsts.NETWORK_CB_KEY_ID,
+ p_req_key))
+ s_net_event = wait_for_event_with_keys(
+ s_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED), (cconsts.NETWORK_CB_KEY_ID,
+ s_req_key))
+
+ p_aware_if = p_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+ s_aware_if = s_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+ p_ipv6 = p_dut.droid.connectivityGetLinkLocalIpv6Address(p_aware_if).split(
+ "%")[0]
+ s_ipv6 = s_dut.droid.connectivityGetLinkLocalIpv6Address(s_aware_if).split(
+ "%")[0]
+
+ return p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6, s_ipv6
+
+def create_oob_ndp_on_sessions(init_dut, resp_dut, init_id, init_mac, resp_id,
+ resp_mac):
+ """Create an NDP on top of existing Aware sessions (using OOB discovery)
+
+ Args:
+ init_dut: Initiator device
+ resp_dut: Responder device
+ init_id: Initiator attach session id
+ init_mac: Initiator discovery MAC address
+ resp_id: Responder attach session id
+ resp_mac: Responder discovery MAC address
+ Returns:
+ init_req_key: Initiator network request
+ resp_req_key: Responder network request
+ init_aware_if: Initiator Aware data interface
+ resp_aware_if: Responder Aware data interface
+ init_ipv6: Initiator IPv6 address
+ resp_ipv6: Responder IPv6 address
+ """
+ # Responder: request network
+ resp_req_key = request_network(
+ resp_dut,
+ resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+ resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, None))
+
+ # Initiator: request network
+ init_req_key = request_network(
+ init_dut,
+ init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+ init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, None))
+
+ # Initiator & Responder: wait for network formation
+ init_net_event = wait_for_event_with_keys(
+ init_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED), (cconsts.NETWORK_CB_KEY_ID,
+ init_req_key))
+ resp_net_event = wait_for_event_with_keys(
+ resp_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED), (cconsts.NETWORK_CB_KEY_ID,
+ resp_req_key))
+
+ init_aware_if = init_net_event['data'][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+ resp_aware_if = resp_net_event['data'][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+ init_ipv6 = init_dut.droid.connectivityGetLinkLocalIpv6Address(
+ init_aware_if).split('%')[0]
+ resp_ipv6 = resp_dut.droid.connectivityGetLinkLocalIpv6Address(
+ resp_aware_if).split('%')[0]
+
+ return (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+ resp_ipv6)
+
+def create_oob_ndp(init_dut, resp_dut):
+ """Create an NDP (using OOB discovery)
+
+ Args:
+ init_dut: Initiator device
+ resp_dut: Responder device
+ """
+ init_dut.pretty_name = 'Initiator'
+ resp_dut.pretty_name = 'Responder'
+
+ # Initiator+Responder: attach and wait for confirmation & identity
+ init_id = init_dut.droid.wifiAwareAttach(True)
+ wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ init_ident_event = wait_for_event(init_dut,
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ init_mac = init_ident_event['data']['mac']
+ resp_id = resp_dut.droid.wifiAwareAttach(True)
+ wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ resp_ident_event = wait_for_event(resp_dut,
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ resp_mac = resp_ident_event['data']['mac']
+
+ # wait for for devices to synchronize with each other - there are no other
+ # mechanisms to make sure this happens for OOB discovery (except retrying
+ # to execute the data-path request)
+ time.sleep(WAIT_FOR_CLUSTER)
+
+ (init_req_key, resp_req_key, init_aware_if,
+ resp_aware_if, init_ipv6, resp_ipv6) = create_oob_ndp_on_sessions(
+ init_dut, resp_dut, init_id, init_mac, resp_id, resp_mac)
+
+ return (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+ resp_ipv6)
diff --git a/acts/framework/acts/utils.py b/acts/framework/acts/utils.py
index 199b0cc..4378f6e 100755
--- a/acts/framework/acts/utils.py
+++ b/acts/framework/acts/utils.py
@@ -29,6 +29,8 @@
import time
import traceback
+from acts.controllers import adb
+
# File name length is limited to 255 chars on some OS, so we need to make sure
# the file names we output fits within the limit.
MAX_FILENAME_LEN = 255
@@ -588,8 +590,7 @@
ad.adb.shell("dumpsys deviceidle force-idle")
ad.droid.goToSleepNow()
time.sleep(5)
- adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep").decode(
- 'utf-8')
+ adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep")
if not adb_shell_result.startswith(DozeModeStatus.IDLE):
info = ("dumpsys deviceidle get deep: {}".format(adb_shell_result))
print(info)
@@ -609,8 +610,7 @@
"""
ad.adb.shell("dumpsys deviceidle disable")
ad.adb.shell("dumpsys battery reset")
- adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep").decode(
- 'utf-8')
+ adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep")
if not adb_shell_result.startswith(DozeModeStatus.ACTIVE):
info = ("dumpsys deviceidle get deep: {}".format(adb_shell_result))
print(info)
@@ -633,8 +633,7 @@
time.sleep(5)
ad.adb.shell("cmd deviceidle enable light")
ad.adb.shell("cmd deviceidle step light")
- adb_shell_result = ad.adb.shell("dumpsys deviceidle get light").decode(
- 'utf-8')
+ adb_shell_result = ad.adb.shell("dumpsys deviceidle get light")
if not adb_shell_result.startswith(DozeModeStatus.IDLE):
info = ("dumpsys deviceidle get light: {}".format(adb_shell_result))
print(info)
@@ -654,8 +653,7 @@
"""
ad.adb.shell("dumpsys battery reset")
ad.adb.shell("cmd deviceidle disable light")
- adb_shell_result = ad.adb.shell("dumpsys deviceidle get light").decode(
- 'utf-8')
+ adb_shell_result = ad.adb.shell("dumpsys deviceidle get light")
if not adb_shell_result.startswith(DozeModeStatus.ACTIVE):
info = ("dumpsys deviceidle get light: {}".format(adb_shell_result))
print(info)
@@ -726,28 +724,62 @@
1 if new_state else 0))
-def bypass_setup_wizard(ad):
+def bypass_setup_wizard(ad, bypass_wait_time=3):
"""Bypass the setup wizard on an input Android device
Args:
ad: android device object.
+ bypass_wait_time: Do not set this variable. Only modified for framework
+ tests.
Returns:
True if Andorid device successfully bypassed the setup wizard.
False if failed.
"""
- ad.adb.shell(
- "am start -n \"com.google.android.setupwizard/.SetupWizardExitActivity\""
- )
+ try:
+ ad.adb.shell("am start -n \"com.google.android.setupwizard/"
+ ".SetupWizardExitActivity\"")
+ logging.debug("No error during default bypass call.")
+ except adb.AdbError as adb_error:
+ if adb_error.stdout == "ADB_CMD_OUTPUT:0":
+ if adb_error.stderr and \
+ not adb_error.stderr.startswith("Error type 3\n"):
+ logging.error(
+ "ADB_CMD_OUTPUT:0, but error is %s " % adb_error.stderr)
+ raise adb_error
+ logging.debug("Bypass wizard call received harmless error 3: "
+ "No setup to bypass.")
+ elif adb_error.stdout == "ADB_CMD_OUTPUT:255":
+ # Run it again as root.
+ ad.adb.root_adb()
+ logging.debug("Need root access to bypass setup wizard.")
+ try:
+ ad.adb.shell("am start -n \"com.google.android.setupwizard/"
+ ".SetupWizardExitActivity\"")
+ logging.debug("No error during rooted bypass call.")
+ except adb.AdbError as adb_error:
+ if adb_error.stdout == "ADB_CMD_OUTPUT:0":
+ if adb_error.stderr and \
+ not adb_error.stderr.startswith("Error type 3\n"):
+ logging.error("Rooted ADB_CMD_OUTPUT:0, but error is "
+ "%s " % adb_error.stderr)
+ raise adb_error
+ logging.debug(
+ "Rooted bypass wizard call received harmless "
+ "error 3: No setup to bypass.")
+
# magical sleep to wait for the gservices override broadcast to complete
- time.sleep(3)
+ time.sleep(bypass_wait_time)
+
provisioned_state = int(
ad.adb.shell("settings get global device_provisioned"))
- if (provisioned_state != 1):
+ if provisioned_state != 1:
logging.error("Failed to bypass setup wizard.")
return False
+ logging.debug("Setup wizard successfully bypassed.")
return True
+
def parse_ping_ouput(ad, count, out, loss_tolerance=20):
"""Ping Parsing util.
@@ -783,7 +815,10 @@
return True
-def adb_shell_ping(ad, count=120, dest_ip="www.google.com", timeout=200,
+def adb_shell_ping(ad,
+ count=120,
+ dest_ip="www.google.com",
+ timeout=200,
loss_tolerance=20):
"""Ping utility using adb shell.
diff --git a/acts/framework/setup.py b/acts/framework/setup.py
index 72fbb8b..131f5de 100755
--- a/acts/framework/setup.py
+++ b/acts/framework/setup.py
@@ -24,7 +24,8 @@
import sys
install_requires = [
- 'future',
+ # Future needs to have a newer version that contains urllib.
+ 'future>=0.16.0',
# mock-1.0.1 is the last version compatible with setuptools <17.1,
# which is what comes with Ubuntu 14.04 LTS.
'mock<=1.0.1',
@@ -76,6 +77,8 @@
pass
def run(self):
+ import pip
+ pip.main(['install', '--upgrade', 'pip'])
required_packages = self.distribution.install_requires
for package in required_packages:
diff --git a/acts/framework/tests/acts_adb_test.py b/acts/framework/tests/acts_adb_test.py
new file mode 100755
index 0000000..b56ef8b
--- /dev/null
+++ b/acts/framework/tests/acts_adb_test.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3.4
+#
+# 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 unittest
+import mock
+from acts.controllers import adb
+
+
+class MockJob(object):
+ def __init__(self, exit_status=0, stderr='', stdout=''):
+ self.exit_status = exit_status
+ self.stderr = stderr
+ self.stdout = stdout
+
+
+class MockAdbProxy(adb.AdbProxy):
+ def __init__(self):
+ pass
+
+
+class ADBTest(unittest.TestCase):
+ """A class for testing acts/controllers/adb.py"""
+
+ def test__exec_cmd_failure_old_adb(self):
+ mock_job = MockJob(exit_status=1, stderr='error: device not found')
+ cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+ with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+ with self.assertRaises(adb.AdbError):
+ MockAdbProxy()._exec_cmd(cmd)
+
+ def test__exec_cmd_failure_new_adb(self):
+ mock_job = MockJob(
+ exit_status=1, stderr='error: device \'DEADBEEF\' not found')
+ cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+ with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+ with self.assertRaises(adb.AdbError):
+ MockAdbProxy()._exec_cmd(cmd)
+
+ def test__exec_cmd_pass_ret_1(self):
+ mock_job = MockJob(exit_status=1, stderr='error not related to adb')
+ cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+ with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+ MockAdbProxy()._exec_cmd(cmd)
+
+ def test__exec_cmd_pass_basic(self):
+ mock_job = MockJob(exit_status=0, stderr='', stdout='FEEDACAB')
+ cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+ with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+ MockAdbProxy()._exec_cmd(cmd)
+
+ def test__exec_cmd_pass_no_stdout(self):
+ mock_job = MockJob(exit_status=0, stderr='', stdout='')
+ cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+ with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+ MockAdbProxy()._exec_cmd(cmd)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/acts/framework/tests/acts_base_class_test.py b/acts/framework/tests/acts_base_class_test.py
index 53849ed..75bd2e2 100755
--- a/acts/framework/tests/acts_base_class_test.py
+++ b/acts/framework/tests/acts_base_class_test.py
@@ -956,9 +956,10 @@
return "test_%s_%s" % (setting, arg)
def logic(self, setting, arg, special_arg=None):
- asserts.assert_true(setting in itrs, (
- "%s is not in acceptable settings range %s") %
- (setting, itrs))
+ asserts.assert_true(
+ setting in itrs,
+ ("%s is not in acceptable settings range %s") % (setting,
+ itrs))
asserts.assert_true(arg == static_arg,
"Expected %s, got %s" % (static_arg, arg))
asserts.assert_true(arg == static_arg, "Expected %s, got %s" %
diff --git a/acts/framework/tests/acts_relay_controller_test.py b/acts/framework/tests/acts_relay_controller_test.py
index 36b36f6..61434da 100755
--- a/acts/framework/tests/acts_relay_controller_test.py
+++ b/acts/framework/tests/acts_relay_controller_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3.4
#
# Copyright 2016 - The Android Open Source Project
#
@@ -20,19 +20,27 @@
import shutil
import unittest
-from acts.controllers.relay_lib.generic_relay_device import GenericRelayDevice
-from acts.controllers.relay_lib.relay import Relay
-from acts.controllers.relay_lib.relay import RelayDict
-from acts.controllers.relay_lib.relay import RelayState
-from acts.controllers.relay_lib.relay import SynchronizeRelays
-from acts.controllers.relay_lib.relay_board import RelayBoard
-from acts.controllers.relay_lib.relay_device import RelayDevice
-from acts.controllers.relay_lib.relay_rig import RelayRig
-from acts.controllers.relay_lib.sain_smart_board import SainSmartBoard
+from acts.controllers.relay_lib import generic_relay_device
+from acts.controllers.relay_lib import relay
+from acts.controllers.relay_lib import relay_board
+from acts.controllers.relay_lib import relay_device
+from acts.controllers.relay_lib import relay_rig
+from acts.controllers.relay_lib import sain_smart_board
import acts.controllers.relay_lib.errors as errors
import acts.controllers.relay_lib.fugu_remote as fugu_remote
+# Shorthand versions of the long class names.
+GenericRelayDevice = generic_relay_device.GenericRelayDevice
+Relay = relay.Relay
+RelayDict = relay.RelayDict
+RelayState = relay.RelayState
+SynchronizeRelays = relay.SynchronizeRelays
+RelayBoard = relay_board.RelayBoard
+RelayDevice = relay_device.RelayDevice
+RelayRig = relay_rig.RelayRig
+SainSmartBoard = sain_smart_board.SainSmartBoard
+
class MockBoard(RelayBoard):
def __init__(self, config):
@@ -356,26 +364,25 @@
def test_init_relay_rig_device_gets_relays(self):
modded_config = copy.deepcopy(self.config)
del modded_config['devices'][0]['relays']['Relay00']
- relay_rig = RelayRigMock(modded_config)
- self.assertEqual(len(relay_rig.relays), 4)
- self.assertEqual(len(relay_rig.devices['device'].relays), 1)
+ rig = RelayRigMock(modded_config)
+ self.assertEqual(len(rig.relays), 4)
+ self.assertEqual(len(rig.devices['device'].relays), 1)
- relay_rig = RelayRigMock(self.config)
- self.assertEqual(len(relay_rig.devices['device'].relays), 2)
+ rig = RelayRigMock(self.config)
+ self.assertEqual(len(rig.devices['device'].relays), 2)
def test_init_relay_rig_correct_device_type(self):
- relay_rig = RelayRigMock(self.config)
- self.assertEqual(len(relay_rig.devices), 1)
- self.assertIsInstance(relay_rig.devices['device'], GenericRelayDevice)
+ rig = RelayRigMock(self.config)
+ self.assertEqual(len(rig.devices), 1)
+ self.assertIsInstance(rig.devices['device'], GenericRelayDevice)
def test_init_relay_rig_missing_devices_creates_generic_device(self):
modded_config = copy.deepcopy(self.config)
del modded_config['devices']
- relay_rig = RelayRigMock(modded_config)
- self.assertEqual(len(relay_rig.devices), 1)
- self.assertIsInstance(relay_rig.devices['device'], GenericRelayDevice)
- self.assertDictEqual(relay_rig.devices['device'].relays,
- relay_rig.relays)
+ rig = RelayRigMock(modded_config)
+ self.assertEqual(len(rig.devices), 1)
+ self.assertIsInstance(rig.devices['device'], GenericRelayDevice)
+ self.assertDictEqual(rig.devices['device'].relays, rig.relays)
class RelayRigMock(RelayRig):
@@ -435,8 +442,8 @@
modified_config = copy.deepcopy(self.device_config)
del modified_config['relays']['r1']
- generic_relay_device = GenericRelayDevice(modified_config, self.rig)
- generic_relay_device.setup()
+ grd = GenericRelayDevice(modified_config, self.rig)
+ grd.setup()
self.assertEqual(self.r0.get_status(), RelayState.NO)
self.assertEqual(self.r1.get_status(), RelayState.NC)
@@ -445,8 +452,8 @@
self.board.set(self.r0.position, RelayState.NC)
self.board.set(self.r1.position, RelayState.NC)
- generic_relay_device = GenericRelayDevice(self.device_config, self.rig)
- generic_relay_device.setup()
+ grd = GenericRelayDevice(self.device_config, self.rig)
+ grd.setup()
self.assertEqual(self.r0.get_status(), RelayState.NO)
self.assertEqual(self.r1.get_status(), RelayState.NO)
@@ -459,8 +466,8 @@
def change_state(self, begin_state, call, end_state, previous_state=None):
self.board.set(self.r0.position, begin_state)
- generic_relay_device = GenericRelayDevice(self.device_config, self.rig)
- call(generic_relay_device)
+ grd = GenericRelayDevice(self.device_config, self.rig)
+ call(grd)
self.assertEqual(self.r0.get_status(), end_state)
if previous_state:
self.assertEqual(
@@ -622,8 +629,8 @@
'r1': 'MockBoard/1'
}
}
- relay_device = rig.create_relay_device(config)
- self.assertIsInstance(relay_device, GenericRelayDevice)
+ device = rig.create_relay_device(config)
+ self.assertIsInstance(device, GenericRelayDevice)
def test_create_relay_device_config_with_type(self):
rig = RelayRigMock()
@@ -637,8 +644,8 @@
'r1': 'MockBoard/1'
}
}
- relay_device = rig.create_relay_device(config)
- self.assertIsInstance(relay_device, GenericRelayDevice)
+ device = rig.create_relay_device(config)
+ self.assertIsInstance(device, GenericRelayDevice)
def test_create_relay_device_raise_on_type_not_found(self):
rig = RelayRigMock()
diff --git a/acts/framework/tests/acts_utils_test.py b/acts/framework/tests/acts_utils_test.py
index 7ee2c9d..fd7b083 100755
--- a/acts/framework/tests/acts_utils_test.py
+++ b/acts/framework/tests/acts_utils_test.py
@@ -16,8 +16,10 @@
import time
import unittest
+import enum
from acts import utils
+from acts.controllers.adb import AdbError
class ActsUtilsTest(unittest.TestCase):
@@ -37,6 +39,97 @@
"Process .* has terminated"):
utils.stop_standing_subprocess(p)
+ def test_bypass_setup_wizard_no_complications(self):
+ ad = MockAd()
+ ad.adb.return_state = BypassSetupWizardReturn.NO_COMPLICATIONS
+ self.assertTrue(utils.bypass_setup_wizard(ad, 0))
+ self.assertFalse(ad.adb.root_adb_called)
+
+ def test_bypass_setup_wizard_unrecognized_error(self):
+ ad = MockAd()
+ ad.adb.return_state = BypassSetupWizardReturn.UNRECOGNIZED_ERR
+ with self.assertRaises(AdbError):
+ utils.bypass_setup_wizard(ad, 0)
+ self.assertFalse(ad.adb.root_adb_called)
+
+ def test_bypass_setup_wizard_need_root_access(self):
+ ad = MockAd()
+ ad.adb.return_state = BypassSetupWizardReturn.ROOT_ADB_NO_COMP
+ self.assertTrue(utils.bypass_setup_wizard(ad, 0))
+ self.assertTrue(ad.adb.root_adb_called)
+
+ def test_bypass_setup_wizard_need_root_already_skipped(self):
+ ad = MockAd()
+ ad.adb.return_state = BypassSetupWizardReturn.ROOT_ADB_SKIPPED
+ self.assertTrue(utils.bypass_setup_wizard(ad, 0))
+ self.assertTrue(ad.adb.root_adb_called)
+
+ def test_bypass_setup_wizard_root_access_still_fails(self):
+ ad = MockAd()
+ ad.adb.return_state = BypassSetupWizardReturn.ROOT_ADB_FAILS
+ with self.assertRaises(AdbError):
+ utils.bypass_setup_wizard(ad, 0)
+ self.assertTrue(ad.adb.root_adb_called)
+
+
+class BypassSetupWizardReturn:
+ # No complications. Bypass works the first time without issues.
+ NO_COMPLICATIONS = AdbError("", "", "", 1)
+ # Fail with doesn't need to be skipped/was skipped already.
+ ALREADY_BYPASSED = AdbError("", "ADB_CMD_OUTPUT:0", "Error type 3\n"
+ "Error: Activity class", 1)
+ # Fail with different error.
+ UNRECOGNIZED_ERR = AdbError("", "ADB_CMD_OUTPUT:0", "Error type 4\n"
+ "Error: Activity class", 0)
+ # Fail, get root access, then no complications arise.
+ ROOT_ADB_NO_COMP = AdbError("", "ADB_CMD_OUTPUT:255",
+ "Security exception: Permission Denial: "
+ "starting Intent { flg=0x10000000 "
+ "cmp=com.google.android.setupwizard/"
+ ".SetupWizardExitActivity } from null "
+ "(pid=5045, uid=2000) not exported from uid "
+ "10000", 0)
+ # Even with root access, the bypass setup wizard doesn't need to be skipped.
+ ROOT_ADB_SKIPPED = AdbError("", "ADB_CMD_OUTPUT:255",
+ "Security exception: Permission Denial: "
+ "starting Intent { flg=0x10000000 "
+ "cmp=com.google.android.setupwizard/"
+ ".SetupWizardExitActivity } from null "
+ "(pid=5045, uid=2000) not exported from "
+ "uid 10000", 0)
+ # Even with root access, the bypass setup wizard fails
+ ROOT_ADB_FAILS = AdbError(
+ "", "ADB_CMD_OUTPUT:255", "Security exception: Permission Denial: "
+ "starting Intent { flg=0x10000000 "
+ "cmp=com.google.android.setupwizard/"
+ ".SetupWizardExitActivity } from null (pid=5045, "
+ "uid=2000) not exported from uid 10000", 0)
+
+
+class MockAd:
+ def __init__(self):
+ self.adb = MockAdb()
+
+
+class MockAdb:
+ def __init__(self):
+ self.return_state = BypassSetupWizardReturn.NO_COMPLICATIONS
+ self.root_adb_called = False
+
+ def shell(self, string):
+ if string == "settings get global device_provisioned":
+ return self.return_state.ret_code
+ raise self.return_state
+
+ def root_adb(self):
+ self.root_adb_called = True
+ if self.return_state is BypassSetupWizardReturn.ROOT_ADB_FAILS:
+ self.return_state = BypassSetupWizardReturn.UNRECOGNIZED_ERR
+ elif self.return_state is not BypassSetupWizardReturn.ROOT_ADB_SKIPPED:
+ self.return_state = BypassSetupWizardReturn.ALREADY_BYPASSED
+ else:
+ self.return_state = BypassSetupWizardReturn.NO_COMPLICATIONS
+
if __name__ == "__main__":
unittest.main()
diff --git a/acts/tests/google/ble/gatt/GattConnectTest.py b/acts/tests/google/ble/gatt/GattConnectTest.py
index 9834ea9..d155232 100644
--- a/acts/tests/google/ble/gatt/GattConnectTest.py
+++ b/acts/tests/google/ble/gatt/GattConnectTest.py
@@ -25,7 +25,6 @@
from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
from acts.test_utils.bt.BtEnum import BluetoothProfile
from acts.test_utils.bt.GattEnum import GattCharacteristic
-from acts.test_utils.bt.GattEnum import GattDescriptor
from acts.test_utils.bt.GattEnum import GattService
from acts.test_utils.bt.GattEnum import MtuSize
from acts.test_utils.bt.GattEnum import GattCbErr
@@ -34,13 +33,14 @@
from acts.test_utils.bt.GattEnum import GattTransport
from acts.test_utils.bt.bt_gatt_utils import GattTestUtilsError
from acts.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts.test_utils.bt.bt_gatt_utils import wait_for_gatt_disconnect_event
+from acts.test_utils.bt.bt_gatt_utils import close_gatt_client
from acts.test_utils.bt.bt_gatt_utils import log_gatt_server_uuids
from acts.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_characteristics
from acts.test_utils.bt.bt_gatt_utils import setup_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_descriptors
from acts.test_utils.bt.bt_gatt_utils import setup_multiple_services
from acts.test_utils.bt.bt_test_utils import get_mac_address_of_generic_advertisement
+from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
class GattConnectTest(BluetoothBaseTest):
@@ -75,6 +75,7 @@
try:
disconnect_gatt_connection(self.cen_ad, bluetooth_gatt,
gatt_callback)
+ close_gatt_client(self.cen_ad, bluetooth_gatt)
if bluetooth_gatt in self.bluetooth_gatt_list:
self.bluetooth_gatt_list.remove(bluetooth_gatt)
except GattTestUtilsError as err:
@@ -224,7 +225,7 @@
try:
self._orchestrate_gatt_disconnection(bluetooth_gatt, gatt_callback)
except Exception as err:
- self.log.info("Failed to orchestrate disconnect: {}".format(e))
+ self.log.info("Failed to orchestrate disconnect: {}".format(err))
return False
return True
@@ -233,7 +234,7 @@
def test_gatt_connect_autoconnect(self):
"""Test GATT connection over LE.
- Test re-establishing a gat connection using autoconnect
+ Test re-establishing a gatt connection using autoconnect
set to True in order to test connection whitelist.
Steps:
@@ -274,6 +275,7 @@
try:
disconnect_gatt_connection(self.cen_ad, bluetooth_gatt,
gatt_callback)
+ close_gatt_client(self.cen_ad, bluetooth_gatt)
if bluetooth_gatt in self.bluetooth_gatt_list:
self.bluetooth_gatt_list.remove(bluetooth_gatt)
except GattTestUtilsError as err:
@@ -282,7 +284,7 @@
autoconnect = True
bluetooth_gatt = self.cen_ad.droid.gattClientConnectGatt(
gatt_callback, mac_address, autoconnect,
- GattTransport.TRANSPORT_AUTO.value,
+ GattTransport.TRANSPORT_AUTO.value, False,
GattPhyMask.PHY_LE_1M_MASK)
self.bluetooth_gatt_list.append(bluetooth_gatt)
expected_event = GattCbStrings.GATT_CONN_CHANGE.value.format(
@@ -294,6 +296,83 @@
self.log.error(
GattCbErr.GATT_CONN_CHANGE_ERR.value.format(expected_event))
test_result = False
+ return self._orchestrate_gatt_disconnection(bluetooth_gatt,
+ gatt_callback)
+
+ @BluetoothBaseTest.bt_test_wrap
+ @test_tracker_info(uuid='e506fa50-7cd9-4bd8-938a-6b85dcfe6bc6')
+ def test_gatt_connect_opportunistic(self):
+ """Test opportunistic GATT connection over LE.
+
+ Test establishing a gatt connection between a GATT server and GATT
+ client in opportunistic mode.
+
+ Steps:
+ 1. Start a generic advertisement.
+ 2. Start a generic scanner.
+ 3. Find the advertisement and extract the mac address.
+ 4. Stop the first scanner.
+ 5. Create GATT connection 1 between the scanner and advertiser normally
+ 6. Create GATT connection 2 between the scanner and advertiser using
+ opportunistic mode
+ 7. Disconnect GATT connection 1
+
+ Expected Result:
+ Verify GATT connection 2 automatically disconnects when GATT connection
+ 1 disconnect
+
+ Returns:
+ Pass if True
+ Fail if False
+
+ TAGS: LE, Advertising, Filtering, Scanning, GATT
+ Priority: 0
+ """
+ gatt_server_cb = self.per_ad.droid.gattServerCreateGattServerCallback()
+ gatt_server = self.per_ad.droid.gattServerOpenGattServer(
+ gatt_server_cb)
+ self.gatt_server_list.append(gatt_server)
+ mac_address, adv_callback = (
+ get_mac_address_of_generic_advertisement(self.cen_ad, self.per_ad))
+ # Make GATT connection 1
+ try:
+ bluetooth_gatt_1, gatt_callback_1 = setup_gatt_connection(
+ self.cen_ad,
+ mac_address,
+ False,
+ transport=GattTransport.TRANSPORT_AUTO.value,
+ opportunistic=False)
+ self.bluetooth_gatt_list.append(bluetooth_gatt_1)
+ except GattTestUtilsError as err:
+ self.log.error(err)
+ return False
+ # Make GATT connection 2
+ try:
+ bluetooth_gatt_2, gatt_callback_2 = setup_gatt_connection(
+ self.cen_ad,
+ mac_address,
+ False,
+ transport=GattTransport.TRANSPORT_AUTO.value,
+ opportunistic=True)
+ self.bluetooth_gatt_list.append(bluetooth_gatt_2)
+ except GattTestUtilsError as err:
+ self.log.error(err)
+ return False
+ # Disconnect GATT connection 1
+ try:
+ disconnect_gatt_connection(self.cen_ad, bluetooth_gatt_1,
+ gatt_callback_1)
+ close_gatt_client(self.cen_ad, bluetooth_gatt_1)
+ if bluetooth_gatt_1 in self.bluetooth_gatt_list:
+ self.bluetooth_gatt_list.remove(bluetooth_gatt_1)
+ except GattTestUtilsError as err:
+ self.log.error(err)
+ return False
+ # Confirm that GATT connection 2 also disconnects
+ wait_for_gatt_disconnect_event(self.cen_ad, gatt_callback_2)
+ close_gatt_client(self.cen_ad, bluetooth_gatt_2)
+ if bluetooth_gatt_2 in self.bluetooth_gatt_list:
+ self.bluetooth_gatt_list.remove(bluetooth_gatt_2)
return True
@BluetoothBaseTest.bt_test_wrap
@@ -716,7 +795,6 @@
return False
test_result = self._orchestrate_gatt_disconnection(bluetooth_gatt,
gatt_callback)
- self.cen_ad.droid.gattClientClose(bluetooth_gatt)
if not test_result:
self.log.info("Failed to disconnect from peripheral device.")
return False
@@ -820,13 +898,33 @@
start_time = time.time() + self.default_timeout
target_name = self.per_ad.droid.bluetoothGetLocalName()
while time.time() < start_time and bonded == False:
- bonded_devices = self.cen_ad.droid.bluetoothGetBondedDevices(
- )
+ bonded_devices = \
+ self.cen_ad.droid.bluetoothGetBondedDevices()
for device in bonded_devices:
if ('name' in device.keys() and
device['name'] == target_name):
bonded = True
break
+ bonded = False
+ target_name = self.cen_ad.droid.bluetoothGetLocalName()
+ while time.time() < start_time and bonded == False:
+ bonded_devices = \
+ self.per_ad.droid.bluetoothGetBondedDevices()
+ for device in bonded_devices:
+ if ('name' in device.keys() and
+ device['name'] == target_name):
+ bonded = True
+ break
+ for ad in [self.cen_ad, self.per_ad]:
+ if not clear_bonded_devices(ad):
+ return False
+ # Necessary sleep time for entries to update unbonded state
+ time.sleep(2)
+ bonded_devices = ad.droid.bluetoothGetBondedDevices()
+ if len(bonded_devices) > 0:
+ self.log.error(
+ "Failed to unbond devices: {}".format(bonded_devices))
+ return False
return self._orchestrate_gatt_disconnection(bluetooth_gatt,
gatt_callback)
diff --git a/acts/tests/google/bt/pts/BtCmdLineTest.py b/acts/tests/google/bt/pts/BtCmdLineTest.py
index 842172d..cb97233 100644
--- a/acts/tests/google/bt/pts/BtCmdLineTest.py
+++ b/acts/tests/google/bt/pts/BtCmdLineTest.py
@@ -37,8 +37,7 @@
self.log.error(
"Missing mandatory user config \"target_mac_address\"!")
return False
- self.target_mac_address = self.user_params["target_mac_address"].upper(
- )
+ self.target_mac_address = self.user_params["target_mac_address"].upper()
self.android_devices[0].droid.bluetoothSetLocalName("CMD LINE Test")
if len(self.android_devices) > 1:
@@ -75,8 +74,8 @@
for filename in filenames:
file = os.path.join(dirname, filename)
#TODO: Handle file paths with spaces
- self.android_devices[0].adb.push("{} {}".format(
- file, android_music_path))
+ self.android_devices[0].adb.push(
+ "{} {}".format(file, android_music_path))
def setup_class(self):
return True
diff --git a/acts/tests/google/bt/pts/cmd_input.py b/acts/tests/google/bt/pts/cmd_input.py
index e1579b2..e97b1a4 100644
--- a/acts/tests/google/bt/pts/cmd_input.py
+++ b/acts/tests/google/bt/pts/cmd_input.py
@@ -16,7 +16,6 @@
"""
Python script for wrappers to various libraries.
"""
-
from acts.test_utils.bt.BtEnum import BluetoothScanModeType
from acts.test_utils.bt.GattEnum import GattServerResponses
from ble_lib import BleLib
@@ -26,6 +25,7 @@
from gatts_lib import GattServerLib
from rfcomm_lib import RfcommLib
+import time
import cmd
import gatt_test_database
"""Various Global Strings"""
@@ -37,6 +37,30 @@
"""Simple command processor for Bluetooth PTS Testing"""
gattc_lib = None
+ def connect_hsp_helper(self, ad):
+ """A helper function for making HSP connections"""
+ end_time = time.time() + 20
+ connected_hsp_devices = len(ad.droid.bluetoothHspGetConnectedDevices())
+ while connected_hsp_devices != 1 and time.time() < end_time:
+ try:
+ ad.droid.bluetoothHspConnect(self.mac_addr)
+ time.sleep(3)
+ if len(ad.droid.bluetoothHspGetConnectedDevices() == 1):
+ break
+ except Exception:
+ self.log.debug("Failed to connect hsp trying again...")
+ try:
+ ad.droid.bluetoothConnectBonded(self.mac_addr)
+ except Exception:
+ self.log.info("Failed to connect to bonded device...")
+ connected_hsp_devices = len(
+ ad.droid.bluetoothHspGetConnectedDevices())
+ if connected_hsp_devices != 1:
+ self.log.error("Failed to reconnect to HSP service...")
+ return False
+ self.log.info("Connected to HSP service...")
+ return True
+
def setup_vars(self, android_devices, mac_addr, log):
self.pri_dut = android_devices[0]
if len(android_devices) > 1:
diff --git a/acts/tests/google/native/bt/BtNativeTest.py b/acts/tests/google/native/bt/BtNativeTest.py
index 9660a8e..5315e34 100644
--- a/acts/tests/google/native/bt/BtNativeTest.py
+++ b/acts/tests/google/native/bt/BtNativeTest.py
@@ -1,20 +1,4 @@
-#/usr/bin/env python3.4
-#
-# Copyright (C) 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.
-
-import time
+mport time
from acts.base_test import BaseTestClass
from acts.controllers import native_android_device
from acts.test_utils.bt.native_bt_test_utils import setup_native_bluetooth
@@ -26,14 +10,15 @@
def __init__(self, controllers):
BaseTestClass.__init__(self, controllers)
- setup_native_bluetooth(self.native_devices)
- self.droid = self.native_devices[0].droid
- self.tests = ("test_binder_get_name",
- "test_binder_get_name_invalid_parameter",
- "test_binder_set_name_get_name",
- "test_binder_get_address", )
- if len(self.native_devices) > 1:
- self.droid1 = self.native_devices[1].droid
+ setup_native_bluetooth(self.native_android_devices)
+ self.droid = self.native_android_devices[0].droid
+ self.tests = (
+ "test_binder_get_name",
+ "test_binder_get_name_invalid_parameter",
+ "test_binder_set_name_get_name",
+ "test_binder_get_address", )
+ if len(self.native_android_devices) > 1:
+ self.droid1 = self.native_android_devices[1].droid
self.tests = self.tests + ("test_two_devices_set_get_name", )
def test_binder_get_name(self):
@@ -67,10 +52,11 @@
def test_two_devices_set_get_name(self):
test_name = generate_id_by_size(4)
- for n in self.native_devices:
+ for n in self.native_android_devices:
d = n.droid
d.BtBinderSetName(test_name)
name = d.BtBinderGetName()
if name != test_name:
return False
return True
+
diff --git a/acts/tests/google/net/CoreNetworkingTest.py b/acts/tests/google/net/CoreNetworkingTest.py
new file mode 100644
index 0000000..262ac81
--- /dev/null
+++ b/acts/tests/google/net/CoreNetworkingTest.py
@@ -0,0 +1,126 @@
+#
+# 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 logging
+import time
+import socket
+
+from acts import asserts
+from acts import base_test
+from acts import test_runner
+from acts import utils
+from acts.controllers import adb
+from acts.test_utils.tel import tel_data_utils
+from acts.test_utils.tel import tel_test_utils
+from acts.test_utils.tel import tel_defines
+from acts.test_utils.wifi import wifi_test_utils
+
+dum_class = "com.android.uid.DummyActivity"
+
+
+class CoreNetworkingTest(base_test.BaseTestClass):
+ """ Tests for UID networking """
+
+ def setup_class(self):
+ """ Setup devices for tests and unpack params """
+ self.dut = self.android_devices[0]
+ wifi_test_utils.wifi_toggle_state(self.dut, False)
+ self.dut.droid.telephonyToggleDataConnection(True)
+ tel_data_utils.wait_for_cell_data_connection(self.log, self.dut, True)
+ asserts.assert_true(
+ tel_test_utils.verify_http_connection(self.log, self.dut),
+ "HTTP verification failed on cell data connection")
+
+ def teardown_class(self):
+ """ Reset devices """
+ wifi_test_utils.wifi_toggle_state(self.dut, True)
+
+ """ Test Cases """
+
+ def test_uid_derace_doze_mode(self):
+ """ Verify UID de-race doze mode
+
+ Steps:
+ 1. Connect to DUT to data network and verify internet
+ 2. Enable doze mode
+ 3. Launch app and verify internet connectiviy
+ 4. Disable doze mode
+ """
+ # Enable doze mode
+ self.log.info("Enable Doze mode")
+ asserts.assert_true(utils.enable_doze(self.dut),
+ "Could not enable doze mode")
+
+ # Launch app, check internet connectivity and close app
+ res = self.dut.droid.launchForResult(dum_class)
+ self.log.info("Internet connectivity status after app launch: %s "
+ % res['extras']['result'])
+
+ # Disable doze mode
+ self.log.info("Disable Doze mode")
+ asserts.assert_true(utils.disable_doze(self.dut),
+ "Could not disable doze mode")
+
+ return res['extras']['result']
+
+ def test_uid_derace_doze_light_mode(self):
+ """ Verify UID de-race doze light mode
+
+ Steps:
+ 1. Connect DUT to data network and verify internet
+ 2. Enable doze light mode
+ 3. Launch app and verify internet connectivity
+ 4. Disable doze light mode
+ """
+ # Enable doze light mode
+ self.log.info("Enable doze light mode")
+ asserts.assert_true(utils.enable_doze_light(self.dut),
+ "Could not enable doze light mode")
+
+ # Launch app, check internet connectivity and close app
+ res = self.dut.droid.launchForResult(dum_class)
+ self.log.info("Internet connectivity status after app launch: %s "
+ % res['extras']['result'])
+
+ # Disable doze light mode
+ self.log.info("Disable doze light mode")
+ asserts.assert_true(utils.disable_doze_light(self.dut),
+ "Could not disable doze light mode")
+
+ return res['extras']['result']
+
+ def test_uid_derace_data_saver_mode(self):
+ """ Verify UID de-race data saver mode
+
+ Steps:
+ 1. Connect DUT to data network and verify internet
+ 2. Enable data saver mode
+ 3. Launch app and verify internet connectivity
+ 4. Disable data saver mode
+ """
+ # Enable data saver mode
+ self.log.info("Enable data saver mode")
+ self.dut.adb.shell("cmd netpolicy set restrict-background true")
+
+ # Launch app, check internet connectivity and close app
+ res = self.dut.droid.launchForResult(dum_class)
+ self.log.info("Internet connectivity status after app launch: %s "
+ % res['extras']['result'])
+
+ # Disable data saver mode
+ self.log.info("Disable data saver mode")
+ self.dut.adb.shell("cmd netpolicy set restrict-background false")
+
+ return res['extras']['result']
diff --git a/acts/tests/google/tel/live/TelLivePreflightTest.py b/acts/tests/google/tel/live/TelLivePreflightTest.py
index 2569726..d10a8c2 100644
--- a/acts/tests/google/tel/live/TelLivePreflightTest.py
+++ b/acts/tests/google/tel/live/TelLivePreflightTest.py
@@ -19,6 +19,8 @@
import time
from queue import Empty
+
+from acts.test_decorators import test_tracker_info
from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
from acts.test_utils.tel.tel_defines import AOSP_PREFIX
from acts.test_utils.tel.tel_defines import CAPABILITY_PHONE
@@ -58,14 +60,13 @@
TelephonyBaseTest.__init__(self, controllers)
self.wifi_network_ssid = self.user_params.get(
- "wifi_network_ssid") or self.user_params.get(
- "wifi_network_ssid_2g")
+ "wifi_network_ssid") or self.user_params.get("wifi_network_ssid_2g")
self.wifi_network_pass = self.user_params.get(
- "wifi_network_pass") or self.user_params.get(
- "wifi_network_pass_2g")
+ "wifi_network_pass") or self.user_params.get("wifi_network_pass_2g")
""" Tests Begin """
+ @test_tracker_info(uuid="8390a2eb-a744-4cda-bade-f94a2cc83f02")
@TelephonyBaseTest.tel_test_wrap
def test_check_environment(self):
ad = self.android_devices[0]
@@ -85,6 +86,7 @@
# TODO: add more environment check here.
return True
+ @test_tracker_info(uuid="7bb23ac7-6b7b-4d5e-b8d6-9dd10032b9ad")
@TelephonyBaseTest.tel_test_wrap
def test_pre_flight_check(self):
for ad in self.android_devices:
@@ -93,6 +95,7 @@
abort_all_tests(ad.log, "Unable to find a valid subscription!")
return True
+ @test_tracker_info(uuid="1070b160-902b-43bf-92a0-92cc2d05bb13")
@TelephonyBaseTest.tel_test_wrap
def test_check_crash(self):
for ad in self.android_devices:
diff --git a/acts/tests/google/wifi/SetupWifiNetworkTest.py b/acts/tests/google/wifi/SetupWifiNetworkTest.py
new file mode 100644
index 0000000..89f794b
--- /dev/null
+++ b/acts/tests/google/wifi/SetupWifiNetworkTest.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3.4
+#
+# 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 logging
+import socket
+import sys
+
+from acts import base_test
+from acts.controllers.ap_lib import hostapd_ap_preset
+from acts.controllers.ap_lib import hostapd_bss_settings
+from acts.controllers.ap_lib import hostapd_constants
+from acts.controllers.ap_lib import hostapd_config
+from acts.controllers.ap_lib import hostapd_security
+
+class SetupWifiNetworkTest(base_test.BaseTestClass):
+
+ def wait_for_test_completion(self):
+ port = int(self.user_params["socket_port"])
+ timeout = float(self.user_params["socket_timeout_secs"])
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+ server_address = ('localhost', port)
+ logging.info("Starting server socket on localhost port %s", port)
+ sock.bind(('localhost', port))
+ sock.settimeout(timeout)
+ sock.listen(1)
+ logging.info("Waiting for client socket connection")
+ try :
+ connection, client_address = sock.accept()
+ except socket.timeout:
+ logging.error("Did not receive signal. Shutting down AP")
+ except socket.error:
+ logging.error("Socket connection errored out. Shutting down AP")
+ finally:
+ connection.close()
+ sock.shutdown(socket.SHUT_RDWR)
+ sock.close()
+
+ def test_set_up_single_ap(self):
+ req_params = ["AccessPoint", "network_type", "ssid", "passphrase",
+ "security", "socket_port", "socket_timeout_secs"]
+ opt_params = []
+ self.unpack_userparams(req_param_names=req_params,
+ opt_param_names=opt_params)
+ bss_settings = []
+
+ self.access_point = self.access_points[0]
+ network_type = self.user_params["network_type"]
+ if (network_type == "2G"):
+ self.channel = hostapd_constants.AP_DEFAULT_CHANNEL_2G
+ else:
+ self.channel = hostapd_constants.AP_DEFAULT_CHANNEL_5G
+
+ self.ssid = self.user_params["ssid"]
+ self.passphrase = self.user_params["passphrase"]
+ self.security = self.user_params["security"]
+ self.hostapd_security = hostapd_security.Security(
+ security_mode=self.security,
+ password=self.passphrase)
+ bss_settings.append(hostapd_bss_settings.BssSettings(
+ name=self.ssid,
+ ssid=self.ssid,
+ security=self.hostapd_security))
+
+ self.config = hostapd_ap_preset.create_ap_preset(
+ channel=self.channel,
+ ssid=self.ssid,
+ security=self.hostapd_security,
+ bss_settings= bss_settings,
+ profile_name='whirlwind')
+ self.access_point.start_ap(self.config)
+ # AP enviroment created. Wait for client to teardown the environment
+ self.wait_for_test_completion()
diff --git a/acts/tests/google/wifi/WifiAwareManagerTest.py b/acts/tests/google/wifi/WifiAwareManagerTest.py
deleted file mode 100644
index a3d4874..0000000
--- a/acts/tests/google/wifi/WifiAwareManagerTest.py
+++ /dev/null
@@ -1,1180 +0,0 @@
-#!/usr/bin/python3.4
-#
-# 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.
-
-import json
-import pprint
-import re
-import queue
-import statistics
-import time
-
-from acts import asserts
-from acts import base_test
-from acts.signals import generated_test
-from acts.test_utils.net import connectivity_const as con_const
-from acts.test_utils.net import nsd_const as nsd_const
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi import wifi_aware_const as aware_const
-
-# arbitrary timeout for events
-EVENT_TIMEOUT = 30
-
-
-class WifiAwareManagerTest(base_test.BaseTestClass):
- # configuration parameters used by tests
- publish_config = {"ServiceName": "GoogleTestServiceX",
- "ServiceSpecificInfo": "Data XYZ",
- "MatchFilter": {"int0": 14,
- "data0": "MESSAGE_ALL"},
- "PublishType": 0,
- "TtlSec": 0}
- subscribe_config = {"ServiceName": "GoogleTestServiceX",
- "ServiceSpecificInfo": "Data ABC",
- "MatchFilter": {"int0": 14,
- "data0": "MESSAGE_ALL"},
- "SubscribeType": 0,
- "TtlSec": 0}
- rtt_24_20 = {"deviceType": 5,
- "requestType": 2,
- "frequency": 2437,
- "channelWidth": 0,
- "centerFreq0": 2437,
- "centerFreq1": 0,
- "numberBurst": 0,
- "numSamplesPerBurst": 5,
- "numRetriesPerMeasurementFrame": 3,
- "numRetriesPerFTMR": 3,
- "LCIRequest": False,
- "LCRRequest": False,
- "burstTimeout": 15,
- "preamble": 2,
- "bandwidth": 4}
- rtt_50_40 = {"deviceType": 5,
- "requestType": 2,
- "frequency": 5200,
- "channelWidth": 1,
- "centerFreq0": 5190,
- "centerFreq1": 0,
- "numberBurst": 0,
- "numSamplesPerBurst": 5,
- "numRetriesPerMeasurementFrame": 3,
- "numRetriesPerFTMR": 3,
- "LCIRequest": False,
- "LCRRequest": False,
- "burstTimeout": 15,
- "preamble": 2,
- "bandwidth": 8}
- rtt_50_80 = {"deviceType": 5,
- "requestType": 2,
- "frequency": 5200,
- "channelWidth": 2,
- "centerFreq0": 5210,
- "centerFreq1": 0,
- "numberBurst": 0,
- "numSamplesPerBurst": 5,
- "numRetriesPerMeasurementFrame": 3,
- "numRetriesPerFTMR": 3,
- "LCIRequest": False,
- "LCRRequest": False,
- "burstTimeout": 15,
- "preamble": 4,
- "bandwidth": 16}
- network_req = {"TransportType": 5}
- nsd_service_info = {"serviceInfoServiceName": "sl4aTestAwareIperf",
- "serviceInfoServiceType": "_simple-tx-rx._tcp.",
- "serviceInfoPort": 2257}
-
- def setup_test(self):
- self.msg_id = 10
- for ad in self.android_devices:
- wutils.wifi_toggle_state(ad, True)
- aware_usage_enabled = ad.droid.wifiIsAwareAvailable()
- if not aware_usage_enabled:
- self.log.info('Aware not enabled. Waiting for %s',
- aware_const.BROADCAST_WIFI_AWARE_AVAILABLE)
- try:
- ad.ed.pop_event(aware_const.BROADCAST_WIFI_AWARE_AVAILABLE,
- EVENT_TIMEOUT)
- self.log.info(aware_const.BROADCAST_WIFI_AWARE_AVAILABLE)
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s' %
- aware_const.BROADCAST_WIFI_AWARE_AVAILABLE)
-
- def teardown_test(self):
- for ad in self.android_devices:
- ad.droid.wifiAwareDestroyAll()
- # asserts.assert_true(
- # wutils.wifi_toggle_state(ad, False),
- # "Failed disabling Wi-Fi interface")
-
- def extract_stats(self, data, results, key_prefix, log_prefix):
- """Extract statistics from the data, store in the dict dictionary, and
- output to the info log.
-
- Args:
- data: A list containing the data to be analyzed.
- results: A dictionary into which to place the statistics.
- key_prefix: A string prefix to use for the dict keys storing the
- extracted stats.
- log_prefix: A string prefix to use for the info log.
- include_data: If True includes the raw data in the dictionary,
- otherwise just the stats.
- """
- num_samples = len(data)
- results['%s_num_samples' % key_prefix] = num_samples
-
- if not data:
- return
-
- data_min = min(data)
- data_max = max(data)
- data_mean = statistics.mean(data)
-
- results['%s_min' % key_prefix] = data_min
- results['%s_max' % key_prefix] = data_max
- results['%s_mean' % key_prefix] = data_mean
- results['%s_raw_data' % key_prefix] = data
-
- if num_samples > 1:
- data_stdev = statistics.stdev(data)
- results['%s_stdev' % key_prefix] = data_stdev
- self.log.info(
- '%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f, stdev=%.2f',
- log_prefix, num_samples, data_min, data_max, data_mean,
- data_stdev)
- else:
- self.log.info('%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f',
- log_prefix, num_samples, data_min, data_max,
- data_mean)
-
- def get_interface_mac(self, device, interface):
- """Get the HW MAC address of the specified interface.
-
- Returns the HW MAC address or raises an exception on failure.
-
- Args:
- device: The 'AndroidDevice' on which to query the interface.
- interface: The name of the interface to query.
-
- Returns:
- mac: MAC address of the interface.
- """
- out = device.adb.shell("ifconfig %s" % interface)
- completed = out.decode('utf-8').strip()
- res = re.match(".* HWaddr (\S+).*", completed, re.S)
- asserts.assert_true(
- res, 'Unable to obtain MAC address for interface %s' % interface)
- return res.group(1)
-
- def get_interface_ipv6_link_local(self, device, interface):
- """Get the link-local IPv6 address of the specified interface.
-
- Returns the link-local IPv6 address of the interface or raises an
- exception on failure.
-
- Args:
- device: The 'AndroidDevice' on which to query the interface.
- interface: The name of the interface to query.
-
- Returns:
- addr: link-local IPv6 address of the interface.
- """
- out = device.adb.shell("ifconfig %s" % interface)
- completed = out.decode('utf-8').strip()
- res = re.match(".*inet6 addr: (\S+)/64 Scope: Link.*", completed, re.S)
- asserts.assert_true(
- res,
- 'Unable to obtain IPv6 link-local for interface %s' % interface)
- return res.group(1)
-
- def exec_connect(self, device, name, config=None):
- """Executes the Aware connection creation operation.
-
- Creates an Aware connection (client) and waits for a confirmation event
- of success. Raise test failure signal if no such event received.
-
- Args:
- device: The 'AndroidDevice' on which to set up the connection.
- name: An arbitary name used for logging.
- config: An optional ConfigRequest (JSON-configured) structure.
- """
- session_id = device.droid.wifiAwareAttach(config)
- try:
- event = device.ed.pop_event(aware_const.EVENT_CB_ON_ATTACHED,
- EVENT_TIMEOUT)
- self.log.info('%s: %s', aware_const.EVENT_CB_ON_ATTACHED,
- event['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on %s' %
- (aware_const.EVENT_CB_ON_ATTACHED, name))
- self.log.debug(event)
- return session_id
-
- def reliable_tx(self, device, session_id, peer, msg):
- """Sends an Aware message.
-
- Sends a message to the peer and waits for success confirmation. Raises
- an exception on failure or timeout.
-
- The message is sent using the MAX retransmission count.
-
- Args:
- device: The 'AndroidDevice' on which to send the message.
- session_id: The session ID context from which to send the message.
- This is the value returned by wifiAwarePublish() or
- wifiAwareSubscribe().
- peer: The peer ID to send the message to. Obtained through a match
- or a received message.
- msg: The message to be transmitted to the peer.
- """
- events_regex = '%s|%s' % (aware_const.SESSION_CB_ON_MESSAGE_SEND_FAILED,
- aware_const.SESSION_CB_ON_MESSAGE_SENT)
- self.msg_id = self.msg_id + 1
-
- while True:
- try:
- device.droid.wifiAwareSendMessage(session_id, peer, self.msg_id,
- msg,
- aware_const.MAX_TX_RETRIES)
- events = device.ed.pop_events(events_regex, EVENT_TIMEOUT)
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s', events_regex)
-
- for event in events:
- self.log.info('%s: %s', event['name'], event['data'])
- if event['data']['messageId'] == self.msg_id:
- asserts.assert_equal(event['name'],
- aware_const.SESSION_CB_ON_MESSAGE_SENT,
- 'Failed (re)transmission')
- return
-
- def exec_rtt(self, device, session_id, peer_id, rtt_param, label,
- repeat_count):
- """Executes an RTT operation.
-
- Args:
- device: The 'AndroidDevice' on which to send the message.
- session_id: The session ID context from which to send the message.
- This is the value returned by wifiAwarePublish() or
- wifiAwareSubscribe().
- peer_id: The peer ID to send the message to. Obtained through a
- match or a received message.
- rtt_param: RTT session parameters.
- msg: Message/tag describing RTT experiment.
- repeat_count: Number of RTT measurements to execute.
- """
- rtt_param['bssid'] = peer_id
- rtt_stats = {
- 'failure_codes': {},
- 'distance': {
- 'sum': 0,
- 'num_samples': 0
- }
- }
- for i in range(repeat_count):
- device.droid.wifiAwareStartRanging(0, session_id, [rtt_param])
-
- events_regex = '%s|%s|%s' % (aware_const.RTT_LISTENER_CB_ON_SUCCESS,
- aware_const.RTT_LISTENER_CB_ON_FAILURE,
- aware_const.RTT_LISTENER_CB_ON_ABORT)
- try:
- events_pub_range = device.ed.pop_events(events_regex,
- EVENT_TIMEOUT)
- for event in events_pub_range:
- self.log.debug('%s: %s: %s', label, event['name'],
- event['data'])
- results = event['data']['Results']
- for rtt_result in results:
- rtt_status = rtt_result['status']
- if rtt_status == 0:
- distance = rtt_result['distance']
- self.log.info('%s: distance=%d', label, distance)
- rtt_stats['distance']['sum'] = (
- rtt_stats['distance']['sum'] + distance)
- rtt_stats['distance']['num_samples'] = (
- rtt_stats['distance']['num_samples'] + 1)
- else:
- self.log.info('%s: status=%d', label, rtt_status)
- if rtt_status not in rtt_stats['failure_codes']:
- rtt_stats['failure_codes'][rtt_status] = 0
- rtt_stats['failure_codes'][rtt_status] = (
- rtt_stats['failure_codes'][rtt_status] + 1)
- except queue.Empty:
- self.log.info('%s: Timed out while waiting for RTT events %s',
- label, events_regex)
- self.log.info('%s:\n\tParam: %s\n\tRTT statistics: %s', label,
- rtt_param, rtt_stats)
-
- def test_cluster_latency(self):
- """Measure the time it takes to make Aware available to an app from
- the time a request is made.
-
- Configuration: 1 device
-
- Logical steps:
- * DUT initiate Aware clustering
- * DUT wait for attach event (measure time)
- * DUT wait for identity change event (measure time)
- * DUT destroy Aware session
- """
- results = {}
- results['num_iterations'] = 100
-
- self.dut = self.android_devices[0]
-
- attach_latency = []
- identity_change_latency = []
- results['num_failed_attaches'] = 0
- for i in range(results['num_iterations']):
- session_id = self.dut.droid.wifiAwareAttach()
- try:
- event_attached = self.dut.ed.pop_event(
- aware_const.EVENT_CB_ON_ATTACHED, EVENT_TIMEOUT)
- attach_latency.append(event_attached['data'][
- aware_const.EVENT_CB_KEY_LATENCY_MS])
- self.log.debug('%s: %s', aware_const.EVENT_CB_ON_ATTACHED,
- event_attached['data'])
-
- event_identity_change = self.dut.ed.pop_event(
- aware_const.EVENT_CB_ON_IDENTITY_CHANGED, EVENT_TIMEOUT)
- identity_change_latency.append(event_identity_change['data'][
- aware_const.EVENT_CB_KEY_TIMESTAMP_MS] - event_attached[
- 'data'][
- aware_const.SESSION_CB_KEY_TIMESTAMP_MS])
- except queue.Empty:
- results['num_failed_attaches'] += 1
- self.log.debug('Timed out while waiting for %s|%s on DUT',
- aware_const.EVENT_CB_ON_ATTACHED,
- aware_const.EVENT_CB_ON_IDENTITY_CHANGED)
- self.dut.droid.wifiAwareDestroy(session_id)
-
- self.extract_stats(attach_latency, results, 'attach_latency', 'Attach')
- self.extract_stats(identity_change_latency, results,
- 'identity_change_latency', 'Identity Change')
- asserts.explicit_pass('test_cluster_latency finished', extras=results)
-
- def run_aware_discovery_session(self, discovery_config):
- """Perform Aware configuration, discovery, and message exchange.
-
- Configuration: 2 devices, one acting as Publisher (P) and the
- other as Subscriber (S)
-
- Logical steps:
- * P & S initiate Aware clustering (if not already up)
- * P & S wait for Aware connection confirmation
- * P starts publishing
- * S starts subscribing
- * S waits for a match (discovery) notification
- * S sends a message to P, confirming that sent successfully
- * P waits for a message and confirms that received (uncorrupted)
- * P sends a message to S, confirming that sent successfully
- * S waits for a message and confirms that received (uncorrupted)
-
- Args:
- discovery_configs: {'Title': description,
- 'PublishConfig': publish_config,
- 'SubscribeConfig': subscribe_config}
-
- Returns:
- True if discovery succeeds, else false.
- """
- # Configure Test
- self.publisher = self.android_devices[0]
- self.subscriber = self.android_devices[1]
-
- sub2pub_msg = "How are you doing? 你好嗎?"
- pub2sub_msg = "Doing ok - thanks! 做的不錯 - 謝謝!"
-
- # Start Test
- pub_connect_id = self.exec_connect(self.publisher, "publisher")
- sub_connect_id = self.exec_connect(self.subscriber, "subscriber")
-
- # Configuration
- publish_config = discovery_config['PublishConfig']
- subscribe_config = discovery_config['SubscribeConfig']
- self.log.debug('Publish config=%s, Subscribe config=%s', publish_config,
- subscribe_config)
-
- pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
- publish_config)
- sub_id = self.subscriber.droid.wifiAwareSubscribe(sub_connect_id,
- subscribe_config)
-
- try:
- event_sub_match = self.subscriber.ed.pop_event(
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
- self.log.info('%s: %s',
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
- event_sub_match['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on Subscriber' %
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
- self.log.debug(event_sub_match)
-
- self.reliable_tx(self.subscriber, sub_id,
- event_sub_match['data']['peerId'], sub2pub_msg)
-
- try:
- event_pub_rx = self.publisher.ed.pop_event(
- aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, EVENT_TIMEOUT)
- self.log.info('%s: %s', aware_const.SESSION_CB_ON_MESSAGE_RECEIVED,
- event_pub_rx['data'])
- asserts.assert_equal(event_pub_rx['data']['messageAsString'],
- sub2pub_msg,
- "Subscriber -> publisher message corrupted")
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on publisher' %
- aware_const.SESSION_CB_ON_MESSAGE_RECEIVED)
-
- self.reliable_tx(self.publisher, pub_id, event_pub_rx['data']['peerId'],
- pub2sub_msg)
-
- try:
- event_sub_rx = self.subscriber.ed.pop_event(
- aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, EVENT_TIMEOUT)
- self.log.info('%s: %s', aware_const.SESSION_CB_ON_MESSAGE_RECEIVED,
- event_sub_rx['data'])
- asserts.assert_equal(event_sub_rx['data']['messageAsString'],
- pub2sub_msg,
- "Publisher -> subscriber message corrupted")
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on subscriber' %
- aware_const.SESSION_CB_ON_MESSAGE_RECEIVED)
-
- if publish_config['TtlSec'] != 0:
- try:
- event_pub_term = self.publisher.ed.pop_event(
- aware_const.SESSION_CB_ON_SESSION_TERMINATED,
- publish_config['TtlSec'] + 5)
- self.log.info('%s: %s',
- aware_const.SESSION_CB_ON_SESSION_TERMINATED,
- event_pub_term['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on publisher' %
- aware_const.SESSION_CB_ON_SESSION_TERMINATED)
- if subscribe_config['TtlSec'] != 0:
- try:
- event_sub_term = self.subscriber.ed.pop_event(
- aware_const.SESSION_CB_ON_SESSION_TERMINATED,
- subscribe_config['TtlSec'] + 5)
- self.log.info('%s: %s',
- aware_const.SESSION_CB_ON_SESSION_TERMINATED,
- event_sub_term['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on subscriber' %
- aware_const.SESSION_CB_ON_SESSION_TERMINATED)
-
- @generated_test
- def test_aware_discovery_session(self):
- """Perform Aware configuration, discovery, and message exchange.
-
- Test multiple discovery types:
- - Unsolicited publish + passive subscribe
- - Solicited publish + active subscribe
- """
-
- discovery_configs = (
- {'Title': 'ActivePub',
- 'PublishConfig': self.publish_config,
- 'SubscribeConfig': self.subscribe_config},
- {'Title': 'ActiveSub',
- 'PublishConfig': dict(self.publish_config, **{'PublishType': 1}),
- 'SubscribeConfig': dict(self.subscribe_config,
- **{'SubscribeType': 1})},
- {'Title': 'ActivePub-LimitedTtl',
- 'PublishConfig': dict(self.publish_config, **{"TtlSec": 20}),
- 'SubscribeConfig': dict(self.subscribe_config, **{"TtlSec": 20})})
- name_func = lambda discovery_config: ("test_aware_discovery_session__%s") % discovery_config['Title']
- self.run_generated_testcases(self.run_aware_discovery_session,
- discovery_configs,
- name_func=name_func)
-
- def run_aware_discovery_latency(self, dw_interval):
- """Measure the latency of Aware discovery on the subscriber.
-
- Args:
- dw_interval: Discovery Window Interval configuration
-
- Configuration: 2 devices, one acting as Publisher (P) and the
- other as Subscriber (S)
-
- Logical steps:
- * P & S if not new_session initiate Aware clustering (if not already up)
- * P & S if not new session wait for Aware connection confirmation
- * P starts publishing
- * Wait for a few seconds to make sure everyone is ready (measuring
- * discovery - not clustering).
- * Loop:
- * S start subscribing
- * Measure latency to S registering a discovery
- * S terminates subscribe
- """
- self.publisher = self.android_devices[0]
- self.subscriber = self.android_devices[1]
- results = {}
- results['num_iterations'] = 100
-
- # Start Test
- pub_connect_id = self.exec_connect(self.publisher, "publisher",
- {"DiscoveryWindowInterval": [dw_interval, dw_interval]})
- sub_connect_id = self.exec_connect(self.subscriber, "subscriber",
- {"DiscoveryWindowInterval": [dw_interval, dw_interval]})
-
- pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
- self.publish_config)
- try:
- self.publisher.ed.pop_event(
- aware_const.SESSION_CB_ON_PUBLISH_STARTED, EVENT_TIMEOUT)
- except:
- asserts.fail('Timed out while waiting for %s on Publisher' %
- aware_const.SESSION_CB_ON_PUBLISH_STARTED)
-
- # another arbitrary long sleep time to make sure that publisher is
- # on-the-air
- time.sleep(10)
-
- sub_session_setup_latency = []
- sub_session_discovery_latency = []
- results['num_failed_discovery'] = 0
- for i in range(results['num_iterations']):
- sub_id = self.subscriber.droid.wifiAwareSubscribe(
- sub_connect_id, self.subscribe_config)
- try:
- event_sub = self.subscriber.ed.pop_event(
- aware_const.SESSION_CB_ON_SUBSCRIBE_STARTED, EVENT_TIMEOUT)
- sub_session_setup_latency.append(event_sub['data'][
- aware_const.SESSION_CB_KEY_LATENCY_MS])
- self.log.debug('%s: %s',
- aware_const.SESSION_CB_ON_SUBSCRIBE_STARTED,
- event_sub['data'])
-
- event_discovery = self.subscriber.ed.pop_event(
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
- sub_session_discovery_latency.append(event_discovery['data'][
- aware_const.SESSION_CB_KEY_TIMESTAMP_MS] - event_sub[
- 'data'][aware_const.SESSION_CB_KEY_TIMESTAMP_MS])
- self.log.debug('%s: %s',
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
- event_discovery['data'])
- except queue.Empty:
- results['num_failed_discovery'] += 1
- self.log.debug(
- 'Timed out while waiting for %s|%s on Subscriber',
- aware_const.SESSION_CB_ON_SUBSCRIBE_STARTED,
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
- self.subscriber.droid.wifiAwareDestroyDiscoverySession(sub_id)
-
- self.extract_stats(sub_session_setup_latency, results,
- 'sub_session_setup_latency',
- 'Subscribe Session Setup')
- self.extract_stats(sub_session_discovery_latency, results,
- 'sub_session_discovery_latency',
- 'Subscribe Session Discovery')
- asserts.explicit_pass('test_aware_discovery_latency finished',
- extras=results)
-
- @generated_test
- def test_aware_discovery_latency(self):
- """Measure the latency of Aware discovery on the subscriber.
-
- Test different discovery window intervals: 1, 2, 3, 4, 5
- """
-
- name_func = lambda dw_interval: "test_aware_discovery_latency__dw_%d" % dw_interval
- self.run_generated_testcases(self.run_aware_discovery_latency,
- [1, 2, 3, 4, 5],
- name_func=name_func)
-
- def run_aware_messaging(self, retry_count):
- """Perform Aware configuration, discovery, and large message exchange.
-
- Args:
- retry_count: retransmission count - from 0 to aware_const.MAX_TX_RETRIES
-
- Configuration: 2 devices, one acting as Publisher (P) and the
- other as Subscriber (S)
-
- Logical steps:
- * P & S initiate Aware clustering (if not already up)
- * P & S wait for Aware connection confirmation
- * P starts publishing
- * S starts subscribing
- * S waits for a match (discovery) notification
- * S sends XX messages to P
- * S confirms that all XXX messages were transmitted
- * P confirms that all XXX messages are received
- """
- self.publisher = self.android_devices[0]
- self.subscriber = self.android_devices[1]
- results = {}
- results['num_non_empty_messages'] = 100
- results[
- 'num_null_and_empty_messages'] = 10 # one of each in sequence until reach count
-
- # Start Test
- pub_connect_id = self.exec_connect(self.publisher, "publisher")
- sub_connect_id = self.exec_connect(self.subscriber, "subscriber")
-
- pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
- self.publish_config)
- sub_id = self.subscriber.droid.wifiAwareSubscribe(sub_connect_id,
- self.subscribe_config)
-
- try:
- event_sub_match = self.subscriber.ed.pop_event(
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
- self.log.info('%s: %s',
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
- event_sub_match['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on Subscriber' %
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
- self.log.debug(event_sub_match)
-
- # send all messages at once
- for i in range(results['num_non_empty_messages']):
- self.msg_id = self.msg_id + 1
- self.subscriber.droid.wifiAwareSendMessage(
- sub_id, event_sub_match['data']['peerId'], self.msg_id,
- "msg %s" % i, retry_count)
-
- # send all empty & null messages
- for i in range(results['num_null_and_empty_messages']):
- self.msg_id = self.msg_id + 1
- msg_to_send = None if (i % 2) else "" # flip between null and ""
- self.subscriber.droid.wifiAwareSendMessage(
- sub_id, event_sub_match['data']['peerId'], self.msg_id,
- msg_to_send, retry_count)
-
- # wait for all messages to be transmitted correctly
- results['num_tx_ok'] = 0
- results['num_tx_fail'] = 0
- tx_ok_stats = []
- tx_fail_stats = []
- events_regex = '%s|%s' % (aware_const.SESSION_CB_ON_MESSAGE_SEND_FAILED,
- aware_const.SESSION_CB_ON_MESSAGE_SENT)
- while (results['num_tx_ok'] + results['num_tx_fail']) < (
- results['num_non_empty_messages'] +
- results['num_null_and_empty_messages']):
- try:
- events = self.subscriber.ed.pop_events(events_regex,
- EVENT_TIMEOUT)
-
- for event in events:
- if event['name'] == aware_const.SESSION_CB_ON_MESSAGE_SENT:
- results['num_tx_ok'] = results['num_tx_ok'] + 1
- if aware_const.SESSION_CB_KEY_LATENCY_MS in event[
- 'data']:
- tx_ok_stats.append(event['data'][
- aware_const.SESSION_CB_KEY_LATENCY_MS])
- if event[
- 'name'] == aware_const.SESSION_CB_ON_MESSAGE_SEND_FAILED:
- results['num_tx_fail'] = results['num_tx_fail'] + 1
- if aware_const.SESSION_CB_KEY_LATENCY_MS in event[
- 'data']:
- tx_fail_stats.append(event['data'][
- aware_const.SESSION_CB_KEY_LATENCY_MS])
- except queue.Empty:
- self.log.warning('Timed out while waiting for %s on Subscriber'
- ' - %d events received', events_regex,
- results['num_tx_ok'] + results['num_tx_fail'])
- break
- self.log.info('Transmission stats: %d success, %d fail',
- results['num_tx_ok'], results['num_tx_fail'])
- self.extract_stats(tx_ok_stats, results, 'tx_ok_latency',
- 'Successful tx')
- self.extract_stats(tx_fail_stats, results, 'tx_fail_latency', 'Fail tx')
-
- # validate that all messages are received (not just the correct
- # number of messages - since on occasion there may be duplicates
- # received).
- results['num_non_empty_received'] = 0
- results['num_unique_received'] = 0
- results['num_empty_received'] = 0
- messages = {}
- while (results['num_unique_received'] + results['num_empty_received'] <
- results['num_tx_ok']):
- try:
- event = self.publisher.ed.pop_event(
- aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, EVENT_TIMEOUT)
- msg = event['data']['messageAsString']
- if msg:
- results['num_non_empty_received'] = results[
- 'num_non_empty_received'] + 1
- if msg not in messages:
- results['num_unique_received'] = results[
- 'num_unique_received'] + 1
- messages[msg] = 0
- messages[msg] = messages[msg] + 1
- else:
- results['num_empty_received'] = results[
- 'num_empty_received'] + 1
- self.log.debug('%s: %s',
- aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, msg)
- except queue.Empty:
- asserts.fail(
- 'Timed out while waiting for %s on Publisher: %d non-empty '
- 'messages received, %d unique messages, %d empty messages' %
- (aware_const.SESSION_CB_ON_MESSAGE_RECEIVED,
- results['num_non_empty_received'],
- results['num_unique_received'],
- results['num_empty_received']),
- extras=results)
- self.log.info(
- 'Reception stats: %d non-empty received (%d unique), %d empty',
- results['num_non_empty_received'], results['num_unique_received'],
- results['num_empty_received'])
- if results['num_non_empty_received'] != results['num_unique_received']:
- self.log.info('%d duplicate receptions of %d messages: %s',
- results['num_non_empty_received'] -
- results['num_unique_received'],
- results['num_non_empty_received'], messages)
- if results['num_empty_received'] != results[
- 'num_null_and_empty_messages']:
- self.log.info('%d extra empty/null message reception',
- results['num_empty_received'] -
- results['num_null_and_empty_messages'])
- asserts.explicit_pass("run_aware_messaging pass finished successfully",
- extras=results)
-
- @generated_test
- def test_aware_messaging(self):
- """Perform Aware configuration, discovery, and large message exchange.
-
- Test multiple message send retry counts.
- """
- name_func = lambda retry_count: "test_aware_messaging__retries_%d" % retry_count
- self.run_generated_testcases(self.run_aware_messaging,
- [0, aware_const.MAX_TX_RETRIES],
- name_func=name_func)
-
- def test_aware_messaging_latency(self):
- """Measure the latency of Aware message transmission which are not queued. Unqueued
- message transmission data is a function of raw protocol and firmware behavior.
-
- Configuration: 2 devices, one acting as Publisher (P) and the
- other as Subscriber (S)
-
- Logical steps:
- * P & S initiate Aware clustering (if not already up)
- * P & S wait for Aware connection confirmation
- * P starts publishing
- * S starts subscribing
- * S waits for a match (discovery) notification
- * Loop:
- * S sends 1 message to P
- * S confirms that message transmitted and measures latency
- """
- self.publisher = self.android_devices[0]
- self.subscriber = self.android_devices[1]
- results = {}
- results['num_messages'] = 100
-
- # Start Test
- pub_connect_id = self.exec_connect(self.publisher, "publisher")
- sub_connect_id = self.exec_connect(self.subscriber, "subscriber")
-
- pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
- self.publish_config)
- sub_id = self.subscriber.droid.wifiAwareSubscribe(sub_connect_id,
- self.subscribe_config)
-
- try:
- event_sub_match = self.subscriber.ed.pop_event(
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
- self.log.info('%s: %s',
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
- event_sub_match['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on Subscriber' %
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
- self.log.debug(event_sub_match)
-
- results['num_tx_ok'] = 0
- results['num_tx_fail'] = 0
- tx_ok_stats = []
- tx_fail_stats = []
- events_regex = '%s|%s' % (aware_const.SESSION_CB_ON_MESSAGE_SEND_FAILED,
- aware_const.SESSION_CB_ON_MESSAGE_SENT)
- for i in range(results['num_messages']):
- self.msg_id = self.msg_id + 1
- self.subscriber.droid.wifiAwareSendMessage(
- sub_id, event_sub_match['data']['peerId'], self.msg_id,
- "msg %s" % i, 0)
- try:
- events = self.subscriber.ed.pop_events(events_regex,
- EVENT_TIMEOUT)
- for event in events:
- if event['name'] == aware_const.SESSION_CB_ON_MESSAGE_SENT:
- results['num_tx_ok'] = results['num_tx_ok'] + 1
- if aware_const.SESSION_CB_KEY_LATENCY_MS in event[
- 'data']:
- tx_ok_stats.append(event['data'][
- aware_const.SESSION_CB_KEY_LATENCY_MS])
- if event[
- 'name'] == aware_const.SESSION_CB_ON_MESSAGE_SEND_FAILED:
- results['num_tx_fail'] = results['num_tx_fail'] + 1
- if aware_const.SESSION_CB_KEY_LATENCY_MS in event[
- 'data']:
- tx_fail_stats.append(event['data'][
- aware_const.SESSION_CB_KEY_LATENCY_MS])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on Subscriber',
- events_regex,
- extras=results)
- self.extract_stats(tx_ok_stats, results, 'tx_ok_latency',
- 'Successful tx')
- self.extract_stats(tx_fail_stats, results, 'tx_fail_latency', 'Fail tx')
- asserts.explicit_pass(
- 'test_aware_messaging_no_queue finished successfully',
- extras=results)
-
- def test_aware_rtt(self):
- """Perform Aware configuration, discovery, and RTT.
-
- Configuration: 2 devices, one acting as Publisher (P) and the
- other as Subscriber (S)
-
- Logical steps:
- * P & S initiate Aware clustering (if not already up)
- * P & S wait for Aware connection confirmation
- * P starts publishing
- * S starts subscribing
- * S waits for a match (discovery) notification
- * S performs 3 RTT measurements with P
- """
- # Configure Test
- self.publisher = self.android_devices[0]
- self.subscriber = self.android_devices[1]
- rtt_iterations = 10
-
- # Start Test
- pub_connect_id = self.exec_connect(self.publisher, "publisher")
- sub_connect_id = self.exec_connect(self.subscriber, "subscriber")
-
- pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
- self.publish_config)
- sub_id = self.subscriber.droid.wifiAwareSubscribe(sub_connect_id,
- self.subscribe_config)
-
- try:
- event_sub_match = self.subscriber.ed.pop_event(
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
- self.log.info('%s: %s',
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
- event_sub_match['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on Subscriber' %
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
- self.log.debug(event_sub_match)
-
- self.exec_rtt(device=self.subscriber,
- session_id=sub_id,
- peer_id=event_sub_match['data']['peerId'],
- rtt_param=self.rtt_24_20, label="2.4GHz / 20MHz BW",
- repeat_count=rtt_iterations)
- self.exec_rtt(device=self.subscriber,
- session_id=sub_id,
- peer_id=event_sub_match['data']['peerId'],
- rtt_param=self.rtt_50_40, label="5Hz / 40MHz BW",
- repeat_count=rtt_iterations)
- self.exec_rtt(device=self.subscriber,
- session_id=sub_id,
- peer_id=event_sub_match['data']['peerId'],
- rtt_param=self.rtt_50_80, label="5GHz / 80MHz BW",
- repeat_count=rtt_iterations)
-
- def test_disable_wifi_during_connection(self):
- """Validate behavior when Wi-Fi is disabled during an active Aware
- connection. Expected behavior: receive an onAwareDown(1002) event.
-
- Configuration: 1 device - the DUT.
-
- Logical steps:
- * DUT initiate Aware clustering (if not already up)
- * DUT waits for Aware connection confirmation
- * DUT starts publishing
- * Disable Wi-Fi
- * DUT waits for an onAwareDown(1002) event and confirms that received
- """
- # Configure Test
- self.dut = self.android_devices[0]
-
- # Start Test
- connect_id = self.dut.droid.wifiAwareAttach()
-
- try:
- event = self.dut.ed.pop_event(aware_const.EVENT_CB_ON_ATTACHED,
- EVENT_TIMEOUT)
- self.log.info('%s: %s', aware_const.EVENT_CB_ON_ATTACHED,
- event['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on dut' %
- aware_const.EVENT_CB_ON_ATTACHED)
- self.log.debug(event)
-
- pub_id = self.dut.droid.wifiAwarePublish(connect_id,
- self.publish_config)
-
- asserts.assert_true(
- wutils.wifi_toggle_state(self.dut, False),
- "Failed disabling Wi-Fi interface on dut")
-
- try:
- event = self.dut.ed.pop_event(
- aware_const.BROADCAST_WIFI_AWARE_NOT_AVAILABLE, EVENT_TIMEOUT)
- self.log.info(aware_const.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on dut' %
- aware_const.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
- self.log.debug(event)
-
- def test_aware_data_path(self):
- """Perform Aware configuration, discovery, data-path setup, and data
- transfer.
-
- Configuration: 2 devices, one acting as Publisher (P) and the
- other as Subscriber (S)
-
- Logical steps:
- * P & S initiate Aware clustering (if not already up)
- * P & S wait for Aware connection confirmation
- * P starts publishing
- * S starts subscribing
- * S waits for a match (discovery) notification
- * S sends a message to P
- * P waits for message
- * P creates an Aware network to S as RESPONDER
- * P sends a message to S
- * S waits for message
- * S creates an Aware network to P as INITIATOR (order important!)
- * Both P & S wait for events confirming network set up
- * NSD option:
- * P registers NSD service
- * S discovers NSD service and obtains P's IPv6 address
- * Direct config option:
- * No communication: script uses address read from P
- * run iperf3 between P (server) and S (client)
- * unregister network callback on S
- """
- # Configure Test
- self.publisher = self.android_devices[0]
- self.subscriber = self.android_devices[1]
- results = {}
-
- sub2pub_msg = "Get ready!"
- pub2sub_msg = "Ready!"
- publisher_passphrase = None
- subscriber_passphrase = None
- use_nsd = False
-
- # Start Test
- pub_connect_id = self.exec_connect(self.publisher, "publisher")
- sub_connect_id = self.exec_connect(self.subscriber, "subscriber")
-
- # Discovery: publish + subscribe + wait for match
- pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
- self.publish_config)
- sub_id = self.subscriber.droid.wifiAwareSubscribe(sub_connect_id,
- self.subscribe_config)
-
- def filter_callbacks(event, key, name):
- return event['data'][key] == name
-
- try:
- event_sub_match = self.subscriber.ed.pop_event(
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
- self.log.info('%s: %s',
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
- event_sub_match['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on Subscriber' %
- aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
- self.log.debug(event_sub_match)
-
- # S sends message to P
- self.reliable_tx(self.subscriber, sub_id,
- event_sub_match['data']['peerId'], sub2pub_msg)
-
- try:
- event_pub_rx = self.publisher.ed.pop_event(
- aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, EVENT_TIMEOUT)
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on publisher' %
- aware_const.SESSION_CB_ON_MESSAGE_RECEIVED)
- self.log.info('%s: %s', aware_const.SESSION_CB_ON_MESSAGE_RECEIVED,
- event_pub_rx['data'])
- asserts.assert_equal(event_pub_rx['data']['messageAsString'],
- sub2pub_msg,
- "Subscriber -> publisher message corrupted")
-
- # P requests an Aware network as RESPONDER
- pub_ns = self.publisher.droid.wifiAwareCreateNetworkSpecifier(pub_id,
- event_pub_rx['data']['peerId'], publisher_passphrase)
- self.log.info("Publisher network specifier - '%s'", pub_ns)
- self.network_req['NetworkSpecifier'] = pub_ns
- pub_req_key = self.publisher.droid.connectivityRequestWifiAwareNetwork(
- self.network_req)
-
- # P sends message to S
- self.reliable_tx(self.publisher, pub_id, event_pub_rx['data']['peerId'],
- pub2sub_msg)
-
- try:
- event_sub_rx = self.subscriber.ed.pop_event(
- aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, EVENT_TIMEOUT)
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on subscriber' %
- aware_const.SESSION_CB_ON_MESSAGE_RECEIVED)
- self.log.info('%s: %s', aware_const.SESSION_CB_ON_MESSAGE_RECEIVED,
- event_sub_rx['data'])
- asserts.assert_equal(event_sub_rx['data']['messageAsString'],
- pub2sub_msg,
- "Publisher -> subscriber message corrupted")
-
- # S requests an Aware network as INITIATOR
- sub_ns = self.subscriber.droid.wifiAwareCreateNetworkSpecifier(sub_id,
- event_sub_rx['data']['peerId'], subscriber_passphrase)
- self.log.info("Subscriber network specifier - '%s'", sub_ns)
- self.network_req['NetworkSpecifier'] = sub_ns
- sub_req_key = self.subscriber.droid.connectivityRequestWifiAwareNetwork(
- self.network_req)
-
- # Wait until both S and P get confirmation that network formed
- try:
- event_network = self.subscriber.ed.wait_for_event(
- con_const.EVENT_NETWORK_CALLBACK,
- filter_callbacks,
- EVENT_TIMEOUT,
- key=con_const.NETWORK_CB_KEY_EVENT,
- name=con_const.NETWORK_CB_LINK_PROPERTIES_CHANGED)
- self.log.info('Subscriber %s: %s', con_const.EVENT_NETWORK_CALLBACK,
- event_network['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s/%s on Subscriber' %
- (con_const.EVENT_NETWORK_CALLBACK,
- con_const.NETWORK_CB_LINK_PROPERTIES_CHANGED))
- self.log.debug(event_network)
- sub_aware_if = event_network['data']['interfaceName']
-
- try:
- event_network = self.publisher.ed.wait_for_event(
- con_const.EVENT_NETWORK_CALLBACK,
- filter_callbacks,
- EVENT_TIMEOUT,
- key=con_const.NETWORK_CB_KEY_EVENT,
- name=con_const.NETWORK_CB_LINK_PROPERTIES_CHANGED)
- self.log.info('Publisher %s: %s', con_const.EVENT_NETWORK_CALLBACK,
- event_network['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s/%s on Publisher' %
- (con_const.EVENT_NETWORK_CALLBACK,
- con_const.NETWORK_CB_LINK_PROPERTIES_CHANGED))
- self.log.debug(event_network)
- pub_aware_if = event_network['data']['interfaceName']
-
- pub_ipv6 = "";
- if use_nsd:
- try:
- # P registers NSD service (i.e. starts publishing)
- nsd_reg = self.publisher.droid.nsdRegisterService(
- self.nsd_service_info)
- try:
- event_nsd = self.publisher.ed.wait_for_event(
- nsd_const.REG_LISTENER_EVENT,
- filter_callbacks,
- EVENT_TIMEOUT,
- key=nsd_const.REG_LISTENER_CALLBACK,
- name=nsd_const.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED)
- self.log.info(
- 'Publisher %s: %s',
- nsd_const.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED,
- event_nsd['data'])
- except queue.Empty:
- asserts.fail('Timed out while waiting for %s on Publisher' %
- nsd_const.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED)
-
- # S starts NSD discovery
- nsd_discovery = self.subscriber.droid.nsdDiscoverServices(
- self.nsd_service_info[
- nsd_const.NSD_SERVICE_INFO_SERVICE_TYPE])
- try:
- event_nsd = self.subscriber.ed.wait_for_event(
- nsd_const.DISCOVERY_LISTENER_EVENT,
- filter_callbacks,
- EVENT_TIMEOUT,
- key=nsd_const.DISCOVERY_LISTENER_DATA_CALLBACK,
- name=nsd_const.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND)
- self.log.info(
- 'Subscriber %s: %s',
- nsd_const.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND,
- event_nsd['data'])
- except queue.Empty:
- asserts.fail(
- 'Timed out while waiting for %s on Subscriber' %
- nsd_const.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND)
-
- # S resolves IP address of P from NSD service discovery
- self.subscriber.droid.nsdResolveService(event_nsd['data'])
- try:
- event_nsd = self.subscriber.ed.wait_for_event(
- nsd_const.RESOLVE_LISTENER_EVENT,
- filter_callbacks,
- EVENT_TIMEOUT,
- key=nsd_const.RESOLVE_LISTENER_DATA_CALLBACK,
- name=nsd_const.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED)
- self.log.info(
- 'Subscriber %s: %s',
- nsd_const.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED,
- event_nsd['data'])
- except queue.Empty:
- asserts.fail(
- 'Timed out while waiting for %s on Subscriber' %
- nsd_const.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED)
-
- # mDNS returns first character as '/' - strip
- # out to get clean IPv6
- pub_ipv6 = event_nsd['data'][
- nsd_const.NSD_SERVICE_INFO_HOST][1:]
- finally:
- # Stop NSD
- if nsd_reg is not None:
- self.publisher.droid.nsdUnregisterService(nsd_reg)
- if nsd_discovery is not None:
- self.subscriber.droid.nsdStopServiceDiscovery(nsd_discovery)
- else:
- pub_ipv6 = self.publisher.droid.connectivityGetLinkLocalIpv6Address(
- pub_aware_if)
- pub_ipv6 = pub_ipv6.split('%')[0] # get rid of <name> - xx:xx%<name>
- self.log.info('Publisher IPv6: %s', pub_ipv6)
-
- # P starts iPerf server
- result, data = self.publisher.run_iperf_server("-D")
- asserts.assert_true(result, "Can't start iperf3 server")
-
- # S starts iPerf client
- result, data = self.subscriber.run_iperf_client(
- "%s%%%s" % (pub_ipv6, sub_aware_if), "-6 -J")
- self.log.debug(data)
- asserts.assert_true(result,
- "Failure starting/running iperf3 in client mode")
- self.log.debug(pprint.pformat(data))
- data_json = json.loads(''.join(data))
- results['tx_rate'] = data_json['end']['sum_sent']['bits_per_second']
- results['rx_rate'] = data_json['end']['sum_received']['bits_per_second']
- self.log.info('iPerf3: Sent = %d bps Received = %d bps',
- results['tx_rate'], results['rx_rate'])
- asserts.explicit_pass('Aware data-path test passes', extras=results)
diff --git a/acts/tests/google/wifi/WifiTetheringPowerTest.py b/acts/tests/google/wifi/WifiTetheringPowerTest.py
new file mode 100644
index 0000000..dd3cf74
--- /dev/null
+++ b/acts/tests/google/wifi/WifiTetheringPowerTest.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3.4
+#
+# 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 threading
+import time
+
+from acts import base_test
+from acts import asserts
+from acts.controllers import adb
+from acts.controllers import monsoon
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.tel import tel_data_utils as tel_utils
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts.test_utils.tel.tel_test_utils import http_file_download_by_chrome
+from acts.utils import force_airplane_mode
+from acts.utils import set_adaptive_brightness
+from acts.utils import set_ambient_display
+from acts.utils import set_auto_rotate
+from acts.utils import set_location_service
+
+
+class WifiTetheringPowerTest(base_test.BaseTestClass):
+
+ def setup_class(self):
+ self.hotspot_device = self.android_devices[0]
+ self.tethered_devices = self.android_devices[1:]
+ req_params = ("ssid", "password", "url")
+ self.unpack_userparams(req_params)
+ self.network = { "SSID": self.ssid, "password": self.password }
+
+ self.offset = 1 * 60
+ self.hz = 5000
+ self.duration = 9 * 60 + self.offset
+ self.mon_data_path = os.path.join(self.log_path, "Monsoon")
+ self.mon = self.monsoons[0]
+ self.mon.set_voltage(4.2)
+ self.mon.set_max_current(7.8)
+ self.mon.attach_device(self.hotspot_device)
+
+ asserts.assert_true(self.mon.usb("auto"),
+ "Failed to turn USB mode to auto on monsoon.")
+ set_location_service(self.hotspot_device, False)
+ set_adaptive_brightness(self.hotspot_device, False)
+ set_ambient_display(self.hotspot_device, False)
+ self.hotspot_device.adb.shell("settings put system screen_brightness 0")
+ set_auto_rotate(self.hotspot_device, False)
+ wutils.wifi_toggle_state(self.hotspot_device, False)
+ self.hotspot_device.droid.telephonyToggleDataConnection(True)
+ tel_utils.wait_for_cell_data_connection(self.log, self.hotspot_device, True)
+ asserts.assert_true(
+ tel_utils.verify_http_connection(self.log, self.hotspot_device),
+ "HTTP verification failed on cell data connection")
+ for ad in self.tethered_devices:
+ wutils.reset_wifi(ad)
+
+ def teardown_class(self):
+ self.mon.usb("on")
+ wutils.wifi_toggle_state(self.hotspot_device, True)
+
+ def on_fail(self, test_name, begin_time):
+ self.hotspot_device.take_bug_report(test_name, begin_time)
+
+ def on_pass(self, test_name, begin_time):
+ self.hotspot_device.take_bug_report(test_name, begin_time)
+
+ """ Helper functions """
+ def _measure_and_process_result(self):
+ """ Measure the current drawn by the device for the period of
+ self.duration, at the frequency of self.hz.
+ """
+ tag = self.current_test_name
+ result = self.mon.measure_power(self.hz,
+ self.duration,
+ tag=tag,
+ offset=self.offset)
+ asserts.assert_true(result,
+ "Got empty measurement data set in %s." % tag)
+ self.log.info(repr(result))
+ data_path = os.path.join(self.mon_data_path, "%s.txt" % tag)
+ monsoon.MonsoonData.save_to_text_file([result], data_path)
+ actual_current = result.average_current
+ actual_current_str = "%.2fmA" % actual_current
+ result_extra = {"Average Current": actual_current_str}
+
+ def _start_wifi_tethering(self, wifi_band):
+ """ Start wifi tethering on hotspot device
+
+ Args:
+ 1. wifi_band: specifies the wifi band to start the hotspot
+ on. The current options are 2G and 5G
+ """
+ wutils.start_wifi_tethering(self.hotspot_device,
+ self.ssid,
+ self.password,
+ wifi_band)
+
+ def _start_traffic_on_device(self, ad):
+ """ Start traffic on the device by downloading data
+ Run the traffic continuosly for self.duration
+
+ Args:
+ 1. ad device object
+ """
+ timeout = time.time() + self.duration
+ while True:
+ if time.time() > timeout:
+ break
+ http_file_download_by_chrome(ad, self.url)
+
+ def _start_traffic_measure_power(self, ad_list):
+ """ Start traffic on the tethered devices and measure power
+
+ Args:
+ 1. ad_list: list of tethered devices to run traffic on
+ """
+ threads = []
+ for ad in ad_list:
+ t = threading.Thread(target = self._start_traffic_on_device,
+ args = (ad,))
+ t.start()
+ threads.append(t)
+ try:
+ self._measure_and_process_result()
+ finally:
+ for t in threads:
+ t.join()
+
+
+ """ Tests begin """
+ @test_tracker_info(uuid="ebb74144-e22a-46e1-b8c1-9ada22b13133")
+ def test_power_wifi_tethering_2ghz_no_devices_connected(self):
+ """ Steps:
+ 1. Start wifi hotspot with 2.4Ghz band
+ 2. No devices connected to hotspot
+ 3. Measure power
+ """
+ self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+ self._measure_and_process_result()
+ wutils.stop_wifi_tethering(self.hotspot_device)
+
+ @test_tracker_info(uuid="2560c088-4010-4354-ade3-6aaac83b1cfd")
+ def test_power_wifi_tethering_5ghz_no_devices_connected(self):
+ """ Steps:
+ 1. Start wifi hotspot with 5Ghz band
+ 2. No devices connected to hotspot
+ 3. Measure power
+ """
+ self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+ self._measure_and_process_result()
+ wutils.stop_wifi_tethering(self.hotspot_device)
+
+ @test_tracker_info(uuid="644795b0-cd30-4a8f-82ee-cc0618c41c6b")
+ def test_power_wifi_tethering_2ghz_connect_1device(self):
+ """ Steps:
+ 1. Start wifi hotspot with 2.4GHz band
+ 2. Connect 1 device to hotspot
+ 3. Measure power
+ """
+ self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+ wutils.wifi_connect(self.tethered_devices[0], self.network)
+ self._measure_and_process_result()
+ wutils.stop_wifi_tethering(self.hotspot_device)
+
+ @test_tracker_info(uuid="8fca9898-f493-44c3-810f-d2262ac72187")
+ def test_power_wifi_tethering_5ghz_connect_1device(self):
+ """ Steps:
+ 1. Start wifi hotspot with 5GHz band
+ 2. Connect 1 device to hotspot
+ 3. Measure power
+ """
+ self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+ wutils.wifi_connect(self.tethered_devices[0], self.network)
+ self._measure_and_process_result()
+ wutils.stop_wifi_tethering(self.hotspot_device)
+
+ @test_tracker_info(uuid="16ef5f63-1a7a-44ae-bf8d-c3a181c89b63")
+ def test_power_wifi_tethering_2ghz_connect_5devices(self):
+ """ Steps:
+ 1. Start wifi hotspot with 2GHz band
+ 2. Connect 5 devices to hotspot
+ 3. Measure power
+ """
+ self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+ for ad in self.tethered_devices:
+ wutils.wifi_connect(ad, self.network)
+ self._measure_and_process_result()
+ wutils.stop_wifi_tethering(self.hotspot_device)
+
+ @test_tracker_info(uuid="769aedfc-d309-40e0-95dd-51ff40f4e097")
+ def test_power_wifi_tethering_5ghz_connect_5devices(self):
+ """ Steps:
+ 1. Start wifi hotspot with 5GHz band
+ 2. Connect 5 devices to hotspot
+ 3. Measure power
+ """
+ self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+ for ad in self.tethered_devices:
+ wutils.wifi_connect(ad, self.network)
+ self._measure_and_process_result()
+ wutils.stop_wifi_tethering(self.hotspot_device)
+
+ @test_tracker_info(uuid="e5b71f34-1dc0-4045-a45e-48c1e9426ec3")
+ def test_power_wifi_tethering_2ghz_connect_1device_with_traffic(self):
+ """ Steps:
+ 1. Start wifi hotspot with 2GHz band
+ 2. Connect 1 device to hotspot device
+ 3. Start traffic and measure power
+ """
+ self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+ wutils.wifi_connect(self.tethered_devices[0], self.network)
+ self._start_traffic_measure_power(self.tethered_devices[0:1])
+ wutils.stop_wifi_tethering(self.hotspot_device)
+
+ @test_tracker_info(uuid="29c5cd6e-8df1-46e5-a735-526dc9154f6e")
+ def test_power_wifi_tethering_5ghz_connect_1device_with_traffic(self):
+ """ Steps:
+ 1. Start wifi hotspot with 5GHz band
+ 2. Connect 1 device to hotspot device
+ 3. Start traffic and measure power
+ """
+ self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+ wutils.wifi_connect(self.tethered_devices[0], self.network)
+ self._start_traffic_measure_power(self.tethered_devices[0:1])
+ wutils.stop_wifi_tethering(self.hotspot_device)
+
+ @test_tracker_info(uuid="da71b06f-7b98-4c14-a2e2-361f395b39a8")
+ def test_power_wifi_tethering_2ghz_connect_5devices_with_traffic(self):
+ """ Steps:
+ 1. Start wifi hotspot with 2GHz band
+ 2. Connect 5 devices to hotspot device
+ 3. Start traffic and measure power
+ """
+ self._start_wifi_tethering(WIFI_CONFIG_APBAND_2G)
+ for ad in self.tethered_devices:
+ wutils.wifi_connect(ad, self.network)
+ self._start_traffic_measure_power(self.tethered_devices)
+ wutils.stop_wifi_tethering(self.hotspot_device)
+
+ @test_tracker_info(uuid="7f3173ab-fd8f-4579-8c45-f9a8c5cd17f7")
+ def test_power_wifi_tethering_5ghz_connect_5devices_with_traffic(self):
+ """ Steps:
+ 1. Start wifi hotspot with 2GHz band
+ 2. Connect 5 devices to hotspot device
+ 3. Start traffic and measure power
+ """
+ self._start_wifi_tethering(WIFI_CONFIG_APBAND_5G)
+ for ad in self.tethered_devices:
+ wutils.wifi_connect(ad, self.network)
+ self._start_traffic_measure_power(self.tethered_devices)
+ wutils.stop_wifi_tethering(self.hotspot_device)
diff --git a/acts/tests/google/wifi/aware/README.md b/acts/tests/google/wifi/aware/README.md
new file mode 100644
index 0000000..3762049
--- /dev/null
+++ b/acts/tests/google/wifi/aware/README.md
@@ -0,0 +1,50 @@
+# Wi-Fi Aware Integrated (ACTS/sl4a) Test Suite
+
+This directory contains ACTS/sl4a test scripts to verify and characterize
+the Wi-Fi Aware implementation in Android.
+
+There are 4 groups of tests (in 4 sub-directories):
+
+* functional: Functional tests that each implementation must pass. These
+are pass/fail tests.
+* performance: Tests which measure performance of an implementation - e.g.
+latency or throughput. Some of the tests may not have pass/fail results -
+they just record the measured performance. Even when tests do have a pass/
+fail criteria - that criteria may not apply to all implementations.
+* stress: Tests which run through a large number of iterations to stress
+test the implementation. Considering that some failures are expected,
+especially in an over-the-air situation, pass/fail criteria are either
+not provided or may not apply to all implementations or test environments.
+* ota (over-the-air): A small number of tests which configure the device
+in a particular mode and expect the tester to capture an over-the-air
+sniffer trace and analyze it for validity. These tests are **not** automated.
+
+The tests can be executed in several ways:
+
+1. Individual test(s): `act.py -c <config> -tc {<test_class>|<test_class>:<test_name>}`
+
+Where a test file is any of the `.py` files in any of the test sub-directories.
+If a test class is specified, then all tests within that test class are executed.
+
+2. All tests in a test group: `act.py -c <config> -tf <test_file>`
+
+Where `<test_file>` is a file containing a list of tests. Each of the test
+group sub-directories contains a file with the same name as that of the
+directory which lists all tests in the directory. E.g. to execute all functional
+tests execute:
+
+`act.py -c <config> -tf ./tools/test/connectivity/acts/tests/google/wifi/aware/functional/functional`
+
+## Test Configurations
+The test configurations, the `<config>` in the commands above, are stored in
+the *config* sub-directory. The configurations simply use all connected
+devices without listing specific serial numbers. Note that some tests use a
+single device while others use 2 devices. In addition, the configurations
+define the following key to configure the test:
+
+* **default_power_mode**: The power mode in which to run all tests. Options
+are `INTERACTIVE` and `NON_INTERACTIVE`.
+
+The following configurations are provided:
+* wifi_aware.json: Normal (high power/interactive) test mode.
+* wifi_aware_non_interactive.json: Low power (non-interactive) test mode.
diff --git a/acts/tests/google/wifi/aware/config/wifi_aware.json b/acts/tests/google/wifi/aware/config/wifi_aware.json
new file mode 100644
index 0000000..7f384ef
--- /dev/null
+++ b/acts/tests/google/wifi/aware/config/wifi_aware.json
@@ -0,0 +1,15 @@
+{
+ "_description": "This is a test configuration file for Wi-Fi Aware tests in INTERACTIVE (high-power) mode.",
+ "testbed":
+ [
+ {
+ "_description": "Wi-Fi Aware testbed: auto-detect all attached devices",
+ "name": "WifiAwareAllAttached",
+ "AndroidDevice": "*"
+ }
+ ],
+ "logpath": "~/logs",
+ "testpaths": ["./tools/test/connectivity/acts/tests/google/wifi"],
+ "adb_logcat_param": "-b all",
+ "default_power_mode": "INTERACTIVE"
+}
diff --git a/acts/tests/google/wifi/aware/config/wifi_aware_non_interactive.json b/acts/tests/google/wifi/aware/config/wifi_aware_non_interactive.json
new file mode 100644
index 0000000..16e26b9
--- /dev/null
+++ b/acts/tests/google/wifi/aware/config/wifi_aware_non_interactive.json
@@ -0,0 +1,15 @@
+{
+ "_description": "This is a test configuration file for Wi-Fi Aware tests in NON-INTERACTIVE (low-power) mode.",
+ "testbed":
+ [
+ {
+ "_description": "Wi-Fi Aware testbed: auto-detect all attached devices",
+ "name": "WifiAwareAllAttached",
+ "AndroidDevice": "*"
+ }
+ ],
+ "logpath": "~/logs",
+ "testpaths": ["./tools/test/connectivity/acts/tests/google/wifi"],
+ "adb_logcat_param": "-b all",
+ "default_power_mode": "NON_INTERACTIVE"
+}
diff --git a/acts/tests/google/wifi/aware/functional/AttachTest.py b/acts/tests/google/wifi/aware/functional/AttachTest.py
new file mode 100644
index 0000000..191a26f
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/AttachTest.py
@@ -0,0 +1,127 @@
+#!/usr/bin/python3.4
+#
+# 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 time
+
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts.utils import force_airplane_mode
+
+
+class AttachTest(AwareBaseTest):
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def test_attach(self):
+ """Functional test case / Attach test cases / attach
+
+ Validates that attaching to the Wi-Fi Aware service works (receive
+ the expected callback).
+ """
+ dut = self.android_devices[0]
+ dut.droid.wifiAwareAttach(False)
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+ autils.fail_on_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+
+ def test_attach_with_identity(self):
+ """Functional test case / Attach test cases / attach with identity callback
+
+ Validates that attaching to the Wi-Fi Aware service works (receive
+ the expected callbacks).
+ """
+ dut = self.android_devices[0]
+ dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+
+ def test_attach_multiple_sessions(self):
+ """Functional test case / Attach test cases / multiple attach sessions
+
+ Validates that when creating multiple attach sessions each can be
+ configured independently as to whether or not to receive an identity
+ callback.
+ """
+ dut = self.android_devices[0]
+
+ # Create 3 attach sessions: 2 without identity callback, 1 with
+ id1 = dut.droid.wifiAwareAttach(False, None, True)
+ time.sleep(10) # to make sure all calls and callbacks are done
+ id2 = dut.droid.wifiAwareAttach(True, None, True)
+ time.sleep(10) # to make sure all calls and callbacks are done
+ id3 = dut.droid.wifiAwareAttach(False, None, True)
+ dut.log.info('id1=%d, id2=%d, id3=%d', id1, id2, id3)
+
+ # Attach session 1: wait for attach, should not get identity
+ autils.wait_for_event(dut,
+ autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED,
+ id1))
+ autils.fail_on_event(dut,
+ autils.decorate_event(
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED, id1))
+
+ # Attach session 2: wait for attach and for identity callback
+ autils.wait_for_event(dut,
+ autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED,
+ id2))
+ autils.wait_for_event(dut,
+ autils.decorate_event(
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED, id2))
+
+ # Attach session 3: wait for attach, should not get identity
+ autils.wait_for_event(dut,
+ autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED,
+ id3))
+ autils.fail_on_event(dut,
+ autils.decorate_event(
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED, id3))
+
+ def test_attach_with_no_wifi(self):
+ """Function test case / Attach test cases / attempt to attach with wifi off
+
+ Validates that if trying to attach with Wi-Fi disabled will receive the
+ expected failure callback. As a side-effect also validates that the broadcast
+ for Aware unavailable is received.
+ """
+ dut = self.android_devices[0]
+ wutils.wifi_toggle_state(dut, False)
+ autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+ dut.droid.wifiAwareAttach()
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACH_FAILED)
+
+ def test_attach_apm_toggle_attach_again(self):
+ """Validates that enabling Airplane mode while Aware is on resets it
+ correctly, and allows it to be re-enabled when Airplane mode is then
+ disabled."""
+ dut = self.android_devices[0]
+
+ # enable Aware (attach)
+ dut.droid.wifiAwareAttach()
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # enable airplane mode
+ force_airplane_mode(dut, True)
+ autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+
+ # wait a few seconds and disable airplane mode
+ time.sleep(10)
+ force_airplane_mode(dut, False)
+ autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+
+ # try enabling Aware again (attach)
+ dut.droid.wifiAwareAttach()
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
diff --git a/acts/tests/google/wifi/aware/functional/CapabilitiesTest.py b/acts/tests/google/wifi/aware/functional/CapabilitiesTest.py
new file mode 100644
index 0000000..55e63e9
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/CapabilitiesTest.py
@@ -0,0 +1,252 @@
+#!/usr/bin/python3.4
+#
+# 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.
+
+from acts import asserts
+from acts import signals
+
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class CapabilitiesTest(AwareBaseTest):
+ """Set of tests for Wi-Fi Aware Capabilities - verifying that the provided
+ capabilities are real (i.e. available)."""
+
+ SERVICE_NAME = "GoogleTestXYZ"
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def create_config(self, dtype, service_name):
+ """Create a discovery configuration based on input parameters.
+
+ Args:
+ dtype: Publish or Subscribe discovery type
+ service_name: Service name.
+
+ Returns:
+ Discovery configuration object.
+ """
+ config = {}
+ config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+ config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+ return config
+
+ def start_discovery_session(self, dut, session_id, is_publish, dtype,
+ service_name, expect_success):
+ """Start a discovery session
+
+ Args:
+ dut: Device under test
+ 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
+ service_name: Service name to use for the discovery session
+ expect_success: True if expect session to be created, False otherwise
+
+ Returns:
+ Discovery session ID.
+ """
+ config = {}
+ config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+ config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+
+ if is_publish:
+ disc_id = dut.droid.wifiAwarePublish(session_id, config)
+ event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+ else:
+ disc_id = dut.droid.wifiAwareSubscribe(session_id, config)
+ event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+
+ if expect_success:
+ autils.wait_for_event(dut, event_name)
+ else:
+ autils.wait_for_event(dut, aconsts.SESSION_CB_ON_SESSION_CONFIG_FAILED)
+
+ return disc_id
+
+ ###############################
+
+ def test_max_discovery_sessions(self):
+ """Validate that the device can create as many discovery sessions as are
+ indicated in the device capabilities
+ """
+ dut = self.android_devices[0]
+
+ # attach
+ session_id = dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ service_name_template = 'GoogleTestService-%s-%d'
+
+ # start the max number of publish sessions
+ for i in range(dut.aware_capabilities[aconsts.CAP_MAX_PUBLISHES]):
+ # create publish discovery session of both types
+ pub_disc_id = self.start_discovery_session(
+ dut, session_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED
+ if i % 2 == 0 else aconsts.PUBLISH_TYPE_SOLICITED,
+ service_name_template % ('pub', i), True)
+
+ # start the max number of subscribe sessions
+ for i in range(dut.aware_capabilities[aconsts.CAP_MAX_SUBSCRIBES]):
+ # create publish discovery session of both types
+ sub_disc_id = self.start_discovery_session(
+ dut, session_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE
+ if i % 2 == 0 else aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ service_name_template % ('sub', i), True)
+
+ # start another publish & subscribe and expect failure
+ self.start_discovery_session(dut, session_id, True,
+ aconsts.PUBLISH_TYPE_UNSOLICITED,
+ service_name_template % ('pub', 900), False)
+ self.start_discovery_session(dut, session_id, False,
+ aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ service_name_template % ('pub', 901), False)
+
+ # delete one of the publishes and try again (see if can create subscribe
+ # instead - should not)
+ dut.droid.wifiAwareDestroyDiscoverySession(pub_disc_id)
+ self.start_discovery_session(dut, session_id, False,
+ aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ service_name_template % ('pub', 902), False)
+ self.start_discovery_session(dut, session_id, True,
+ aconsts.PUBLISH_TYPE_UNSOLICITED,
+ service_name_template % ('pub', 903), True)
+
+ # delete one of the subscribes and try again (see if can create publish
+ # instead - should not)
+ dut.droid.wifiAwareDestroyDiscoverySession(sub_disc_id)
+ self.start_discovery_session(dut, session_id, True,
+ aconsts.PUBLISH_TYPE_UNSOLICITED,
+ service_name_template % ('pub', 904), False)
+ self.start_discovery_session(dut, session_id, False,
+ aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ service_name_template % ('pub', 905), True)
+
+ def test_max_ndp(self):
+ """Validate that the device can create as many NDPs as are specified
+ by its capabilities.
+
+ Mechanics:
+ - Publisher on DUT (first device)
+ - Subscribers on all other devices
+ - On discovery set up NDP
+
+ Note: the test requires MAX_NDP + 2 devices to be validated. If these are
+ not available it will be skipped (not failed).
+ """
+ dut = self.android_devices[0]
+
+ # get max NDP: using first available device (assumes all devices are the
+ # same)
+ max_ndp = dut.aware_capabilities[aconsts.CAP_MAX_NDP_SESSIONS]
+
+ if len(self.android_devices) < max_ndp + 2:
+ raise signals.TestSkip('Setup does not contain a sufficient number of '
+ 'devices: need %d, have %d' % (max_ndp + 2,
+ len(self.android_devices)))
+
+ # attach
+ session_id = dut.droid.wifiAwareAttach()
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # start publisher
+ p_disc_id = self.start_discovery_session(
+ dut,
+ session_id,
+ is_publish=True,
+ dtype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ service_name=self.SERVICE_NAME,
+ expect_success=True)
+
+ # loop over other DUTs
+ for i in range(max_ndp + 1):
+ other_dut = self.android_devices[i + 1]
+
+ # attach
+ other_session_id = other_dut.droid.wifiAwareAttach()
+ autils.wait_for_event(other_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # start subscriber
+ s_disc_id = self.start_discovery_session(
+ other_dut,
+ other_session_id,
+ is_publish=False,
+ dtype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ service_name=self.SERVICE_NAME,
+ expect_success=True)
+
+ discovery_event = autils.wait_for_event(
+ other_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+ peer_id_on_sub = discovery_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+
+ # Subscriber: send message to peer (Publisher - so it knows our address)
+ other_dut.droid.wifiAwareSendMessage(
+ s_disc_id, peer_id_on_sub,
+ self.get_next_msg_id(), "ping", aconsts.MAX_TX_RETRIES)
+ autils.wait_for_event(other_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+
+ # Publisher: wait for received message
+ pub_rx_msg_event = autils.wait_for_event(
+ dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+ peer_id_on_pub = pub_rx_msg_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+
+ # publisher (responder): request network
+ p_req_key = autils.request_network(
+ dut,
+ dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub))
+
+ # subscriber (initiator): request network
+ s_req_key = autils.request_network(
+ other_dut,
+ other_dut.droid.wifiAwareCreateNetworkSpecifier(
+ s_disc_id, peer_id_on_sub))
+
+ # wait for network (or not - on the last iteration)
+ if i != max_ndp:
+ p_net_event = autils.wait_for_event_with_keys(
+ dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+ (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+ s_net_event = autils.wait_for_event_with_keys(
+ other_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+ (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+ p_aware_if = p_net_event['data'][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+ s_aware_if = s_net_event['data'][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+ self.log.info('Interface names: p=%s, s=%s', p_aware_if, s_aware_if)
+
+ p_ipv6 = dut.droid.connectivityGetLinkLocalIpv6Address(
+ p_aware_if).split('%')[0]
+ s_ipv6 = other_dut.droid.connectivityGetLinkLocalIpv6Address(
+ s_aware_if).split('%')[0]
+ self.log.info('Interface addresses (IPv6): p=%s, s=%s', p_ipv6, s_ipv6)
+ else:
+ autils.fail_on_event_with_keys(
+ dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+ (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+ autils.fail_on_event_with_keys(
+ other_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+ (cconsts.NETWORK_CB_KEY_ID, s_req_key))
diff --git a/acts/tests/google/wifi/aware/functional/DataPathTest.py b/acts/tests/google/wifi/aware/functional/DataPathTest.py
new file mode 100644
index 0000000..8414c1d
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/DataPathTest.py
@@ -0,0 +1,729 @@
+#!/usr/bin/python3.4
+#
+# 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 time
+
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class DataPathTest(AwareBaseTest):
+ """Set of tests for Wi-Fi Aware data-path."""
+
+ # configuration parameters used by tests
+ ENCR_TYPE_OPEN = 0
+ ENCR_TYPE_PASSPHRASE = 1
+ ENCR_TYPE_PMK = 2
+
+ PASSPHRASE = "This is some random passphrase - very very secure!!"
+ PASSPHRASE_MIN = "01234567"
+ PASSPHRASE_MAX = "012345678901234567890123456789012345678901234567890123456789012"
+ PMK = "ODU0YjE3YzdmNDJiNWI4NTQ2NDJjNDI3M2VkZTQyZGU="
+ PASSPHRASE2 = "This is some random passphrase - very very secure - but diff!!"
+ PMK2 = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="
+
+ PING_MSG = "ping"
+
+ # message re-transmit counter (increases reliability in open-environment)
+ # Note: reliability of message transmission is tested elsewhere
+ MSG_RETX_COUNT = 5 # hard-coded max value, internal API
+
+ # number of second to 'reasonably' wait to make sure that devices synchronize
+ # with each other - useful for OOB test cases, where the OOB discovery would
+ # take some time
+ WAIT_FOR_CLUSTER = 5
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def create_config(self, dtype):
+ """Create a base configuration based on input parameters.
+
+ Args:
+ dtype: Publish or Subscribe discovery type
+
+ Returns:
+ Discovery configuration object.
+ """
+ config = {}
+ config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+ config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceDataPath"
+ return config
+
+ def request_network(self, dut, ns):
+ """Request a Wi-Fi Aware network.
+
+ Args:
+ dut: Device
+ ns: Network specifier
+ Returns: the request key
+ """
+ network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+ return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+ def set_up_discovery(self, ptype, stype, get_peer_id):
+ """Set up discovery sessions and wait for service discovery.
+
+ Args:
+ ptype: Publish discovery type
+ stype: Subscribe discovery type
+ get_peer_id: Send a message across to get the peer's id
+ """
+ p_dut = self.android_devices[0]
+ p_dut.pretty_name = "Publisher"
+ s_dut = self.android_devices[1]
+ s_dut.pretty_name = "Subscriber"
+
+ # Publisher+Subscriber: attach and wait for confirmation
+ p_id = p_dut.droid.wifiAwareAttach()
+ autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ time.sleep(self.device_startup_offset)
+ s_id = s_dut.droid.wifiAwareAttach()
+ autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # Publisher: start publish and wait for confirmation
+ p_disc_id = p_dut.droid.wifiAwarePublish(p_id, self.create_config(ptype))
+ autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+ # Subscriber: start subscribe and wait for confirmation
+ s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, self.create_config(stype))
+ autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+ # Subscriber: wait for service discovery
+ discovery_event = autils.wait_for_event(
+ s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+ peer_id_on_sub = discovery_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+ peer_id_on_pub = None
+ if get_peer_id: # only need message to receive peer ID
+ # Subscriber: send message to peer (Publisher - so it knows our address)
+ s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+ self.get_next_msg_id(), self.PING_MSG,
+ self.MSG_RETX_COUNT)
+ autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+
+ # Publisher: wait for received message
+ pub_rx_msg_event = autils.wait_for_event(
+ p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+ peer_id_on_pub = pub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+ return (p_dut, s_dut, p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+ peer_id_on_pub)
+
+ def run_ib_data_path_test(self,
+ ptype,
+ stype,
+ encr_type,
+ use_peer_id,
+ passphrase_to_use=None):
+ """Runs the in-band data-path tests.
+
+ Args:
+ ptype: Publish discovery type
+ stype: Subscribe discovery type
+ encr_type: Encryption type, one of ENCR_TYPE_*
+ use_peer_id: On Responder (publisher): True to use peer ID, False to
+ accept any request
+ passphrase_to_use: The passphrase to use if encr_type=ENCR_TYPE_PASSPHRASE
+ If None then use self.PASSPHRASE
+ """
+ (p_dut, s_dut, p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+ peer_id_on_pub) = self.set_up_discovery(ptype, stype, use_peer_id)
+
+ passphrase = None
+ pmk = None
+ if encr_type == self.ENCR_TYPE_PASSPHRASE:
+ passphrase = self.PASSPHRASE if passphrase_to_use == None else passphrase_to_use
+ elif encr_type == self.ENCR_TYPE_PMK:
+ pmk = self.PMK
+
+ # Publisher: request network
+ p_req_key = self.request_network(
+ p_dut,
+ p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub if
+ use_peer_id else None, passphrase, pmk))
+
+ # Subscriber: request network
+ s_req_key = self.request_network(
+ s_dut,
+ s_dut.droid.wifiAwareCreateNetworkSpecifier(s_disc_id, peer_id_on_sub,
+ passphrase, pmk))
+
+ # Publisher & Subscriber: wait for network formation
+ p_net_event = autils.wait_for_event_with_keys(
+ p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+ autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+ (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+ s_net_event = autils.wait_for_event_with_keys(
+ s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+ autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+ (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+ p_aware_if = p_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+ s_aware_if = s_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+ self.log.info("Interface names: p=%s, s=%s", p_aware_if, s_aware_if)
+
+ p_ipv6 = p_dut.droid.connectivityGetLinkLocalIpv6Address(p_aware_if).split(
+ "%")[0]
+ s_ipv6 = s_dut.droid.connectivityGetLinkLocalIpv6Address(s_aware_if).split(
+ "%")[0]
+ self.log.info("Interface addresses (IPv6): p=%s, s=%s", p_ipv6, s_ipv6)
+
+ # TODO: possibly send messages back and forth, prefer to use netcat/nc
+
+ # terminate sessions and wait for ON_LOST callbacks
+ p_dut.droid.wifiAwareDestroy(p_id)
+ s_dut.droid.wifiAwareDestroy(s_id)
+
+ autils.wait_for_event_with_keys(
+ p_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LOST), (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+ autils.wait_for_event_with_keys(
+ s_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LOST), (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+ # clean-up
+ p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+ s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+ def run_oob_data_path_test(self, encr_type, use_peer_id):
+ """Runs the out-of-band data-path tests.
+
+ Args:
+ encr_type: Encryption type, one of ENCR_TYPE_*
+ use_peer_id: On Responder: True to use peer ID, False to accept any
+ request
+ """
+ init_dut = self.android_devices[0]
+ init_dut.pretty_name = "Initiator"
+ resp_dut = self.android_devices[1]
+ resp_dut.pretty_name = "Responder"
+
+ # Initiator+Responder: attach and wait for confirmation & identity
+ init_id = init_dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ init_ident_event = autils.wait_for_event(
+ init_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ init_mac = init_ident_event["data"]["mac"]
+ time.sleep(self.device_startup_offset)
+ resp_id = resp_dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ resp_ident_event = autils.wait_for_event(
+ resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ resp_mac = resp_ident_event["data"]["mac"]
+
+ # wait for for devices to synchronize with each other - there are no other
+ # mechanisms to make sure this happens for OOB discovery (except retrying
+ # to execute the data-path request)
+ time.sleep(self.WAIT_FOR_CLUSTER)
+
+ passphrase = None
+ pmk = None
+ if encr_type == self.ENCR_TYPE_PASSPHRASE:
+ passphrase = self.PASSPHRASE
+ elif encr_type == self.ENCR_TYPE_PMK:
+ pmk = self.PMK
+
+ # Responder: request network
+ resp_req_key = self.request_network(
+ resp_dut,
+ resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+ resp_id, aconsts.DATA_PATH_RESPONDER, init_mac
+ if use_peer_id else None, passphrase, pmk))
+
+ # Initiator: request network
+ init_req_key = self.request_network(
+ init_dut,
+ init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+ init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, passphrase, pmk))
+
+ # Initiator & Responder: wait for network formation
+ init_net_event = autils.wait_for_event_with_keys(
+ init_dut, cconsts.EVENT_NETWORK_CALLBACK,
+ autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+ (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+ resp_net_event = autils.wait_for_event_with_keys(
+ resp_dut, cconsts.EVENT_NETWORK_CALLBACK,
+ autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+ (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+ init_aware_if = init_net_event["data"][
+ cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+ resp_aware_if = resp_net_event["data"][
+ cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+ self.log.info("Interface names: I=%s, R=%s", init_aware_if, resp_aware_if)
+
+ init_ipv6 = init_dut.droid.connectivityGetLinkLocalIpv6Address(
+ init_aware_if).split("%")[0]
+ resp_ipv6 = resp_dut.droid.connectivityGetLinkLocalIpv6Address(
+ resp_aware_if).split("%")[0]
+ self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+ resp_ipv6)
+
+ # TODO: possibly send messages back and forth, prefer to use netcat/nc
+
+ # terminate sessions and wait for ON_LOST callbacks
+ init_dut.droid.wifiAwareDestroy(init_id)
+ resp_dut.droid.wifiAwareDestroy(resp_id)
+
+ autils.wait_for_event_with_keys(
+ init_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LOST), (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+ autils.wait_for_event_with_keys(
+ resp_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+ (cconsts.NETWORK_CB_KEY_EVENT,
+ cconsts.NETWORK_CB_LOST), (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+ # clean-up
+ resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+ init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+ def run_mismatched_ib_data_path_test(self, pub_mismatch, sub_mismatch):
+ """Runs the negative in-band data-path tests: mismatched peer ID.
+
+ Args:
+ pub_mismatch: Mismatch the publisher's ID
+ sub_mismatch: Mismatch the subscriber's ID
+ """
+ (p_dut, s_dut, p_id, s_id, p_disc_id, s_disc_id,
+ peer_id_on_sub, peer_id_on_pub) = self.set_up_discovery(
+ aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE, True)
+
+ if pub_mismatch:
+ peer_id_on_pub = peer_id_on_pub -1
+ if sub_mismatch:
+ peer_id_on_sub = peer_id_on_sub - 1
+
+ # Publisher: request network
+ p_req_key = self.request_network(
+ p_dut,
+ p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub,
+ None))
+
+ # Subscriber: request network
+ s_req_key = self.request_network(
+ s_dut,
+ s_dut.droid.wifiAwareCreateNetworkSpecifier(s_disc_id, peer_id_on_sub,
+ None))
+
+ # Publisher & Subscriber: fail on network formation
+ time.sleep(autils.EVENT_NDP_TIMEOUT)
+ autils.fail_on_event(p_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
+ autils.fail_on_event(s_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
+
+ # clean-up
+ p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+ s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+ def run_mismatched_oob_data_path_test(self,
+ init_mismatch_mac=False,
+ resp_mismatch_mac=False,
+ init_encr_type=ENCR_TYPE_OPEN,
+ resp_encr_type=ENCR_TYPE_OPEN):
+ """Runs the negative out-of-band data-path tests: mismatched information
+ between Responder and Initiator.
+
+ Args:
+ init_mismatch_mac: True to mismatch the Initiator MAC address
+ resp_mismatch_mac: True to mismatch the Responder MAC address
+ init_encr_type: Encryption type of Initiator - ENCR_TYPE_*
+ resp_encr_type: Encryption type of Responder - ENCR_TYPE_*
+ """
+ init_dut = self.android_devices[0]
+ init_dut.pretty_name = "Initiator"
+ resp_dut = self.android_devices[1]
+ resp_dut.pretty_name = "Responder"
+
+ # Initiator+Responder: attach and wait for confirmation & identity
+ init_id = init_dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ init_ident_event = autils.wait_for_event(
+ init_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ init_mac = init_ident_event["data"]["mac"]
+ time.sleep(self.device_startup_offset)
+ resp_id = resp_dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ resp_ident_event = autils.wait_for_event(
+ resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ resp_mac = resp_ident_event["data"]["mac"]
+
+ if init_mismatch_mac: # assumes legit ones don't start with "00"
+ init_mac = "00" + init_mac[2:]
+ if resp_mismatch_mac:
+ resp_mac = "00" + resp_mac[2:]
+
+ # wait for for devices to synchronize with each other - there are no other
+ # mechanisms to make sure this happens for OOB discovery (except retrying
+ # to execute the data-path request)
+ time.sleep(self.WAIT_FOR_CLUSTER)
+
+ # set up separate keys: even if types are the same we want a mismatch
+ init_passphrase = None
+ init_pmk = None
+ if init_encr_type == self.ENCR_TYPE_PASSPHRASE:
+ init_passphrase = self.PASSPHRASE
+ elif init_encr_type == self.ENCR_TYPE_PMK:
+ init_pmk = self.PMK
+
+ resp_passphrase = None
+ resp_pmk = None
+ if resp_encr_type == self.ENCR_TYPE_PASSPHRASE:
+ resp_passphrase = self.PASSPHRASE2
+ elif resp_encr_type == self.ENCR_TYPE_PMK:
+ resp_pmk = self.PMK2
+
+ # Responder: request network
+ resp_req_key = self.request_network(
+ resp_dut,
+ resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+ resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, resp_passphrase,
+ resp_pmk))
+
+ # Initiator: request network
+ init_req_key = self.request_network(
+ init_dut,
+ init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+ init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, init_passphrase,
+ init_pmk))
+
+ # Initiator & Responder: fail on network formation
+ time.sleep(autils.EVENT_NDP_TIMEOUT)
+ autils.fail_on_event(init_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
+ autils.fail_on_event(resp_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
+
+ # clean-up
+ resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+ init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+
+ #######################################
+ # Positive In-Band (IB) tests key:
+ #
+ # names is: test_ib_<pub_type>_<sub_type>_<encr_type>_<peer_spec>
+ # where:
+ #
+ # pub_type: Type of publish discovery session: unsolicited or solicited.
+ # sub_type: Type of subscribe discovery session: passive or active.
+ # encr_type: Encription type: open, passphrase
+ # peer_spec: Peer specification method: any or specific
+ #
+ # Note: In-Band means using Wi-Fi Aware for discovery and referring to the
+ # peer using the Aware-provided peer handle (as opposed to a MAC address).
+ #######################################
+
+ def test_ib_unsolicited_passive_open_specific(self):
+ """Data-path: in-band, unsolicited/passive, open encryption, specific peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ encr_type=self.ENCR_TYPE_OPEN,
+ use_peer_id=True)
+
+ def test_ib_unsolicited_passive_open_any(self):
+ """Data-path: in-band, unsolicited/passive, open encryption, any peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ encr_type=self.ENCR_TYPE_OPEN,
+ use_peer_id=False)
+
+ def test_ib_unsolicited_passive_passphrase_specific(self):
+ """Data-path: in-band, unsolicited/passive, passphrase, specific peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ encr_type=self.ENCR_TYPE_PASSPHRASE,
+ use_peer_id=True)
+
+ def test_ib_unsolicited_passive_passphrase_any(self):
+ """Data-path: in-band, unsolicited/passive, passphrase, any peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ encr_type=self.ENCR_TYPE_PASSPHRASE,
+ use_peer_id=False)
+
+ def test_ib_unsolicited_passive_pmk_specific(self):
+ """Data-path: in-band, unsolicited/passive, PMK, specific peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ encr_type=self.ENCR_TYPE_PMK,
+ use_peer_id=True)
+
+ def test_ib_unsolicited_passive_pmk_any(self):
+ """Data-path: in-band, unsolicited/passive, PMK, any peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ encr_type=self.ENCR_TYPE_PMK,
+ use_peer_id=False)
+
+ def test_ib_solicited_active_open_specific(self):
+ """Data-path: in-band, solicited/active, open encryption, specific peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ encr_type=self.ENCR_TYPE_OPEN,
+ use_peer_id=True)
+
+ def test_ib_solicited_active_open_any(self):
+ """Data-path: in-band, solicited/active, open encryption, any peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ encr_type=self.ENCR_TYPE_OPEN,
+ use_peer_id=False)
+
+ def test_ib_solicited_active_passphrase_specific(self):
+ """Data-path: in-band, solicited/active, passphrase, specific peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ encr_type=self.ENCR_TYPE_PASSPHRASE,
+ use_peer_id=True)
+
+ def test_ib_solicited_active_passphrase_any(self):
+ """Data-path: in-band, solicited/active, passphrase, any peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ encr_type=self.ENCR_TYPE_PASSPHRASE,
+ use_peer_id=False)
+
+ def test_ib_solicited_active_pmk_specific(self):
+ """Data-path: in-band, solicited/active, PMK, specific peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ encr_type=self.ENCR_TYPE_PMK,
+ use_peer_id=True)
+
+ def test_ib_solicited_active_pmk_any(self):
+ """Data-path: in-band, solicited/active, PMK, any peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_ib_data_path_test(
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ encr_type=self.ENCR_TYPE_PMK,
+ use_peer_id=False)
+
+ #######################################
+ # Positive Out-of-Band (OOB) tests key:
+ #
+ # names is: test_oob_<encr_type>_<peer_spec>
+ # where:
+ #
+ # encr_type: Encription type: open, passphrase
+ # peer_spec: Peer specification method: any or specific
+ #
+ # Note: Out-of-Band means using a non-Wi-Fi Aware mechanism for discovery and
+ # exchange of MAC addresses and then Wi-Fi Aware for data-path.
+ #######################################
+
+ def test_oob_open_specific(self):
+ """Data-path: out-of-band, open encryption, specific peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_oob_data_path_test(
+ encr_type=self.ENCR_TYPE_OPEN,
+ use_peer_id=True)
+
+ def test_oob_open_any(self):
+ """Data-path: out-of-band, open encryption, any peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_oob_data_path_test(
+ encr_type=self.ENCR_TYPE_OPEN,
+ use_peer_id=False)
+
+ def test_oob_passphrase_specific(self):
+ """Data-path: out-of-band, passphrase, specific peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_oob_data_path_test(
+ encr_type=self.ENCR_TYPE_PASSPHRASE,
+ use_peer_id=True)
+
+ def test_oob_passphrase_any(self):
+ """Data-path: out-of-band, passphrase, any peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_oob_data_path_test(
+ encr_type=self.ENCR_TYPE_PASSPHRASE,
+ use_peer_id=False)
+
+ def test_oob_pmk_specific(self):
+ """Data-path: out-of-band, PMK, specific peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_oob_data_path_test(
+ encr_type=self.ENCR_TYPE_PMK,
+ use_peer_id=True)
+
+ def test_oob_pmk_any(self):
+ """Data-path: out-of-band, PMK, any peer
+
+ Verifies end-to-end discovery + data-path creation.
+ """
+ self.run_oob_data_path_test(
+ encr_type=self.ENCR_TYPE_PMK,
+ use_peer_id=False)
+
+ ##############################################################
+
+ def test_passphrase_min(self):
+ """Data-path: minimum passphrase length
+
+ Use in-band, unsolicited/passive, any peer combination
+ """
+ self.run_ib_data_path_test(ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ encr_type=self.ENCR_TYPE_PASSPHRASE,
+ use_peer_id=False,
+ passphrase_to_use=self.PASSPHRASE_MIN)
+
+ def test_passphrase_max(self):
+ """Data-path: maximum passphrase length
+
+ Use in-band, unsolicited/passive, any peer combination
+ """
+ self.run_ib_data_path_test(ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ encr_type=self.ENCR_TYPE_PASSPHRASE,
+ use_peer_id=False,
+ passphrase_to_use=self.PASSPHRASE_MAX)
+
+ def test_negative_mismatch_publisher_peer_id(self):
+ """Data-path: failure when publisher peer ID is mismatched"""
+ self.run_mismatched_ib_data_path_test(pub_mismatch=True, sub_mismatch=False)
+
+ def test_negative_mismatch_subscriber_peer_id(self):
+ """Data-path: failure when subscriber peer ID is mismatched"""
+ self.run_mismatched_ib_data_path_test(pub_mismatch=False, sub_mismatch=True)
+
+ def test_negative_mismatch_init_mac(self):
+ """Data-path: failure when Initiator MAC address mismatch"""
+ self.run_mismatched_oob_data_path_test(
+ init_mismatch_mac=True,
+ resp_mismatch_mac=False)
+
+ def test_negative_mismatch_resp_mac(self):
+ """Data-path: failure when Responder MAC address mismatch"""
+ self.run_mismatched_oob_data_path_test(
+ init_mismatch_mac=False,
+ resp_mismatch_mac=True)
+
+ def test_negative_mismatch_passphrase(self):
+ """Data-path: failure when passphrases mismatch"""
+ self.run_mismatched_oob_data_path_test(
+ init_encr_type=self.ENCR_TYPE_PASSPHRASE,
+ resp_encr_type=self.ENCR_TYPE_PASSPHRASE)
+
+ def test_negative_mismatch_pmk(self):
+ """Data-path: failure when PMK mismatch"""
+ self.run_mismatched_oob_data_path_test(
+ init_encr_type=self.ENCR_TYPE_PMK,
+ resp_encr_type=self.ENCR_TYPE_PMK)
+
+ def test_negative_mismatch_open_passphrase(self):
+ """Data-path: failure when initiator is open, and responder passphrase"""
+ self.run_mismatched_oob_data_path_test(
+ init_encr_type=self.ENCR_TYPE_OPEN,
+ resp_encr_type=self.ENCR_TYPE_PASSPHRASE)
+
+ def test_negative_mismatch_open_pmk(self):
+ """Data-path: failure when initiator is open, and responder PMK"""
+ self.run_mismatched_oob_data_path_test(
+ init_encr_type=self.ENCR_TYPE_OPEN,
+ resp_encr_type=self.ENCR_TYPE_PMK)
+
+ def test_negative_mismatch_pmk_passphrase(self):
+ """Data-path: failure when initiator is pmk, and responder passphrase"""
+ self.run_mismatched_oob_data_path_test(
+ init_encr_type=self.ENCR_TYPE_PMK,
+ resp_encr_type=self.ENCR_TYPE_PASSPHRASE)
+
+ def test_negative_mismatch_passphrase_open(self):
+ """Data-path: failure when initiator is passphrase, and responder open"""
+ self.run_mismatched_oob_data_path_test(
+ init_encr_type=self.ENCR_TYPE_PASSPHRASE,
+ resp_encr_type=self.ENCR_TYPE_OPEN)
+
+ def test_negative_mismatch_pmk_open(self):
+ """Data-path: failure when initiator is PMK, and responder open"""
+ self.run_mismatched_oob_data_path_test(
+ init_encr_type=self.ENCR_TYPE_PMK,
+ resp_encr_type=self.ENCR_TYPE_OPEN)
+
+ def test_negative_mismatch_passphrase_pmk(self):
+ """Data-path: failure when initiator is passphrase, and responder pmk"""
+ self.run_mismatched_oob_data_path_test(
+ init_encr_type=self.ENCR_TYPE_PASSPHRASE,
+ resp_encr_type=self.ENCR_TYPE_OPEN)
diff --git a/acts/tests/google/wifi/aware/functional/DiscoveryTest.py b/acts/tests/google/wifi/aware/functional/DiscoveryTest.py
new file mode 100644
index 0000000..a1ace3e
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/DiscoveryTest.py
@@ -0,0 +1,811 @@
+#!/usr/bin/python3.4
+#
+# 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 string
+import time
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class DiscoveryTest(AwareBaseTest):
+ """Set of tests for Wi-Fi Aware discovery."""
+
+ # configuration parameters used by tests
+ PAYLOAD_SIZE_MIN = 0
+ PAYLOAD_SIZE_TYPICAL = 1
+ PAYLOAD_SIZE_MAX = 2
+
+ # message strings
+ query_msg = "How are you doing? 你好嗎?"
+ response_msg = "Doing ok - thanks! 做的不錯 - 謝謝!"
+
+ # message re-transmit counter (increases reliability in open-environment)
+ # Note: reliability of message transmission is tested elsewhere
+ msg_retx_count = 5 # hard-coded max value, internal API
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def create_base_config(self, caps, is_publish, ptype, stype, payload_size,
+ ttl, term_ind_on, null_match):
+ """Create a base configuration based on input parameters.
+
+ Args:
+ caps: device capability dictionary
+ is_publish: True if a publish config, else False
+ ptype: unsolicited or solicited (used if is_publish is True)
+ stype: passive or active (used if is_publish is False)
+ payload_size: min, typical, max (PAYLOAD_SIZE_xx)
+ ttl: time-to-live configuration (0 - forever)
+ term_ind_on: is termination indication enabled
+ null_match: null-out the middle match filter
+ Returns:
+ publish discovery configuration object.
+ """
+ config = {}
+ if is_publish:
+ config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = ptype
+ else:
+ config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = stype
+ config[aconsts.DISCOVERY_KEY_TTL] = ttl
+ config[aconsts.DISCOVERY_KEY_TERM_CB_ENABLED] = term_ind_on
+ if payload_size == self.PAYLOAD_SIZE_MIN:
+ config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "a"
+ config[aconsts.DISCOVERY_KEY_SSI] = None
+ config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = []
+ elif payload_size == self.PAYLOAD_SIZE_TYPICAL:
+ config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceX"
+ if is_publish:
+ config[aconsts.DISCOVERY_KEY_SSI] = string.ascii_letters
+ else:
+ config[aconsts.DISCOVERY_KEY_SSI] = string.ascii_letters[::
+ -1] # reverse
+ config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(
+ [(10).to_bytes(1, byteorder="big"), "hello there string"
+ if not null_match else None,
+ bytes(range(40))])
+ else: # PAYLOAD_SIZE_MAX
+ config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "VeryLong" + "X" * (
+ caps[aconsts.CAP_MAX_SERVICE_NAME_LEN] - 8)
+ config[aconsts.DISCOVERY_KEY_SSI] = ("P" if is_publish else "S") * caps[
+ aconsts.CAP_MAX_SERVICE_SPECIFIC_INFO_LEN]
+ mf = autils.construct_max_match_filter(
+ caps[aconsts.CAP_MAX_MATCH_FILTER_LEN])
+ if null_match:
+ mf[2] = None
+ config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(mf)
+
+ return config
+
+ def create_publish_config(self, caps, ptype, payload_size, ttl, term_ind_on,
+ null_match):
+ """Create a publish configuration based on input parameters.
+
+ Args:
+ caps: device capability dictionary
+ ptype: unsolicited or solicited
+ payload_size: min, typical, max (PAYLOAD_SIZE_xx)
+ ttl: time-to-live configuration (0 - forever)
+ term_ind_on: is termination indication enabled
+ null_match: null-out the middle match filter
+ Returns:
+ publish discovery configuration object.
+ """
+ return self.create_base_config(caps, True, ptype, None, payload_size, ttl,
+ term_ind_on, null_match)
+
+ def create_subscribe_config(self, caps, stype, payload_size, ttl, term_ind_on,
+ null_match):
+ """Create a subscribe configuration based on input parameters.
+
+ Args:
+ caps: device capability dictionary
+ stype: passive or active
+ payload_size: min, typical, max (PAYLOAD_SIZE_xx)
+ ttl: time-to-live configuration (0 - forever)
+ term_ind_on: is termination indication enabled
+ null_match: null-out the middle match filter
+ Returns:
+ subscribe discovery configuration object.
+ """
+ return self.create_base_config(caps, False, None, stype, payload_size, ttl,
+ term_ind_on, null_match)
+
+ def positive_discovery_test_utility(self, ptype, stype, payload_size):
+ """Utility which runs a positive discovery test:
+ - Discovery (publish/subscribe) with TTL=0 (non-self-terminating)
+ - Exchange messages
+ - Update publish/subscribe
+ - Terminate
+
+ Args:
+ ptype: Publish discovery type
+ stype: Subscribe discovery type
+ payload_size: One of PAYLOAD_SIZE_* constants - MIN, TYPICAL, MAX
+ """
+ p_dut = self.android_devices[0]
+ p_dut.pretty_name = "Publisher"
+ s_dut = self.android_devices[1]
+ s_dut.pretty_name = "Subscriber"
+
+ # Publisher+Subscriber: attach and wait for confirmation
+ p_id = p_dut.droid.wifiAwareAttach(False)
+ autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ time.sleep(self.device_startup_offset)
+ s_id = s_dut.droid.wifiAwareAttach(False)
+ autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # Publisher: start publish and wait for confirmation
+ p_config = self.create_publish_config(
+ p_dut.aware_capabilities,
+ ptype,
+ payload_size,
+ ttl=0,
+ term_ind_on=False,
+ null_match=False)
+ p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
+ autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+ # Subscriber: start subscribe and wait for confirmation
+ s_config = self.create_subscribe_config(
+ s_dut.aware_capabilities,
+ stype,
+ payload_size,
+ ttl=0,
+ term_ind_on=False,
+ null_match=True)
+ s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
+ autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+ # Subscriber: wait for service discovery
+ discovery_event = autils.wait_for_event(
+ s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+ peer_id_on_sub = discovery_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+ # Subscriber: validate contents of discovery:
+ # - SSI: publisher's
+ # - Match filter: UNSOLICITED - publisher, SOLICITED - subscriber
+ autils.assert_equal_strings(
+ bytes(discovery_event["data"][
+ aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode("utf-8"),
+ p_config[aconsts.DISCOVERY_KEY_SSI],
+ "Discovery mismatch: service specific info (SSI)")
+ asserts.assert_equal(
+ autils.decode_list(
+ discovery_event["data"][aconsts.SESSION_CB_KEY_MATCH_FILTER_LIST]),
+ autils.decode_list(p_config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]
+ if ptype == aconsts.PUBLISH_TYPE_UNSOLICITED else
+ s_config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]),
+ "Discovery mismatch: match filter")
+
+ # Subscriber: send message to peer (Publisher)
+ s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+ self.get_next_msg_id(), self.query_msg,
+ self.msg_retx_count)
+ autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+
+ # Publisher: wait for received message
+ pub_rx_msg_event = autils.wait_for_event(
+ p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+ peer_id_on_pub = pub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+ # Publisher: validate contents of message
+ asserts.assert_equal(
+ pub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+ self.query_msg, "Subscriber -> Publisher message corrupted")
+
+ # Publisher: send message to peer (Subscriber)
+ p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub,
+ self.get_next_msg_id(), self.response_msg,
+ self.msg_retx_count)
+ autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+
+ # Subscriber: wait for received message
+ sub_rx_msg_event = autils.wait_for_event(
+ s_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+
+ # Subscriber: validate contents of message
+ asserts.assert_equal(
+ sub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_PEER_ID],
+ peer_id_on_sub,
+ "Subscriber received message from different peer ID then discovery!?")
+ autils.assert_equal_strings(
+ sub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+ self.response_msg, "Publisher -> Subscriber message corrupted")
+
+ # Subscriber: validate that we're not getting another Service Discovery
+ autils.fail_on_event(s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+
+ # Publisher: update publish and wait for confirmation
+ p_config[aconsts.DISCOVERY_KEY_SSI] = "something else"
+ p_dut.droid.wifiAwareUpdatePublish(p_disc_id, p_config)
+ autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED)
+
+ # Subscriber: expect a new service discovery
+ discovery_event = autils.wait_for_event(
+ s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+
+ # Subscriber: validate contents of discovery
+ autils.assert_equal_strings(
+ bytes(discovery_event["data"][
+ aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode("utf-8"),
+ p_config[aconsts.DISCOVERY_KEY_SSI],
+ "Discovery mismatch (after pub update): service specific info (SSI)")
+ asserts.assert_equal(
+ autils.decode_list(
+ discovery_event["data"][aconsts.SESSION_CB_KEY_MATCH_FILTER_LIST]),
+ autils.decode_list(p_config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]
+ if ptype == aconsts.PUBLISH_TYPE_UNSOLICITED else
+ s_config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]),
+ "Discovery mismatch: match filter")
+
+ # Subscribe: update subscribe and wait for confirmation
+ s_config = self.create_subscribe_config(
+ s_dut.aware_capabilities,
+ stype,
+ payload_size,
+ ttl=0,
+ term_ind_on=False,
+ null_match=False)
+ s_dut.droid.wifiAwareUpdateSubscribe(s_disc_id, s_config)
+ autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED)
+
+ # Publisher+Subscriber: Terminate sessions
+ p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
+ s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+
+ # sleep for timeout period and then verify all 'fail_on_event' together
+ time.sleep(autils.EVENT_TIMEOUT)
+
+ # verify that there were no other events
+ autils.verify_no_more_events(p_dut, timeout=0)
+ autils.verify_no_more_events(s_dut, timeout=0)
+
+ # verify that forbidden callbacks aren't called
+ autils.validate_forbidden_callbacks(p_dut, {aconsts.CB_EV_MATCH: 0})
+
+ def verify_discovery_session_term(self, dut, disc_id, config, is_publish,
+ term_ind_on):
+ """Utility to verify that the specified discovery session has terminated (by
+ waiting for the TTL and then attempting to reconfigure).
+
+ Args:
+ dut: device under test
+ disc_id: discovery id for the existing session
+ config: configuration of the existing session
+ is_publish: True if the configuration was publish, False if subscribe
+ term_ind_on: True if a termination indication is expected, False otherwise
+ """
+ # Wait for session termination
+ if term_ind_on:
+ autils.wait_for_event(
+ dut,
+ autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_TERMINATED,
+ disc_id))
+ else:
+ # can't defer wait to end since in any case have to wait for session to
+ # expire
+ autils.fail_on_event(
+ dut,
+ autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_TERMINATED,
+ disc_id))
+
+ # Validate that session expired by trying to configure it (expect failure)
+ config[aconsts.DISCOVERY_KEY_SSI] = "something else"
+ if is_publish:
+ dut.droid.wifiAwareUpdatePublish(disc_id, config)
+ else:
+ dut.droid.wifiAwareUpdateSubscribe(disc_id, config)
+
+ # The response to update discovery session is:
+ # term_ind_on=True: session was cleaned-up so won't get an explicit failure, but won't get a
+ # success either. Can check for no SESSION_CB_ON_SESSION_CONFIG_UPDATED but
+ # will defer to the end of the test (no events on queue).
+ # term_ind_on=False: session was not cleaned-up (yet). So expect
+ # SESSION_CB_ON_SESSION_CONFIG_FAILED.
+ if not term_ind_on:
+ autils.wait_for_event(
+ dut,
+ autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_CONFIG_FAILED,
+ disc_id))
+
+ def positive_ttl_test_utility(self, is_publish, ptype, stype, term_ind_on):
+ """Utility which runs a positive discovery session TTL configuration test
+
+ Iteration 1: Verify session started with TTL
+ Iteration 2: Verify session started without TTL and reconfigured with TTL
+ Iteration 3: Verify session started with (long) TTL and reconfigured with
+ (short) TTL
+
+ Args:
+ is_publish: True if testing publish, False if testing subscribe
+ ptype: Publish discovery type (used if is_publish is True)
+ stype: Subscribe discovery type (used if is_publish is False)
+ term_ind_on: Configuration of termination indication
+ """
+ SHORT_TTL = 5 # 5 seconds
+ LONG_TTL = 100 # 100 seconds
+ dut = self.android_devices[0]
+
+ # Attach and wait for confirmation
+ id = dut.droid.wifiAwareAttach(False)
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # Iteration 1: Start discovery session with TTL
+ config = self.create_base_config(dut.aware_capabilities, is_publish, ptype,
+ stype, self.PAYLOAD_SIZE_TYPICAL,
+ SHORT_TTL, term_ind_on, False)
+ if is_publish:
+ disc_id = dut.droid.wifiAwarePublish(id, config, True)
+ autils.wait_for_event(dut,
+ autils.decorate_event(
+ aconsts.SESSION_CB_ON_PUBLISH_STARTED, disc_id))
+ else:
+ disc_id = dut.droid.wifiAwareSubscribe(id, config, True)
+ autils.wait_for_event(
+ dut,
+ autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+ disc_id))
+
+ # Wait for session termination & verify
+ self.verify_discovery_session_term(dut, disc_id, config, is_publish,
+ term_ind_on)
+
+ # Iteration 2: Start a discovery session without TTL
+ config = self.create_base_config(dut.aware_capabilities, is_publish, ptype,
+ stype, self.PAYLOAD_SIZE_TYPICAL, 0,
+ term_ind_on, False)
+ if is_publish:
+ disc_id = dut.droid.wifiAwarePublish(id, config, True)
+ autils.wait_for_event(dut,
+ autils.decorate_event(
+ aconsts.SESSION_CB_ON_PUBLISH_STARTED, disc_id))
+ else:
+ disc_id = dut.droid.wifiAwareSubscribe(id, config, True)
+ autils.wait_for_event(
+ dut,
+ autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+ disc_id))
+
+ # Update with a TTL
+ config = self.create_base_config(dut.aware_capabilities, is_publish, ptype,
+ stype, self.PAYLOAD_SIZE_TYPICAL,
+ SHORT_TTL, term_ind_on, False)
+ if is_publish:
+ dut.droid.wifiAwareUpdatePublish(disc_id, config)
+ else:
+ dut.droid.wifiAwareUpdateSubscribe(disc_id, config)
+ autils.wait_for_event(
+ dut,
+ autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED,
+ disc_id))
+
+ # Wait for session termination & verify
+ self.verify_discovery_session_term(dut, disc_id, config, is_publish,
+ term_ind_on)
+
+ # Iteration 3: Start a discovery session with (long) TTL
+ config = self.create_base_config(dut.aware_capabilities, is_publish, ptype,
+ stype, self.PAYLOAD_SIZE_TYPICAL, LONG_TTL,
+ term_ind_on, False)
+ if is_publish:
+ disc_id = dut.droid.wifiAwarePublish(id, config, True)
+ autils.wait_for_event(dut,
+ autils.decorate_event(
+ aconsts.SESSION_CB_ON_PUBLISH_STARTED, disc_id))
+ else:
+ disc_id = dut.droid.wifiAwareSubscribe(id, config, True)
+ autils.wait_for_event(
+ dut,
+ autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+ disc_id))
+
+ # Update with a TTL
+ config = self.create_base_config(dut.aware_capabilities, is_publish, ptype,
+ stype, self.PAYLOAD_SIZE_TYPICAL,
+ SHORT_TTL, term_ind_on, False)
+ if is_publish:
+ dut.droid.wifiAwareUpdatePublish(disc_id, config)
+ else:
+ dut.droid.wifiAwareUpdateSubscribe(disc_id, config)
+ autils.wait_for_event(
+ dut,
+ autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED,
+ disc_id))
+
+ # Wait for session termination & verify
+ self.verify_discovery_session_term(dut, disc_id, config, is_publish,
+ term_ind_on)
+
+ # verify that there were no other events
+ autils.verify_no_more_events(dut)
+
+ # verify that forbidden callbacks aren't called
+ if not term_ind_on:
+ autils.validate_forbidden_callbacks(dut, {
+ aconsts.CB_EV_PUBLISH_TERMINATED: 0,
+ aconsts.CB_EV_SUBSCRIBE_TERMINATED: 0
+ })
+
+ def discovery_mismatch_test_utility(self,
+ is_expected_to_pass,
+ p_type,
+ s_type,
+ p_service_name=None,
+ s_service_name=None,
+ p_mf_1=None,
+ s_mf_1=None):
+ """Utility which runs the negative discovery test for mismatched service
+ configs.
+
+ Args:
+ is_expected_to_pass: True if positive test, False if negative
+ p_type: Publish discovery type
+ s_type: Subscribe discovery type
+ p_service_name: Publish service name (or None to leave unchanged)
+ s_service_name: Subscribe service name (or None to leave unchanged)
+ p_mf_1: Publish match filter element [1] (or None to leave unchanged)
+ s_mf_1: Subscribe match filter element [1] (or None to leave unchanged)
+ """
+ p_dut = self.android_devices[0]
+ p_dut.pretty_name = "Publisher"
+ s_dut = self.android_devices[1]
+ s_dut.pretty_name = "Subscriber"
+
+ # create configurations
+ p_config = self.create_publish_config(
+ p_dut.aware_capabilities,
+ p_type,
+ self.PAYLOAD_SIZE_TYPICAL,
+ ttl=0,
+ term_ind_on=False,
+ null_match=False)
+ if p_service_name is not None:
+ p_config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = p_service_name
+ if p_mf_1 is not None:
+ p_config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(
+ [(10).to_bytes(1, byteorder="big"),
+ p_mf_1,
+ bytes(range(40))])
+ s_config = self.create_publish_config(
+ s_dut.aware_capabilities,
+ s_type,
+ self.PAYLOAD_SIZE_TYPICAL,
+ ttl=0,
+ term_ind_on=False,
+ null_match=False)
+ if s_service_name is not None:
+ s_config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = s_service_name
+ if s_mf_1 is not None:
+ s_config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(
+ [(10).to_bytes(1, byteorder="big"),
+ s_mf_1,
+ bytes(range(40))])
+
+ p_id = p_dut.droid.wifiAwareAttach(False)
+ autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ time.sleep(self.device_startup_offset)
+ s_id = s_dut.droid.wifiAwareAttach(False)
+ autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # Publisher: start publish and wait for confirmation
+ p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
+ autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+ # Subscriber: start subscribe and wait for confirmation
+ s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
+ autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+ # Subscriber: fail on service discovery
+ if is_expected_to_pass:
+ autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+ else:
+ autils.fail_on_event(s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+
+ # Publisher+Subscriber: Terminate sessions
+ p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
+ s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+
+ # verify that there were no other events (including terminations)
+ time.sleep(autils.EVENT_TIMEOUT)
+ autils.verify_no_more_events(p_dut, timeout=0)
+ autils.verify_no_more_events(s_dut, timeout=0)
+
+
+ #######################################
+ # Positive tests key:
+ #
+ # names is: test_<pub_type>_<sub_type>_<size>
+ # where:
+ #
+ # pub_type: Type of publish discovery session: unsolicited or solicited.
+ # sub_type: Type of subscribe discovery session: passive or active.
+ # size: Size of payload fields (service name, service specific info, and match
+ # filter: typical, max, or min.
+ #######################################
+
+ def test_positive_unsolicited_passive_typical(self):
+ """Functional test case / Discovery test cases / positive test case:
+ - Solicited publish + passive subscribe
+ - Typical payload fields size
+
+ Verifies that discovery and message exchange succeeds.
+ """
+ self.positive_discovery_test_utility(
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ payload_size=self.PAYLOAD_SIZE_TYPICAL)
+
+ def test_positive_unsolicited_passive_min(self):
+ """Functional test case / Discovery test cases / positive test case:
+ - Solicited publish + passive subscribe
+ - Minimal payload fields size
+
+ Verifies that discovery and message exchange succeeds.
+ """
+ self.positive_discovery_test_utility(
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ payload_size=self.PAYLOAD_SIZE_MIN)
+
+ def test_positive_unsolicited_passive_max(self):
+ """Functional test case / Discovery test cases / positive test case:
+ - Solicited publish + passive subscribe
+ - Maximal payload fields size
+
+ Verifies that discovery and message exchange succeeds.
+ """
+ self.positive_discovery_test_utility(
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ payload_size=self.PAYLOAD_SIZE_MAX)
+
+
+ def test_positive_solicited_active_typical(self):
+ """Functional test case / Discovery test cases / positive test case:
+ - Unsolicited publish + active subscribe
+ - Typical payload fields size
+
+ Verifies that discovery and message exchange succeeds.
+ """
+ self.positive_discovery_test_utility(
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ payload_size=self.PAYLOAD_SIZE_TYPICAL)
+
+ def test_positive_solicited_active_min(self):
+ """Functional test case / Discovery test cases / positive test case:
+ - Unsolicited publish + active subscribe
+ - Minimal payload fields size
+
+ Verifies that discovery and message exchange succeeds.
+ """
+ self.positive_discovery_test_utility(
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ payload_size=self.PAYLOAD_SIZE_MIN)
+
+ def test_positive_solicited_active_max(self):
+ """Functional test case / Discovery test cases / positive test case:
+ - Unsolicited publish + active subscribe
+ - Maximal payload fields size
+
+ Verifies that discovery and message exchange succeeds.
+ """
+ self.positive_discovery_test_utility(
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ payload_size=self.PAYLOAD_SIZE_MAX)
+
+ #######################################
+ # TTL tests key:
+ #
+ # names is: test_ttl_<pub_type|sub_type>_<term_ind>
+ # where:
+ #
+ # pub_type: Type of publish discovery session: unsolicited or solicited.
+ # sub_type: Type of subscribe discovery session: passive or active.
+ # term_ind: ind_on or ind_off
+ #######################################
+
+ def test_ttl_unsolicited_ind_on(self):
+ """Functional test case / Discovery test cases / TTL test case:
+ - Unsolicited publish
+ - Termination indication enabled
+ """
+ self.positive_ttl_test_utility(
+ is_publish=True,
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=None,
+ term_ind_on=True)
+
+ def test_ttl_unsolicited_ind_off(self):
+ """Functional test case / Discovery test cases / TTL test case:
+ - Unsolicited publish
+ - Termination indication disabled
+ """
+ self.positive_ttl_test_utility(
+ is_publish=True,
+ ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ stype=None,
+ term_ind_on=False)
+
+ def test_ttl_solicited_ind_on(self):
+ """Functional test case / Discovery test cases / TTL test case:
+ - Solicited publish
+ - Termination indication enabled
+ """
+ self.positive_ttl_test_utility(
+ is_publish=True,
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=None,
+ term_ind_on=True)
+
+ def test_ttl_solicited_ind_off(self):
+ """Functional test case / Discovery test cases / TTL test case:
+ - Solicited publish
+ - Termination indication disabled
+ """
+ self.positive_ttl_test_utility(
+ is_publish=True,
+ ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+ stype=None,
+ term_ind_on=False)
+
+ def test_ttl_passive_ind_on(self):
+ """Functional test case / Discovery test cases / TTL test case:
+ - Passive subscribe
+ - Termination indication enabled
+ """
+ self.positive_ttl_test_utility(
+ is_publish=False,
+ ptype=None,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ term_ind_on=True)
+
+ def test_ttl_passive_ind_off(self):
+ """Functional test case / Discovery test cases / TTL test case:
+ - Passive subscribe
+ - Termination indication disabled
+ """
+ self.positive_ttl_test_utility(
+ is_publish=False,
+ ptype=None,
+ stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ term_ind_on=False)
+
+ def test_ttl_active_ind_on(self):
+ """Functional test case / Discovery test cases / TTL test case:
+ - Active subscribe
+ - Termination indication enabled
+ """
+ self.positive_ttl_test_utility(
+ is_publish=False,
+ ptype=None,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ term_ind_on=True)
+
+ def test_ttl_active_ind_off(self):
+ """Functional test case / Discovery test cases / TTL test case:
+ - Active subscribe
+ - Termination indication disabled
+ """
+ self.positive_ttl_test_utility(
+ is_publish=False,
+ ptype=None,
+ stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ term_ind_on=False)
+
+ #######################################
+ # Mismatched service name tests key:
+ #
+ # names is: test_mismatch_service_name_<pub_type>_<sub_type>
+ # where:
+ #
+ # pub_type: Type of publish discovery session: unsolicited or solicited.
+ # sub_type: Type of subscribe discovery session: passive or active.
+ #######################################
+
+ def test_mismatch_service_name_unsolicited_passive(self):
+ """Functional test case / Discovery test cases / Mismatch service name
+ - Unsolicited publish
+ - Passive subscribe
+ """
+ self.discovery_mismatch_test_utility(
+ is_expected_to_pass=False,
+ p_type=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ s_type=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ p_service_name="GoogleTestServiceXXX",
+ s_service_name="GoogleTestServiceYYY")
+
+ def test_mismatch_service_name_solicited_active(self):
+ """Functional test case / Discovery test cases / Mismatch service name
+ - Solicited publish
+ - Active subscribe
+ """
+ self.discovery_mismatch_test_utility(
+ is_expected_to_pass=False,
+ p_type=aconsts.PUBLISH_TYPE_SOLICITED,
+ s_type=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ p_service_name="GoogleTestServiceXXX",
+ s_service_name="GoogleTestServiceYYY")
+
+ #######################################
+ # Mismatched discovery session type tests key:
+ #
+ # names is: test_mismatch_service_type_<pub_type>_<sub_type>
+ # where:
+ #
+ # pub_type: Type of publish discovery session: unsolicited or solicited.
+ # sub_type: Type of subscribe discovery session: passive or active.
+ #######################################
+
+ def test_mismatch_service_type_unsolicited_active(self):
+ """Functional test case / Discovery test cases / Mismatch service name
+ - Unsolicited publish
+ - Active subscribe
+ """
+ self.discovery_mismatch_test_utility(
+ is_expected_to_pass=True,
+ p_type=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ s_type=aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+ def test_mismatch_service_type_solicited_passive(self):
+ """Functional test case / Discovery test cases / Mismatch service name
+ - Unsolicited publish
+ - Active subscribe
+ """
+ self.discovery_mismatch_test_utility(
+ is_expected_to_pass=False,
+ p_type=aconsts.PUBLISH_TYPE_SOLICITED,
+ s_type=aconsts.SUBSCRIBE_TYPE_PASSIVE)
+
+ #######################################
+ # Mismatched discovery match filter tests key:
+ #
+ # names is: test_mismatch_match_filter_<pub_type>_<sub_type>
+ # where:
+ #
+ # pub_type: Type of publish discovery session: unsolicited or solicited.
+ # sub_type: Type of subscribe discovery session: passive or active.
+ #######################################
+
+ def test_mismatch_match_filter_unsolicited_passive(self):
+ """Functional test case / Discovery test cases / Mismatch match filter
+ - Unsolicited publish
+ - Passive subscribe
+ """
+ self.discovery_mismatch_test_utility(
+ is_expected_to_pass=False,
+ p_type=aconsts.PUBLISH_TYPE_UNSOLICITED,
+ s_type=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ p_mf_1="hello there string",
+ s_mf_1="goodbye there string")
+
+ def test_mismatch_match_filter_solicited_active(self):
+ """Functional test case / Discovery test cases / Mismatch match filter
+ - Solicited publish
+ - Active subscribe
+ """
+ self.discovery_mismatch_test_utility(
+ is_expected_to_pass=False,
+ p_type=aconsts.PUBLISH_TYPE_SOLICITED,
+ s_type=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ p_mf_1="hello there string",
+ s_mf_1="goodbye there string")
diff --git a/acts/tests/google/wifi/aware/functional/MacRandomTest.py b/acts/tests/google/wifi/aware/functional/MacRandomTest.py
new file mode 100644
index 0000000..8dabb04
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/MacRandomTest.py
@@ -0,0 +1,133 @@
+#!/usr/bin/python3.4
+#
+# 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 time
+
+from acts import asserts
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class MacRandomTest(AwareBaseTest):
+ """Set of tests for Wi-Fi Aware MAC address randomization of NMI (NAN
+ management interface) and NDI (NAN data interface)."""
+
+ NUM_ITERATIONS = 10
+
+ # number of second to 'reasonably' wait to make sure that devices synchronize
+ # with each other - useful for OOB test cases, where the OOB discovery would
+ # take some time
+ WAIT_FOR_CLUSTER = 5
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def request_network(self, dut, ns):
+ """Request a Wi-Fi Aware network.
+
+ Args:
+ dut: Device
+ ns: Network specifier
+ Returns: the request key
+ """
+ network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+ return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+ ##########################################################################
+
+ def test_nmi_ndi_randomization_on_enable(self):
+ """Validate randomization of the NMI (NAN management interface) and all NDIs
+ (NAN data-interface) on each enable/disable cycle"""
+ dut = self.android_devices[0]
+
+ # DUT: attach and wait for confirmation & identity 10 times
+ mac_addresses = {}
+ for i in range(self.NUM_ITERATIONS):
+ id = dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+ ident_event = autils.wait_for_event(dut,
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+
+ # process NMI
+ mac = ident_event["data"]["mac"]
+ dut.log.info("NMI=%s", mac)
+ if mac in mac_addresses:
+ mac_addresses[mac] = mac_addresses[mac] + 1
+ else:
+ mac_addresses[mac] = 1
+
+ # process NDIs
+ time.sleep(5) # wait for NDI creation to complete
+ for j in range(dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES]):
+ ndi_interface = "%s%d" % (aconsts.AWARE_NDI_PREFIX, j)
+ ndi_mac = autils.get_mac_addr(dut, ndi_interface)
+ dut.log.info("NDI %s=%s", ndi_interface, ndi_mac)
+ if ndi_mac in mac_addresses:
+ mac_addresses[ndi_mac] = mac_addresses[ndi_mac] + 1
+ else:
+ mac_addresses[ndi_mac] = 1
+
+ dut.droid.wifiAwareDestroy(id)
+
+ # Test for uniqueness
+ for mac in mac_addresses.keys():
+ if mac_addresses[mac] != 1:
+ asserts.fail("MAC address %s repeated %d times (all=%s)" % (mac,
+ mac_addresses[mac], mac_addresses))
+
+ # Verify that infra interface (e.g. wlan0) MAC address is not used for NMI
+ infra_mac = autils.get_wifi_mac_address(dut)
+ asserts.assert_false(
+ infra_mac in mac_addresses,
+ "Infrastructure MAC address (%s) is used for Aware NMI (all=%s)" %
+ (infra_mac, mac_addresses))
+
+ def test_nmi_randomization_on_interval(self):
+ """Validate randomization of the NMI (NAN management interface) on a set
+ interval. Default value is 30 minutes - change to a small value to allow
+ testing in real-time"""
+ RANDOM_INTERVAL = 120 # minimal value in current implementation
+
+ dut = self.android_devices[0]
+
+ # set randomization interval to 5 seconds
+ dut.adb.shell("cmd wifiaware native_api set mac_random_interval_sec %d" %
+ RANDOM_INTERVAL)
+
+ # attach and wait for first identity
+ id = dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+ ident_event = autils.wait_for_event(dut,
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ mac1 = ident_event["data"]["mac"]
+
+ # wait for second identity callback
+ # Note: exact randomization interval is not critical, just approximate,
+ # hence giving a few more seconds.
+ ident_event = autils.wait_for_event(dut,
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED,
+ timeout=RANDOM_INTERVAL + 5)
+ mac2 = ident_event["data"]["mac"]
+
+ # validate MAC address is randomized
+ asserts.assert_false(
+ mac1 == mac2,
+ "Randomized MAC addresses (%s, %s) should be different" % (mac1, mac2))
+
+ # clean-up
+ dut.droid.wifiAwareDestroy(id)
diff --git a/acts/tests/google/wifi/aware/functional/MatchFilterTest.py b/acts/tests/google/wifi/aware/functional/MatchFilterTest.py
new file mode 100644
index 0000000..e01bfff
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/MatchFilterTest.py
@@ -0,0 +1,191 @@
+#!/usr/bin/python3.4
+#
+# 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 base64
+import time
+import queue
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class MatchFilterTest(AwareBaseTest):
+ """Set of tests for Wi-Fi Aware Discovery Match Filter behavior. These all
+ use examples from Appendix H of the Wi-Fi Aware standard."""
+
+ SERVICE_NAME = "GoogleTestServiceMFMFMF"
+
+ MF_NNNNN = bytes([0x0, 0x0, 0x0, 0x0, 0x0])
+ MF_12345 = bytes([0x1, 0x1, 0x1, 0x2, 0x1, 0x3, 0x1, 0x4, 0x1, 0x5])
+ MF_12145 = bytes([0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x4, 0x1, 0x5])
+ MF_1N3N5 = bytes([0x1, 0x1, 0x0, 0x1, 0x3, 0x0, 0x1, 0x5])
+ MF_N23N5 = bytes([0x0, 0x1, 0x2, 0x1, 0x3, 0x0, 0x1, 0x5])
+ MF_N2N4 = bytes([0x0, 0x1, 0x2, 0x0, 0x1, 0x4])
+ MF_1N3N = bytes([0x1, 0x1, 0x0, 0x1, 0x3, 0x0])
+
+ # Set of sample match filters from the spec. There is a set of matched
+ # filters:
+ # - Filter 1
+ # - Filter 2
+ # - Expected to match if the Subscriber uses Filter 1 as Tx and the Publisher
+ # uses Filter 2 as Rx (implies Solicited/Active)
+ # - (the reverse) Expected to match if the Publisher uses Filter 1 as Tx and
+ # the Subscriber uses Filter 2 as Rx (implies Unsolicited/Passive)
+ match_filters = [
+ [None, None, True, True],
+ [None, MF_NNNNN, True, True],
+ [MF_NNNNN, None, True, True],
+ [None, MF_12345, True, False],
+ [MF_12345, None, False, True],
+ [MF_NNNNN, MF_12345, True, True],
+ [MF_12345, MF_NNNNN, True, True],
+ [MF_12345, MF_12345, True, True],
+ [MF_12345, MF_12145, False, False],
+ [MF_1N3N5, MF_12345, True,True],
+ [MF_12345, MF_N23N5, True, True],
+ [MF_N2N4, MF_12345, True, False],
+ [MF_12345, MF_1N3N, False, True]
+ ]
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def run_discovery(self, p_dut, s_dut, p_mf, s_mf, do_unsolicited_passive,
+ expect_discovery):
+ """Creates a discovery session (publish and subscribe) with the specified
+ configuration.
+
+ Args:
+ p_dut: Device to use as publisher.
+ s_dut: Device to use as subscriber.
+ p_mf: Publish's match filter.
+ s_mf: Subscriber's match filter.
+ do_unsolicited_passive: True to use an Unsolicited/Passive discovery,
+ False for a Solicited/Active discovery session.
+ expect_discovery: True if service should be discovered, False otherwise.
+ Returns: True on success, False on failure (based on expect_discovery arg)
+ """
+ # Encode the match filters
+ p_mf = base64.b64encode(p_mf).decode("utf-8") if p_mf is not None else None
+ s_mf = base64.b64encode(s_mf).decode("utf-8") if s_mf is not None else None
+
+ # Publisher+Subscriber: attach and wait for confirmation
+ p_id = p_dut.droid.wifiAwareAttach()
+ autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ time.sleep(self.device_startup_offset)
+ s_id = s_dut.droid.wifiAwareAttach()
+ autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # Publisher: start publish and wait for confirmation
+ p_dut.droid.wifiAwarePublish(p_id,
+ autils.create_discovery_config(
+ self.SERVICE_NAME,
+ d_type=aconsts.PUBLISH_TYPE_UNSOLICITED
+ if do_unsolicited_passive else
+ aconsts.PUBLISH_TYPE_SOLICITED,
+ match_filter=p_mf))
+ autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+ # Subscriber: start subscribe and wait for confirmation
+ s_dut.droid.wifiAwareSubscribe(s_id,
+ autils.create_discovery_config(
+ self.SERVICE_NAME,
+ d_type=aconsts.SUBSCRIBE_TYPE_PASSIVE
+ if do_unsolicited_passive else
+ aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ match_filter=s_mf))
+ autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+ # Subscriber: wait or fail on service discovery
+ event = None
+ try:
+ event = s_dut.ed.pop_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+ autils.EVENT_TIMEOUT)
+ s_dut.log.info("[Subscriber] SESSION_CB_ON_SERVICE_DISCOVERED: %s", event)
+ except queue.Empty:
+ s_dut.log.info("[Subscriber] No SESSION_CB_ON_SERVICE_DISCOVERED")
+
+ # clean-up
+ p_dut.droid.wifiAwareDestroy(p_id)
+ s_dut.droid.wifiAwareDestroy(s_id)
+
+ if expect_discovery:
+ return event is not None
+ else:
+ return event is None
+
+ def run_match_filters_per_spec(self, do_unsolicited_passive):
+ """Validate all the match filter combinations in the Wi-Fi Aware spec,
+ Appendix H.
+
+ Args:
+ do_unsolicited_passive: True to run the Unsolicited/Passive tests, False
+ to run the Solicited/Active tests.
+ """
+ p_dut = self.android_devices[0]
+ p_dut.pretty_name = "Publisher"
+ s_dut = self.android_devices[1]
+ s_dut.pretty_name = "Subscriber"
+
+ fails = []
+ for i in range(len(self.match_filters)):
+ test_info = self.match_filters[i]
+ if do_unsolicited_passive:
+ pub_type = "Unsolicited"
+ sub_type = "Passive"
+ pub_mf = test_info[0]
+ sub_mf = test_info[1]
+ expect_discovery = test_info[3]
+ else:
+ pub_type = "Solicited"
+ sub_type = "Active"
+ pub_mf = test_info[1]
+ sub_mf = test_info[0]
+ expect_discovery = test_info[2]
+
+ self.log.info("Test #%d: %s Pub MF=%s, %s Sub MF=%s: Discovery %s", i,
+ pub_type, pub_mf, sub_type, sub_mf, "EXPECTED"
+ if test_info[2] else "UNEXPECTED")
+ result = self.run_discovery(
+ p_dut,
+ s_dut,
+ p_mf=pub_mf,
+ s_mf=sub_mf,
+ do_unsolicited_passive=do_unsolicited_passive,
+ expect_discovery=expect_discovery)
+ self.log.info("Test #%d %s Pub/%s Sub %s", i, pub_type, sub_type, "PASS"
+ if result else "FAIL")
+ if not result:
+ fails.append(i)
+
+ asserts.assert_true(
+ len(fails) == 0, "Some match filter tests are failing", extras=fails)
+
+ ###############################################################
+
+ def test_match_filters_per_spec_unsolicited_passive(self):
+ """Validate all the match filter combinations in the Wi-Fi Aware spec,
+ Appendix H for Unsolicited Publish (tx filter) Passive Subscribe (rx
+ filter)"""
+ self.run_match_filters_per_spec(do_unsolicited_passive=True)
+
+ def test_match_filters_per_spec_solicited_active(self):
+ """Validate all the match filter combinations in the Wi-Fi Aware spec,
+ Appendix H for Solicited Publish (rx filter) Active Subscribe (tx
+ filter)"""
+ self.run_match_filters_per_spec(do_unsolicited_passive=False)
diff --git a/acts/tests/google/wifi/aware/functional/MessageTest.py b/acts/tests/google/wifi/aware/functional/MessageTest.py
new file mode 100644
index 0000000..1abca68
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/MessageTest.py
@@ -0,0 +1,437 @@
+#!/usr/bin/python3.4
+#
+# 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 string
+import time
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class MessageTest(AwareBaseTest):
+ """Set of tests for Wi-Fi Aware L2 (layer 2) message exchanges."""
+
+ # configuration parameters used by tests
+ PAYLOAD_SIZE_MIN = 0
+ PAYLOAD_SIZE_TYPICAL = 1
+ PAYLOAD_SIZE_MAX = 2
+
+ NUM_MSGS_NO_QUEUE = 10
+ NUM_MSGS_QUEUE_DEPTH_MULT = 2 # number of messages = mult * queue depth
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def create_msg(self, caps, payload_size, id):
+ """Creates a message string of the specified size containing the input id.
+
+ Args:
+ caps: Device capabilities.
+ payload_size: The size of the message to create - min (null or empty
+ message), typical, max (based on device capabilities). Use
+ the PAYLOAD_SIZE_xx constants.
+ id: Information to include in the generated message (or None).
+
+ Returns: A string of the requested size, optionally containing the id.
+ """
+ if payload_size == self.PAYLOAD_SIZE_MIN:
+ # arbitrarily return a None or an empty string (equivalent messages)
+ return None if id % 2 == 0 else ""
+ elif payload_size == self.PAYLOAD_SIZE_TYPICAL:
+ return "*** ID=%d ***" % id + string.ascii_uppercase
+ else: # PAYLOAD_SIZE_MAX
+ return "*** ID=%4d ***" % id + "M" * (
+ caps[aconsts.CAP_MAX_SERVICE_SPECIFIC_INFO_LEN] - 15)
+
+ def create_config(self, is_publish, extra_diff=None):
+ """Create a base configuration based on input parameters.
+
+ Args:
+ is_publish: True for publish, False for subscribe sessions.
+ extra_diff: String to add to service name: allows differentiating
+ discovery sessions.
+
+ Returns:
+ publish discovery configuration object.
+ """
+ config = {}
+ if is_publish:
+ config[aconsts.
+ DISCOVERY_KEY_DISCOVERY_TYPE] = aconsts.PUBLISH_TYPE_UNSOLICITED
+ else:
+ config[
+ aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = aconsts.SUBSCRIBE_TYPE_PASSIVE
+ config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceX" + (
+ extra_diff if extra_diff is not None else "")
+ return config
+
+ def prep_message_exchange(self, extra_diff=None):
+ """Creates a discovery session (publish and subscribe), and waits for
+ service discovery - at that point the sessions are ready for message
+ exchange.
+
+ Args:
+ extra_diff: String to add to service name: allows differentiating
+ discovery sessions.
+ """
+ p_dut = self.android_devices[0]
+ p_dut.pretty_name = "Publisher"
+ s_dut = self.android_devices[1]
+ s_dut.pretty_name = "Subscriber"
+
+ # if differentiating (multiple) sessions then should decorate events with id
+ use_id = extra_diff is not None
+
+ # Publisher+Subscriber: attach and wait for confirmation
+ p_id = p_dut.droid.wifiAwareAttach(False, None, use_id)
+ autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED
+ if not use_id else autils.decorate_event(
+ aconsts.EVENT_CB_ON_ATTACHED, p_id))
+ time.sleep(self.device_startup_offset)
+ s_id = s_dut.droid.wifiAwareAttach(False, None, use_id)
+ autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED
+ if not use_id else autils.decorate_event(
+ aconsts.EVENT_CB_ON_ATTACHED, s_id))
+
+ # Publisher: start publish and wait for confirmation
+ p_disc_id = p_dut.droid.wifiAwarePublish(p_id,
+ self.create_config(
+ True, extra_diff=extra_diff),
+ use_id)
+ autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED
+ if not use_id else autils.decorate_event(
+ aconsts.SESSION_CB_ON_PUBLISH_STARTED, p_disc_id))
+
+ # Subscriber: start subscribe and wait for confirmation
+ s_disc_id = s_dut.droid.wifiAwareSubscribe(
+ s_id, self.create_config(False, extra_diff=extra_diff), use_id)
+ autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+ if not use_id else autils.decorate_event(
+ aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+ s_disc_id))
+
+ # Subscriber: wait for service discovery
+ discovery_event = autils.wait_for_event(
+ s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED
+ if not use_id else autils.decorate_event(
+ aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id))
+ peer_id_on_sub = discovery_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+ return {
+ "p_dut": p_dut,
+ "s_dut": s_dut,
+ "p_id": p_id,
+ "s_id": s_id,
+ "p_disc_id": p_disc_id,
+ "s_disc_id": s_disc_id,
+ "peer_id_on_sub": peer_id_on_sub
+ }
+
+ def run_message_no_queue(self, payload_size):
+ """Validate L2 message exchange between publisher & subscriber with no
+ queueing - i.e. wait for an ACK on each message before sending the next
+ message.
+
+ Args:
+ payload_size: min, typical, or max (PAYLOAD_SIZE_xx).
+ """
+ discovery_info = self.prep_message_exchange()
+ p_dut = discovery_info["p_dut"]
+ s_dut = discovery_info["s_dut"]
+ p_disc_id = discovery_info["p_disc_id"]
+ s_disc_id = discovery_info["s_disc_id"]
+ peer_id_on_sub = discovery_info["peer_id_on_sub"]
+
+ for i in range(self.NUM_MSGS_NO_QUEUE):
+ msg = self.create_msg(s_dut.aware_capabilities, payload_size, i)
+ msg_id = self.get_next_msg_id()
+ s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id, msg,
+ 0)
+ tx_event = autils.wait_for_event(s_dut,
+ aconsts.SESSION_CB_ON_MESSAGE_SENT)
+ rx_event = autils.wait_for_event(p_dut,
+ aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+ asserts.assert_equal(msg_id,
+ tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID],
+ "Subscriber -> Publisher message ID corrupted")
+ autils.assert_equal_strings(
+ msg, rx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+ "Subscriber -> Publisher message %d corrupted" % i)
+
+ peer_id_on_pub = rx_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+ for i in range(self.NUM_MSGS_NO_QUEUE):
+ msg = self.create_msg(s_dut.aware_capabilities, payload_size, 1000 + i)
+ msg_id = self.get_next_msg_id()
+ p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub, msg_id, msg,
+ 0)
+ tx_event = autils.wait_for_event(p_dut,
+ aconsts.SESSION_CB_ON_MESSAGE_SENT)
+ rx_event = autils.wait_for_event(s_dut,
+ aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+ asserts.assert_equal(msg_id,
+ tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID],
+ "Publisher -> Subscriber message ID corrupted")
+ autils.assert_equal_strings(
+ msg, rx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+ "Publisher -> Subscriber message %d corrupted" % i)
+
+ # verify there are no more events
+ time.sleep(autils.EVENT_TIMEOUT)
+ autils.verify_no_more_events(p_dut, timeout=0)
+ autils.verify_no_more_events(s_dut, timeout=0)
+
+ def wait_for_messages(self, tx_msgs, tx_msg_ids, tx_disc_id, rx_disc_id,
+ tx_dut, rx_dut, are_msgs_empty=False):
+ """Validate that all expected messages are transmitted correctly and
+ received as expected. Method is called after the messages are sent into
+ the transmission queue.
+
+ Note: that message can be transmitted and received out-of-order (which is
+ acceptable and the method handles that correctly).
+
+ Args:
+ tx_msgs: dictionary of transmitted messages
+ tx_msg_ids: dictionary of transmitted message ids
+ tx_disc_id: transmitter discovery session id (None for no decoration)
+ rx_disc_id: receiver discovery session id (None for no decoration)
+ tx_dut: transmitter device
+ rx_dut: receiver device
+ are_msgs_empty: True if the messages are None or empty (changes dup detection)
+
+ Returns: the peer ID from any of the received messages
+ """
+ # peer id on receiver
+ peer_id_on_rx = None
+
+ # wait for all messages to be transmitted
+ still_to_be_tx = len(tx_msg_ids)
+ while still_to_be_tx != 0:
+ tx_event = autils.wait_for_event(
+ tx_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT
+ if tx_disc_id is None else autils.decorate_event(
+ aconsts.SESSION_CB_ON_MESSAGE_SENT, tx_disc_id))
+ tx_msg_id = tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID]
+ tx_msg_ids[tx_msg_id] = tx_msg_ids[tx_msg_id] + 1
+ if tx_msg_ids[tx_msg_id] == 1:
+ still_to_be_tx = still_to_be_tx - 1
+
+ # check for any duplicate transmit notifications
+ asserts.assert_equal(
+ len(tx_msg_ids),
+ sum(tx_msg_ids.values()),
+ "Duplicate transmit message IDs: %s" % tx_msg_ids)
+
+ # wait for all messages to be received
+ still_to_be_rx = len(tx_msg_ids)
+ while still_to_be_rx != 0:
+ rx_event = autils.wait_for_event(
+ rx_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED
+ if rx_disc_id is None else autils.decorate_event(
+ aconsts.SESSION_CB_ON_MESSAGE_RECEIVED, rx_disc_id))
+ peer_id_on_rx = rx_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+ if are_msgs_empty:
+ still_to_be_rx = still_to_be_rx - 1
+ else:
+ rx_msg = rx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
+ asserts.assert_true(
+ rx_msg in tx_msgs,
+ "Received a message we did not send!? -- '%s'" % rx_msg)
+ tx_msgs[rx_msg] = tx_msgs[rx_msg] + 1
+ if tx_msgs[rx_msg] == 1:
+ still_to_be_rx = still_to_be_rx - 1
+
+ # check for any duplicate received messages
+ if not are_msgs_empty:
+ asserts.assert_equal(
+ len(tx_msgs),
+ sum(tx_msgs.values()), "Duplicate transmit messages: %s" % tx_msgs)
+
+ return peer_id_on_rx
+
+ def run_message_with_queue(self, payload_size):
+ """Validate L2 message exchange between publisher & subscriber with
+ queueing - i.e. transmit all messages and then wait for ACKs.
+
+ Args:
+ payload_size: min, typical, or max (PAYLOAD_SIZE_xx).
+ """
+ discovery_info = self.prep_message_exchange()
+ p_dut = discovery_info["p_dut"]
+ s_dut = discovery_info["s_dut"]
+ p_disc_id = discovery_info["p_disc_id"]
+ s_disc_id = discovery_info["s_disc_id"]
+ peer_id_on_sub = discovery_info["peer_id_on_sub"]
+
+ msgs = {}
+ msg_ids = {}
+ for i in range(
+ self.NUM_MSGS_QUEUE_DEPTH_MULT *
+ s_dut.aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+ msg = self.create_msg(s_dut.aware_capabilities, payload_size, i)
+ msg_id = self.get_next_msg_id()
+ msgs[msg] = 0
+ msg_ids[msg_id] = 0
+ s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id, msg,
+ 0)
+ peer_id_on_pub = self.wait_for_messages(
+ msgs, msg_ids, None, None, s_dut, p_dut,
+ payload_size == self.PAYLOAD_SIZE_MIN)
+
+ msgs = {}
+ msg_ids = {}
+ for i in range(
+ self.NUM_MSGS_QUEUE_DEPTH_MULT *
+ p_dut.aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+ msg = self.create_msg(p_dut.aware_capabilities, payload_size, 1000 + i)
+ msg_id = self.get_next_msg_id()
+ msgs[msg] = 0
+ msg_ids[msg_id] = 0
+ p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub, msg_id, msg,
+ 0)
+ self.wait_for_messages(msgs, msg_ids, None, None, p_dut, s_dut,
+ payload_size == self.PAYLOAD_SIZE_MIN)
+
+ # verify there are no more events
+ time.sleep(autils.EVENT_TIMEOUT)
+ autils.verify_no_more_events(p_dut, timeout=0)
+ autils.verify_no_more_events(s_dut, timeout=0)
+
+ def run_message_multi_session_with_queue(self, payload_size):
+ """Validate L2 message exchange between publishers & subscribers with
+ queueing - i.e. transmit all messages and then wait for ACKs. Uses 2
+ discovery sessions running concurrently and validates that messages
+ arrive at the correct destination.
+
+ Args:
+ payload_size: min, typical, or max (PAYLOAD_SIZE_xx)
+ """
+ discovery_info1 = self.prep_message_exchange(extra_diff="-111")
+ p_dut = discovery_info1["p_dut"] # same for both sessions
+ s_dut = discovery_info1["s_dut"] # same for both sessions
+ p_disc_id1 = discovery_info1["p_disc_id"]
+ s_disc_id1 = discovery_info1["s_disc_id"]
+ peer_id_on_sub1 = discovery_info1["peer_id_on_sub"]
+
+ discovery_info2 = self.prep_message_exchange(extra_diff="-222")
+ p_disc_id2 = discovery_info2["p_disc_id"]
+ s_disc_id2 = discovery_info2["s_disc_id"]
+ peer_id_on_sub2 = discovery_info2["peer_id_on_sub"]
+
+ msgs1 = {}
+ msg_ids1 = {}
+ msgs2 = {}
+ msg_ids2 = {}
+ for i in range(
+ self.NUM_MSGS_QUEUE_DEPTH_MULT *
+ s_dut.aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+ msg1 = self.create_msg(s_dut.aware_capabilities, payload_size, i)
+ msg_id1 = self.get_next_msg_id()
+ msgs1[msg1] = 0
+ msg_ids1[msg_id1] = 0
+ s_dut.droid.wifiAwareSendMessage(s_disc_id1, peer_id_on_sub1, msg_id1,
+ msg1, 0)
+ msg2 = self.create_msg(s_dut.aware_capabilities, payload_size, 100 + i)
+ msg_id2 = self.get_next_msg_id()
+ msgs2[msg2] = 0
+ msg_ids2[msg_id2] = 0
+ s_dut.droid.wifiAwareSendMessage(s_disc_id2, peer_id_on_sub2, msg_id2,
+ msg2, 0)
+
+ peer_id_on_pub1 = self.wait_for_messages(
+ msgs1, msg_ids1, s_disc_id1, p_disc_id1, s_dut, p_dut,
+ payload_size == self.PAYLOAD_SIZE_MIN)
+ peer_id_on_pub2 = self.wait_for_messages(
+ msgs2, msg_ids2, s_disc_id2, p_disc_id2, s_dut, p_dut,
+ payload_size == self.PAYLOAD_SIZE_MIN)
+
+ msgs1 = {}
+ msg_ids1 = {}
+ msgs2 = {}
+ msg_ids2 = {}
+ for i in range(
+ self.NUM_MSGS_QUEUE_DEPTH_MULT *
+ p_dut.aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+ msg1 = self.create_msg(p_dut.aware_capabilities, payload_size, 1000 + i)
+ msg_id1 = self.get_next_msg_id()
+ msgs1[msg1] = 0
+ msg_ids1[msg_id1] = 0
+ p_dut.droid.wifiAwareSendMessage(p_disc_id1, peer_id_on_pub1, msg_id1,
+ msg1, 0)
+ msg2 = self.create_msg(p_dut.aware_capabilities, payload_size, 1100 + i)
+ msg_id2 = self.get_next_msg_id()
+ msgs2[msg2] = 0
+ msg_ids2[msg_id2] = 0
+ p_dut.droid.wifiAwareSendMessage(p_disc_id2, peer_id_on_pub2, msg_id2,
+ msg2, 0)
+
+ self.wait_for_messages(msgs1, msg_ids1, p_disc_id1, s_disc_id1, p_dut,
+ s_dut, payload_size == self.PAYLOAD_SIZE_MIN)
+ self.wait_for_messages(msgs2, msg_ids2, p_disc_id2, s_disc_id2, p_dut,
+ s_dut, payload_size == self.PAYLOAD_SIZE_MIN)
+
+ # verify there are no more events
+ time.sleep(autils.EVENT_TIMEOUT)
+ autils.verify_no_more_events(p_dut, timeout=0)
+ autils.verify_no_more_events(s_dut, timeout=0)
+
+ ############################################################################
+
+ def test_message_no_queue_min(self):
+ """Functional / Message / No queue
+ - Minimal payload size (None or "")
+ """
+ self.run_message_no_queue(self.PAYLOAD_SIZE_MIN)
+
+ def test_message_no_queue_typical(self):
+ """Functional / Message / No queue
+ - Typical payload size
+ """
+ self.run_message_no_queue(self.PAYLOAD_SIZE_TYPICAL)
+
+ def test_message_no_queue_max(self):
+ """Functional / Message / No queue
+ - Max payload size (based on device capabilities)
+ """
+ self.run_message_no_queue(self.PAYLOAD_SIZE_MAX)
+
+ def test_message_with_queue_min(self):
+ """Functional / Message / With queue
+ - Minimal payload size (none or "")
+ """
+ self.run_message_with_queue(self.PAYLOAD_SIZE_MIN)
+
+ def test_message_with_queue_typical(self):
+ """Functional / Message / With queue
+ - Typical payload size
+ """
+ self.run_message_with_queue(self.PAYLOAD_SIZE_TYPICAL)
+
+ def test_message_with_queue_max(self):
+ """Functional / Message / With queue
+ - Max payload size (based on device capabilities)
+ """
+ self.run_message_with_queue(self.PAYLOAD_SIZE_MAX)
+
+ def test_message_with_multiple_discovery_sessions_typical(self):
+ """Functional / Message / Multiple sessions
+
+ Sets up 2 discovery sessions on 2 devices. Sends a message in each
+ direction on each discovery session and verifies that reaches expected
+ destination.
+ """
+ self.run_message_multi_session_with_queue(self.PAYLOAD_SIZE_TYPICAL)
diff --git a/acts/tests/google/wifi/aware/functional/NonConcurrencyTest.py b/acts/tests/google/wifi/aware/functional/NonConcurrencyTest.py
new file mode 100644
index 0000000..d0b83cf
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/NonConcurrencyTest.py
@@ -0,0 +1,115 @@
+#!/usr/bin/python3.4
+#
+# 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.
+
+from acts import asserts
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class NonConcurrencyTest(AwareBaseTest):
+ """Tests lack of concurrency scenarios Wi-Fi Aware with WFD (p2p) and
+ SoftAP
+
+ Note: these tests should be modified if the concurrency behavior changes!"""
+
+ SERVICE_NAME = "GoogleTestXYZ"
+ TETHER_SSID = "GoogleTestSoftApXYZ"
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def teardown_test(self):
+ AwareBaseTest.teardown_test(self)
+ for ad in self.android_devices:
+ ad.droid.wifiP2pClose()
+
+ def run_aware_then_incompat_service(self, is_p2p):
+ """Run test to validate that a running Aware session terminates when an
+ Aware-incompatible service is started.
+
+ Args:
+ is_p2p: True for p2p, False for SoftAP
+ """
+ dut = self.android_devices[0]
+
+ # start Aware
+ id = dut.droid.wifiAwareAttach()
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # start other service
+ if is_p2p:
+ dut.droid.wifiP2pInitialize()
+ else:
+ wutils.start_wifi_tethering(dut, self.TETHER_SSID, password=None)
+
+ # expect an announcement about Aware non-availability
+ autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+
+ # local clean-up
+ if not is_p2p:
+ wutils.stop_wifi_tethering(dut)
+
+ def run_incompat_service_then_aware(self, is_p2p):
+ """Validate that if an Aware-incompatible service is already up then any
+ Aware operation fails"""
+ dut = self.android_devices[0]
+
+ # start other service
+ if is_p2p:
+ dut.droid.wifiP2pInitialize()
+ else:
+ wutils.start_wifi_tethering(dut, self.TETHER_SSID, password=None)
+
+ # expect an announcement about Aware non-availability
+ autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+
+ # try starting anyway (expect failure)
+ dut.droid.wifiAwareAttach()
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACH_FAILED)
+
+ # stop other service
+ if is_p2p:
+ dut.droid.wifiP2pClose()
+ else:
+ wutils.stop_wifi_tethering(dut)
+
+ # expect an announcement about Aware availability
+ autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+
+ # try starting Aware
+ dut.droid.wifiAwareAttach()
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ ##########################################################################
+
+ def test_run_p2p_then_aware(self):
+ """Validate that if p2p is already up then any Aware operation fails"""
+ self.run_incompat_service_then_aware(is_p2p=True)
+
+ def test_run_aware_then_p2p(self):
+ """Validate that a running Aware session terminates when p2p is started"""
+ self.run_aware_then_incompat_service(is_p2p=True)
+
+ def test_run_softap_then_aware(self):
+ """Validate that if SoftAp is already up then any Aware operation fails"""
+ self.run_incompat_service_then_aware(is_p2p=False)
+
+ def test_run_aware_then_softap(self):
+ """Validate that a running Aware session terminates when softAp is
+ started"""
+ self.run_aware_then_incompat_service(is_p2p=False)
diff --git a/acts/tests/google/wifi/aware/functional/ProtocolsTest.py b/acts/tests/google/wifi/aware/functional/ProtocolsTest.py
new file mode 100644
index 0000000..bf4a561
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/ProtocolsTest.py
@@ -0,0 +1,193 @@
+#!/usr/bin/python3.4
+#
+# 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.
+
+from acts import asserts
+from acts.test_utils.net import nsd_const as nconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class ProtocolsTest(AwareBaseTest):
+ """Set of tests for Wi-Fi Aware data-paths: validating protocols running on
+ top of a data-path"""
+
+ SERVICE_NAME = "GoogleTestServiceXY"
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def run_ping6(self, dut, peer_ipv6, dut_if):
+ """Run a ping6 over the specified device/link
+
+ Args:
+ dut: Device on which to execute ping6
+ peer_ipv6: IPv6 address of the peer to ping
+ dut_if: interface name on the dut
+ """
+ cmd = "ping6 -c 3 -W 5 %s%%%s" % (peer_ipv6, dut_if)
+ results = dut.adb.shell(cmd)
+ self.log.info("cmd='%s' -> '%s'", cmd, results)
+ if results == "":
+ asserts.fail("ping6 empty results - seems like a failure")
+
+ ########################################################################
+
+ def test_ping6_oob(self):
+ """Validate that ping6 works correctly on an NDP created using OOB (out-of
+ band) discovery"""
+ init_dut = self.android_devices[0]
+ resp_dut = self.android_devices[1]
+
+ # create NDP
+ (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+ resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+ self.log.info("Interface names: I=%s, R=%s", init_aware_if, resp_aware_if)
+ self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+ resp_ipv6)
+
+ # run ping6
+ self.run_ping6(init_dut, resp_ipv6, init_aware_if)
+ self.run_ping6(resp_dut, init_ipv6, resp_aware_if)
+
+ # clean-up
+ resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+ init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+ def test_ping6_ib_unsolicited_passive(self):
+ """Validate that ping6 works correctly on an NDP created using Aware
+ discovery with UNSOLICITED/PASSIVE sessions."""
+ p_dut = self.android_devices[0]
+ s_dut = self.android_devices[1]
+
+ # create NDP
+ (p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6,
+ s_ipv6) = autils.create_ib_ndp(
+ p_dut,
+ s_dut,
+ p_config=autils.create_discovery_config(
+ self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+ s_config=autils.create_discovery_config(
+ self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+ device_startup_offset=self.device_startup_offset)
+ self.log.info("Interface names: P=%s, S=%s", p_aware_if, s_aware_if)
+ self.log.info("Interface addresses (IPv6): P=%s, S=%s", p_ipv6, s_ipv6)
+
+ # run ping6
+ self.run_ping6(p_dut, s_ipv6, p_aware_if)
+ self.run_ping6(s_dut, p_ipv6, s_aware_if)
+
+ # clean-up
+ p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+ s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+ def test_ping6_ib_solicited_active(self):
+ """Validate that ping6 works correctly on an NDP created using Aware
+ discovery with SOLICITED/ACTIVE sessions."""
+ p_dut = self.android_devices[0]
+ s_dut = self.android_devices[1]
+
+ # create NDP
+ (p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6,
+ s_ipv6) = autils.create_ib_ndp(
+ p_dut,
+ s_dut,
+ p_config=autils.create_discovery_config(
+ self.SERVICE_NAME, aconsts.PUBLISH_TYPE_SOLICITED),
+ s_config=autils.create_discovery_config(self.SERVICE_NAME,
+ aconsts.SUBSCRIBE_TYPE_ACTIVE),
+ device_startup_offset=self.device_startup_offset)
+ self.log.info("Interface names: P=%s, S=%s", p_aware_if, s_aware_if)
+ self.log.info("Interface addresses (IPv6): P=%s, S=%s", p_ipv6, s_ipv6)
+
+ # run ping6
+ self.run_ping6(p_dut, s_ipv6, p_aware_if)
+ self.run_ping6(s_dut, p_ipv6, s_aware_if)
+
+ # clean-up
+ p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+ s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+ def test_nsd_oob(self):
+ """Validate that NSD (mDNS) works correctly on an NDP created using OOB
+ (out-of band) discovery"""
+ init_dut = self.android_devices[0]
+ resp_dut = self.android_devices[1]
+
+ # create NDP
+ (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+ resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+ self.log.info("Interface names: I=%s, R=%s", init_aware_if, resp_aware_if)
+ self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+ resp_ipv6)
+
+ # run NSD
+ nsd_service_info = {
+ "serviceInfoServiceName": "sl4aTestAwareNsd",
+ "serviceInfoServiceType": "_simple-tx-rx._tcp.",
+ "serviceInfoPort": 2257
+ }
+ nsd_reg = None
+ nsd_discovery = None
+ try:
+ # Initiator registers an NSD service
+ nsd_reg = init_dut.droid.nsdRegisterService(nsd_service_info)
+ event_nsd = autils.wait_for_event_with_keys(
+ init_dut, nconsts.REG_LISTENER_EVENT, autils.EVENT_TIMEOUT,
+ (nconsts.REG_LISTENER_CALLBACK,
+ nconsts.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED))
+ self.log.info("Initiator %s: %s",
+ nconsts.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED,
+ event_nsd["data"])
+
+ # Responder starts an NSD discovery
+ nsd_discovery = resp_dut.droid.nsdDiscoverServices(
+ nsd_service_info[nconsts.NSD_SERVICE_INFO_SERVICE_TYPE])
+ event_nsd = autils.wait_for_event_with_keys(
+ resp_dut, nconsts.DISCOVERY_LISTENER_EVENT, autils.EVENT_TIMEOUT,
+ (nconsts.DISCOVERY_LISTENER_DATA_CALLBACK,
+ nconsts.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND))
+ self.log.info("Responder %s: %s",
+ nconsts.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND,
+ event_nsd["data"])
+
+ # Responder resolves IP address of Initiator from NSD service discovery
+ resp_dut.droid.nsdResolveService(event_nsd["data"])
+ event_nsd = autils.wait_for_event_with_keys(
+ resp_dut, nconsts.RESOLVE_LISTENER_EVENT, autils.EVENT_TIMEOUT,
+ (nconsts.RESOLVE_LISTENER_DATA_CALLBACK,
+ nconsts.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED))
+ self.log.info("Responder %s: %s",
+ nconsts.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED,
+ event_nsd["data"])
+
+ # mDNS returns first character as '/' - strip
+ # out to get clean IPv6
+ init_ipv6_nsd = event_nsd["data"][nconsts.NSD_SERVICE_INFO_HOST][1:]
+
+ asserts.assert_equal(
+ init_ipv6, init_ipv6_nsd,
+ "Initiator's IPv6 address obtained through NSD doesn't match!?")
+ finally:
+ # Stop NSD
+ if nsd_reg is not None:
+ init_dut.droid.nsdUnregisterService(nsd_reg)
+ if nsd_discovery is not None:
+ resp_dut.droid.nsdStopServiceDiscovery(nsd_discovery)
+
+ # clean-up
+ resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+ init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
diff --git a/acts/tests/google/wifi/aware/functional/functional b/acts/tests/google/wifi/aware/functional/functional
new file mode 100644
index 0000000..e13a470
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/functional
@@ -0,0 +1,9 @@
+AttachTest
+DiscoveryTest
+MessageTest
+DataPathTest
+MacRandomTest
+CapabilitiesTest
+ProtocolsTest
+NonConcurrencyTest
+MatchFilterTest
\ No newline at end of file
diff --git a/acts/tests/google/wifi/aware/ota/ServiceIdsTest.py b/acts/tests/google/wifi/aware/ota/ServiceIdsTest.py
new file mode 100644
index 0000000..1137303
--- /dev/null
+++ b/acts/tests/google/wifi/aware/ota/ServiceIdsTest.py
@@ -0,0 +1,100 @@
+#!/usr/bin/python3.4
+#
+# 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 time
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class ServiceIdsTest(AwareBaseTest):
+ """Set of tests for Wi-Fi Aware to verify that beacons include service IDs
+ for discovery.
+
+ Note: this test is an OTA (over-the-air) and requires a Sniffer.
+ """
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def start_discovery_session(self, dut, session_id, is_publish, dtype,
+ service_name):
+ """Start a discovery session
+
+ Args:
+ dut: Device under test
+ 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
+ service_name: Service name to use for the discovery session
+
+ Returns:
+ Discovery session ID.
+ """
+ config = {}
+ config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+ config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+
+ if is_publish:
+ disc_id = dut.droid.wifiAwarePublish(session_id, config)
+ event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+ else:
+ disc_id = dut.droid.wifiAwareSubscribe(session_id, config)
+ event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+
+ autils.wait_for_event(dut, event_name)
+ return disc_id
+
+ ####################################################################
+
+ def test_service_ids_in_beacon(self):
+ """Verify that beacons include service IDs for both publish and subscribe
+ sessions of all types: solicited/unsolicited/active/passive."""
+ dut = self.android_devices[0]
+
+ self.log.info("Reminder: start a sniffer before running test")
+
+ # attach
+ session_id = dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+ ident_event = autils.wait_for_event(dut,
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ mac = ident_event["data"]["mac"]
+ self.log.info("Source MAC Address of 'interesting' packets = %s", mac)
+ self.log.info("Wireshark filter = 'wlan.ta == %s:%s:%s:%s:%s:%s'", mac[0:2],
+ mac[2:4], mac[4:6], mac[6:8], mac[8:10], mac[10:12])
+
+ time.sleep(5) # get some samples pre-discovery
+
+ # start 4 discovery session (one of each type)
+ self.start_discovery_session(dut, session_id, True,
+ aconsts.PUBLISH_TYPE_UNSOLICITED,
+ "GoogleTestService-Pub-Unsolicited")
+ self.start_discovery_session(dut, session_id, True,
+ aconsts.PUBLISH_TYPE_SOLICITED,
+ "GoogleTestService-Pub-Solicited")
+ self.start_discovery_session(dut, session_id, False,
+ aconsts.SUBSCRIBE_TYPE_ACTIVE,
+ "GoogleTestService-Sub-Active")
+ self.start_discovery_session(dut, session_id, False,
+ aconsts.SUBSCRIBE_TYPE_PASSIVE,
+ "GoogleTestService-Sub-Passive")
+
+ time.sleep(15) # get some samples while discovery is alive
+
+ self.log.info("Reminder: stop sniffer")
diff --git a/acts/tests/google/wifi/aware/ota/ota b/acts/tests/google/wifi/aware/ota/ota
new file mode 100644
index 0000000..27f6724
--- /dev/null
+++ b/acts/tests/google/wifi/aware/ota/ota
@@ -0,0 +1 @@
+ServiceIdsTest
\ No newline at end of file
diff --git a/acts/tests/google/wifi/aware/performance/LatencyTest.py b/acts/tests/google/wifi/aware/performance/LatencyTest.py
new file mode 100644
index 0000000..20176c1
--- /dev/null
+++ b/acts/tests/google/wifi/aware/performance/LatencyTest.py
@@ -0,0 +1,555 @@
+#!/usr/bin/python3.4
+#
+# 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 queue
+import time
+
+from acts import asserts
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class LatencyTest(AwareBaseTest):
+ """Set of tests for Wi-Fi Aware to measure latency of Aware operations."""
+ SERVICE_NAME = "GoogleTestServiceXY"
+
+ # number of second to 'reasonably' wait to make sure that devices synchronize
+ # with each other - useful for OOB test cases, where the OOB discovery would
+ # take some time
+ WAIT_FOR_CLUSTER = 5
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def start_discovery_session(self, dut, session_id, is_publish, dtype):
+ """Start a discovery session
+
+ Args:
+ dut: Device under test
+ 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
+
+ Returns:
+ Discovery session started event.
+ """
+ config = {}
+ config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+ config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceXY"
+
+ if is_publish:
+ disc_id = dut.droid.wifiAwarePublish(session_id, config)
+ event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+ else:
+ disc_id = dut.droid.wifiAwareSubscribe(session_id, config)
+ event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+
+ event = autils.wait_for_event(dut, event_name)
+ return disc_id, event
+
+ def run_synchronization_latency(self, results, do_unsolicited_passive,
+ dw_24ghz, dw_5ghz, num_iterations,
+ startup_offset, timeout_period):
+ """Run the synchronization latency test with the specified DW intervals.
+ There is no direct measure of synchronization. Instead starts a discovery
+ session as soon as possible and measures both probability of discovery
+ within a timeout period and the actual discovery time (not necessarily
+ accurate).
+
+ Args:
+ results: Result array to be populated - will add results (not erase it)
+ do_unsolicited_passive: True for unsolicited/passive, False for
+ solicited/active.
+ dw_24ghz: DW interval in the 2.4GHz band.
+ dw_5ghz: DW interval in the 5GHz band.
+ startup_offset: The start-up gap (in seconds) between the two devices
+ timeout_period: Time period over which to measure synchronization
+ """
+ key = "%s_dw24_%d_dw5_%d_offset_%d" % (
+ "unsolicited_passive" if do_unsolicited_passive else "solicited_active",
+ dw_24ghz, dw_5ghz, startup_offset)
+ results[key] = {}
+ results[key]["num_iterations"] = num_iterations
+
+ p_dut = self.android_devices[0]
+ p_dut.pretty_name = "Publisher"
+ s_dut = self.android_devices[1]
+ s_dut.pretty_name = "Subscriber"
+
+ # override the default DW configuration
+ autils.config_dw_all_modes(p_dut, dw_24ghz, dw_5ghz)
+ autils.config_dw_all_modes(s_dut, dw_24ghz, dw_5ghz)
+
+ latencies = []
+ failed_discoveries = 0
+ for i in range(num_iterations):
+ # Publisher+Subscriber: attach and wait for confirmation
+ p_id = p_dut.droid.wifiAwareAttach(False)
+ autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ time.sleep(startup_offset)
+ s_id = s_dut.droid.wifiAwareAttach(False)
+ autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # start publish
+ p_disc_id, p_disc_event = self.start_discovery_session(
+ p_dut, p_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED
+ if do_unsolicited_passive else aconsts.PUBLISH_TYPE_SOLICITED)
+
+ # start subscribe
+ s_disc_id, s_session_event = self.start_discovery_session(
+ s_dut, s_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE
+ if do_unsolicited_passive else aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+ # 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
+ # errors)
+ try:
+ discovery_event = s_dut.ed.pop_event(
+ aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, timeout_period)
+ s_dut.log.info("[Subscriber] SESSION_CB_ON_SERVICE_DISCOVERED: %s",
+ discovery_event["data"])
+ except queue.Empty:
+ s_dut.log.info("[Subscriber] Timed out while waiting for "
+ "SESSION_CB_ON_SERVICE_DISCOVERED")
+ failed_discoveries = failed_discoveries + 1
+ continue
+ finally:
+ # destroy sessions
+ p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
+ s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+ p_dut.droid.wifiAwareDestroy(p_id)
+ s_dut.droid.wifiAwareDestroy(s_id)
+
+ # collect latency information
+ latencies.append(
+ discovery_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS] -
+ s_session_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS])
+ self.log.info("Latency #%d = %d" % (i, latencies[-1]))
+
+ autils.extract_stats(
+ s_dut,
+ data=latencies,
+ results=results[key],
+ key_prefix="",
+ log_prefix="Subscribe Session Sync/Discovery (%s, dw24=%d, dw5=%d)" %
+ ("Unsolicited/Passive"
+ if do_unsolicited_passive else "Solicited/Active", dw_24ghz, dw_5ghz))
+ results[key]["num_failed_discovery"] = failed_discoveries
+
+ def run_discovery_latency(self, results, do_unsolicited_passive, dw_24ghz,
+ dw_5ghz, num_iterations):
+ """Run the service discovery latency test with the specified DW intervals.
+
+ Args:
+ results: Result array to be populated - will add results (not erase it)
+ do_unsolicited_passive: True for unsolicited/passive, False for
+ solicited/active.
+ dw_24ghz: DW interval in the 2.4GHz band.
+ dw_5ghz: DW interval in the 5GHz band.
+ """
+ key = "%s_dw24_%d_dw5_%d" % (
+ "unsolicited_passive"
+ if do_unsolicited_passive else "solicited_active", dw_24ghz, dw_5ghz)
+ results[key] = {}
+ results[key]["num_iterations"] = num_iterations
+
+ p_dut = self.android_devices[0]
+ p_dut.pretty_name = "Publisher"
+ s_dut = self.android_devices[1]
+ s_dut.pretty_name = "Subscriber"
+
+ # override the default DW configuration
+ autils.config_dw_all_modes(p_dut, dw_24ghz, dw_5ghz)
+ autils.config_dw_all_modes(s_dut, dw_24ghz, dw_5ghz)
+
+ # Publisher+Subscriber: attach and wait for confirmation
+ p_id = p_dut.droid.wifiAwareAttach(False)
+ autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ time.sleep(self.device_startup_offset)
+ s_id = s_dut.droid.wifiAwareAttach(False)
+ autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ # start publish
+ p_disc_event = self.start_discovery_session(
+ p_dut, p_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED
+ if do_unsolicited_passive else aconsts.PUBLISH_TYPE_SOLICITED)
+
+ # wait for for devices to synchronize with each other - used so that first
+ # discovery isn't biased by synchronization.
+ time.sleep(self.WAIT_FOR_CLUSTER)
+
+ # loop, perform discovery, and collect latency information
+ latencies = []
+ failed_discoveries = 0
+ for i in range(num_iterations):
+ # start subscribe
+ s_disc_id, s_session_event = self.start_discovery_session(
+ s_dut, s_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE
+ if do_unsolicited_passive else aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+ # 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
+ # errors)
+ try:
+ discovery_event = s_dut.ed.pop_event(
+ aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, autils.EVENT_TIMEOUT)
+ except queue.Empty:
+ s_dut.log.info("[Subscriber] Timed out while waiting for "
+ "SESSION_CB_ON_SERVICE_DISCOVERED")
+ failed_discoveries = failed_discoveries + 1
+ continue
+ finally:
+ # destroy subscribe
+ s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+
+ # collect latency information
+ latencies.append(
+ discovery_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS] -
+ s_session_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS])
+ self.log.info("Latency #%d = %d" % (i, latencies[-1]))
+
+ autils.extract_stats(
+ s_dut,
+ data=latencies,
+ results=results[key],
+ key_prefix="",
+ log_prefix="Subscribe Session Discovery (%s, dw24=%d, dw5=%d)" %
+ ("Unsolicited/Passive"
+ if do_unsolicited_passive else "Solicited/Active", dw_24ghz, dw_5ghz))
+ results[key]["num_failed_discovery"] = failed_discoveries
+
+ # clean up
+ p_dut.droid.wifiAwareDestroyAll()
+ s_dut.droid.wifiAwareDestroyAll()
+
+ def run_message_latency(self, results, dw_24ghz, dw_5ghz, num_iterations):
+ """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.
+ """
+ key = "dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
+ results[key] = {}
+ results[key]["num_iterations"] = num_iterations
+
+ p_dut = self.android_devices[0]
+ s_dut = self.android_devices[1]
+
+ # override the default DW configuration
+ autils.config_dw_all_modes(p_dut, dw_24ghz, dw_5ghz)
+ autils.config_dw_all_modes(s_dut, dw_24ghz, dw_5ghz)
+
+ # Start up a discovery session
+ (p_id, s_id, p_disc_id, s_disc_id,
+ peer_id_on_sub) = autils.create_discovery_pair(
+ p_dut,
+ s_dut,
+ p_config=autils.create_discovery_config(
+ self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+ s_config=autils.create_discovery_config(
+ self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+ device_startup_offset=self.device_startup_offset)
+
+ latencies = []
+ failed_tx = 0
+ messages_rx = 0
+ missing_rx = 0
+ corrupted_rx = 0
+ for i in range(num_iterations):
+ # send message
+ 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:
+ sub_tx_msg_event = s_dut.ed.pop_event(
+ aconsts.SESSION_CB_ON_MESSAGE_SENT, 2 * autils.EVENT_TIMEOUT)
+ latencies.append(
+ sub_tx_msg_event["data"][aconsts.SESSION_CB_KEY_LATENCY_MS])
+ except queue.Empty:
+ s_dut.log.info("[Subscriber] Timed out while waiting for "
+ "SESSION_CB_ON_MESSAGE_SENT")
+ failed_tx = failed_tx + 1
+ continue
+
+ # wait for Rx confirmation (and validate contents)
+ try:
+ pub_rx_msg_event = p_dut.ed.pop_event(
+ aconsts.SESSION_CB_ON_MESSAGE_RECEIVED, 2 * autils.EVENT_TIMEOUT)
+ messages_rx = messages_rx + 1
+ if (pub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
+ != msg_s2p):
+ corrupted_rx = corrupted_rx + 1
+ except queue.Empty:
+ s_dut.log.info("[Publisher] Timed out while waiting for "
+ "SESSION_CB_ON_MESSAGE_RECEIVED")
+ missing_rx = missing_rx + 1
+ continue
+
+ autils.extract_stats(
+ s_dut,
+ data=latencies,
+ results=results[key],
+ key_prefix="",
+ log_prefix="Subscribe Session Discovery (dw24=%d, dw5=%d)" %
+ (dw_24ghz, dw_5ghz))
+ results[key]["failed_tx"] = failed_tx
+ results[key]["messages_rx"] = messages_rx
+ results[key]["missing_rx"] = missing_rx
+ results[key]["corrupted_rx"] = corrupted_rx
+
+ # clean up
+ p_dut.droid.wifiAwareDestroyAll()
+ s_dut.droid.wifiAwareDestroyAll()
+
+ def run_ndp_oob_latency(self, results, dw_24ghz, dw_5ghz, num_iterations):
+ """Runs the NDP setup with OOB (out-of-band) discovery latency test.
+
+ 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.
+ """
+ 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)
+ results[key_avail] = {}
+ results[key_link_props] = {}
+ results[key_avail]["num_iterations"] = num_iterations
+
+ init_dut = self.android_devices[0]
+ init_dut.pretty_name = 'Initiator'
+ resp_dut = self.android_devices[1]
+ resp_dut.pretty_name = 'Responder'
+
+ # override the default DW configuration
+ autils.config_dw_all_modes(init_dut, dw_24ghz, dw_5ghz)
+ autils.config_dw_all_modes(resp_dut, dw_24ghz, dw_5ghz)
+
+ # Initiator+Responder: attach and wait for confirmation & identity
+ init_id = init_dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ init_ident_event = autils.wait_for_event(init_dut,
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ init_mac = init_ident_event['data']['mac']
+ time.sleep(self.device_startup_offset)
+ resp_id = resp_dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ resp_ident_event = autils.wait_for_event(resp_dut,
+ aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ resp_mac = resp_ident_event['data']['mac']
+
+ # wait for for devices to synchronize with each other - there are no other
+ # mechanisms to make sure this happens for OOB discovery (except retrying
+ # to execute the data-path request)
+ time.sleep(autils.WAIT_FOR_CLUSTER)
+
+ on_available_latencies = []
+ link_props_latencies = []
+ ndp_setup_failures = 0
+ for i in range(num_iterations):
+ # Responder: request network
+ resp_req_key = autils.request_network(
+ resp_dut,
+ resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+ resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, None))
+
+ # Initiator: request network
+ init_req_key = autils.request_network(
+ init_dut,
+ init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+ init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, None))
+
+ # Initiator & Responder: wait for network formation
+ got_on_available = False
+ got_on_link_props = False
+ while not got_on_available or not got_on_link_props:
+ try:
+ nc_event = init_dut.ed.pop_event(cconsts.EVENT_NETWORK_CALLBACK,
+ autils.EVENT_NDP_TIMEOUT)
+ if nc_event["data"][
+ cconsts.NETWORK_CB_KEY_EVENT] == cconsts.NETWORK_CB_AVAILABLE:
+ got_on_available = True
+ on_available_latencies.append(
+ nc_event["data"][cconsts.NETWORK_CB_KEY_CURRENT_TS] -
+ nc_event["data"][cconsts.NETWORK_CB_KEY_CREATE_TS])
+ elif (nc_event["data"][cconsts.NETWORK_CB_KEY_EVENT] ==
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+ got_on_link_props = True
+ link_props_latencies.append(
+ nc_event["data"][cconsts.NETWORK_CB_KEY_CURRENT_TS] -
+ nc_event["data"][cconsts.NETWORK_CB_KEY_CREATE_TS])
+ except queue.Empty:
+ ndp_setup_failures = ndp_setup_failures + 1
+ init_dut.log.info("[Initiator] Timed out while waiting for "
+ "EVENT_NETWORK_CALLBACK")
+ break
+
+ # clean-up
+ init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+ resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+
+ # wait before trying another iteration (need to let CM clean-up)
+ time.sleep(10)
+
+ autils.extract_stats(
+ init_dut,
+ data=on_available_latencies,
+ results=results[key_avail],
+ key_prefix="",
+ log_prefix="NDP setup OnAvailable(dw24=%d, dw5=%d)" % (dw_24ghz,
+ dw_5ghz))
+ autils.extract_stats(
+ init_dut,
+ data=link_props_latencies,
+ results=results[key_link_props],
+ key_prefix="",
+ log_prefix="NDP setup OnLinkProperties (dw24=%d, dw5=%d)" % (dw_24ghz,
+ dw_5ghz))
+ results[key_avail]["ndp_setup_failures"] = ndp_setup_failures
+
+
+ ########################################################################
+
+ def test_synchronization_default_dws(self):
+ """Measure the device synchronization for default dws. Loop over values
+ from 0 to 4 seconds."""
+ results = {}
+ for startup_offset in range(5):
+ self.run_synchronization_latency(
+ results=results,
+ do_unsolicited_passive=True,
+ dw_24ghz=aconsts.DW_24_INTERACTIVE,
+ dw_5ghz=aconsts.DW_5_INTERACTIVE,
+ num_iterations=10,
+ startup_offset=startup_offset,
+ timeout_period=20)
+ asserts.explicit_pass(
+ "test_synchronization_default_dws finished", extras=results)
+
+ def test_synchronization_non_interactive_dws(self):
+ """Measure the device synchronization for non-interactive dws. Loop over
+ values from 0 to 4 seconds."""
+ results = {}
+ for startup_offset in range(5):
+ self.run_synchronization_latency(
+ results=results,
+ do_unsolicited_passive=True,
+ dw_24ghz=aconsts.DW_24_NON_INTERACTIVE,
+ dw_5ghz=aconsts.DW_5_NON_INTERACTIVE,
+ num_iterations=10,
+ startup_offset=startup_offset,
+ timeout_period=20)
+ asserts.explicit_pass(
+ "test_synchronization_non_interactive_dws finished", extras=results)
+
+ def test_discovery_latency_default_dws(self):
+ """Measure the service discovery latency with the default DW configuration.
+ """
+ results = {}
+ self.run_discovery_latency(
+ results=results,
+ do_unsolicited_passive=True,
+ dw_24ghz=aconsts.DW_24_INTERACTIVE,
+ dw_5ghz=aconsts.DW_5_INTERACTIVE,
+ num_iterations=100)
+ asserts.explicit_pass(
+ "test_discovery_latency_default_parameters finished", extras=results)
+
+ def test_discovery_latency_non_interactive_dws(self):
+ """Measure the service discovery latency with the DW configuration for non
+ -interactive mode (lower power)."""
+ results = {}
+ self.run_discovery_latency(
+ results=results,
+ do_unsolicited_passive=True,
+ dw_24ghz=aconsts.DW_24_NON_INTERACTIVE,
+ dw_5ghz=aconsts.DW_5_NON_INTERACTIVE,
+ num_iterations=100)
+ asserts.explicit_pass(
+ "test_discovery_latency_non_interactive_dws finished", extras=results)
+
+ def test_discovery_latency_all_dws(self):
+ """Measure the service discovery latency with all DW combinations (low
+ iteration count)"""
+ results = {}
+ for dw24 in range(1, 6): # permitted values: 1-5
+ for dw5 in range(0, 6): # permitted values: 0, 1-5
+ self.run_discovery_latency(
+ results=results,
+ do_unsolicited_passive=True,
+ dw_24ghz=dw24,
+ dw_5ghz=dw5,
+ num_iterations=10)
+ asserts.explicit_pass(
+ "test_discovery_latency_all_dws finished", extras=results)
+
+ def test_message_latency_default_dws(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.DW_24_INTERACTIVE,
+ dw_5ghz=aconsts.DW_5_INTERACTIVE,
+ num_iterations=100)
+ 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 -
+ i.e. waiting for confirmation of reception (ACK) before sending the next
+ message."""
+ results = {}
+ self.run_message_latency(
+ results=results,
+ dw_24ghz=aconsts.DW_24_NON_INTERACTIVE,
+ dw_5ghz=aconsts.DW_5_NON_INTERACTIVE,
+ num_iterations=100)
+ asserts.explicit_pass(
+ "test_message_latency_non_interactive_dws finished", extras=results)
+
+ def test_oob_ndp_setup_latency_default_dws(self):
+ """Measure the NDP setup latency with the default DW configuration. The
+ NDP is setup with OOB (out-of-band) configuration."""
+ results = {}
+ self.run_ndp_oob_latency(
+ results=results,
+ dw_24ghz=aconsts.DW_24_INTERACTIVE,
+ dw_5ghz=aconsts.DW_5_INTERACTIVE,
+ num_iterations=10)
+ asserts.explicit_pass(
+ "test_ndp_setup_latency_default_dws finished", extras=results)
+
+ def test_oob_ndp_setup_latency_non_interactive_dws(self):
+ """Measure the NDP setup latency with the DW configuration for
+ non-interactive mode. The NDP is setup with OOB (out-of-band)
+ configuration"""
+ results = {}
+ self.run_ndp_oob_latency(
+ results=results,
+ dw_24ghz=aconsts.DW_24_NON_INTERACTIVE,
+ dw_5ghz=aconsts.DW_5_NON_INTERACTIVE,
+ num_iterations=10)
+ asserts.explicit_pass(
+ "test_ndp_setup_latency_non_interactive_dws finished", extras=results)
diff --git a/acts/tests/google/wifi/aware/performance/ThroughputTest.py b/acts/tests/google/wifi/aware/performance/ThroughputTest.py
new file mode 100644
index 0000000..fe2fd0c
--- /dev/null
+++ b/acts/tests/google/wifi/aware/performance/ThroughputTest.py
@@ -0,0 +1,116 @@
+#!/usr/bin/python3.4
+#
+# 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 json
+import pprint
+import time
+
+from acts import asserts
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class ThroughputTest(AwareBaseTest):
+ """Set of tests for Wi-Fi Aware to measure latency of Aware operations."""
+
+ SERVICE_NAME = "GoogleTestServiceXYZ"
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def request_network(self, dut, ns):
+ """Request a Wi-Fi Aware network.
+
+ Args:
+ dut: Device
+ ns: Network specifier
+ Returns: the request key
+ """
+ network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+ return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+ def run_iperf_single_ndp_aware_only(self, use_ib, results):
+ """Measure iperf performance on a single NDP, with Aware enabled and no
+ infrastructure connection - i.e. device is not associated to an AP.
+
+ Args:
+ use_ib: True to use in-band discovery, False to use out-of-band discovery.
+ results: Dictionary into which to place test results.
+ """
+ init_dut = self.android_devices[0]
+ resp_dut = self.android_devices[1]
+
+ if use_ib:
+ # note: Publisher = Responder, Subscribe = Initiator
+ (resp_req_key, init_req_key, resp_aware_if,
+ init_aware_if, resp_ipv6, init_ipv6) = autils.create_ib_ndp(
+ resp_dut, init_dut,
+ autils.create_discovery_config(self.SERVICE_NAME,
+ aconsts.PUBLISH_TYPE_UNSOLICITED),
+ autils.create_discovery_config(self.SERVICE_NAME,
+ aconsts.SUBSCRIBE_TYPE_PASSIVE),
+ self.device_startup_offset)
+ else:
+ (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+ resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+ self.log.info("Interface names: I=%s, R=%s", init_aware_if, resp_aware_if)
+ self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+ resp_ipv6)
+
+ # Run iperf3
+ result, data = init_dut.run_iperf_server("-D")
+ asserts.assert_true(result, "Can't start iperf3 server")
+
+ result, data = resp_dut.run_iperf_client(
+ "%s%%%s" % (init_ipv6, resp_aware_if), "-6 -J")
+ self.log.debug(data)
+ asserts.assert_true(result,
+ "Failure starting/running iperf3 in client mode")
+ self.log.debug(pprint.pformat(data))
+
+ # clean-up
+ resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+ init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+ # Collect results
+ data_json = json.loads("".join(data))
+ if "error" in data_json:
+ asserts.fail(
+ "iperf run failed: %s" % data_json["error"], extras=data_json)
+ results["tx_rate"] = data_json["end"]["sum_sent"]["bits_per_second"]
+ results["rx_rate"] = data_json["end"]["sum_received"]["bits_per_second"]
+ self.log.info("iPerf3: Sent = %d bps Received = %d bps", results["tx_rate"],
+ results["rx_rate"])
+
+ ########################################################################
+
+ def test_iperf_single_ndp_aware_only_ib(self):
+ """Measure throughput using iperf on a single NDP, with Aware enabled and
+ no infrastructure connection. Use in-band discovery."""
+ results = {}
+ self.run_iperf_single_ndp_aware_only(use_ib=True, results=results)
+ asserts.explicit_pass(
+ "test_iperf_single_ndp_aware_only_ib passes", extras=results)
+
+ def test_iperf_single_ndp_aware_only_oob(self):
+ """Measure throughput using iperf on a single NDP, with Aware enabled and
+ no infrastructure connection. Use out-of-band discovery."""
+ results = {}
+ self.run_iperf_single_ndp_aware_only(use_ib=False, results=results)
+ asserts.explicit_pass(
+ "test_iperf_single_ndp_aware_only_ib passes", extras=results)
diff --git a/acts/tests/google/wifi/aware/performance/performance b/acts/tests/google/wifi/aware/performance/performance
new file mode 100644
index 0000000..17e3963
--- /dev/null
+++ b/acts/tests/google/wifi/aware/performance/performance
@@ -0,0 +1,2 @@
+LatencyTest
+ThroughputTest
\ No newline at end of file
diff --git a/acts/tests/google/wifi/aware/stress/DataPathStressTest.py b/acts/tests/google/wifi/aware/stress/DataPathStressTest.py
new file mode 100644
index 0000000..ab204b6
--- /dev/null
+++ b/acts/tests/google/wifi/aware/stress/DataPathStressTest.py
@@ -0,0 +1,149 @@
+#!/usr/bin/python3.4
+#
+# 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 queue
+import time
+
+from acts import asserts
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class DataPathStressTest(AwareBaseTest):
+
+ # Number of iterations on create/destroy Attach sessions.
+ ATTACH_ITERATIONS = 2
+
+ # Number of iterations on create/destroy NDP in each discovery session.
+ NDP_ITERATIONS = 5
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ ################################################################
+
+ def test_oob_ndp_stress(self):
+ """Run NDP (NAN data-path) stress test creating and destroying Aware
+ attach sessions, discovery sessions, and NDPs."""
+ init_dut = self.android_devices[0]
+ init_dut.pretty_name = 'Initiator'
+ resp_dut = self.android_devices[1]
+ resp_dut.pretty_name = 'Responder'
+
+ ndp_init_setup_success = 0
+ ndp_init_setup_failures = 0
+ ndp_resp_setup_success = 0
+ ndp_resp_setup_failures = 0
+
+ for attach_iter in range(self.ATTACH_ITERATIONS):
+ init_id = init_dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ init_ident_event = autils.wait_for_event(
+ init_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ init_mac = init_ident_event['data']['mac']
+ time.sleep(self.device_startup_offset)
+ resp_id = resp_dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+ resp_ident_event = autils.wait_for_event(
+ resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+ resp_mac = resp_ident_event['data']['mac']
+
+ # wait for for devices to synchronize with each other - there are no other
+ # mechanisms to make sure this happens for OOB discovery (except retrying
+ # to execute the data-path request)
+ time.sleep(autils.WAIT_FOR_CLUSTER)
+
+ for ndp_iteration in range(self.NDP_ITERATIONS):
+ # Responder: request network
+ resp_req_key = autils.request_network(
+ resp_dut,
+ resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+ resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, None))
+
+ # Initiator: request network
+ init_req_key = autils.request_network(
+ init_dut,
+ init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+ init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, None))
+
+ # Initiator: wait for network formation
+ got_on_available = False
+ got_on_link_props = False
+ while not got_on_available or not got_on_link_props:
+ try:
+ nc_event = init_dut.ed.pop_event(cconsts.EVENT_NETWORK_CALLBACK,
+ autils.EVENT_NDP_TIMEOUT)
+ if nc_event['data'][
+ cconsts.NETWORK_CB_KEY_EVENT] == cconsts.NETWORK_CB_AVAILABLE:
+ got_on_available = True
+ elif (nc_event['data'][cconsts.NETWORK_CB_KEY_EVENT] ==
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+ got_on_link_props = True
+ except queue.Empty:
+ ndp_init_setup_failures = ndp_init_setup_failures + 1
+ init_dut.log.info('[Initiator] Timed out while waiting for '
+ 'EVENT_NETWORK_CALLBACK')
+ break
+
+ if got_on_available and got_on_link_props:
+ ndp_init_setup_success = ndp_init_setup_success + 1
+
+ # Responder: wait for network formation
+ got_on_available = False
+ got_on_link_props = False
+ while not got_on_available or not got_on_link_props:
+ try:
+ nc_event = resp_dut.ed.pop_event(cconsts.EVENT_NETWORK_CALLBACK,
+ autils.EVENT_NDP_TIMEOUT)
+ if nc_event['data'][
+ cconsts.NETWORK_CB_KEY_EVENT] == cconsts.NETWORK_CB_AVAILABLE:
+ got_on_available = True
+ elif (nc_event['data'][cconsts.NETWORK_CB_KEY_EVENT] ==
+ cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+ got_on_link_props = True
+ except queue.Empty:
+ ndp_resp_setup_failures = ndp_resp_setup_failures + 1
+ init_dut.log.info('[Responder] Timed out while waiting for '
+ 'EVENT_NETWORK_CALLBACK')
+ break
+
+ if got_on_available and got_on_link_props:
+ ndp_resp_setup_success = ndp_resp_setup_success + 1
+
+ # clean-up
+ init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+ resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+
+ # wait before trying another iteration (need to let CM clean-up)
+ time.sleep(10)
+
+ # clean-up at end of iteration
+ init_dut.droid.wifiAwareDestroy(init_id)
+ resp_dut.droid.wifiAwareDestroy(resp_id)
+
+ results = {}
+ results['ndp_init_setup_success'] = ndp_init_setup_success
+ results['ndp_init_setup_failures'] = ndp_init_setup_failures
+ results['ndp_resp_setup_success'] = ndp_resp_setup_success
+ results['ndp_resp_setup_failures'] = ndp_resp_setup_failures
+ asserts.assert_equal(
+ ndp_init_setup_failures + ndp_resp_setup_failures,
+ 0,
+ 'test_oob_ndp_stress finished',
+ extras=results)
+ asserts.explicit_pass("test_oob_ndp_stress done", extras=results)
diff --git a/acts/tests/google/wifi/aware/stress/DiscoveryStressTest.py b/acts/tests/google/wifi/aware/stress/DiscoveryStressTest.py
new file mode 100644
index 0000000..8b3d925
--- /dev/null
+++ b/acts/tests/google/wifi/aware/stress/DiscoveryStressTest.py
@@ -0,0 +1,109 @@
+#!/usr/bin/python3.4
+#
+# 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 queue
+import time
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class DiscoveryStressTest(AwareBaseTest):
+ """Stress tests for Discovery sessions"""
+
+ # Number of iterations on create/destroy Attach sessions.
+ ATTACH_ITERATIONS = 2
+
+ # Number of iterations on create/destroy Discovery sessions
+ DISCOVERY_ITERATIONS = 40
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ ####################################################################
+
+ def test_discovery_stress(self):
+ """Create and destroy a random array of discovery sessions, up to the
+ limit of capabilities."""
+ dut = self.android_devices[0]
+
+ discovery_setup_success = 0
+ discovery_setup_fail = 0
+
+ for attach_iter in range(self.ATTACH_ITERATIONS):
+ # attach
+ session_id = dut.droid.wifiAwareAttach(True)
+ autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+ p_discovery_ids = []
+ s_discovery_ids = []
+ for discovery_iter in range(self.DISCOVERY_ITERATIONS):
+ service_name = 'GoogleTestService-%d-%d' % (attach_iter, discovery_iter)
+
+ p_config = None
+ s_config = None
+
+ if discovery_iter % 4 == 0: # publish/unsolicited
+ p_config = autils.create_discovery_config(
+ service_name, aconsts.PUBLISH_TYPE_UNSOLICITED)
+ elif discovery_iter % 4 == 1: # publish/solicited
+ p_config = autils.create_discovery_config(
+ service_name, aconsts.PUBLISH_TYPE_SOLICITED)
+ elif discovery_iter % 4 == 2: # subscribe/passive
+ s_config = autils.create_discovery_config(
+ service_name, aconsts.SUBSCRIBE_TYPE_PASSIVE)
+ elif discovery_iter % 4 == 3: # subscribe/active
+ s_config = autils.create_discovery_config(
+ service_name, aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+ if p_config is not None:
+ if len(p_discovery_ids) == dut.aware_capabilities[
+ aconsts.CAP_MAX_PUBLISHES]:
+ dut.droid.wifiAwareDestroyDiscoverySession(
+ p_discovery_ids.pop(dut.aware_capabilities[
+ aconsts.CAP_MAX_PUBLISHES] // 2))
+ disc_id = dut.droid.wifiAwarePublish(session_id, p_config)
+ event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+ p_discovery_ids.append(disc_id)
+ else:
+ if len(s_discovery_ids) == dut.aware_capabilities[
+ aconsts.CAP_MAX_SUBSCRIBES]:
+ dut.droid.wifiAwareDestroyDiscoverySession(
+ s_discovery_ids.pop(dut.aware_capabilities[
+ aconsts.CAP_MAX_SUBSCRIBES] // 2))
+ disc_id = dut.droid.wifiAwareSubscribe(session_id, s_config)
+ event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+ s_discovery_ids.append(disc_id)
+
+ try:
+ dut.ed.pop_event(event_name, autils.EVENT_TIMEOUT)
+ discovery_setup_success = discovery_setup_success + 1
+ except queue.Empty:
+ discovery_setup_fail = discovery_setup_fail + 1
+
+ dut.droid.wifiAwareDestroy(session_id)
+
+ results = {}
+ results['discovery_setup_success'] = discovery_setup_success
+ results['discovery_setup_fail'] = discovery_setup_fail
+ asserts.assert_equal(
+ discovery_setup_fail,
+ 0,
+ 'test_discovery_stress finished',
+ extras=results)
+ asserts.explicit_pass('test_discovery_stress done', extras=results)
diff --git a/acts/tests/google/wifi/aware/stress/MessagesStressTest.py b/acts/tests/google/wifi/aware/stress/MessagesStressTest.py
new file mode 100644
index 0000000..5871d61
--- /dev/null
+++ b/acts/tests/google/wifi/aware/stress/MessagesStressTest.py
@@ -0,0 +1,269 @@
+#!/usr/bin/python3.4
+#
+# 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 queue
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+KEY_ID = "id"
+KEY_TX_OK_COUNT = "tx_ok_count"
+KEY_TX_FAIL_COUNT = "tx_fail_count"
+KEY_RX_COUNT = "rx_count"
+
+
+class MessagesStressTest(AwareBaseTest):
+ """Set of stress tests for Wi-Fi Aware L2 (layer 2) message exchanges."""
+ NUM_ITERATIONS = 100
+ SERVICE_NAME = "GoogleTestServiceXY"
+
+ def __init__(self, controllers):
+ AwareBaseTest.__init__(self, controllers)
+
+ def init_info(self, msg, id, messages_by_msg, messages_by_id):
+ """Initialize the message data structures.
+
+ Args:
+ msg: message text
+ id: message id
+ messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+ messages_by_id: {id -> text}
+ """
+ messages_by_msg[msg] = {}
+ messages_by_msg[msg][KEY_ID] = id
+ messages_by_msg[msg][KEY_TX_OK_COUNT] = 0
+ messages_by_msg[msg][KEY_TX_FAIL_COUNT] = 0
+ messages_by_msg[msg][KEY_RX_COUNT] = 0
+ messages_by_id[id] = msg
+
+ def wait_for_tx_events(self, dut, num_msgs, messages_by_msg, messages_by_id):
+ """Wait for messages to be transmitted and update data structures.
+
+ Args:
+ dut: device under test
+ num_msgs: number of expected message tx
+ messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+ messages_by_id: {id -> text}
+ """
+ num_ok_tx_confirmations = 0
+ num_fail_tx_confirmations = 0
+ num_unexpected_ids = 0
+ tx_events_regex = "%s|%s" % (aconsts.SESSION_CB_ON_MESSAGE_SEND_FAILED,
+ aconsts.SESSION_CB_ON_MESSAGE_SENT)
+ while num_ok_tx_confirmations + num_fail_tx_confirmations < num_msgs:
+ try:
+ events = dut.ed.pop_events(tx_events_regex, autils.EVENT_TIMEOUT)
+ for event in events:
+ if (event["name"] != aconsts.SESSION_CB_ON_MESSAGE_SENT and
+ event["name"] != aconsts.SESSION_CB_ON_MESSAGE_SEND_FAILED):
+ asserts.fail("Unexpected event: %s" % event)
+ is_tx_ok = event["name"] == aconsts.SESSION_CB_ON_MESSAGE_SENT
+
+ id = event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID]
+ if id in messages_by_id:
+ msg = messages_by_id[id]
+ if is_tx_ok:
+ messages_by_msg[msg][
+ KEY_TX_OK_COUNT] = messages_by_msg[msg][KEY_TX_OK_COUNT] + 1
+ if messages_by_msg[msg][KEY_TX_OK_COUNT] == 1:
+ num_ok_tx_confirmations = num_ok_tx_confirmations + 1
+ else:
+ messages_by_msg[msg][KEY_TX_FAIL_COUNT] = (
+ messages_by_msg[msg][KEY_TX_FAIL_COUNT] + 1)
+ if messages_by_msg[msg][KEY_TX_FAIL_COUNT] == 1:
+ num_fail_tx_confirmations = num_fail_tx_confirmations + 1
+ else:
+ self.log.warning(
+ "Tx confirmation of unknown message ID received: %s", event)
+ num_unexpected_ids = num_unexpected_ids + 1
+ except queue.Empty:
+ self.log.warning("[%s] Timed out waiting for any MESSAGE_SEND* event - "
+ "assuming the rest are not coming", dut.pretty_name)
+ break
+
+ return (num_ok_tx_confirmations, num_fail_tx_confirmations,
+ num_unexpected_ids)
+
+ def wait_for_rx_events(self, dut, num_msgs, messages_by_msg):
+ """Wait for messages to be received and update data structures
+
+ Args:
+ dut: device under test
+ num_msgs: number of expected messages to receive
+ messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+ """
+ num_rx_msgs = 0
+ while num_rx_msgs < num_msgs:
+ try:
+ event = dut.ed.pop_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+ autils.EVENT_TIMEOUT)
+ msg = event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
+ if msg not in messages_by_msg:
+ messages_by_msg[msg] = {}
+ messages_by_msg[msg][KEY_ID] = -1
+ messages_by_msg[msg][KEY_TX_OK_COUNT] = 0
+ messages_by_msg[msg][KEY_TX_FAIL_COUNT] = 0
+ messages_by_msg[msg][KEY_RX_COUNT] = 1
+
+ messages_by_msg[msg][
+ KEY_RX_COUNT] = messages_by_msg[msg][KEY_RX_COUNT] + 1
+ if messages_by_msg[msg][KEY_RX_COUNT] == 1:
+ num_rx_msgs = num_rx_msgs + 1
+ except queue.Empty:
+ self.log.warning(
+ "[%s] Timed out waiting for ON_MESSAGE_RECEIVED event - "
+ "assuming the rest are not coming", dut.pretty_name)
+ break
+
+ def analyze_results(self, results, messages_by_msg):
+ """Analyze the results of the stress message test and add to the results
+ dictionary
+
+ Args:
+ results: result dictionary into which to add data
+ messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+ """
+ results["raw_data"] = messages_by_msg
+ results["tx_count_success"] = 0
+ results["tx_count_duplicate_success"] = 0
+ results["tx_count_fail"] = 0
+ results["tx_count_duplicate_fail"] = 0
+ results["tx_count_neither"] = 0
+ results["tx_count_tx_ok_but_no_rx"] = 0
+ results["rx_count"] = 0
+ results["rx_count_duplicate"] = 0
+ results["rx_count_no_ok_tx_indication"] = 0
+ results["rx_count_fail_tx_indication"] = 0
+ results["rx_count_no_tx_message"] = 0
+
+ for msg, data in messages_by_msg.items():
+ if data[KEY_TX_OK_COUNT] > 0:
+ results["tx_count_success"] = results["tx_count_success"] + 1
+ if data[KEY_TX_OK_COUNT] > 1:
+ results["tx_count_duplicate_success"] = (
+ results["tx_count_duplicate_success"] + 1)
+ if data[KEY_TX_FAIL_COUNT] > 0:
+ results["tx_count_fail"] = results["tx_count_fail"] + 1
+ if data[KEY_TX_FAIL_COUNT] > 1:
+ results[
+ "tx_count_duplicate_fail"] = results["tx_count_duplicate_fail"] + 1
+ if (data[KEY_TX_OK_COUNT] == 0 and data[KEY_TX_FAIL_COUNT] == 0 and
+ data[KEY_ID] != -1):
+ results["tx_count_neither"] = results["tx_count_neither"] + 1
+ if data[KEY_TX_OK_COUNT] > 0 and data[KEY_RX_COUNT] == 0:
+ results["tx_count_tx_ok_but_no_rx"] = (
+ results["tx_count_tx_ok_but_no_rx"] + 1)
+ if data[KEY_RX_COUNT] > 0:
+ results["rx_count"] = results["rx_count"] + 1
+ if data[KEY_RX_COUNT] > 1:
+ results["rx_count_duplicate"] = results["rx_count_duplicate"] + 1
+ if data[KEY_RX_COUNT] > 0 and data[KEY_TX_OK_COUNT] == 0:
+ results["rx_count_no_ok_tx_indication"] = (
+ results["rx_count_no_ok_tx_indication"] + 1)
+ if data[KEY_RX_COUNT] > 0 and data[KEY_TX_FAIL_COUNT] > 0:
+ results["rx_count_fail_tx_indication"] = (
+ results["rx_count_fail_tx_indication"] + 1)
+ if data[KEY_RX_COUNT] > 0 and data[KEY_ID] == -1:
+ results[
+ "rx_count_no_tx_message"] = results["rx_count_no_tx_message"] + 1
+
+ #######################################################################
+
+ def test_stress_message(self):
+ """Stress test for bi-directional message transmission and reception."""
+ p_dut = self.android_devices[0]
+ s_dut = self.android_devices[1]
+
+ # Start up a discovery session
+ discovery_data = autils.create_discovery_pair(
+ p_dut,
+ s_dut,
+ p_config=autils.create_discovery_config(
+ self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+ s_config=autils.create_discovery_config(self.SERVICE_NAME,
+ aconsts.SUBSCRIBE_TYPE_PASSIVE),
+ device_startup_offset=self.device_startup_offset,
+ msg_id=self.get_next_msg_id())
+ p_id = discovery_data[0]
+ s_id = discovery_data[1]
+ p_disc_id = discovery_data[2]
+ s_disc_id = discovery_data[3]
+ peer_id_on_sub = discovery_data[4]
+ peer_id_on_pub = discovery_data[5]
+
+ # Store information on Tx & Rx messages
+ messages_by_msg = {} # keyed by message text
+ # {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+ messages_by_id = {} # keyed by message ID {id -> text}
+
+ # send all messages at once (one in each direction)
+ for i in range(self.NUM_ITERATIONS):
+ msg_p2s = "Message Publisher -> Subscriber #%d" % i
+ next_msg_id = self.get_next_msg_id()
+ self.init_info(msg_p2s, next_msg_id, messages_by_msg, messages_by_id)
+ p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub, next_msg_id,
+ msg_p2s, 0)
+
+ msg_s2p = "Message Subscriber -> Publisher #%d" % i
+ next_msg_id = self.get_next_msg_id()
+ self.init_info(msg_s2p, next_msg_id, messages_by_msg, messages_by_id)
+ s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, next_msg_id,
+ msg_s2p, 0)
+
+ # wait for message tx confirmation
+ (p_tx_ok_count, p_tx_fail_count, p_tx_unknown_id) = self.wait_for_tx_events(
+ p_dut, self.NUM_ITERATIONS, messages_by_msg, messages_by_id)
+ (s_tx_ok_count, s_tx_fail_count, s_tx_unknown_id) = self.wait_for_tx_events(
+ s_dut, self.NUM_ITERATIONS, messages_by_msg, messages_by_id)
+ self.log.info("Transmission done: pub=%d, sub=%d transmitted successfully",
+ p_tx_ok_count, s_tx_ok_count)
+
+ # wait for message rx confirmation (giving it the total number of messages
+ # transmitted rather than just those transmitted correctly since sometimes
+ # the Tx doesn't get that information correctly. I.e. a message the Tx
+ # thought was not transmitted correctly is actually received - missing ACK?
+ # bug?)
+ self.wait_for_rx_events(p_dut, self.NUM_ITERATIONS, messages_by_msg)
+ self.wait_for_rx_events(s_dut, self.NUM_ITERATIONS, messages_by_msg)
+
+ # analyze results
+ results = {}
+ results["tx_count"] = 2 * self.NUM_ITERATIONS
+ results["tx_unknown_ids"] = p_tx_unknown_id + s_tx_unknown_id
+ self.analyze_results(results, messages_by_msg)
+
+ # clear errors
+ asserts.assert_equal(results["tx_unknown_ids"], 0, "Message ID corruption",
+ results)
+ asserts.assert_equal(results["tx_count_duplicate_fail"], 0,
+ "Duplicate Tx fail messages", results)
+ asserts.assert_equal(results["tx_count_duplicate_success"], 0,
+ "Duplicate Tx success messages", results)
+ asserts.assert_equal(results["rx_count_no_tx_message"], 0,
+ "Rx message which wasn't sent - message corruption?",
+ results)
+ asserts.assert_equal(results["tx_count_tx_ok_but_no_rx"], 0,
+ "Tx got ACK but Rx didn't get message", results)
+
+ # possibly ok - but flag since most frequently a bug
+ asserts.assert_equal(results["rx_count_no_ok_tx_indication"], 0,
+ "Message received but Tx didn't get ACK", results)
+ asserts.assert_equal(results["rx_count_fail_tx_indication"], 0,
+ "Message received but Tx didn't get ACK", results)
+
+ asserts.explicit_pass("test_stress_message done", extras=results)
diff --git a/acts/tests/google/wifi/aware/stress/stress b/acts/tests/google/wifi/aware/stress/stress
new file mode 100644
index 0000000..0860507
--- /dev/null
+++ b/acts/tests/google/wifi/aware/stress/stress
@@ -0,0 +1,3 @@
+MessagesStressTest
+DataPathStressTest
+DiscoveryStressTest
\ No newline at end of file
diff --git a/acts/tests/sample/RelayDeviceSampleTest.py b/acts/tests/sample/RelayDeviceSampleTest.py
old mode 100755
new mode 100644
index e4622ed..040ef62
--- a/acts/tests/sample/RelayDeviceSampleTest.py
+++ b/acts/tests/sample/RelayDeviceSampleTest.py
@@ -17,6 +17,7 @@
from acts import test_runner
from acts.controllers.relay_lib.relay import SynchronizeRelays
+
class RelayDeviceSampleTest(base_test.BaseTestClass):
""" Demonstrates example usage of a configurable access point."""
@@ -27,7 +28,7 @@
# You can use this workaround to get devices by name:
relay_rig = self.relay_devices[0].rig
- #self.other_relay_device = relay_rig.devices['UniqueDeviceName']
+ self.other_relay_device = relay_rig.devices['UniqueDeviceName']
# Note: If the "devices" key from the config is missing
# a GenericRelayDevice that contains every switch in the config
# will be stored in relay_devices[0]. Its name will be
@@ -43,13 +44,61 @@
# Unless overridden, the default state is all switches set to off.
self.relay_device.clean_up()
+ # Typical use of a GenericRelayDevice looks like this:
+ def test_relay_device(self):
+
+ # This function call will sleep until .25 seconds are up.
+ # Blocking_nc_for will emulate a button press, which turns on the relay
+ # (or stays on if it already was on) for the given time, and then turns
+ # off.
+ self.relay_device.relays['BT_Power_Button'].set_nc_for(.25)
+
+ # do_something_after_turning_on_bt_power()
+
+ # Note that the relays are mechanical switches, and do take real time
+ # to go from one state to the next.
+
+ self.relay_device.relays['BT_Pair'].set_nc()
+
+ # do_something_while_holding_down_the_pair_button()
+
+ self.relay_device.relays['BT_Pair'].set_no()
+
+ # do_something_after_releasing_bt_pair()
+
+ # Note that although cleanup sets the relays to the 'NO' state after
+ # each test, they do not press things like the power button to turn
+ # off whatever hardware is attached. When using a GenericRelayDevice,
+ # you'll have to do this manually.
+ # Other RelayDevices may handle this for you in their clean_up() call.
+ self.relay_device.relays['BT_Power_Button'].set_nc_for(.25)
def test_toggling(self):
# This test just spams the toggle on each relay.
- ## print(self.relay_device.relays.mac_address)
- print("--->" + self.relay_device.mac_address + "<---")
for _ in range(0, 2):
- self.relay_device.relays['Play'].toggle()
+ self.relay_device.relays['BT_Power_Button'].toggle()
+ self.relay_device.relays['BT_Pair'].toggle()
+ self.relay_device.relays['BT_Reset'].toggle()
+ self.relay_device.relays['BT_SomethingElse'].toggle()
+
+ def test_synchronize_relays(self):
+ """Toggles relays using SynchronizeRelays().
+
+ This makes each relay do it's action at the same time, without waiting
+ after each relay to swap. Instead, all relays swap at the same time, and
+ the wait is done after exiting the with statement.
+ """
+ for _ in range(0, 10):
+ with SynchronizeRelays():
+ self.relay_device.relays['BT_Power_Button'].toggle()
+ self.relay_device.relays['BT_Pair'].toggle()
+ self.relay_device.relays['BT_Reset'].toggle()
+ self.relay_device.relays['BT_SomethingElse'].toggle()
+
+ # For more fine control over the wait time of relays, you can set
+ # Relay.transition_wait_time. This is not recommended unless you are
+ # using solid state relays, or async calls.
+
if __name__ == "__main__":
test_runner.main()
diff --git a/tools/.gitignore b/tools/.gitignore
new file mode 100644
index 0000000..c436601
--- /dev/null
+++ b/tools/.gitignore
@@ -0,0 +1,67 @@
+.DS_Store
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+#Ipython Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
diff --git a/tools/commit_message_check.py b/tools/commit_message_check.py
new file mode 100755
index 0000000..092c18a
--- /dev/null
+++ b/tools/commit_message_check.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+#
+# 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.
+from __future__ import print_function
+
+import logging
+import os
+import re
+import sys
+
+from acts.libs.proc import job
+
+GLOBAL_KEYWORDS_FILEPATH = 'vendor/google_testing/comms/framework/etc/' \
+ 'commit_keywords'
+LOCAL_KEYWORDS_FILEPATH = '~/.repo_acts_commit_keywords'
+
+FIND_COMMIT_KEYWORDS = 'git log @{u}..| grep -i %s'
+GET_EMAIL_ADDRESS = 'git log --format=%ce -1'
+
+
+def main(argv):
+ file_path = os.path.join(
+ os.path.dirname(__file__), "../../../../%s" % GLOBAL_KEYWORDS_FILEPATH)
+
+ if not os.path.isfile(file_path):
+ file_path = os.path.expanduser(LOCAL_KEYWORDS_FILEPATH)
+ if not os.path.exists(file_path) or not os.path.isfile(file_path):
+ result = job.run(GET_EMAIL_ADDRESS)
+ if result.stdout.endswith('@google.com'):
+ logging.error(
+ 'You do not have the necessary file %s. Please run '
+ 'tools/ignore_commit_keywords.sh, or link it with the '
+ 'following command:\n ln -sf <internal_master_root>/%s %s'
+ % (LOCAL_KEYWORDS_FILEPATH, GLOBAL_KEYWORDS_FILEPATH,
+ LOCAL_KEYWORDS_FILEPATH))
+ exit(1)
+ return
+
+ with open(file_path) as file:
+ # Places every line within quotes with -e before them.
+ # i.e. '-e "line1" -e "line2" -e "line3"'
+ grep_args = re.sub('^(.+?)$\n?', ' -e "\\1"',
+ file.read(), 0, re.MULTILINE)
+
+ result = job.run(FIND_COMMIT_KEYWORDS % grep_args, ignore_status=True)
+
+ if result.stdout:
+ logging.error('Your commit message contains at least one keyword.')
+ logging.error('Keyword(s) found in the following line(s):')
+ logging.error(result.stdout)
+ logging.error('Please fix/remove these before committing.')
+ exit(1)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/tools/ignore_commit_keywords.sh b/tools/ignore_commit_keywords.sh
new file mode 100755
index 0000000..3a564b8
--- /dev/null
+++ b/tools/ignore_commit_keywords.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# 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.
+
+touch ~/.repo_acts_commit_keywords
+echo "Ignoring repo commit keywords list."
diff --git a/tools/lab/README.md b/tools/lab/README.md
new file mode 100644
index 0000000..0b2ebbb
--- /dev/null
+++ b/tools/lab/README.md
@@ -0,0 +1 @@
+This folder contains tools to be used in the testing lab.
diff --git a/tools/lab/main.py b/tools/lab/main.py
new file mode 100755
index 0000000..873f682
--- /dev/null
+++ b/tools/lab/main.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+#
+# 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.
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import argparse
+import sys
+
+from runner import InstantRunner
+from metrics.usb_metric import UsbMetric
+from reporter import LoggerReporter
+
+
+class RunnerFactory(object):
+ _reporter_constructor = {
+ 'logger': lambda: LoggerReporter(),
+ }
+
+ _metric_constructor = {
+ 'usb_io': lambda param: UsbMetric(),
+ 'disk': lambda param: DiskMetric(),
+ 'uptime': lambda param: UptimeMetric(),
+ 'verify_devices': lambda param: VerifyMetric(),
+ 'ram': lambda param: RamMetric(),
+ 'cpu': lambda param: CpuMetric(),
+ }
+
+ @classmethod
+ def create(cls, arguments):
+ """ Creates the Runner Class that will take care of gather metrics
+ and determining how to report those metrics.
+
+ Args:
+ arguments: The arguments passed in through command line, a dict.
+
+ Returns:
+ Returns a Runner that was created by passing in a list of
+ metrics and list of reporters.
+ """
+ arg_dict = arguments
+ metrics = []
+
+ # If no reporter specified, default to logger.
+ reporters = arg_dict.pop('reporter')
+ if reporters is None:
+ reporters = ['logger']
+
+ # Check keys and values to see what metrics to include.
+ for key in arg_dict:
+ val = arg_dict[key]
+ if val is not None:
+ metrics.append(cls._metric_constructor[key](val))
+
+ return InstantRunner(metrics, reporters)
+
+
+def _argparse():
+ parser = argparse.ArgumentParser(
+ description='Tool for getting lab health of android testing lab',
+ prog='Lab Health')
+
+ parser.add_argument(
+ '-v',
+ '--version',
+ action='version',
+ version='%(prog)s v0.1.0',
+ help='specify version of program')
+ parser.add_argument(
+ '-i',
+ '--usb-io',
+ action='store_true',
+ default=None,
+ help='display recent USB I/O')
+ parser.add_argument(
+ '-u',
+ '--uptime',
+ action='store_true',
+ default=None,
+ help='display uptime of current lab station')
+ parser.add_argument(
+ '-d',
+ '--disk',
+ choices=['size', 'used', 'avail', 'percent'],
+ nargs='*',
+ help='display the disk space statistics')
+ parser.add_argument(
+ '-ra',
+ '--ram',
+ action='store_true',
+ default=None,
+ help='display the current RAM usage')
+ parser.add_argument(
+ '-c',
+ '--cpu',
+ action='count',
+ default=None,
+ help='display the current CPU usage as percent')
+ parser.add_argument(
+ '-vd',
+ '--verify-devices',
+ action='store_true',
+ default=None,
+ help='verify all devices connected are in \'device\' mode')
+ parser.add_argument(
+ '-r',
+ '--reporter',
+ choices=['logger', 'proto', 'json'],
+ nargs='+',
+ help='choose the reporting method needed')
+ parser.add_argument(
+ '-p',
+ '--program',
+ choices=['python', 'adb', 'fastboot', 'os', 'kernel'],
+ nargs='*',
+ help='display the versions of chosen programs (default = all)')
+
+ return parser
+
+
+def main():
+ parser = _argparse()
+
+ if len(sys.argv) == 1:
+ parser.print_help()
+ sys.exit(1)
+
+ RunnerFactory().create(vars(parser.parse_args()))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/lab/main_test.py b/tools/lab/main_test.py
new file mode 100644
index 0000000..366767f
--- /dev/null
+++ b/tools/lab/main_test.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+#
+# 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 unittest
+
+from main import RunnerFactory
+from metrics.usb_metric import UsbMetric
+
+
+class RunnerFactoryTestCase(unittest.TestCase):
+ def test_create_with_reporter(self):
+ self.assertEqual(
+ RunnerFactory.create({
+ 'reporter': ['proto']
+ }).reporter_list, ['proto'])
+
+ def test_create_without_reporter(self):
+ self.assertEqual(
+ RunnerFactory.create({
+ 'reporter': None
+ }).reporter_list, ['logger'])
+
+ def test_metric_none(self):
+ self.assertEqual(
+ RunnerFactory.create({
+ 'disk': None,
+ 'reporter': None
+ }).metric_list, [])
+
+ def test_metric_true(self):
+ self.assertIsInstance(
+ RunnerFactory.create({
+ 'usb_io': True,
+ 'reporter': None
+ }).metric_list[0], UsbMetric)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/lab/metrics/__init__.py b/tools/lab/metrics/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/lab/metrics/__init__.py
diff --git a/tools/lab/metrics/disk_metric.py b/tools/lab/metrics/disk_metric.py
new file mode 100644
index 0000000..8357b42
--- /dev/null
+++ b/tools/lab/metrics/disk_metric.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+#
+# 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 metric
+
+
+class DiskMetric(metric.Metric):
+
+ COMMAND = "df /var/tmp"
+ # Fields for response dictionary
+ TOTAL = 'total'
+ USED = 'used'
+ AVAIL = 'avail'
+ PERCENT_USED = 'percent_used'
+
+ def gather_metric(self):
+ """Finds disk space metrics for /var/tmp
+
+ Returns:
+ A dict with the following fields:
+ total: int representing total space in 1k blocks
+ used: int representing total used in 1k blocks
+ avail: int representing total available in 1k blocks
+ percent_used: int representing percentage space used (0-100)
+ """
+ # Run shell command
+ result = self._shell.run(self.COMMAND)
+ """Example stdout:
+ Filesystem 1K-blocks Used Available Use% Mounted on
+ /dev/dm-1 57542652 18358676 36237928 34% /
+ """
+ # Get only second line
+ output = result.stdout.splitlines()[1]
+ # Split by space
+ fields = output.split()
+ # Create response dictionary
+ response = {
+ self.TOTAL: int(fields[1]),
+ self.USED: int(fields[2]),
+ self.AVAIL: int(fields[3]),
+ # Strip the percentage symbol
+ self.PERCENT_USED: int(fields[4][:-1])
+ }
+ return (response)
diff --git a/tools/lab/metrics/metric.py b/tools/lab/metrics/metric.py
new file mode 100644
index 0000000..9f54237
--- /dev/null
+++ b/tools/lab/metrics/metric.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+#
+# 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.
+
+from utils import job
+from utils import shell
+
+
+class Metric(object):
+ """Interface class for metric gathering.
+
+ Attributes:
+ _shell: a shell.ShellCommand object
+ """
+
+ def __init__(self, shell=shell.ShellCommand(job)):
+ self._shell = shell
+
+ def gather_metric(self):
+ """Gathers all values that this metric watches.
+
+ Mandatory for every class that extends Metric. Should always return.
+
+ Returns:
+ A dict mapping keys (class level constant strings representing
+ fields of a metric) to their statistics.
+
+ Raises:
+ NotImplementedError: A metric did not implement this function.
+ """
+ raise NotImplementedError()
diff --git a/tools/lab/metrics/usb_metric.py b/tools/lab/metrics/usb_metric.py
new file mode 100644
index 0000000..2bead7a
--- /dev/null
+++ b/tools/lab/metrics/usb_metric.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+#
+# 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.
+
+from metrics.metric import Metric
+import job
+
+
+class UsbMetric(Metric):
+ def check_usbmon(self):
+ try:
+ job.run('grep usbmon /proc/modules')
+ except job.Error:
+ print('Kernel module not loaded, attempting to load usbmon')
+ result = job.run('modprobe usbmon', ignore_status=True)
+ if result.exit_status != 0:
+ print result.stderr
+
+ def gather_metric(self):
+ self.check_usbmon()
diff --git a/tools/lab/reporter.py b/tools/lab/reporter.py
new file mode 100644
index 0000000..ce11412
--- /dev/null
+++ b/tools/lab/reporter.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+#
+# 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.
+
+
+class Reporter(object):
+ """ Base class for the multiple ways to report the data gathered.
+
+ The method report takes in a dictionary where the key is the class that
+ generated the value, and the value is the actual data gathered from
+ collecting that metric. For example, an UptimeMetric, would have
+ UptimeMetric() as key, and '1-02:22:42' as the value.
+ """
+
+ def report(self, responses):
+ raise NotImplementedError('Must implement this method')
+
+
+class LoggerReporter(Reporter):
+ def report(self, response_dict):
+ for key in response_dict:
+ print(response_dict[key])
+
+
+class FileReporter(Reporter):
+ pass
diff --git a/tools/lab/runner.py b/tools/lab/runner.py
new file mode 100644
index 0000000..7506938
--- /dev/null
+++ b/tools/lab/runner.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+#
+# 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.
+
+
+class Runner:
+ """Calls metrics and passes response to reporters.
+
+ Attributes:
+ metric_list: a list of metric objects
+ reporter_list: a list of reporter objects
+ object and value is dictionary returned by that response
+ """
+
+ def __init__(self, metric_list, reporter_list):
+ self.metric_list = metric_list
+ self.reporter_list = reporter_list
+
+ def run(self):
+ """Calls metrics and passes response to reporters."""
+ raise NotImplementedError()
+
+
+class InstantRunner(Runner):
+ def run(self):
+ """Calls all metrics, passes responses to reporters."""
+ responses = {}
+ for metric in self.metric_list:
+ responses[metric] = metric.gatherMetric()
+ for reporter in self.reporter_list:
+ reporter.report(responses)
diff --git a/tools/lab/test_main.py b/tools/lab/test_main.py
new file mode 100755
index 0000000..48dc714
--- /dev/null
+++ b/tools/lab/test_main.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+#
+# 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 unittest
+
+if __name__ == "__main__":
+ suite = unittest.TestLoader().discover('.', pattern="*_test.py")
+ unittest.TextTestRunner().run(suite)
diff --git a/tools/lab/tests/__init__.py b/tools/lab/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/lab/tests/__init__.py
diff --git a/tools/lab/tests/disk_metric_test.py b/tools/lab/tests/disk_metric_test.py
new file mode 100755
index 0000000..339ed49
--- /dev/null
+++ b/tools/lab/tests/disk_metric_test.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+#
+# 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 unittest
+
+from metrics import disk_metric
+from tests import fake
+
+
+class DiskMetricTest(unittest.TestCase):
+ """Class for testing DiskMetric."""
+
+ def setUp(self):
+ pass
+
+ def test_return_total_used_avail_percent(self):
+ # Create sample stdout string ShellCommand.run() would return
+ stdout_string = ('Filesystem 1K-blocks Used Available Use% '
+ 'mounted on\n/dev/dm-1 57542652 18358676 '
+ '36237928 34% /')
+ self.FAKE_RESULT = fake.FakeResult(stdout=stdout_string)
+ fake_shell = fake.MockShellCommand(fake_result=self.FAKE_RESULT)
+ self.metric_obj = disk_metric.DiskMetric(shell=fake_shell)
+
+ expected_result = {
+ disk_metric.DiskMetric.TOTAL: 57542652,
+ disk_metric.DiskMetric.USED: 18358676,
+ disk_metric.DiskMetric.AVAIL: 36237928,
+ disk_metric.DiskMetric.PERCENT_USED: 34
+ }
+ self.assertEqual(expected_result, self.metric_obj.gather_metric())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/lab/tests/fake.py b/tools/lab/tests/fake.py
new file mode 100644
index 0000000..628a725
--- /dev/null
+++ b/tools/lab/tests/fake.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+#
+# 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.
+
+
+class FakeResult(object):
+ """A fake version of the object returned from ShellCommand.run. """
+
+ def __init__(self, exit_status=1, stdout='', stderr=''):
+ self.exit_status = exit_status
+ self.stdout = stdout
+ self.stderr = stderr
+
+
+class MockShellCommand(object):
+ """A fake ShellCommand object.
+
+ Attributes:
+ fake_result: a FakeResult object
+ """
+
+ def __init__(self, fake_result):
+ self._fake_result = fake_result
+
+ """Returns a FakeResult object.
+
+ Args:
+ Same as ShellCommand.run, but none are used in function
+
+ Returns:
+ The FakeResult object it was initalized with
+ """
+
+ def run(self, command, timeout=3600):
+ return self._fake_result
diff --git a/tools/lab/utils/__init__.py b/tools/lab/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/lab/utils/__init__.py
diff --git a/tools/lab/utils/job.py b/tools/lab/utils/job.py
new file mode 100644
index 0000000..da565c7
--- /dev/null
+++ b/tools/lab/utils/job.py
@@ -0,0 +1,201 @@
+# 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 logging
+import os
+import shlex
+import sys
+import time
+
+if os.name == 'posix' and sys.version_info[0] < 3:
+ import subprocess32 as subprocess
+ DEVNULL = open(os.devnull, 'wb')
+else:
+ import subprocess
+ # Only exists in python3.3
+ from subprocess import DEVNULL
+
+
+class Error(Exception):
+ """Indicates that a command failed, is fatal to the test unless caught."""
+
+ def __init__(self, result):
+ super(Error, self).__init__(result)
+ self.result = result
+
+
+class TimeoutError(Error):
+ """Thrown when a BackgroundJob times out on wait."""
+
+
+class Result(object):
+ """Command execution result.
+
+ Contains information on subprocess execution after it has exited.
+
+ Attributes:
+ command: An array containing the command and all arguments that
+ was executed.
+ exit_status: Integer exit code of the process.
+ stdout_raw: The raw bytes output from standard out.
+ stderr_raw: The raw bytes output from standard error
+ duration: How long the process ran for.
+ did_timeout: True if the program timed out and was killed.
+ """
+
+ @property
+ def stdout(self):
+ """String representation of standard output."""
+ if not self._stdout_str:
+ self._stdout_str = self._raw_stdout.decode(encoding=self._encoding)
+ self._stdout_str = self._stdout_str.strip()
+ return self._stdout_str
+
+ @property
+ def stderr(self):
+ """String representation of standard error."""
+ if not self._stderr_str:
+ self._stderr_str = self._raw_stderr.decode(encoding=self._encoding)
+ self._stderr_str = self._stderr_str.strip()
+ return self._stderr_str
+
+ def __init__(self,
+ command=[],
+ stdout=bytes(),
+ stderr=bytes(),
+ exit_status=None,
+ duration=0,
+ did_timeout=False,
+ encoding='utf-8'):
+ """
+ Args:
+ command: The command that was run. This will be a list containing
+ the executed command and all args.
+ stdout: The raw bytes that standard output gave.
+ stderr: The raw bytes that standard error gave.
+ exit_status: The exit status of the command.
+ duration: How long the command ran.
+ did_timeout: True if the command timed out.
+ encoding: The encoding standard that the program uses.
+ """
+ self.command = command
+ self.exit_status = exit_status
+ self._raw_stdout = stdout
+ self._raw_stderr = stderr
+ self._stdout_str = None
+ self._stderr_str = None
+ self._encoding = encoding
+ self.duration = duration
+ self.did_timeout = did_timeout
+
+ def __repr__(self):
+ return ('job.Result(command=%r, stdout=%r, stderr=%r, exit_status=%r, '
+ 'duration=%r, did_timeout=%r, encoding=%r)') % (
+ self.command, self._raw_stdout, self._raw_stderr,
+ self.exit_status, self.duration, self.did_timeout,
+ self._encoding)
+
+
+def run(command,
+ timeout=60,
+ ignore_status=False,
+ env=None,
+ io_encoding='utf-8'):
+ """Execute a command in a subproccess and return its output.
+
+ Commands can be either shell commands (given as strings) or the
+ path and arguments to an executable (given as a list). This function
+ will block until the subprocess finishes or times out.
+
+ Args:
+ command: The command to execute. Can be either a string or a list.
+ timeout: number seconds to wait for command to finish.
+ ignore_status: bool True to ignore the exit code of the remote
+ subprocess. Note that if you do ignore status codes,
+ you should handle non-zero exit codes explicitly.
+ env: dict enviroment variables to setup on the remote host.
+ io_encoding: str unicode encoding of command output.
+
+ Returns:
+ A job.Result containing the results of the ssh command.
+
+ Raises:
+ job.TimeoutError: When the remote command took to long to execute.
+ Error: When the ssh connection failed to be created.
+ CommandError: Ssh worked, but the command had an error executing.
+ """
+ start_time = time.time()
+ proc = subprocess.Popen(
+ command,
+ env=env,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ shell=not isinstance(command, list))
+ # Wait on the process terminating
+ timed_out = False
+ out = bytes()
+ err = bytes()
+ try:
+ (out, err) = proc.communicate(timeout=timeout)
+ except subprocess.TimeoutExpired:
+ timed_out = True
+ proc.kill()
+ proc.wait()
+
+ result = Result(
+ command=command,
+ stdout=out,
+ stderr=err,
+ exit_status=proc.returncode,
+ duration=time.time() - start_time,
+ encoding=io_encoding,
+ did_timeout=timed_out)
+ logging.debug(result)
+
+ if timed_out:
+ logging.error("Command %s with %s timeout setting timed out", command,
+ timeout)
+ raise TimeoutError(result)
+
+ if not ignore_status and proc.returncode != 0:
+ raise Error(result)
+
+ return result
+
+
+def run_async(command, env=None):
+ """Execute a command in a subproccess asynchronously.
+
+ It is the callers responsibility to kill/wait on the resulting
+ subprocess.Popen object.
+
+ Commands can be either shell commands (given as strings) or the
+ path and arguments to an executable (given as a list). This function
+ will not block.
+
+ Args:
+ command: The command to execute. Can be either a string or a list.
+ env: dict enviroment variables to setup on the remote host.
+
+ Returns:
+ A subprocess.Popen object representing the created subprocess.
+
+ """
+ return subprocess.Popen(
+ command,
+ env=env,
+ close_fds=True,
+ shell=not isinstance(command, list),
+ stdout=DEVNULL,
+ stderr=subprocess.STDOUT)
diff --git a/tools/lab/utils/shell.py b/tools/lab/utils/shell.py
new file mode 100644
index 0000000..5655dd4
--- /dev/null
+++ b/tools/lab/utils/shell.py
@@ -0,0 +1,238 @@
+# 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 shellescape
+import signal
+import time
+
+from utils import job
+
+
+class ShellCommand(object):
+ """Wraps basic commands that tend to be tied very closely to a shell.
+
+ This class is a wrapper for running basic shell commands through
+ any object that has a run command. Basic shell functionality for managing
+ the system, programs, and files in wrapped within this class.
+
+ Note: At the moment this only works with the ssh runner.
+ """
+
+ def __init__(self, runner, working_dir=None):
+ """Creates a new shell command invoker.
+
+ Args:
+ runner: The object that will run the shell commands.
+ working_dir: The directory that all commands should work in,
+ if none then the runners enviroment default is used.
+ """
+ self._runner = runner
+ self._working_dir = working_dir
+
+ def run(self, command, timeout=3600):
+ """Runs a generic command through the runner.
+
+ Takes the command and prepares it to be run in the target shell using
+ this objects settings.
+
+ Args:
+ command: The command to run.
+ timeout: How long to wait for the command (in seconds).
+
+ Returns:
+ A CmdResult object containing the results of the shell command.
+
+ Raises:
+ job.Error: When the command executed but had an error.
+ """
+ if self._working_dir:
+ command_str = 'cd %s; %s' % (self._working_dir, command)
+ else:
+ command_str = command
+
+ return self._runner.run(command_str, timeout=timeout)
+
+ def is_alive(self, identifier):
+ """Checks to see if a program is alive.
+
+ Checks to see if a program is alive on the shells enviroment. This can
+ be used to check on generic programs, or a specific program using
+ a pid.
+
+ Args:
+ identifier: string or int, Used to identify the program to check.
+ if given an int then it is assumed to be a pid. If
+ given a string then it will be used as a search key
+ to compare on the running processes.
+ Returns:
+ True if a process was found running, false otherwise.
+ """
+ try:
+ if isinstance(identifier, str):
+ self.run('ps aux | grep -v grep | grep %s' % identifier)
+ elif isinstance(identifier, int):
+ self.signal(identifier, 0)
+ else:
+ raise ValueError('Bad type was given for identifier')
+
+ return True
+ except job.Error:
+ return False
+
+ def get_pids(self, identifier):
+ """Gets the pids of a program.
+
+ Searches for a program with a specific name and grabs the pids for all
+ programs that match.
+
+ Args:
+ identifier: A search term that identifies the program.
+
+ Returns: An array of all pids that matched the identifier, or None
+ if no pids were found.
+ """
+ try:
+ result = self.run('ps aux | grep -v grep | grep %s' % identifier)
+ except job.Error:
+ raise StopIteration
+
+ lines = result.stdout.splitlines()
+
+ # The expected output of the above command is like so:
+ # bob 14349 0.0 0.0 34788 5552 pts/2 Ss Oct10 0:03 bash
+ # bob 52967 0.0 0.0 34972 5152 pts/4 Ss Oct10 0:00 bash
+ # Where the format is:
+ # USER PID ...
+ for line in lines:
+ pieces = line.split()
+ yield int(pieces[1])
+
+ def search_file(self, search_string, file_name):
+ """Searches through a file for a string.
+
+ Args:
+ search_string: The string or pattern to look for.
+ file_name: The name of the file to search.
+
+ Returns:
+ True if the string or pattern was found, False otherwise.
+ """
+ try:
+ self.run('grep %s %s' % (shellescape.quote(search_string),
+ file_name))
+ return True
+ except job.Error:
+ return False
+
+ def read_file(self, file_name):
+ """Reads a file through the shell.
+
+ Args:
+ file_name: The name of the file to read.
+
+ Returns:
+ A string of the files contents.
+ """
+ return self.run('cat %s' % file_name).stdout
+
+ def write_file(self, file_name, data):
+ """Writes a block of data to a file through the shell.
+
+ Args:
+ file_name: The name of the file to write to.
+ data: The string of data to write.
+ """
+ return self.run('echo %s > %s' % (shellescape.quote(data), file_name))
+
+ def append_file(self, file_name, data):
+ """Appends a block of data to a file through the shell.
+
+ Args:
+ file_name: The name of the file to write to.
+ data: The string of data to write.
+ """
+ return self.run('echo %s >> %s' % (shellescape.quote(data), file_name))
+
+ def touch_file(self, file_name):
+ """Creates a file through the shell.
+
+ Args:
+ file_name: The name of the file to create.
+ """
+ self.write_file(file_name, '')
+
+ def delete_file(self, file_name):
+ """Deletes a file through the shell.
+
+ Args:
+ file_name: The name of the file to delete.
+ """
+ try:
+ self.run('rm %s' % file_name)
+ except job.Error as e:
+ if 'No such file or directory' in e.result.stderr:
+ return
+
+ raise
+
+ def kill(self, identifier, timeout=10):
+ """Kills a program or group of programs through the shell.
+
+ Kills all programs that match an identifier through the shell. This
+ will send an increasing queue of kill signals to all programs
+ that match the identifier until either all are dead or the timeout
+ finishes.
+
+ Programs are guranteed to be killed after running this command.
+
+ Args:
+ identifier: A string used to identify the program.
+ timeout: The time to wait for all programs to die. Each signal will
+ take an equal portion of this time.
+ """
+ if isinstance(identifier, int):
+ pids = [identifier]
+ else:
+ pids = list(self.get_pids(identifier))
+
+ signal_queue = [signal.SIGINT, signal.SIGTERM, signal.SIGKILL]
+
+ signal_duration = timeout / len(signal_queue)
+ for sig in signal_queue:
+ for pid in pids:
+ try:
+ self.signal(pid, sig)
+ except job.Error:
+ pass
+
+ start_time = time.time()
+ while pids and time.time() - start_time < signal_duration:
+ time.sleep(0.1)
+ pids = [pid for pid in pids if self.is_alive(pid)]
+
+ if not pids:
+ break
+
+ def signal(self, pid, sig):
+ """Sends a specific signal to a program.
+
+ Args:
+ pid: The process id of the program to kill.
+ sig: The singal to send.
+
+ Raises:
+ job.Error: Raised when the signal fail to reach
+ the specified program.
+ """
+ self.run('kill -%d %d' % (sig, pid))