Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(90)

Side by Side Diff: build/android/pylib/device/device_utils.py

Issue 1234153004: Cache device apk checksums in device_utils.py (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@gtest-fast
Patch Set: hook uninstall Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | build/android/pylib/device/device_utils_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Provides a variety of device interactions based on adb. 5 """Provides a variety of device interactions based on adb.
6 6
7 Eventually, this will be based on adb_wrapper. 7 Eventually, this will be based on adb_wrapper.
8 """ 8 """
9 # pylint: disable=unused-argument 9 # pylint: disable=unused-argument
10 10
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after
169 elif isinstance(device, pylib.android_commands.AndroidCommands): 169 elif isinstance(device, pylib.android_commands.AndroidCommands):
170 self.adb = adb_wrapper.AdbWrapper(device.GetDevice()) 170 self.adb = adb_wrapper.AdbWrapper(device.GetDevice())
171 self.old_interface = device 171 self.old_interface = device
172 else: 172 else:
173 raise ValueError('Unsupported device value: %r' % device) 173 raise ValueError('Unsupported device value: %r' % device)
174 self._commands_installed = None 174 self._commands_installed = None
175 self._default_timeout = default_timeout 175 self._default_timeout = default_timeout
176 self._default_retries = default_retries 176 self._default_retries = default_retries
177 self._cache = {} 177 self._cache = {}
178 self._client_caches = {} 178 self._client_caches = {}
179 self._package_to_device_apk_checksums_cache = {}
180 self._package_to_device_apk_paths_cache = {}
179 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) 181 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)
180 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) 182 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR)
181 183
184 # We need to flush the cache on uninstall, so wrap self.adb.Uninstall
185 # in order to ensure we detect calls to it.
186 old_adb_uninstall = self.adb.Uninstall
187 def WrappedAdbUninstall(package_name, *args, **kwargs):
188 try:
189 old_adb_uninstall(package_name, *args, **kwargs)
190 except:
191 # Clear cache since we can't be sure of the state.
192 self._package_to_device_apk_paths_cache.pop(package_name, 0)
193 self._package_to_device_apk_checksums_cache.pop(package_name, 0)
194 raise
195 self._package_to_device_apk_paths_cache[package_name] = []
jbudorick 2015/08/06 19:28:22 I would still prefer to use self._cache and do som
agrieve 2015/08/11 14:07:46 Done.
196 self._package_to_device_apk_checksums_cache[package_name] = set()
197 self.adb.Uninstall = WrappedAdbUninstall
198
182 def __eq__(self, other): 199 def __eq__(self, other):
183 """Checks whether |other| refers to the same device as |self|. 200 """Checks whether |other| refers to the same device as |self|.
184 201
185 Args: 202 Args:
186 other: The object to compare to. This can be a basestring, an instance 203 other: The object to compare to. This can be a basestring, an instance
187 of adb_wrapper.AdbWrapper, or an instance of DeviceUtils. 204 of adb_wrapper.AdbWrapper, or an instance of DeviceUtils.
188 Returns: 205 Returns:
189 Whether |other| refers to the same device as |self|. 206 Whether |other| refers to the same device as |self|.
190 """ 207 """
191 return self.adb.GetDeviceSerial() == str(other) 208 return self.adb.GetDeviceSerial() == str(other)
(...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after
345 @decorators.WithTimeoutAndRetriesFromInstance() 362 @decorators.WithTimeoutAndRetriesFromInstance()
346 def GetApplicationPaths(self, package, timeout=None, retries=None): 363 def GetApplicationPaths(self, package, timeout=None, retries=None):
347 """Get the paths of the installed apks on the device for the given package. 364 """Get the paths of the installed apks on the device for the given package.
348 365
349 Args: 366 Args:
350 package: Name of the package. 367 package: Name of the package.
351 368
352 Returns: 369 Returns:
353 List of paths to the apks on the device for the given package. 370 List of paths to the apks on the device for the given package.
354 """ 371 """
372 return self._GetApplicationPathsInternal(package)
373
374 def _GetApplicationPathsInternal(self, package, skip_cache=False):
375 cached_result = self._package_to_device_apk_paths_cache.get(package)
376 if cached_result is not None and not skip_cache:
377 return list(cached_result)
355 # 'pm path' is liable to incorrectly exit with a nonzero number starting 378 # 'pm path' is liable to incorrectly exit with a nonzero number starting
356 # in Lollipop. 379 # in Lollipop.
357 # TODO(jbudorick): Check if this is fixed as new Android versions are 380 # TODO(jbudorick): Check if this is fixed as new Android versions are
358 # released to put an upper bound on this. 381 # released to put an upper bound on this.
359 should_check_return = (self.build_version_sdk < 382 should_check_return = (self.build_version_sdk <
360 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) 383 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
361 output = self.RunShellCommand( 384 output = self.RunShellCommand(
362 ['pm', 'path', package], check_return=should_check_return) 385 ['pm', 'path', package], check_return=should_check_return)
363 apks = [] 386 apks = []
364 for line in output: 387 for line in output:
365 if not line.startswith('package:'): 388 if not line.startswith('package:'):
366 raise device_errors.CommandFailedError( 389 raise device_errors.CommandFailedError(
367 'pm path returned: %r' % '\n'.join(output), str(self)) 390 'pm path returned: %r' % '\n'.join(output), str(self))
368 apks.append(line[len('package:'):]) 391 apks.append(line[len('package:'):])
392 self._package_to_device_apk_paths_cache[package] = list(apks)
369 return apks 393 return apks
370 394
371 @decorators.WithTimeoutAndRetriesFromInstance() 395 @decorators.WithTimeoutAndRetriesFromInstance()
372 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): 396 def GetApplicationDataDirectory(self, package, timeout=None, retries=None):
373 """Get the data directory on the device for the given package. 397 """Get the data directory on the device for the given package.
374 398
375 Args: 399 Args:
376 package: Name of the package. 400 package: Name of the package.
377 401
378 Returns: 402 Returns:
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
411 def sd_card_ready(): 435 def sd_card_ready():
412 try: 436 try:
413 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], 437 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()],
414 check_return=True) 438 check_return=True)
415 return True 439 return True
416 except device_errors.AdbCommandFailedError: 440 except device_errors.AdbCommandFailedError:
417 return False 441 return False
418 442
419 def pm_ready(): 443 def pm_ready():
420 try: 444 try:
421 return self.GetApplicationPaths('android') 445 return self._GetApplicationPathsInternal('android', skip_cache=True)
422 except device_errors.CommandFailedError: 446 except device_errors.CommandFailedError:
423 return False 447 return False
424 448
425 def boot_completed(): 449 def boot_completed():
426 return self.GetProp('sys.boot_completed') == '1' 450 return self.GetProp('sys.boot_completed') == '1'
427 451
428 def wifi_enabled(): 452 def wifi_enabled():
429 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], 453 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'],
430 check_return=False) 454 check_return=False)
431 455
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
481 reinstall: A boolean indicating if we should keep any existing app data. 505 reinstall: A boolean indicating if we should keep any existing app data.
482 timeout: timeout in seconds 506 timeout: timeout in seconds
483 retries: number of retries 507 retries: number of retries
484 508
485 Raises: 509 Raises:
486 CommandFailedError if the installation fails. 510 CommandFailedError if the installation fails.
487 CommandTimeoutError if the installation times out. 511 CommandTimeoutError if the installation times out.
488 DeviceUnreachableError on missing device. 512 DeviceUnreachableError on missing device.
489 """ 513 """
490 package_name = apk_helper.GetPackageName(apk_path) 514 package_name = apk_helper.GetPackageName(apk_path)
491 device_paths = self.GetApplicationPaths(package_name) 515 device_paths = self._GetApplicationPathsInternal(package_name)
492 if device_paths: 516 if device_paths:
493 if len(device_paths) > 1: 517 if len(device_paths) > 1:
494 logging.warning( 518 logging.warning(
495 'Installing single APK (%s) when split APKs (%s) are currently ' 519 'Installing single APK (%s) when split APKs (%s) are currently '
496 'installed.', apk_path, ' '.join(device_paths)) 520 'installed.', apk_path, ' '.join(device_paths))
497 (files_to_push, _) = self._GetChangedAndStaleFiles( 521 apks_to_install, host_checksums = (
498 apk_path, device_paths[0]) 522 self._ComputeStaleApks(package_name, [apk_path]))
499 should_install = bool(files_to_push) 523 should_install = bool(apks_to_install)
500 if should_install and not reinstall: 524 if should_install and not reinstall:
501 self.adb.Uninstall(package_name) 525 self.adb.Uninstall(package_name)
502 else: 526 else:
503 should_install = True 527 should_install = True
528 host_checksums = None
504 if should_install: 529 if should_install:
505 self.adb.Install(apk_path, reinstall=reinstall) 530 # We won't know the resulting device apk names.
531 self._package_to_device_apk_paths_cache.pop(package_name, 0)
532 try:
533 self.adb.Install(apk_path, reinstall=reinstall)
534 self._package_to_device_apk_checksums_cache[package_name] = (
535 host_checksums)
536 except:
537 self._package_to_device_apk_paths_cache.pop(package_name, 0)
538 raise
506 539
507 @decorators.WithTimeoutAndRetriesDefaults( 540 @decorators.WithTimeoutAndRetriesDefaults(
508 INSTALL_DEFAULT_TIMEOUT, 541 INSTALL_DEFAULT_TIMEOUT,
509 INSTALL_DEFAULT_RETRIES) 542 INSTALL_DEFAULT_RETRIES)
510 def InstallSplitApk(self, base_apk, split_apks, reinstall=False, 543 def InstallSplitApk(self, base_apk, split_apks, reinstall=False,
511 timeout=None, retries=None): 544 timeout=None, retries=None):
512 """Install a split APK. 545 """Install a split APK.
513 546
514 Noop if all of the APK splits are already installed. 547 Noop if all of the APK splits are already installed.
515 548
516 Args: 549 Args:
517 base_apk: A string of the path to the base APK. 550 base_apk: A string of the path to the base APK.
518 split_apks: A list of strings of paths of all of the APK splits. 551 split_apks: A list of strings of paths of all of the APK splits.
519 reinstall: A boolean indicating if we should keep any existing app data. 552 reinstall: A boolean indicating if we should keep any existing app data.
520 timeout: timeout in seconds 553 timeout: timeout in seconds
521 retries: number of retries 554 retries: number of retries
522 555
523 Raises: 556 Raises:
524 CommandFailedError if the installation fails. 557 CommandFailedError if the installation fails.
525 CommandTimeoutError if the installation times out. 558 CommandTimeoutError if the installation times out.
526 DeviceUnreachableError on missing device. 559 DeviceUnreachableError on missing device.
527 DeviceVersionError if device SDK is less than Android L. 560 DeviceVersionError if device SDK is less than Android L.
528 """ 561 """
529 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) 562 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
530 563
531 all_apks = [base_apk] + split_select.SelectSplits( 564 all_apks = [base_apk] + split_select.SelectSplits(
532 self, base_apk, split_apks) 565 self, base_apk, split_apks)
533 package_name = apk_helper.GetPackageName(base_apk) 566 package_name = apk_helper.GetPackageName(base_apk)
534 device_apk_paths = self.GetApplicationPaths(package_name) 567 device_apk_paths = self._GetApplicationPathsInternal(package_name)
535 568
536 if device_apk_paths: 569 if device_apk_paths:
537 partial_install_package = package_name 570 partial_install_package = package_name
538 device_checksums = md5sum.CalculateDeviceMd5Sums(device_apk_paths, self) 571 apks_to_install, host_checksums = (
539 host_checksums = md5sum.CalculateHostMd5Sums(all_apks) 572 self._ComputeStaleApks(package_name, all_apks))
540 apks_to_install = [k for (k, v) in host_checksums.iteritems()
541 if v not in device_checksums.values()]
542 if apks_to_install and not reinstall: 573 if apks_to_install and not reinstall:
543 self.adb.Uninstall(package_name) 574 self.adb.Uninstall(package_name)
544 partial_install_package = None 575 partial_install_package = None
545 apks_to_install = all_apks 576 apks_to_install = all_apks
546 else: 577 else:
547 partial_install_package = None 578 partial_install_package = None
548 apks_to_install = all_apks 579 apks_to_install = all_apks
580 host_checksums = None
549 if apks_to_install: 581 if apks_to_install:
550 self.adb.InstallMultiple( 582 # We won't know the resulting device apk names.
551 apks_to_install, partial=partial_install_package, reinstall=reinstall) 583 self._package_to_device_apk_paths_cache.pop(package_name, 0)
584 try:
585 self.adb.InstallMultiple(
586 apks_to_install, partial=partial_install_package,
587 reinstall=reinstall)
588 self._package_to_device_apk_checksums_cache[package_name] = (
589 host_checksums)
590 except:
591 self._package_to_device_apk_paths_cache.pop(package_name, 0)
jbudorick 2015/08/06 19:28:22 Do you need to do this? If we hit an exception in
agrieve 2015/08/11 14:07:46 Nice catch. Gone.
592 raise
552 593
553 def _CheckSdkLevel(self, required_sdk_level): 594 def _CheckSdkLevel(self, required_sdk_level):
554 """Raises an exception if the device does not have the required SDK level. 595 """Raises an exception if the device does not have the required SDK level.
555 """ 596 """
556 if self.build_version_sdk < required_sdk_level: 597 if self.build_version_sdk < required_sdk_level:
557 raise device_errors.DeviceVersionError( 598 raise device_errors.DeviceVersionError(
558 ('Requires SDK level %s, device is SDK level %s' % 599 ('Requires SDK level %s, device is SDK level %s' %
559 (required_sdk_level, self.build_version_sdk)), 600 (required_sdk_level, self.build_version_sdk)),
560 device_serial=self.adb.GetDeviceSerial()) 601 device_serial=self.adb.GetDeviceSerial())
561 602
(...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after
886 927
887 Raises: 928 Raises:
888 CommandTimeoutError on timeout. 929 CommandTimeoutError on timeout.
889 DeviceUnreachableError on missing device. 930 DeviceUnreachableError on missing device.
890 """ 931 """
891 # Check that the package exists before clearing it for android builds below 932 # Check that the package exists before clearing it for android builds below
892 # JB MR2. Necessary because calling pm clear on a package that doesn't exist 933 # JB MR2. Necessary because calling pm clear on a package that doesn't exist
893 # may never return. 934 # may never return.
894 if ((self.build_version_sdk >= 935 if ((self.build_version_sdk >=
895 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) 936 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2)
896 or self.GetApplicationPaths(package)): 937 or self._GetApplicationPathsInternal(package)):
897 self.RunShellCommand(['pm', 'clear', package], check_return=True) 938 self.RunShellCommand(['pm', 'clear', package], check_return=True)
898 939
899 @decorators.WithTimeoutAndRetriesFromInstance() 940 @decorators.WithTimeoutAndRetriesFromInstance()
900 def SendKeyEvent(self, keycode, timeout=None, retries=None): 941 def SendKeyEvent(self, keycode, timeout=None, retries=None):
901 """Sends a keycode to the device. 942 """Sends a keycode to the device.
902 943
903 See the pylib.constants.keyevent module for suitable keycode values. 944 See the pylib.constants.keyevent module for suitable keycode values.
904 945
905 Args: 946 Args:
906 keycode: A integer keycode to send to the device. 947 keycode: A integer keycode to send to the device.
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after
1010 to_push = [] 1051 to_push = []
1011 for host_abs_path, host_checksum in host_checksums.iteritems(): 1052 for host_abs_path, host_checksum in host_checksums.iteritems():
1012 device_abs_path = '%s/%s' % ( 1053 device_abs_path = '%s/%s' % (
1013 real_device_path, os.path.relpath(host_abs_path, real_host_path)) 1054 real_device_path, os.path.relpath(host_abs_path, real_host_path))
1014 device_checksum = device_checksums.pop(device_abs_path, None) 1055 device_checksum = device_checksums.pop(device_abs_path, None)
1015 if device_checksum != host_checksum: 1056 if device_checksum != host_checksum:
1016 to_push.append((host_abs_path, device_abs_path)) 1057 to_push.append((host_abs_path, device_abs_path))
1017 to_delete = device_checksums.keys() 1058 to_delete = device_checksums.keys()
1018 return (to_push, to_delete) 1059 return (to_push, to_delete)
1019 1060
1061 def _ComputeDeviceChecksumsForApks(self, package_name):
1062 ret = self._package_to_device_apk_checksums_cache.get(package_name)
1063 if ret is None:
1064 device_paths = self._GetApplicationPathsInternal(package_name)
1065 file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self)
1066 ret = set(file_to_checksums.values())
1067 self._package_to_device_apk_checksums_cache[package_name] = ret
1068 return ret
1069
1070 def _ComputeStaleApks(self, package_name, host_apk_paths):
jbudorick 2015/08/06 19:28:22 I'm curious about the component time savings of ca
agrieve 2015/08/11 14:07:46 When I first started down this path, I tried to ad
1071 host_checksums = md5sum.CalculateHostMd5Sums(host_apk_paths)
1072 device_checksums = self._ComputeDeviceChecksumsForApks(package_name)
1073 stale_apks = [k for (k, v) in host_checksums.iteritems()
1074 if v not in device_checksums]
1075 return stale_apks, set(host_checksums.values())
1076
1020 def _PushFilesImpl(self, host_device_tuples, files): 1077 def _PushFilesImpl(self, host_device_tuples, files):
1021 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) 1078 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files)
1022 file_count = len(files) 1079 file_count = len(files)
1023 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) 1080 dir_size = sum(host_utils.GetRecursiveDiskUsage(h)
1024 for h, _ in host_device_tuples) 1081 for h, _ in host_device_tuples)
1025 dir_file_count = 0 1082 dir_file_count = 0
1026 for h, _ in host_device_tuples: 1083 for h, _ in host_device_tuples:
1027 if os.path.isdir(h): 1084 if os.path.isdir(h):
1028 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) 1085 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h))
1029 else: 1086 else:
(...skipping 675 matching lines...) Expand 10 before | Expand all | Expand 10 after
1705 """Returns client cache.""" 1762 """Returns client cache."""
1706 if client_name not in self._client_caches: 1763 if client_name not in self._client_caches:
1707 self._client_caches[client_name] = {} 1764 self._client_caches[client_name] = {}
1708 return self._client_caches[client_name] 1765 return self._client_caches[client_name]
1709 1766
1710 def _ClearCache(self): 1767 def _ClearCache(self):
1711 """Clears all caches.""" 1768 """Clears all caches."""
1712 for client in self._client_caches: 1769 for client in self._client_caches:
1713 self._client_caches[client].clear() 1770 self._client_caches[client].clear()
1714 self._cache.clear() 1771 self._cache.clear()
1772 self._package_to_device_apk_paths_cache.clear()
1773 self._package_to_device_apk_checksums_cache.clear()
1715 1774
1716 @classmethod 1775 @classmethod
1717 def parallel(cls, devices=None, async=False): 1776 def parallel(cls, devices=None, async=False):
1718 """Creates a Parallelizer to operate over the provided list of devices. 1777 """Creates a Parallelizer to operate over the provided list of devices.
1719 1778
1720 If |devices| is either |None| or an empty list, the Parallelizer will 1779 If |devices| is either |None| or an empty list, the Parallelizer will
1721 operate over all attached devices that have not been blacklisted. 1780 operate over all attached devices that have not been blacklisted.
1722 1781
1723 Args: 1782 Args:
1724 devices: A list of either DeviceUtils instances or objects from 1783 devices: A list of either DeviceUtils instances or objects from
(...skipping 20 matching lines...) Expand all
1745 def HealthyDevices(cls): 1804 def HealthyDevices(cls):
1746 blacklist = device_blacklist.ReadBlacklist() 1805 blacklist = device_blacklist.ReadBlacklist()
1747 def blacklisted(adb): 1806 def blacklisted(adb):
1748 if adb.GetDeviceSerial() in blacklist: 1807 if adb.GetDeviceSerial() in blacklist:
1749 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) 1808 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial())
1750 return True 1809 return True
1751 return False 1810 return False
1752 1811
1753 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() 1812 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices()
1754 if not blacklisted(adb)] 1813 if not blacklisted(adb)]
OLDNEW
« no previous file with comments | « no previous file | build/android/pylib/device/device_utils_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698