Snap for 4404534 from 0de194cfeebe455954ccfbc2a5b51d978b7778a7 to oc-mr1-release
Change-Id: Icffda05a0ed9e6a85bb160936b1642f8d5661c5f
diff --git a/acts/framework/acts/libs/ota/__init__.py b/acts/framework/acts/libs/ota/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/acts/libs/ota/__init__.py
diff --git a/acts/framework/acts/libs/ota/ota_runners/__init__.py b/acts/framework/acts/libs/ota/ota_runners/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/acts/libs/ota/ota_runners/__init__.py
diff --git a/acts/framework/acts/libs/ota/ota_runners/ota_runner.py b/acts/framework/acts/libs/ota/ota_runners/ota_runner.py
new file mode 100644
index 0000000..dd58943
--- /dev/null
+++ b/acts/framework/acts/libs/ota/ota_runners/ota_runner.py
@@ -0,0 +1,127 @@
+#!/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 time
+
+SL4A_SERVICE_SETUP_TIME = 5
+
+
+class OtaError(Exception):
+ """Raised when an error in the OTA Update process occurs."""
+
+
+class OtaRunner(object):
+ """The base class for all OTA Update Runners."""
+
+ def __init__(self, ota_tool, android_device):
+ self.ota_tool = ota_tool
+ self.android_device = android_device
+ self.serial = self.android_device.serial
+
+ def _update(self):
+ logging.info('Stopping services.')
+ self.android_device.stop_services()
+ logging.info('Beginning tool.')
+ self.ota_tool.update(self)
+ logging.info('Tool finished. Waiting for boot completion.')
+ self.android_device.wait_for_boot_completion()
+ logging.info('Boot completed. Rooting adb.')
+ self.android_device.root_adb()
+ logging.info('Root complete. Installing new SL4A.')
+ output = self.android_device.adb.install('-r %s' % self.get_sl4a_apk)
+ logging.info('SL4A install output: %s' % output)
+ time.sleep(SL4A_SERVICE_SETUP_TIME)
+ logging.info('Starting services.')
+ self.android_device.start_services()
+ logging.info('Services started. Running ota tool cleanup.')
+ self.ota_tool.cleanup(self)
+ logging.info('Cleanup complete.')
+
+ def can_update(self):
+ """Whether or not an update package is available for the device."""
+ return NotImplementedError()
+
+ def get_ota_package(self):
+ raise NotImplementedError()
+
+ def get_sl4a_apk(self):
+ raise NotImplementedError()
+
+
+class SingleUseOtaRunner(OtaRunner):
+ """A single use OtaRunner.
+
+ SingleUseOtaRunners can only be ran once. If a user attempts to run it more
+ than once, an error will be thrown. Users can avoid the error by checking
+ can_update() before calling update().
+ """
+
+ def __init__(self, ota_tool, android_device, ota_package, sl4a_apk):
+ super(SingleUseOtaRunner, self).__init__(ota_tool, android_device)
+ self._ota_package = ota_package
+ self._sl4a_apk = sl4a_apk
+ self._called = False
+
+ def can_update(self):
+ return not self._called
+
+ def update(self):
+ """Starts the update process."""
+ if not self.can_update():
+ raise OtaError('A SingleUseOtaTool instance cannot update a phone '
+ 'multiple times.')
+ self._called = True
+ self._update()
+
+ def get_ota_package(self):
+ return self._ota_package
+
+ def get_sl4a_apk(self):
+ return self._sl4a_apk
+
+
+class MultiUseOtaRunner(OtaRunner):
+ """A multiple use OtaRunner.
+
+ MultiUseOtaRunner can only be ran for as many times as there have been
+ packages provided to them. If a user attempts to run it more than the number
+ of provided packages, an error will be thrown. Users can avoid the error by
+ checking can_update() before calling update().
+ """
+
+ def __init__(self, ota_tool, android_device, ota_packages, sl4a_apks):
+ super(MultiUseOtaRunner, self).__init__(ota_tool, android_device)
+ self._ota_packages = ota_packages
+ self._sl4a_apks = sl4a_apks
+ self.current_update_number = 0
+
+ def can_update(self):
+ return not self.current_update_number == len(self._ota_packages)
+
+ def update(self):
+ """Starts the update process."""
+ if not self.can_update():
+ raise OtaError('This MultiUseOtaRunner has already updated all '
+ 'given packages onto the phone.')
+ self._update()
+ self.current_update_number += 1
+
+ def get_ota_package(self):
+ return self._ota_packages[self.current_update_number]
+
+ def get_sl4a_apk(self):
+ return self._sl4a_apks[self.current_update_number]
diff --git a/acts/framework/acts/libs/ota/ota_runners/ota_runner_factory.py b/acts/framework/acts/libs/ota/ota_runners/ota_runner_factory.py
new file mode 100644
index 0000000..fa6ab19
--- /dev/null
+++ b/acts/framework/acts/libs/ota/ota_runners/ota_runner_factory.py
@@ -0,0 +1,204 @@
+#!/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
+
+from acts.config_parser import ActsConfigError
+from acts.libs.ota.ota_runners import ota_runner
+from acts.libs.ota.ota_tools import ota_tool_factory
+from acts.libs.ota.ota_tools import adb_sideload_ota_tool
+
+_bound_devices = {}
+
+DEFAULT_OTA_TOOL = adb_sideload_ota_tool.AdbSideloadOtaTool.__name__
+DEFAULT_OTA_COMMAND = 'adb'
+
+
+def create_all_from_configs(config, android_devices):
+ """Creates a new OtaTool for each given AndroidDevice.
+
+ After an OtaTool is assigned to a device, another OtaTool cannot be created
+ for that device. This will prevent OTA Update tests that accidentally flash
+ the same build onto a device more than once.
+
+ Args:
+ config: the ACTS config user_params.
+ android_devices: The devices to run an OTA Update on.
+
+ Returns:
+ A list of OtaRunners responsible for updating the given devices. The
+ indexes match the indexes of the corresponding AndroidDevice in
+ android_devices.
+ """
+ return [create_from_configs(config, ad) for ad in android_devices]
+
+
+def create_from_configs(config, android_device):
+ """Creates a new OtaTool for the given AndroidDevice.
+
+ After an OtaTool is assigned to a device, another OtaTool cannot be created
+ for that device. This will prevent OTA Update tests that accidentally flash
+ the same build onto a device more than once.
+
+ Args:
+ config: the ACTS config user_params.
+ android_device: The device to run the OTA Update on.
+
+ Returns:
+ An OtaRunner responsible for updating the given device.
+ """
+ # Default to adb sideload
+ try:
+ ota_tool_class_name = get_ota_value_from_config(
+ config, 'ota_tool', android_device)
+ except ActsConfigError:
+ ota_tool_class_name = DEFAULT_OTA_TOOL
+
+ if ota_tool_class_name not in config:
+ if ota_tool_class_name is not DEFAULT_OTA_TOOL:
+ raise ActsConfigError(
+ 'If the ota_tool is overloaded, the path to the tool must be '
+ 'added to the ACTS config file under {"OtaToolName": '
+ '"path/to/tool"} (in this case, {"%s": "path/to/tool"}.' %
+ ota_tool_class_name)
+ else:
+ command = DEFAULT_OTA_COMMAND
+ else:
+ command = config[ota_tool_class_name]
+ if type(command) is list:
+ # If file came as a list in the config.
+ if len(command) == 1:
+ command = command[0]
+ else:
+ raise ActsConfigError(
+ 'Config value for "%s" must be either a string or a list '
+ 'of exactly one element' % ota_tool_class_name)
+
+ ota_package = get_ota_value_from_config(config, 'ota_package',
+ android_device)
+ ota_sl4a = get_ota_value_from_config(config, 'ota_sl4a', android_device)
+ if type(ota_sl4a) != type(ota_package):
+ raise ActsConfigError(
+ 'The ota_package and ota_sl4a must either both be strings, or '
+ 'both be lists. Device with serial "%s" has mismatched types.' %
+ android_device.serial)
+ return create(ota_package, ota_sl4a, android_device, ota_tool_class_name,
+ command)
+
+
+def create(ota_package,
+ ota_sl4a,
+ android_device,
+ ota_tool_class_name=DEFAULT_OTA_TOOL,
+ command=DEFAULT_OTA_COMMAND,
+ use_cached_runners=True):
+ """
+ Args:
+ ota_package: A string or list of strings corresponding to the
+ update.zip package location(s) for running an OTA update.
+ ota_sl4a: A string or list of strings corresponding to the
+ sl4a.apk package location(s) for running an OTA update.
+ ota_tool_class_name: The class name for the desired ota_tool
+ command: The command line tool name for the updater
+ android_device: The AndroidDevice to run the OTA Update on.
+ use_cached_runners: Whether or not to use runners cached by previous
+ create calls.
+
+ Returns:
+ An OtaRunner with the given properties from the arguments.
+ """
+ ota_tool = ota_tool_factory.create(ota_tool_class_name, command)
+ return create_from_package(ota_package, ota_sl4a, android_device, ota_tool,
+ use_cached_runners)
+
+
+def create_from_package(ota_package,
+ ota_sl4a,
+ android_device,
+ ota_tool,
+ use_cached_runners=True):
+ """
+ Args:
+ ota_package: A string or list of strings corresponding to the
+ update.zip package location(s) for running an OTA update.
+ ota_sl4a: A string or list of strings corresponding to the
+ sl4a.apk package location(s) for running an OTA update.
+ ota_tool: The OtaTool to be paired with the returned OtaRunner
+ android_device: The AndroidDevice to run the OTA Update on.
+ use_cached_runners: Whether or not to use runners cached by previous
+ create calls.
+
+ Returns:
+ An OtaRunner with the given properties from the arguments.
+ """
+ if android_device in _bound_devices and use_cached_runners:
+ logging.warning('Android device %s has already been assigned an '
+ 'OtaRunner. Returning previously created runner.')
+ return _bound_devices[android_device]
+
+ if type(ota_package) != type(ota_sl4a):
+ raise TypeError(
+ 'The ota_package and ota_sl4a must either both be strings, or '
+ 'both be lists. Device with serial "%s" has requested mismatched '
+ 'types.' % android_device.serial)
+
+ if type(ota_package) is str:
+ runner = ota_runner.SingleUseOtaRunner(ota_tool, android_device,
+ ota_package, ota_sl4a)
+ elif type(ota_package) is list:
+ runner = ota_runner.MultiUseOtaRunner(ota_tool, android_device,
+ ota_package, ota_sl4a)
+ else:
+ raise TypeError('The "ota_package" value in the acts config must be '
+ 'either a list or a string.')
+
+ _bound_devices[android_device] = runner
+ return runner
+
+
+def get_ota_value_from_config(config, key, android_device):
+ """Returns a key for the given AndroidDevice.
+
+ Args:
+ config: The ACTS config
+ key: The base key desired (ota_tool, ota_sl4a, or ota_package)
+ android_device: An AndroidDevice
+
+ Returns: The value at the specified key.
+ Throws: ActsConfigError if the value cannot be determined from the config.
+ """
+ suffix = ''
+ if 'ota_map' in config:
+ if android_device.serial in config['ota_map']:
+ suffix = '_%s' % config['ota_map'][android_device.serial]
+
+ ota_package_key = '%s%s' % (key, suffix)
+ if ota_package_key not in config:
+ if suffix is not '':
+ raise ActsConfigError(
+ 'Asked for an OTA Update without specifying a required value. '
+ '"ota_map" has entry {"%s": "%s"}, but there is no '
+ 'corresponding entry {"%s":"/path/to/file"} found within the '
+ 'ACTS config.' % (android_device.serial, suffix[1:],
+ ota_package_key))
+ else:
+ raise ActsConfigError(
+ 'Asked for an OTA Update without specifying a required value. '
+ '"ota_map" does not exist or have a key for serial "%s", and '
+ 'the default value entry "%s" cannot be found within the ACTS '
+ 'config.' % (android_device.serial, ota_package_key))
+
+ return config[ota_package_key]
diff --git a/acts/framework/acts/libs/ota/ota_tools/__init__.py b/acts/framework/acts/libs/ota/ota_tools/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/acts/libs/ota/ota_tools/__init__.py
diff --git a/acts/framework/acts/libs/ota/ota_tools/adb_sideload_ota_tool.py b/acts/framework/acts/libs/ota/ota_tools/adb_sideload_ota_tool.py
new file mode 100644
index 0000000..f94a762
--- /dev/null
+++ b/acts/framework/acts/libs/ota/ota_tools/adb_sideload_ota_tool.py
@@ -0,0 +1,49 @@
+#!/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
+
+from acts.libs.ota.ota_tools.ota_tool import OtaTool
+
+# OTA Packages can be upwards of 1 GB. This may take some time to transfer over
+# USB 2.0.
+PUSH_TIMEOUT = 10 * 60
+
+
+class AdbSideloadOtaTool(OtaTool):
+ """Updates an AndroidDevice using adb sideload."""
+
+ def __init__(self, ignored_command):
+ # "command" is ignored. The ACTS adb version is used to prevent
+ # differing adb versions from constantly killing adbd.
+ super(AdbSideloadOtaTool, self).__init__(ignored_command)
+
+ def update(self, ota_runner):
+ logging.info('Rooting adb')
+ ota_runner.android_device.root_adb()
+ logging.info('Rebooting to sideload')
+ ota_runner.android_device.adb.reboot('sideload')
+ ota_runner.android_device.adb.wait_for_sideload()
+ logging.info('Sideloading ota package')
+ package_path = ota_runner.get_ota_package()
+ logging.info('Running adb sideload with package "%s"' % package_path)
+ sideload_result = ota_runner.android_device.adb.sideload(
+ package_path, timeout=PUSH_TIMEOUT)
+ logging.info('Sideload output: %s' % sideload_result)
+ logging.info('Sideload complete. Waiting for device to come back up.')
+ ota_runner.android_device.adb.wait_for_recovery()
+ ota_runner.android_device.adb.reboot()
+ logging.info('Device is up. Update complete.')
diff --git a/acts/framework/acts/libs/ota/ota_tools/ota_tool.py b/acts/framework/acts/libs/ota/ota_tools/ota_tool.py
new file mode 100644
index 0000000..e51fe6b
--- /dev/null
+++ b/acts/framework/acts/libs/ota/ota_tools/ota_tool.py
@@ -0,0 +1,47 @@
+#!/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.
+
+
+class OtaTool(object):
+ """A Wrapper for an OTA Update command or tool.
+
+ Each OtaTool acts as a facade to the underlying command or tool used to
+ update the device.
+ """
+
+ def __init__(self, command):
+ """Creates an OTA Update tool with the given properties.
+
+ Args:
+ command: A string that is used as the command line tool
+ """
+ self.command = command
+
+ def update(self, ota_runner):
+ """Begins the OTA Update. Returns after the update has installed.
+
+ Args:
+ ota_runner: The OTA Runner that handles the device information.
+ """
+ raise NotImplementedError()
+
+ def cleanup(self, ota_runner):
+ """A cleanup method for the OTA Tool to run after the update completes.
+
+ Args:
+ ota_runner: The OTA Runner that handles the device information.
+ """
+ pass
diff --git a/acts/framework/acts/libs/ota/ota_tools/ota_tool_factory.py b/acts/framework/acts/libs/ota/ota_tools/ota_tool_factory.py
new file mode 100644
index 0000000..ac81646
--- /dev/null
+++ b/acts/framework/acts/libs/ota/ota_tools/ota_tool_factory.py
@@ -0,0 +1,52 @@
+#!/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.
+
+from acts.libs.ota.ota_tools.adb_sideload_ota_tool import AdbSideloadOtaTool
+from acts.libs.ota.ota_tools.update_device_ota_tool import UpdateDeviceOtaTool
+
+_CONSTRUCTORS = {
+ AdbSideloadOtaTool.__name__: lambda command: AdbSideloadOtaTool(command),
+ UpdateDeviceOtaTool.__name__: lambda command: UpdateDeviceOtaTool(command),
+}
+_constructed_tools = {}
+
+
+def create(ota_tool_class, command):
+ """Returns an OtaTool with the given class name.
+
+ If the tool has already been created, the existing instance will be
+ returned.
+
+ Args:
+ ota_tool_class: the class/type of the tool you wish to use.
+ command: the command line tool being used.
+
+ Returns:
+ An OtaTool.
+ """
+ if ota_tool_class in _constructed_tools:
+ return _constructed_tools[ota_tool_class]
+
+ if ota_tool_class not in _CONSTRUCTORS:
+ raise KeyError('Given Ota Tool class name does not match a known '
+ 'name. Found "%s". Expected any of %s. If this tool '
+ 'does exist, add it to the _CONSTRUCTORS dict in this '
+ 'module.' % (ota_tool_class, _CONSTRUCTORS.keys()))
+
+ new_update_tool = _CONSTRUCTORS[ota_tool_class](command)
+ _constructed_tools[ota_tool_class] = new_update_tool
+
+ return new_update_tool
diff --git a/acts/framework/acts/libs/ota/ota_tools/update_device_ota_tool.py b/acts/framework/acts/libs/ota/ota_tools/update_device_ota_tool.py
new file mode 100644
index 0000000..0ab9091
--- /dev/null
+++ b/acts/framework/acts/libs/ota/ota_tools/update_device_ota_tool.py
@@ -0,0 +1,58 @@
+#!/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 os
+import shutil
+import tempfile
+
+from acts.libs.ota.ota_tools import ota_tool
+from acts.libs.proc import job
+from acts import utils
+
+# OTA Packages can be upwards of 1 GB. This may take some time to transfer over
+# USB 2.0. A/B devices must also complete the update in the background.
+UPDATE_TIMEOUT = 20 * 60
+UPDATE_LOCATION = '/data/ota_package/update.zip'
+
+
+class UpdateDeviceOtaTool(ota_tool.OtaTool):
+ """Runs an OTA Update with system/update_engine/scripts/update_device.py."""
+
+ def __init__(self, command):
+ super(UpdateDeviceOtaTool, self).__init__(command)
+
+ self.unzip_path = tempfile.mkdtemp()
+ utils.unzip_maintain_permissions(self.command, self.unzip_path)
+
+ self.command = os.path.join(self.unzip_path, 'update_device.py')
+
+ def update(self, ota_runner):
+ logging.info('Forcing adb to be in root mode.')
+ ota_runner.android_device.root_adb()
+ update_command = '%s -s %s %s' % (self.command, ota_runner.serial,
+ ota_runner.get_ota_package())
+ logging.info('Running %s' % update_command)
+ result = job.run(update_command, timeout=UPDATE_TIMEOUT)
+ logging.info('Output: %s' % result.stdout)
+
+ logging.info('Rebooting device for update to go live.')
+ ota_runner.android_device.adb.reboot()
+ logging.info('Reboot sent.')
+
+ def __del__(self):
+ """Delete the unzipped update_device folder before ACTS exits."""
+ shutil.rmtree(self.unzip_path)
diff --git a/acts/framework/acts/libs/ota/ota_updater.py b/acts/framework/acts/libs/ota/ota_updater.py
new file mode 100644
index 0000000..ed300aa
--- /dev/null
+++ b/acts/framework/acts/libs/ota/ota_updater.py
@@ -0,0 +1,66 @@
+#!/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.
+
+from acts.libs.ota.ota_runners import ota_runner_factory
+
+# Maps AndroidDevices to OtaRunners
+ota_runners = {}
+
+
+def initialize(user_params, android_devices):
+ """Initialize OtaRunners for each device.
+
+ Args:
+ user_params: The user_params from the ACTS config.
+ android_devices: The android_devices in the test.
+ """
+ for ad in android_devices:
+ ota_runners[ad] = ota_runner_factory.create_from_configs(
+ user_params, ad)
+
+
+def _check_initialization(android_device):
+ """Check if a given device was initialized."""
+ if android_device not in ota_runners:
+ raise KeyError('Android Device with serial "%s" has not been '
+ 'initialized for OTA Updates. Did you forget to call'
+ 'ota_updater.initialize()?' % android_device.serial)
+
+
+def update(android_device, ignore_update_errors=False):
+ """Update a given AndroidDevice.
+
+ Args:
+ android_device: The device to update
+ ignore_update_errors: Whether or not to ignore update errors such as
+ no more updates available for a given device. Default is false.
+ Throws:
+ OtaError if ignore_update_errors is false and the OtaRunner has run out
+ of packages to update the phone with.
+ """
+ _check_initialization(android_device)
+ try:
+ ota_runners[android_device].update()
+ except:
+ if ignore_update_errors:
+ return
+ raise
+
+
+def can_update(android_device):
+ """Whether or not a device can be updated."""
+ _check_initialization(android_device)
+ return ota_runners[android_device].can_update()
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 5732351..c778466 100644
--- a/acts/framework/acts/test_utils/tel/tel_test_utils.py
+++ b/acts/framework/acts/test_utils/tel/tel_test_utils.py
@@ -4575,7 +4575,10 @@
"""
ad.log.debug("Ensuring no tcpdump is running in background")
- ad.adb.shell("killall -9 tcpdump")
+ try:
+ ad.adb.shell("killall -9 tcpdump")
+ except AdbError:
+ self.log.warn("Killing existing tcpdump processes failed")
begin_time = epoch_to_log_line_timestamp(get_current_epoch_time())
begin_time = normalize_log_line_timestamp(begin_time)
file_name = "/sdcard/tcpdump{}{}{}.pcap".format(ad.serial, test_name,
diff --git a/acts/framework/acts/utils.py b/acts/framework/acts/utils.py
index 5c398f8..e13b964 100755
--- a/acts/framework/acts/utils.py
+++ b/acts/framework/acts/utils.py
@@ -28,6 +28,7 @@
import subprocess
import time
import traceback
+import zipfile
from acts.controllers import adb
@@ -846,3 +847,28 @@
return False
finally:
ad.adb.shell("rm /data/ping.txt", timeout=10, ignore_status=True)
+
+
+def unzip_maintain_permissions(zip_path, extract_location):
+ """Unzip a .zip file while maintaining permissions.
+
+ Args:
+ zip_path: The path to the zipped file.
+ extract_location: the directory to extract to.
+ """
+ with zipfile.ZipFile(zip_path, 'r') as zip_file:
+ for info in zip_file.infolist():
+ _extract_file(zip_file, info, extract_location)
+
+
+def _extract_file(zip_file, zip_info, extract_location):
+ """Extracts a single entry from a ZipFile while maintaining permissions.
+
+ Args:
+ zip_file: A zipfile.ZipFile.
+ zip_info: A ZipInfo object from zip_file.
+ extract_location: The directory to extract to.
+ """
+ out_path = zip_file.extract(zip_info.filename, path=extract_location)
+ perm = zip_info.external_attr >> 16
+ os.chmod(out_path, perm)
diff --git a/acts/tests/google/bt/ota/BtOtaTest.py b/acts/tests/google/bt/ota/BtOtaTest.py
new file mode 100644
index 0000000..91e51bb
--- /dev/null
+++ b/acts/tests/google/bt/ota/BtOtaTest.py
@@ -0,0 +1,137 @@
+# 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.
+"""
+Test script for Bluetooth OTA testing.
+"""
+
+from acts.libs.ota import ota_updater
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts.test_utils.bt.bt_test_utils import pair_pri_to_sec
+from acts import signals
+
+
+class BtOtaTest(BluetoothBaseTest):
+ def setup_class(self):
+ super(BtOtaTest, self).setup_class()
+ ota_updater.initialize(self.user_params, self.android_devices)
+ self.dut = self.android_devices[0]
+ self.pre_ota_name = self.dut.droid.bluetoothGetLocalName()
+ self.pre_ota_address = self.dut.droid.bluetoothGetLocalAddress()
+ self.sec_address = self.android_devices[
+ 1].droid.bluetoothGetLocalAddress()
+
+ # Pairing devices
+ if not pair_pri_to_sec(self.dut, self.android_devices[1]):
+ raise signals.TestSkipClass(
+ "Failed to bond devices prior to update")
+
+ #Run OTA below, if ota fails then abort all tests
+ try:
+ ota_updater.update(self.dut)
+ except Exception as err:
+ raise signals.TestSkipClass(
+ "Failed up apply OTA update. Aborting tests")
+
+ @BluetoothBaseTest.bt_test_wrap
+ @test_tracker_info(uuid='57545ef0-2c2e-463c-9dbf-28da73cc76df')
+ def test_device_name_persists(self):
+ """Test device name persists after OTA update
+
+ Test device name persists after OTA update
+
+ Steps:
+ 1. Verify pre OTA device name matches post OTA device name
+
+ Expected Result:
+ Bluetooth Device name persists
+
+ Returns:
+ Pass if True
+ Fail if False
+
+ TAGS: OTA
+ Priority: 2
+ """
+ return self.pre_ota_name == self.dut.droid.bluetoothGetLocalName()
+
+ @BluetoothBaseTest.bt_test_wrap
+ @test_tracker_info(uuid='1fd5e1a5-d930-499c-aebc-c1872ab49568')
+ def test_device_address_persists(self):
+ """Test device address persists after OTA update
+
+ Test device address persists after OTA update
+
+ Steps:
+ 1. Verify pre OTA device address matches post OTA device address
+
+ Expected Result:
+ Bluetooth Device address persists
+
+ Returns:
+ Pass if True
+ Fail if False
+
+ TAGS: OTA
+ Priority: 2
+ """
+ return self.pre_ota_address == self.dut.droid.bluetoothGetLocalAddress(
+ )
+
+ @BluetoothBaseTest.bt_test_wrap
+ @test_tracker_info(uuid='2e6704e6-3df0-43fb-8425-41ff841d7473')
+ def test_bluetooth_state_persists(self):
+ """Test device Bluetooth state persists after OTA update
+
+ Test device Bluetooth state persists after OTA update
+
+ Steps:
+ 1. Verify post OTA Bluetooth state is on
+
+ Expected Result:
+ Bluetooth Device Bluetooth state is on
+
+ Returns:
+ Pass if True
+ Fail if False
+
+ TAGS: OTA
+ Priority: 2
+ """
+ return self.dut.droid.bluetoothCheckState()
+
+ @BluetoothBaseTest.bt_test_wrap
+ @test_tracker_info(uuid='eb1c0a22-4b4e-4984-af17-ace3bcd203de')
+ def test_bonded_devices_persist(self):
+ """Test device bonded devices persists after OTA update
+
+ Test device address persists after OTA update
+
+ Steps:
+ 1. Verify pre OTA device bonded devices matches post OTA device
+ bonded devices
+
+ Expected Result:
+ Bluetooth Device bonded devices persists
+
+ Returns:
+ Pass if True
+ Fail if False
+
+ TAGS: OTA
+ Priority: 1
+ """
+ bonded_devices = self.dut.droid.bluetoothGetBondedDevices()
+ for b in bonded_devices:
+ if b['address'] == self.sec_address:
+ return True
+ return False
diff --git a/acts/tests/google/wifi/WifiEnterpriseTest.py b/acts/tests/google/wifi/WifiEnterpriseTest.py
index b1a5391..785d91f 100755
--- a/acts/tests/google/wifi/WifiEnterpriseTest.py
+++ b/acts/tests/google/wifi/WifiEnterpriseTest.py
@@ -22,6 +22,8 @@
from acts import base_test
from acts import signals
from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.tel_test_utils import start_adb_tcpdump
+from acts.test_utils.tel.tel_test_utils import stop_adb_tcpdump
from acts.test_utils.wifi import wifi_test_utils as wutils
WifiEnums = wutils.WifiEnums
@@ -127,6 +129,8 @@
del self.config_passpoint_ttls[WifiEnums.SSID_KEY]
# Set screen lock password so ConfigStore is unlocked.
self.dut.droid.setDevicePassword(self.device_password)
+ self.tcpdump_pid = None
+ self.tcpdump_file = None
def teardown_class(self):
wutils.reset_wifi(self.dut)
@@ -139,8 +143,16 @@
self.dut.droid.wakeUpNow()
wutils.reset_wifi(self.dut)
self.dut.ed.clear_all_events()
+ (self.tcpdump_pid, self.tcpdump_file) = start_adb_tcpdump(
+ self.dut, self.test_name, mask='all')
def teardown_test(self):
+ if self.tcpdump_pid:
+ stop_adb_tcpdump(self.dut,
+ self.tcpdump_pid,
+ self.tcpdump_file,
+ pull_tcpdump=True)
+ self.tcpdump_pid = None
self.dut.droid.wakeLockRelease()
self.dut.droid.goToSleepNow()
self.dut.droid.wifiStopTrackingStateChange()
diff --git a/acts/tests/google/wifi/WifiTetheringTest.py b/acts/tests/google/wifi/WifiTetheringTest.py
index 57a8e66..ef79f3d 100644
--- a/acts/tests/google/wifi/WifiTetheringTest.py
+++ b/acts/tests/google/wifi/WifiTetheringTest.py
@@ -44,7 +44,7 @@
self.convert_byte_to_mb = 1024.0 * 1024.0
self.new_ssid = "wifi_tethering_test2"
- self.data_usage_error = 0.3
+ self.data_usage_error = 1
self.hotspot_device = self.android_devices[0]
self.tethered_devices = self.android_devices[1:]
@@ -89,6 +89,8 @@
def on_fail(self, test_name, begin_time):
""" Collect bug report on failure """
self.hotspot_device.take_bug_report(test_name, begin_time)
+ for ad in self.tethered_devices:
+ ad.take_bug_report(test_name, begin_time)
""" Helper functions """
@@ -143,6 +145,7 @@
"""
default_route_substr = "::/0 -> "
link_properties = dut.droid.connectivityGetActiveLinkProperties()
+ self.log.info("LINK PROPERTIES:\n%s\n" % link_properties)
return link_properties and default_route_substr in link_properties
def _verify_ipv6_tethering(self, dut):
@@ -182,6 +185,8 @@
for _ in range(50):
dut_id = random.randint(0, len(self.tethered_devices)-1)
dut = self.tethered_devices[dut_id]
+ # wait for 1 sec between connect & disconnect stress test
+ time.sleep(1)
if device_connected[dut_id]:
wutils.wifi_forget_network(dut, self.network["SSID"])
else:
@@ -271,12 +276,12 @@
Steps:
1. Start wifi tethering on hotspot device
- 2. Verify IPv6 address on hotspot device
+ 2. Verify IPv6 address on hotspot device (VZW & TMO only)
3. Connect tethered device to hotspot device
- 4. Verify IPv6 address on the client's link properties
- 5. Verify ping on client using ping6 which should pass
+ 4. Verify IPv6 address on the client's link properties (VZW only)
+ 5. Verify ping on client using ping6 which should pass (VZW only)
6. Disable mobile data on provider and verify that link properties
- does not have IPv6 address and default route
+ does not have IPv6 address and default route (VZW only)
"""
# Start wifi tethering on the hotspot device
wutils.toggle_wifi_off_and_on(self.hotspot_device)
@@ -320,14 +325,15 @@
result = self._find_ipv6_default_route(self.tethered_devices[0])
self.hotspot_device.droid.telephonyToggleDataConnection(True)
- if not result:
+ if result:
asserts.fail("Found IPv6 default route in link properties:Data off")
+ self.log.info("Did not find IPv6 address in link properties")
# Disable wifi tethering
wutils.stop_wifi_tethering(self.hotspot_device)
@test_tracker_info(uuid="110b61d1-8af2-4589-8413-11beac7a3025")
- def test_wifi_tethering_2ghz_traffic_between_2tethered_devices(self):
+ def wifi_tethering_2ghz_traffic_between_2tethered_devices(self):
""" Steps:
1. Start wifi hotspot with 2G band
@@ -341,7 +347,7 @@
wutils.stop_wifi_tethering(self.hotspot_device)
@test_tracker_info(uuid="953f6e2e-27bd-4b73-85a6-d2eaa4e755d5")
- def test_wifi_tethering_5ghz_traffic_between_2tethered_devices(self):
+ def wifi_tethering_5ghz_traffic_between_2tethered_devices(self):
""" Steps:
1. Start wifi hotspot with 5ghz band
@@ -435,7 +441,8 @@
end_time = int(time.time() * 1000)
bytes_before_download = dut.droid.connectivityGetRxBytesForDevice(
subscriber_id, 0, end_time)
- self.log.info("Bytes before download %s" % bytes_before_download)
+ self.log.info("Data usage before download: %s MB" %
+ (bytes_before_download/self.convert_byte_to_mb))
# download file
self.log.info("Download file of size %sMB" % self.file_size)
@@ -446,7 +453,8 @@
end_time = int(time.time() * 1000)
bytes_after_download = dut.droid.connectivityGetRxBytesForDevice(
subscriber_id, 0, end_time)
- self.log.info("Bytes after download %s" % bytes_after_download)
+ self.log.info("Data usage after download: %s MB" %
+ (bytes_after_download/self.convert_byte_to_mb))
bytes_diff = bytes_after_download - bytes_before_download
wutils.stop_wifi_tethering(self.hotspot_device)
@@ -461,9 +469,9 @@
def test_wifi_tethering_data_usage_limit(self):
""" Steps:
- 1. Set the data usage limit to current data usage + 2MB
+ 1. Set the data usage limit to current data usage + 10MB
2. Start wifi tethering and connect a dut to the SSID
- 3. Download 5MB data on tethered device
+ 3. Download 20MB data on tethered device
a. file download should stop
b. tethered device will lose internet connectivity
c. data usage limit reached message should be displayed
@@ -472,7 +480,7 @@
"""
wutils.toggle_wifi_off_and_on(self.hotspot_device)
dut = self.hotspot_device
- data_usage_2mb = 2 * self.convert_byte_to_mb
+ data_usage_inc = 10 * self.convert_byte_to_mb
subscriber_id = dut.droid.telephonyGetSubscriberId()
self._start_wifi_tethering()
@@ -483,11 +491,11 @@
old_data_usage = dut.droid.connectivityQuerySummaryForDevice(
subscriber_id, 0, end_time)
- # set data usage limit to current usage limit + 2MB
+ # set data usage limit to current usage limit + 10MB
dut.droid.connectivitySetDataUsageLimit(
- subscriber_id, str(int(old_data_usage + data_usage_2mb)))
+ subscriber_id, str(int(old_data_usage + data_usage_inc)))
- # download file - size 5MB
+ # download file - size 20MB
http_file_download_by_chrome(self.tethered_devices[0],
self.download_file,
timeout=120)
@@ -503,8 +511,10 @@
dut.droid.connectivityFactoryResetNetworkPolicies(subscriber_id)
wutils.stop_wifi_tethering(self.hotspot_device)
- old_data_usage = (old_data_usage+data_usage_2mb)/self.convert_byte_to_mb
+ old_data_usage = (old_data_usage+data_usage_inc)/self.convert_byte_to_mb
new_data_usage = new_data_usage/self.convert_byte_to_mb
+ self.log.info("Expected data usage: %s MB" % old_data_usage)
+ self.log.info("Actual data usage: %s MB" % new_data_usage)
return (new_data_usage-old_data_usage) < self.data_usage_error
diff --git a/acts/tests/sample/OtaSampleTest.py b/acts/tests/sample/OtaSampleTest.py
new file mode 100644
index 0000000..aeb735e
--- /dev/null
+++ b/acts/tests/sample/OtaSampleTest.py
@@ -0,0 +1,37 @@
+#!/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.
+
+from acts import base_test
+from acts.libs.ota import ota_updater
+
+
+class OtaSampleTest(base_test.BaseTestClass):
+ """Demonstrates an example OTA Update test."""
+
+ def setup_class(self):
+ ota_updater.initialize(self.user_params, self.android_devices)
+ self.dut = self.android_devices[0]
+
+ def test_my_test(self):
+ self.pre_ota()
+ ota_updater.update(self.dut)
+ self.post_ota()
+
+ def pre_ota(self):
+ pass
+
+ def post_ota(self):
+ pass