diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2017-10-26 20:03:34 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2017-10-26 20:03:34 +0000 |
commit | 581351db82f450d673c78e3d038b49c349a37997 (patch) | |
tree | fe7f6f6171d4fdd6ad6b93a78248dcd4ac638525 | |
parent | 27d33855c36b23922013bc802bce654e10c4272f (diff) | |
parent | 91b8da9d0822fc0610deadc9d6e9e0a9482e3e14 (diff) | |
download | platform_tools_test_connectivity-oreo-m2-s3-release.tar.gz platform_tools_test_connectivity-oreo-m2-s3-release.tar.bz2 platform_tools_test_connectivity-oreo-m2-s3-release.zip |
Snap for 4417144 from 91b8da9d0822fc0610deadc9d6e9e0a9482e3e14 to oc-m2-releaseandroid-8.1.0_r8android-8.1.0_r52android-8.1.0_r50android-8.1.0_r47android-8.1.0_r46android-8.1.0_r43android-8.1.0_r41android-8.1.0_r36android-8.1.0_r35android-8.1.0_r33android-8.1.0_r30android-8.1.0_r26android-8.1.0_r25android-8.1.0_r20android-8.1.0_r2oreo-m7-releaseoreo-m6-s4-releaseoreo-m6-s3-releaseoreo-m6-s2-releaseoreo-m2-s5-releaseoreo-m2-s4-releaseoreo-m2-s3-releaseoreo-m2-s2-releaseoreo-m2-s1-releaseoreo-m2-release
Change-Id: Ia7aee081a5eed00aac66951fdc90f8f438ae07a2
17 files changed, 857 insertions, 20 deletions
diff --git a/acts/framework/acts/libs/ota/__init__.py b/acts/framework/acts/libs/ota/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /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 0000000000..e69de29bb2 --- /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 0000000000..776c9008a1 --- /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 0000000000..fa6ab19790 --- /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 0000000000..e69de29bb2 --- /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 0000000000..f94a7627fe --- /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 0000000000..e51fe6bce0 --- /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 0000000000..ac81646011 --- /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 0000000000..978842ffb7 --- /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 = 'python2.7 %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 0000000000..ed300aab67 --- /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/bt/PowerBaseTest.py b/acts/framework/acts/test_utils/bt/PowerBaseTest.py index 525317e0a2..a175c9e43b 100644 --- a/acts/framework/acts/test_utils/bt/PowerBaseTest.py +++ b/acts/framework/acts/test_utils/bt/PowerBaseTest.py @@ -56,6 +56,17 @@ class PowerBaseTest(BluetoothBaseTest): "PMCMainActivity") PMC_VERBOSE_CMD = "setprop log.tag.PMC VERBOSE" + def setup_test(self): + self.timer_list = [] + for a in self.android_devices: + a.ed.clear_all_events() + a.droid.setScreenTimeout(20) + self.ad.go_to_sleep() + return True + + def teardown_test(self): + return True + def setup_class(self): # Not to call Base class setup_class() # since it removes the bonded devices @@ -85,8 +96,6 @@ class PowerBaseTest(BluetoothBaseTest): set_ambient_display(self.ad, False) self.ad.adb.shell("settings put system screen_brightness 0") set_auto_rotate(self.ad, False) - set_phone_screen_on(self.log, self.ad, self.SCREEN_TIME_OFF) - self.ad.go_to_sleep() wutils.wifi_toggle_state(self.ad, False) 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 5732351d83..c778466a39 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 @@ def start_adb_tcpdump(ad, test_name, mask="ims"): """ 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 5c398f8358..e13b964853 100755 --- a/acts/framework/acts/utils.py +++ b/acts/framework/acts/utils.py @@ -28,6 +28,7 @@ import string import subprocess import time import traceback +import zipfile from acts.controllers import adb @@ -846,3 +847,28 @@ def adb_shell_ping(ad, 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 0000000000..91e51bb7f7 --- /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 b1a5391ab9..785d91ff2d 100755 --- a/acts/tests/google/wifi/WifiEnterpriseTest.py +++ b/acts/tests/google/wifi/WifiEnterpriseTest.py @@ -22,6 +22,8 @@ from acts import asserts 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 @@ class WifiEnterpriseTest(base_test.BaseTestClass): 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 @@ class WifiEnterpriseTest(base_test.BaseTestClass): 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 57a8e66fec..ef79f3d7a5 100644 --- a/acts/tests/google/wifi/WifiTetheringTest.py +++ b/acts/tests/google/wifi/WifiTetheringTest.py @@ -44,7 +44,7 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 @@ class WifiTetheringTest(base_test.BaseTestClass): """ 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 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 @@ class WifiTetheringTest(base_test.BaseTestClass): """ 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 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 @@ class WifiTetheringTest(base_test.BaseTestClass): 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 0000000000..aeb735e808 --- /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 |