Update to latest catapult (1ff7619f)

bug:21925298
bug:29643805
bug:30299278
bug:30397774
bug:30953297

Change-Id: I4d73c3c5454541a50703253ebeb63e5b2ea01fec
diff --git a/catapult/devil/devil/android/device_utils_test.py b/catapult/devil/devil/android/device_utils_test.py
index 38849ec..18fda54 100755
--- a/catapult/devil/devil/android/device_utils_test.py
+++ b/catapult/devil/devil/android/device_utils_test.py
@@ -10,7 +10,10 @@
 # pylint: disable=protected-access
 # pylint: disable=unused-argument
 
+import json
 import logging
+import os
+import stat
 import unittest
 
 from devil import devil_env
@@ -27,6 +30,17 @@
   import mock  # pylint: disable=import-error
 
 
+class AnyStringWith(object):
+  def __init__(self, value):
+    self._value = value
+
+  def __eq__(self, other):
+    return self._value in other
+
+  def __repr__(self):
+    return '<AnyStringWith: %s>' % self._value
+
+
 class _MockApkHelper(object):
 
   def __init__(self, path, package_name, perms=None):
@@ -41,6 +55,10 @@
     return self.perms
 
 
+class _MockMultipleDevicesError(Exception):
+  pass
+
+
 class DeviceUtilsInitTest(unittest.TestCase):
 
   def testInitWithStr(self):
@@ -172,6 +190,12 @@
     return mock.Mock(side_effect=device_errors.CommandTimeoutError(
         msg, str(self.device)))
 
+  def EnsureCacheInitialized(self, props=None, sdcard='/sdcard'):
+    props = props or []
+    ret = [sdcard, 'TOKEN'] + props
+    return (self.call.device.RunShellCommand(
+        AnyStringWith('getprop'), check_return=True, large_output=True), ret)
+
 
 class DeviceUtilsEqTest(DeviceUtilsTest):
 
@@ -305,13 +329,14 @@
 class DeviceUtilsGetExternalStoragePathTest(DeviceUtilsTest):
 
   def testGetExternalStoragePath_succeeds(self):
-    with self.assertCall(
-        self.call.adb.Shell('echo $EXTERNAL_STORAGE'), '/fake/storage/path\n'):
+    with self.assertCalls(
+        self.EnsureCacheInitialized(sdcard='/fake/storage/path')):
       self.assertEquals('/fake/storage/path',
                         self.device.GetExternalStoragePath())
 
   def testGetExternalStoragePath_fails(self):
-    with self.assertCall(self.call.adb.Shell('echo $EXTERNAL_STORAGE'), '\n'):
+    with self.assertCalls(
+        self.EnsureCacheInitialized(sdcard='')):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.GetExternalStoragePath()
 
@@ -451,6 +476,23 @@
         (self.call.device.GetProp('sys.boot_completed', cache=False), '1')):
       self.device.WaitUntilFullyBooted(wifi=False)
 
+  def testWaitUntilFullyBooted_deviceBrieflyOffline(self):
+    with self.assertCalls(
+        self.call.adb.WaitForDevice(),
+        # sd_card_ready
+        (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        # pm_ready
+        (self.call.device._GetApplicationPathsInternal('android',
+                                                       skip_cache=True),
+         ['package:/some/fake/path']),
+        # boot_completed
+        (self.call.device.GetProp('sys.boot_completed', cache=False),
+         self.AdbCommandError()),
+        # boot_completed
+        (self.call.device.GetProp('sys.boot_completed', cache=False), '1')):
+      self.device.WaitUntilFullyBooted(wifi=False)
+
   def testWaitUntilFullyBooted_sdCardReadyFails_noPath(self):
     with self.assertCalls(
         self.call.adb.WaitForDevice(),
@@ -835,6 +877,12 @@
       self.assertEquals(['file1', 'file2', 'file3'],
                         self.device.RunShellCommand(cmd))
 
+  def testRunShellCommand_manyLinesRawOutput(self):
+    cmd = 'ls /some/path'
+    with self.assertCall(self.call.adb.Shell(cmd), '\rfile1\nfile2\r\nfile3\n'):
+      self.assertEquals('\rfile1\nfile2\r\nfile3\n',
+                        self.device.RunShellCommand(cmd, raw_output=True))
+
   def testRunShellCommand_singleLine_success(self):
     cmd = 'echo $VALUE'
     with self.assertCall(self.call.adb.Shell(cmd), 'some value\n'):
@@ -1198,12 +1246,15 @@
     test_intent = intent.Intent(action='android.intent.action.VIEW',
                                 package='test.package',
                                 activity='.Main',
-                                flags='0x10000000')
+                                flags=[
+                                  intent.FLAG_ACTIVITY_NEW_TASK,
+                                  intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                                ])
     with self.assertCall(
         self.call.adb.Shell('am start '
                             '-a android.intent.action.VIEW '
                             '-n test.package/.Main '
-                            '-f 0x10000000'),
+                            '-f 0x10200000'),
         'Starting: Intent { act=android.intent.action.VIEW }'):
       self.device.StartActivity(test_intent)
 
@@ -1599,10 +1650,7 @@
 
   def testReadFile_exists(self):
     with self.assertCalls(
-        (self.call.device.RunShellCommand(
-            ['ls', '-l', '/read/this/test/file'],
-            as_root=False, check_return=True),
-         ['-rw-rw---- root foo 256 1970-01-01 00:00 file']),
+        (self.call.device.FileSize('/read/this/test/file', as_root=False), 256),
         (self.call.device.RunShellCommand(
             ['cat', '/read/this/test/file'],
             as_root=False, check_return=True),
@@ -1613,10 +1661,7 @@
   def testReadFile_exists2(self):
     # Same as testReadFile_exists, but uses Android N ls output.
     with self.assertCalls(
-        (self.call.device.RunShellCommand(
-            ['ls', '-l', '/read/this/test/file'],
-            as_root=False, check_return=True),
-         ['-rw-rw-rw- 1 root root 256 2016-03-15 03:27 /read/this/test/file']),
+        (self.call.device.FileSize('/read/this/test/file', as_root=False), 256),
         (self.call.device.RunShellCommand(
             ['cat', '/read/this/test/file'],
             as_root=False, check_return=True),
@@ -1626,19 +1671,15 @@
 
   def testReadFile_doesNotExist(self):
     with self.assertCall(
-        self.call.device.RunShellCommand(
-            ['ls', '-l', '/this/file/does.not.exist'],
-            as_root=False, check_return=True),
+        self.call.device.FileSize('/this/file/does.not.exist', as_root=False),
         self.CommandError('File does not exist')):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.ReadFile('/this/file/does.not.exist')
 
   def testReadFile_zeroSize(self):
     with self.assertCalls(
-        (self.call.device.RunShellCommand(
-            ['ls', '-l', '/this/file/has/zero/size'],
-            as_root=False, check_return=True),
-         ['-r--r--r-- root foo 0 1970-01-01 00:00 zero_size_file']),
+        (self.call.device.FileSize('/this/file/has/zero/size', as_root=False),
+         0),
         (self.call.device._ReadFileWithPull('/this/file/has/zero/size'),
          'but it has contents\n')):
       self.assertEqual('but it has contents\n',
@@ -1646,10 +1687,8 @@
 
   def testReadFile_withSU(self):
     with self.assertCalls(
-        (self.call.device.RunShellCommand(
-            ['ls', '-l', '/this/file/can.be.read.with.su'],
-            as_root=True, check_return=True),
-         ['-rw------- root root 256 1970-01-01 00:00 can.be.read.with.su']),
+        (self.call.device.FileSize(
+            '/this/file/can.be.read.with.su', as_root=True), 256),
         (self.call.device.RunShellCommand(
             ['cat', '/this/file/can.be.read.with.su'],
             as_root=True, check_return=True),
@@ -1662,10 +1701,8 @@
   def testReadFile_withPull(self):
     contents = 'a' * 123456
     with self.assertCalls(
-        (self.call.device.RunShellCommand(
-            ['ls', '-l', '/read/this/big/test/file'],
-            as_root=False, check_return=True),
-         ['-rw-rw---- root foo 123456 1970-01-01 00:00 file']),
+        (self.call.device.FileSize('/read/this/big/test/file', as_root=False),
+         123456),
         (self.call.device._ReadFileWithPull('/read/this/big/test/file'),
          contents)):
       self.assertEqual(
@@ -1674,10 +1711,8 @@
   def testReadFile_withPullAndSU(self):
     contents = 'b' * 123456
     with self.assertCalls(
-        (self.call.device.RunShellCommand(
-            ['ls', '-l', '/this/big/file/can.be.read.with.su'],
-            as_root=True, check_return=True),
-         ['-rw------- root root 123456 1970-01-01 00:00 can.be.read.with.su']),
+        (self.call.device.FileSize(
+            '/this/big/file/can.be.read.with.su', as_root=True), 123456),
         (self.call.device.NeedsSU(), True),
         (mock.call.devil.android.device_temp_file.DeviceTempFile(self.adb),
          MockTempFile('/sdcard/tmp/on.device')),
@@ -1768,52 +1803,189 @@
       self.device.WriteFile('/test/file', 'contents', as_root=True)
 
 
-class DeviceUtilsLsTest(DeviceUtilsTest):
+class DeviceUtilsStatDirectoryTest(DeviceUtilsTest):
+  # Note: Also tests ListDirectory in testStatDirectory_fileList.
 
-  def testLs_directory(self):
-    result = [('.', adb_wrapper.DeviceStat(16889, 4096, 1417436123)),
-              ('..', adb_wrapper.DeviceStat(16873, 4096, 12382237)),
-              ('testfile.txt', adb_wrapper.DeviceStat(33206, 3, 1417436122))]
-    with self.assertCalls(
-        (self.call.adb.Ls('/data/local/tmp'), result)):
-      self.assertEquals(result,
-                        self.device.Ls('/data/local/tmp'))
+  EXAMPLE_LS_OUTPUT = [
+    'total 12345',
+    'drwxr-xr-x  19 root   root          0 1970-04-06 18:03 .',
+    'drwxr-xr-x  19 root   root          0 1970-04-06 18:03 ..',
+    'drwxr-xr-x   6 root   root            1970-01-01 00:00 some_dir',
+    '-rw-r--r--   1 root   root        723 1971-01-01 07:04 some_file',
+    '-rw-r-----   1 root   root        327 2009-02-13 23:30 My Music File',
+    # Older Android versions do not print st_nlink
+    'lrwxrwxrwx root     root              1970-01-01 00:00 lnk -> /some/path',
+    'srwxrwx--- system   system            2016-05-31 17:25 a_socket1',
+    'drwxrwxrwt system   misc              1970-11-23 02:25 tmp',
+    'drwxr-s--- system   shell             1970-11-23 02:24 my_cmd',
+    'cr--r----- root     system    10, 183 1971-01-01 07:04 random',
+    'brw------- root     root       7,   0 1971-01-01 07:04 block_dev',
+    '-rwS------ root     shell      157404 2015-04-13 15:44 silly',
+  ]
 
-  def testLs_nothing(self):
-    with self.assertCalls(
-        (self.call.adb.Ls('/data/local/tmp/testfile.txt'), [])):
-      self.assertEquals([],
-                        self.device.Ls('/data/local/tmp/testfile.txt'))
+  FILENAMES = [
+    'some_dir', 'some_file', 'My Music File', 'lnk', 'a_socket1',
+    'tmp', 'my_cmd', 'random', 'block_dev', 'silly']
+
+  def getStatEntries(self, path_given='/', path_listed='/'):
+    with self.assertCall(
+        self.call.device.RunShellCommand(
+            ['ls', '-a', '-l', path_listed],
+            check_return=True, as_root=False, env={'TZ': 'utc'}),
+        self.EXAMPLE_LS_OUTPUT):
+      entries = self.device.StatDirectory(path_given)
+    return {f['filename']: f for f in entries}
+
+  def getListEntries(self):
+    with self.assertCall(
+        self.call.device.RunShellCommand(
+            ['ls', '-a', '-l', '/'],
+            check_return=True, as_root=False, env={'TZ': 'utc'}),
+        self.EXAMPLE_LS_OUTPUT):
+      return self.device.ListDirectory('/')
+
+  def testStatDirectory_forceTrailingSlash(self):
+    self.getStatEntries(path_given='/foo/bar/', path_listed='/foo/bar/')
+    self.getStatEntries(path_given='/foo/bar', path_listed='/foo/bar/')
+
+  def testStatDirectory_fileList(self):
+    self.assertItemsEqual(self.getStatEntries().keys(), self.FILENAMES)
+    self.assertItemsEqual(self.getListEntries(), self.FILENAMES)
+
+  def testStatDirectory_fileModes(self):
+    expected_modes = (
+      ('some_dir', stat.S_ISDIR),
+      ('some_file', stat.S_ISREG),
+      ('lnk', stat.S_ISLNK),
+      ('a_socket1', stat.S_ISSOCK),
+      ('block_dev', stat.S_ISBLK),
+      ('random', stat.S_ISCHR),
+    )
+    entries = self.getStatEntries()
+    for filename, check in expected_modes:
+      self.assertTrue(check(entries[filename]['st_mode']))
+
+  def testStatDirectory_filePermissions(self):
+    should_have = (
+      ('some_file', stat.S_IWUSR),  # Owner can write.
+      ('tmp', stat.S_IXOTH),  # Others can execute.
+      ('tmp', stat.S_ISVTX),  # Has sticky bit.
+      ('my_cmd', stat.S_ISGID),  # Has set-group-ID bit.
+      ('silly', stat.S_ISUID),  # Has set UID bit.
+    )
+    should_not_have = (
+      ('some_file', stat.S_IWOTH),  # Others can't write.
+      ('block_dev', stat.S_IRGRP),  # Group can't read.
+      ('silly', stat.S_IXUSR),  # Owner can't execute.
+    )
+    entries = self.getStatEntries()
+    for filename, bit in should_have:
+      self.assertTrue(entries[filename]['st_mode'] & bit)
+    for filename, bit in should_not_have:
+      self.assertFalse(entries[filename]['st_mode'] & bit)
+
+  def testStatDirectory_numHardLinks(self):
+    entries = self.getStatEntries()
+    self.assertEqual(entries['some_dir']['st_nlink'], 6)
+    self.assertEqual(entries['some_file']['st_nlink'], 1)
+    self.assertFalse('st_nlink' in entries['tmp'])
+
+  def testStatDirectory_fileOwners(self):
+    entries = self.getStatEntries()
+    self.assertEqual(entries['some_dir']['st_owner'], 'root')
+    self.assertEqual(entries['my_cmd']['st_owner'], 'system')
+    self.assertEqual(entries['my_cmd']['st_group'], 'shell')
+    self.assertEqual(entries['tmp']['st_group'], 'misc')
+
+  def testStatDirectory_fileSize(self):
+    entries = self.getStatEntries()
+    self.assertEqual(entries['some_file']['st_size'], 723)
+    self.assertEqual(entries['My Music File']['st_size'], 327)
+    # Sizes are sometimes not reported for non-regular files, don't try to
+    # guess the size in those cases.
+    self.assertFalse('st_size' in entries['some_dir'])
+
+  def testStatDirectory_fileDateTime(self):
+    entries = self.getStatEntries()
+    self.assertEqual(entries['some_dir']['st_mtime'], 0)  # Epoch!
+    self.assertEqual(entries['My Music File']['st_mtime'], 1234567800)
+
+  def testStatDirectory_deviceType(self):
+    entries = self.getStatEntries()
+    self.assertEqual(entries['random']['st_rdev_pair'], (10, 183))
+    self.assertEqual(entries['block_dev']['st_rdev_pair'], (7, 0))
+
+  def testStatDirectory_symbolicLinks(self):
+    entries = self.getStatEntries()
+    self.assertEqual(entries['lnk']['symbolic_link_to'], '/some/path')
+    for d in entries.itervalues():
+      self.assertEqual('symbolic_link_to' in d, stat.S_ISLNK(d['st_mode']))
 
 
-class DeviceUtilsStatTest(DeviceUtilsTest):
+class DeviceUtilsStatPathTest(DeviceUtilsTest):
 
-  def testStat_file(self):
-    result = [('.', adb_wrapper.DeviceStat(16889, 4096, 1417436123)),
-              ('..', adb_wrapper.DeviceStat(16873, 4096, 12382237)),
-              ('testfile.txt', adb_wrapper.DeviceStat(33206, 3, 1417436122))]
-    with self.assertCalls(
-        (self.call.adb.Ls('/data/local/tmp'), result)):
-      self.assertEquals(adb_wrapper.DeviceStat(33206, 3, 1417436122),
-                        self.device.Stat('/data/local/tmp/testfile.txt'))
+  EXAMPLE_DIRECTORY = [
+    {'filename': 'foo.txt', 'st_size': 123, 'st_time': 456},
+    {'filename': 'some_dir', 'st_time': 0}
+  ]
+  INDEX = {e['filename']: e for e in EXAMPLE_DIRECTORY}
 
-  def testStat_directory(self):
-    result = [('.', adb_wrapper.DeviceStat(16873, 4096, 12382237)),
-              ('..', adb_wrapper.DeviceStat(16873, 4096, 12382237)),
-              ('tmp', adb_wrapper.DeviceStat(16889, 4096, 1417436123))]
-    with self.assertCalls(
-        (self.call.adb.Ls('/data/local'), result)):
-      self.assertEquals(adb_wrapper.DeviceStat(16889, 4096, 1417436123),
-                        self.device.Stat('/data/local/tmp'))
+  def testStatPath_file(self):
+    with self.assertCall(
+        self.call.device.StatDirectory('/data/local/tmp', as_root=False),
+        self.EXAMPLE_DIRECTORY):
+      self.assertEquals(self.INDEX['foo.txt'],
+                        self.device.StatPath('/data/local/tmp/foo.txt'))
 
-  def testStat_doesNotExist(self):
-    result = [('.', adb_wrapper.DeviceStat(16889, 4096, 1417436123)),
-              ('..', adb_wrapper.DeviceStat(16873, 4096, 12382237)),
-              ('testfile.txt', adb_wrapper.DeviceStat(33206, 3, 1417436122))]
-    with self.assertCalls(
-        (self.call.adb.Ls('/data/local/tmp'), result)):
+  def testStatPath_directory(self):
+    with self.assertCall(
+        self.call.device.StatDirectory('/data/local/tmp', as_root=False),
+        self.EXAMPLE_DIRECTORY):
+      self.assertEquals(self.INDEX['some_dir'],
+                        self.device.StatPath('/data/local/tmp/some_dir'))
+
+  def testStatPath_directoryWithTrailingSlash(self):
+    with self.assertCall(
+        self.call.device.StatDirectory('/data/local/tmp', as_root=False),
+        self.EXAMPLE_DIRECTORY):
+      self.assertEquals(self.INDEX['some_dir'],
+                        self.device.StatPath('/data/local/tmp/some_dir/'))
+
+  def testStatPath_doesNotExist(self):
+    with self.assertCall(
+        self.call.device.StatDirectory('/data/local/tmp', as_root=False),
+        self.EXAMPLE_DIRECTORY):
       with self.assertRaises(device_errors.CommandFailedError):
-        self.device.Stat('/data/local/tmp/does.not.exist.txt')
+        self.device.StatPath('/data/local/tmp/does.not.exist.txt')
+
+
+class DeviceUtilsFileSizeTest(DeviceUtilsTest):
+
+  EXAMPLE_DIRECTORY = [
+    {'filename': 'foo.txt', 'st_size': 123, 'st_mtime': 456},
+    {'filename': 'some_dir', 'st_mtime': 0}
+  ]
+
+  def testFileSize_file(self):
+    with self.assertCall(
+        self.call.device.StatDirectory('/data/local/tmp', as_root=False),
+        self.EXAMPLE_DIRECTORY):
+      self.assertEquals(123,
+                        self.device.FileSize('/data/local/tmp/foo.txt'))
+
+  def testFileSize_doesNotExist(self):
+    with self.assertCall(
+        self.call.device.StatDirectory('/data/local/tmp', as_root=False),
+        self.EXAMPLE_DIRECTORY):
+      with self.assertRaises(device_errors.CommandFailedError):
+        self.device.FileSize('/data/local/tmp/does.not.exist.txt')
+
+  def testFileSize_directoryWithNoSize(self):
+    with self.assertCall(
+        self.call.device.StatDirectory('/data/local/tmp', as_root=False),
+        self.EXAMPLE_DIRECTORY):
+      with self.assertRaises(device_errors.CommandFailedError):
+        self.device.FileSize('/data/local/tmp/some_dir')
 
 
 class DeviceUtilsSetJavaAssertsTest(DeviceUtilsTest):
@@ -1866,6 +2038,34 @@
       self.assertFalse(self.device.SetJavaAsserts(True))
 
 
+class DeviceUtilsEnsureCacheInitializedTest(DeviceUtilsTest):
+
+  def testEnsureCacheInitialized_noCache_success(self):
+    self.assertIsNone(self.device._cache['token'])
+    with self.assertCall(
+        self.call.device.RunShellCommand(
+            AnyStringWith('getprop'), check_return=True, large_output=True),
+        ['/sdcard', 'TOKEN']):
+      self.device._EnsureCacheInitialized()
+    self.assertIsNotNone(self.device._cache['token'])
+
+  def testEnsureCacheInitialized_noCache_failure(self):
+    self.assertIsNone(self.device._cache['token'])
+    with self.assertCall(
+        self.call.device.RunShellCommand(
+            AnyStringWith('getprop'), check_return=True, large_output=True),
+        self.TimeoutError()):
+      with self.assertRaises(device_errors.CommandTimeoutError):
+        self.device._EnsureCacheInitialized()
+    self.assertIsNone(self.device._cache['token'])
+
+  def testEnsureCacheInitialized_cache(self):
+    self.device._cache['token'] = 'TOKEN'
+    with self.assertCalls():
+      self.device._EnsureCacheInitialized()
+    self.assertIsNotNone(self.device._cache['token'])
+
+
 class DeviceUtilsGetPropTest(DeviceUtilsTest):
 
   def testGetProp_exists(self):
@@ -1889,30 +2089,12 @@
       self.assertEqual('', self.device.GetProp('property.does.not.exist'))
 
   def testGetProp_cachedRoProp(self):
-    with self.assertCall(
-        self.call.device.RunShellCommand(
-            ['getprop'], check_return=True, large_output=True,
-            timeout=self.device._default_timeout,
-            retries=self.device._default_retries),
-        ['[ro.build.type]: [userdebug]']):
-      self.assertEqual('userdebug',
-                       self.device.GetProp('ro.build.type', cache=True))
-      self.assertEqual('userdebug',
-                       self.device.GetProp('ro.build.type', cache=True))
-
-  def testGetProp_retryAndCache(self):
     with self.assertCalls(
-        (self.call.device.RunShellCommand(
-            ['getprop'], check_return=True, large_output=True,
-            timeout=self.device._default_timeout,
-            retries=3),
-         ['[ro.build.type]: [userdebug]'])):
+        self.EnsureCacheInitialized(props=['[ro.build.type]: [userdebug]'])):
       self.assertEqual('userdebug',
-                       self.device.GetProp('ro.build.type',
-                                           cache=True, retries=3))
+                       self.device.GetProp('ro.build.type', cache=True))
       self.assertEqual('userdebug',
-                       self.device.GetProp('ro.build.type',
-                                           cache=True, retries=3))
+                       self.device.GetProp('ro.build.type', cache=True))
 
 
 class DeviceUtilsSetPropTest(DeviceUtilsTest):
@@ -2139,7 +2321,7 @@
 
 class DeviceUtilsHealthyDevicesTest(mock_calls.TestCase):
 
-  def testHealthyDevices_emptyBlacklist(self):
+  def testHealthyDevices_emptyBlacklist_defaultDeviceArg(self):
     test_serials = ['0123456789abcdef', 'fedcba9876543210']
     with self.assertCalls(
         (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
@@ -2150,7 +2332,7 @@
       self.assertTrue(isinstance(device, device_utils.DeviceUtils))
       self.assertEquals(serial, device.adb.GetDeviceSerial())
 
-  def testHealthyDevices_blacklist(self):
+  def testHealthyDevices_blacklist_defaultDeviceArg(self):
     test_serials = ['0123456789abcdef', 'fedcba9876543210']
     with self.assertCalls(
         (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
@@ -2162,6 +2344,81 @@
     self.assertTrue(isinstance(devices[0], device_utils.DeviceUtils))
     self.assertEquals('0123456789abcdef', devices[0].adb.GetDeviceSerial())
 
+  def testHealthyDevices_noneDeviceArg_multiple_attached(self):
+    test_serials = ['0123456789abcdef', 'fedcba9876543210']
+    with self.assertCalls(
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
+         [_AdbWrapperMock(s) for s in test_serials]),
+        (mock.call.devil.android.device_errors.MultipleDevicesError(mock.ANY),
+         _MockMultipleDevicesError())):
+      with self.assertRaises(_MockMultipleDevicesError):
+        device_utils.DeviceUtils.HealthyDevices(device_arg=None)
+
+  def testHealthyDevices_noneDeviceArg_one_attached(self):
+    test_serials = ['0123456789abcdef']
+    with self.assertCalls(
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
+         [_AdbWrapperMock(s) for s in test_serials])):
+      devices = device_utils.DeviceUtils.HealthyDevices(device_arg=None)
+    self.assertEquals(1, len(devices))
+
+  def testHealthyDevices_noneDeviceArg_no_attached(self):
+    test_serials = []
+    with self.assertCalls(
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
+         [_AdbWrapperMock(s) for s in test_serials])):
+      with self.assertRaises(device_errors.NoDevicesError):
+        device_utils.DeviceUtils.HealthyDevices(device_arg=None)
+
+  def testHealthyDevices_noneDeviceArg_multiple_attached_ANDROID_SERIAL(self):
+    try:
+      os.environ['ANDROID_SERIAL'] = '0123456789abcdef'
+      with self.assertCalls():  # Should skip adb devices when device is known.
+        device_utils.DeviceUtils.HealthyDevices(device_arg=None)
+    finally:
+      del os.environ['ANDROID_SERIAL']
+
+  def testHealthyDevices_stringDeviceArg(self):
+    with self.assertCalls():  # Should skip adb devices when device is known.
+      devices = device_utils.DeviceUtils.HealthyDevices(
+          device_arg='0123456789abcdef')
+    self.assertEquals(1, len(devices))
+
+  def testHealthyDevices_EmptyListDeviceArg_multiple_attached(self):
+    test_serials = ['0123456789abcdef', 'fedcba9876543210']
+    with self.assertCalls(
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
+         [_AdbWrapperMock(s) for s in test_serials])):
+      devices = device_utils.DeviceUtils.HealthyDevices(device_arg=())
+    self.assertEquals(2, len(devices))
+
+  def testHealthyDevices_EmptyListDeviceArg_ANDROID_SERIAL(self):
+    try:
+      os.environ['ANDROID_SERIAL'] = '0123456789abcdef'
+      with self.assertCalls():  # Should skip adb devices when device is known.
+        devices = device_utils.DeviceUtils.HealthyDevices(device_arg=())
+    finally:
+      del os.environ['ANDROID_SERIAL']
+    self.assertEquals(1, len(devices))
+
+  def testHealthyDevices_EmptyListDeviceArg_no_attached(self):
+    test_serials = []
+    with self.assertCalls(
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
+         [_AdbWrapperMock(s) for s in test_serials])):
+      with self.assertRaises(device_errors.NoDevicesError):
+        device_utils.DeviceUtils.HealthyDevices(device_arg=[])
+
+  def testHealthyDevices_ListDeviceArg(self):
+    device_arg = ['0123456789abcdef', 'fedcba9876543210']
+    try:
+      os.environ['ANDROID_SERIAL'] = 'should-not-apply'
+      with self.assertCalls():  # Should skip adb devices when device is known.
+        devices = device_utils.DeviceUtils.HealthyDevices(device_arg=device_arg)
+    finally:
+      del os.environ['ANDROID_SERIAL']
+    self.assertEquals(2, len(devices))
+
 
 class DeviceUtilsRestartAdbdTest(DeviceUtilsTest):
 
@@ -2307,6 +2564,31 @@
         (self.call.device.IsScreenOn(), False)):
       self.device.SetScreen(False)
 
+class DeviecUtilsLoadCacheData(DeviceUtilsTest):
+
+  def testTokenMissing(self):
+    with self.assertCalls(
+        self.EnsureCacheInitialized()):
+      self.assertFalse(self.device.LoadCacheData('{}'))
+
+  def testTokenStale(self):
+    with self.assertCalls(
+        self.EnsureCacheInitialized()):
+      self.assertFalse(self.device.LoadCacheData('{"token":"foo"}'))
+
+  def testTokenMatches(self):
+    with self.assertCalls(
+        self.EnsureCacheInitialized()):
+      self.assertTrue(self.device.LoadCacheData('{"token":"TOKEN"}'))
+
+  def testDumpThenLoad(self):
+    with self.assertCalls(
+        self.EnsureCacheInitialized()):
+      data = json.loads(self.device.DumpCacheData())
+      data['token'] = 'TOKEN'
+      self.assertTrue(self.device.LoadCacheData(json.dumps(data)))
+
+
 if __name__ == '__main__':
   logging.getLogger().setLevel(logging.DEBUG)
   unittest.main(verbosity=2)