From f117f4656fc15bed8a2d8c788a6765465d83a5d0 Mon Sep 17 00:00:00 2001 From: "jbudorick@chromium.org" Date: Thu, 12 Jun 2014 16:24:23 +0000 Subject: [PATCH] [Android] Switch to DeviceUtils versions of Reboot and Install. BUG=267773 NOTRY=true Review URL: https://codereview.chromium.org/292313015 git-svn-id: http://src.chromium.org/svn/trunk/src/build@276702 4ff67af0-8c30-449e-8e8b-ad334ec8d88c --- android/adb_install_apk.py | 12 +- android/enable_asserts.py | 5 +- android/provision_devices.py | 7 +- android/pylib/device/device_utils.py | 104 +++++++++++++++--- android/pylib/device/device_utils_test.py | 37 ++++++- android/pylib/gtest/test_package_apk.py | 3 +- android/pylib/instrumentation/test_package.py | 3 +- android/pylib/instrumentation/test_runner.py | 5 +- android/update_verification.py | 4 + 9 files changed, 148 insertions(+), 32 deletions(-) diff --git a/android/adb_install_apk.py b/android/adb_install_apk.py index 7a781eee6..ea39aeaa6 100755 --- a/android/adb_install_apk.py +++ b/android/adb_install_apk.py @@ -13,7 +13,6 @@ import sys from pylib import android_commands from pylib import constants from pylib.device import device_utils -from pylib.utils import apk_helper def AddInstallAPKOption(option_parser): @@ -22,8 +21,8 @@ def AddInstallAPKOption(option_parser): help=('DEPRECATED The name of the apk containing the' ' application (with the .apk extension).')) option_parser.add_option('--apk_package', - help=('The package name used by the apk containing ' - 'the application.')) + help=('DEPRECATED The package name used by the apk ' + 'containing the application.')) option_parser.add_option('--keep_data', action='store_true', default=False, @@ -74,11 +73,8 @@ def main(argv): if not devices: raise Exception('Error: no connected devices') - if not options.apk_package: - options.apk_package = apk_helper.GetPackageName(options.apk) - - device_utils.DeviceUtils.parallel(devices).old_interface.ManagedInstall( - options.apk, options.keep_data, options.apk_package).pFinish(None) + device_utils.DeviceUtils.parallel(devices).Install( + options.apk, reinstall=options.keep_data) if __name__ == '__main__': diff --git a/android/enable_asserts.py b/android/enable_asserts.py index 9ed402e8a..34f8e3cab 100755 --- a/android/enable_asserts.py +++ b/android/enable_asserts.py @@ -28,7 +28,10 @@ def main(argv): for device in [device_utils.DeviceUtils(serial) for serial in devices]: if options.set_asserts != None: if device.old_interface.SetJavaAssertsEnabled(options.set_asserts): - device.old_interface.Reboot(full_reboot=False) + # TODO(jbudorick) How to best do shell restarts after the + # android_commands refactor? + device.old_interface.RunShellCommand('stop') + device.old_interface.RunShellCommand('start') if __name__ == '__main__': diff --git a/android/provision_devices.py b/android/provision_devices.py index d282737f0..c4f8f340d 100755 --- a/android/provision_devices.py +++ b/android/provision_devices.py @@ -131,7 +131,7 @@ def ProvisionDevices(options): device.old_interface.EnableAdbRoot() WipeDeviceData(device) try: - device_utils.DeviceUtils.parallel(devices).old_interface.Reboot(True) + device_utils.DeviceUtils.parallel(devices).Reboot(True) except errors.DeviceUnresponsiveError: pass for device_serial in devices: @@ -148,7 +148,7 @@ def ProvisionDevices(options): device, device_settings.NETWORK_DISABLED_SETTINGS) device.old_interface.RunShellCommandWithSU('date -u %f' % time.time()) try: - device_utils.DeviceUtils.parallel(devices).old_interface.Reboot(True) + device_utils.DeviceUtils.parallel(devices).Reboot(True) except errors.DeviceUnresponsiveError: pass for device_serial in devices: @@ -186,8 +186,7 @@ def main(argv): device = device_utils.DeviceUtils(device_serial) WipeDeviceData(device) try: - (device_utils.DeviceUtils.parallel(devices) - .old_interface.Reboot(True).pFinish(None)) + device_utils.DeviceUtils.parallel(devices).Reboot(True) except errors.DeviceUnresponsiveError: pass else: diff --git a/android/pylib/device/device_utils.py b/android/pylib/device/device_utils.py index 4adde1d47..970192363 100644 --- a/android/pylib/device/device_utils.py +++ b/android/pylib/device/device_utils.py @@ -2,8 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Provides a variety of device interactions based on adb. +"""Provides a variety of device interactions based on adb. Eventually, this will be based on adb_wrapper. """ @@ -15,6 +14,7 @@ import pylib.android_commands from pylib.device import adb_wrapper from pylib.device import decorators from pylib.device import device_errors +from pylib.utils import apk_helper from pylib.utils import parallelizer _DEFAULT_TIMEOUT = 30 @@ -24,7 +24,7 @@ _DEFAULT_RETRIES = 3 @decorators.WithExplicitTimeoutAndRetries( _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) def GetAVDs(): - """ Returns a list of Android Virtual Devices. + """Returns a list of Android Virtual Devices. Returns: A list containing the configured AVDs. @@ -35,7 +35,7 @@ def GetAVDs(): @decorators.WithExplicitTimeoutAndRetries( _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) def RestartServer(): - """ Restarts the adb server. + """Restarts the adb server. Raises: CommandFailedError if we fail to kill or restart the server. @@ -47,7 +47,7 @@ class DeviceUtils(object): def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, default_retries=_DEFAULT_RETRIES): - """ DeviceUtils constructor. + """DeviceUtils constructor. Args: device: Either a device serial, an existing AdbWrapper instance, an @@ -77,7 +77,7 @@ class DeviceUtils(object): @decorators.WithTimeoutAndRetriesFromInstance() def IsOnline(self, timeout=None, retries=None): - """ Checks whether the device is online. + """Checks whether the device is online. Args: timeout: An integer containing the number of seconds to wait for the @@ -91,7 +91,7 @@ class DeviceUtils(object): @decorators.WithTimeoutAndRetriesFromInstance() def HasRoot(self, timeout=None, retries=None): - """ Checks whether or not adbd has root privileges. + """Checks whether or not adbd has root privileges. Args: timeout: Same as for |IsOnline|. @@ -103,7 +103,7 @@ class DeviceUtils(object): @decorators.WithTimeoutAndRetriesFromInstance() def EnableRoot(self, timeout=None, retries=None): - """ Restarts adbd with root privileges. + """Restarts adbd with root privileges. Args: timeout: Same as for |IsOnline|. @@ -113,11 +113,11 @@ class DeviceUtils(object): """ if not self.old_interface.EnableAdbRoot(): raise device_errors.CommandFailedError( - 'adb root', 'Could not enable root.') + ['adb', 'root'], 'Could not enable root.') @decorators.WithTimeoutAndRetriesFromInstance() def GetExternalStoragePath(self, timeout=None, retries=None): - """ Get the device's path to its SD card. + """Get the device's path to its SD card. Args: timeout: Same as for |IsOnline|. @@ -128,11 +128,12 @@ class DeviceUtils(object): try: return self.old_interface.GetExternalStorage() except AssertionError as e: - raise device_errors.CommandFailedError(str(e)) + raise device_errors.CommandFailedError( + ['adb', 'shell', 'echo', '$EXTERNAL_STORAGE'], str(e)) @decorators.WithTimeoutAndRetriesFromInstance() def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): - """ Wait for the device to fully boot. + """Wait for the device to fully boot. This means waiting for the device to boot, the package manager to be available, and the SD card to be ready. It can optionally mean waiting @@ -146,6 +147,25 @@ class DeviceUtils(object): CommandTimeoutError if one of the component waits times out. DeviceUnreachableError if the device becomes unresponsive. """ + self._WaitUntilFullyBootedImpl(wifi=wifi, timeout=timeout) + + def _WaitUntilFullyBootedImpl(self, wifi=False, timeout=None): + """ Implementation of WaitUntilFullyBooted. + + This is split from WaitUntilFullyBooted to allow other DeviceUtils methods + to call WaitUntilFullyBooted without spawning a new timeout thread. + + TODO(jbudorick) Remove the timeout parameter once this is no longer + implemented via AndroidCommands. + + Args: + wifi: Same as for |WaitUntilFullyBooted|. + timeout: Same as for |IsOnline|. + Raises: + Same as for |WaitUntilFullyBooted|. + """ + if timeout is None: + timeout = self._default_timeout self.old_interface.WaitForSystemBootCompleted(timeout) self.old_interface.WaitForDevicePm() self.old_interface.WaitForSdCardReady(timeout) @@ -154,13 +174,71 @@ class DeviceUtils(object): self.old_interface.RunShellCommand('dumpsys wifi')): time.sleep(0.1) + @decorators.WithTimeoutAndRetriesDefaults( + 10 * _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) + def Reboot(self, block=True, timeout=None, retries=None): + """Reboot the device. + + Args: + block: A boolean indicating if we should wait for the reboot to complete. + timeout: Same as for |IsOnline|. + retries: Same as for |IsOnline|. + """ + self.old_interface.Reboot() + if block: + self._WaitUntilFullyBootedImpl(timeout=timeout) + + @decorators.WithTimeoutAndRetriesDefaults( + 4 * _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) + def Install(self, apk_path, reinstall=False, timeout=None, retries=None): + """Install an APK. + + Noop if an identical APK is already installed. + + Args: + apk_path: A string containing the path to the APK to install. + reinstall: A boolean indicating if we should keep any existing app data. + timeout: Same as for |IsOnline|. + retries: Same as for |IsOnline|. + Raises: + CommandFailedError if the installation fails. + CommandTimeoutError if the installation times out. + """ + package_name = apk_helper.GetPackageName(apk_path) + device_path = self.old_interface.GetApplicationPath(package_name) + if device_path is not None: + files_changed = self.old_interface.GetFilesChanged( + apk_path, device_path, ignore_filenames=True) + if len(files_changed) > 0: + should_install = True + if not reinstall: + out = self.old_interface.Uninstall(package_name) + for line in out.splitlines(): + if 'Failure' in line: + raise device_errors.CommandFailedError( + ['adb', 'uninstall', package_name], line.strip()) + else: + should_install = False + else: + should_install = True + if should_install: + try: + out = self.old_interface.Install(apk_path, reinstall=reinstall) + for line in out.splitlines(): + if 'Failure' in line: + raise device_errors.CommandFailedError( + ['adb', 'install', apk_path], line.strip()) + except AssertionError as e: + raise device_errors.CommandFailedError( + ['adb', 'install', apk_path], str(e)) + def __str__(self): """Returns the device serial.""" return self.old_interface.GetDevice() @staticmethod def parallel(devices=None, async=False): - """ Creates a Parallelizer to operate over the provided list of devices. + """Creates a Parallelizer to operate over the provided list of devices. If |devices| is either |None| or an empty list, the Parallelizer will operate over all attached devices. diff --git a/android/pylib/device/device_utils_test.py b/android/pylib/device/device_utils_test.py index 1ee7a06ed..4c4369fc6 100644 --- a/android/pylib/device/device_utils_test.py +++ b/android/pylib/device/device_utils_test.py @@ -9,6 +9,7 @@ Unit tests for the contents of device_utils.py (mostly DeviceUtils). # pylint: disable=W0212 # pylint: disable=W0613 +import functools import random import time import unittest @@ -20,10 +21,20 @@ from pylib.device import device_errors from pylib.device import device_utils +def TestRequiresDevice(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + if len(adb_wrapper.AdbWrapper.GetDevices()) > 0: + return f(*args, **kwargs) + return unittest.skip('Test requires an attached device.') + return wrapper + + class DeviceUtilsTest(unittest.TestCase): def testGetAVDs(self): pass + @TestRequiresDevice def testRestartServerNotRunning(self): self.assertEqual(0, cmd_helper.RunCmd(['pkill', 'adb']), msg='Unable to kill adb during setup.') @@ -32,6 +43,7 @@ class DeviceUtilsTest(unittest.TestCase): device_utils.RestartServer() self.assertEqual(0, cmd_helper.RunCmd(['pgrep', 'adb'])) + @TestRequiresDevice def testRestartServerAlreadyRunning(self): if cmd_helper.RunCmd(['pgrep', 'adb']) != 0: device_utils.RestartServer() @@ -89,12 +101,14 @@ class DeviceUtilsTest(unittest.TestCase): if serial not in used_devices: return serial + @TestRequiresDevice def testIsOnline(self): d = device_utils.DeviceUtils(self._getTestAdbWrapper()) self.assertTrue(d is None or d.IsOnline()) d = device_utils.DeviceUtils(self._getUnusedSerial()) self.assertFalse(d.IsOnline()) + @TestRequiresDevice def testHasRoot(self): a = self._getTestAdbWrapper() d = device_utils.DeviceUtils(a) @@ -112,6 +126,7 @@ class DeviceUtilsTest(unittest.TestCase): else: self.assertTrue(d.HasRoot()) + @TestRequiresDevice def testEnableRoot(self): a = self._getTestAdbWrapper() d = device_utils.DeviceUtils(a) @@ -148,6 +163,7 @@ class DeviceUtilsTest(unittest.TestCase): d.EnableRoot() self.assertTrue(d.HasRoot()) + @TestRequiresDevice def testGetExternalStorage(self): a = self._getTestAdbWrapper() d = device_utils.DeviceUtils(a) @@ -156,6 +172,7 @@ class DeviceUtilsTest(unittest.TestCase): if actual_external_storage and len(actual_external_storage) != 0: self.assertEquals(actual_external_storage, d.GetExternalStoragePath()) + @TestRequiresDevice def testWaitUntilFullyBooted(self): a = self._getTestAdbWrapper() d = device_utils.DeviceUtils(a) @@ -172,7 +189,25 @@ class DeviceUtilsTest(unittest.TestCase): self.assertTrue( 'Wi-Fi is enabled' in a.Shell('dumpsys wifi').splitlines()) + @TestRequiresDevice + def testBlockingReboot(self): + a = self._getTestAdbWrapper() + d = device_utils.DeviceUtils(a) + + old_boot_time = a.Shell('getprop ro.runtime.firstboot').strip() + if old_boot_time and len(old_boot_time): + d.Reboot(block=True, timeout=120) + self.assertNotEquals(old_boot_time, + a.Shell('getprop ro.runtime.firstboot').strip()) + self.assertEquals( + '1', a.Shell('getprop sys.boot_completed').splitlines()[0]) + self.assertTrue( + a.Shell('pm path android').splitlines()[0].startswith('package:')) + self.assertTrue(a.Shell('ls $EXTERNAL_STORAGE')) + else: + self.skipTest("No 'ro.runtime.firstboot' property on %s." % str(a)) + if __name__ == '__main__': - unittest.main(verbosity=2, buffer=True) + unittest.main(verbosity=2) diff --git a/android/pylib/gtest/test_package_apk.py b/android/pylib/gtest/test_package_apk.py index 9f077b6b9..83b6b0019 100644 --- a/android/pylib/gtest/test_package_apk.py +++ b/android/pylib/gtest/test_package_apk.py @@ -131,5 +131,4 @@ class TestPackageApk(TestPackage): #override def Install(self, device): self.tool.CopyFiles() - device.old_interface.ManagedInstall( - self.suite_path, False, package_name=self._package_info.package) + device.Install(self.suite_path) diff --git a/android/pylib/instrumentation/test_package.py b/android/pylib/instrumentation/test_package.py index ca03a4dc9..52e2e0995 100644 --- a/android/pylib/instrumentation/test_package.py +++ b/android/pylib/instrumentation/test_package.py @@ -34,6 +34,5 @@ class TestPackage(test_jar.TestJar): # Override. def Install(self, device): - device.old_interface.ManagedInstall( - self.GetApkPath(), package_name=self.GetPackageName()) + device.Install(self.GetApkPath()) diff --git a/android/pylib/instrumentation/test_runner.py b/android/pylib/instrumentation/test_runner.py index 0124281df..12ae70254 100644 --- a/android/pylib/instrumentation/test_runner.py +++ b/android/pylib/instrumentation/test_runner.py @@ -152,7 +152,10 @@ class TestRunner(base_test_runner.BaseTestRunner): str(self.device)) else: if self.device.old_interface.SetJavaAssertsEnabled(True): - self.device.old_interface.Reboot(full_reboot=False) + # TODO(jbudorick) How to best do shell restart after the + # android_commands refactor? + self.device.old_interface.RunShellCommand('stop') + self.device.old_interface.RunShellCommand('start') # We give different default value to launch HTTP server based on shard index # because it may have race condition when multiple processes are trying to diff --git a/android/update_verification.py b/android/update_verification.py index 91c223be3..fe895670f 100755 --- a/android/update_verification.py +++ b/android/update_verification.py @@ -29,6 +29,7 @@ def _SaveAppData(device, package_name, from_apk=None, data_dir=None): if from_apk: logging.info('Installing %s...', from_apk) + # TODO(jbudorick) Switch to AdbWrapper.Install on the impl switch. output = device.old_interface.Install(from_apk, reinstall=True) if 'Success' not in output: raise Exception('Unable to install %s. output: %s' % (from_apk, output)) @@ -47,6 +48,7 @@ def _VerifyAppUpdate(device, to_apk, app_data, from_apk=None): if from_apk: logging.info('Installing %s...', from_apk) + # TODO(jbudorick) Switch to AdbWrapper.Install on the impl switch. output = device.old_interface.Install(from_apk, reinstall=True) if 'Success' not in output: raise Exception('Unable to install %s. output: %s' % (from_apk, output)) @@ -57,6 +59,7 @@ def _VerifyAppUpdate(device, to_apk, app_data, from_apk=None): logging.info('Verifying that %s cannot be installed side-by-side...', to_apk) + # TODO(jbudorick) Switch to AdbWrapper.Install on the impl switch. output = device.old_interface.Install(to_apk) if 'INSTALL_FAILED_ALREADY_EXISTS' not in output: if 'Success' in output: @@ -65,6 +68,7 @@ def _VerifyAppUpdate(device, to_apk, app_data, from_apk=None): raise Exception(output) logging.info('Verifying that %s can be overinstalled...', to_apk) + # TODO(jbudorick) Switch to AdbWrapper.Install on the impl switch. output = device.old_interface.Install(to_apk, reinstall=True) if 'Success' not in output: raise Exception('Unable to install %s.\n output: %s' % (to_apk, output))