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

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: add Uninstall() Created 5 years, 4 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 175 matching lines...) Expand 10 before | Expand all | Expand 10 after
186 else: 186 else:
187 raise ValueError('Unsupported device value: %r' % device) 187 raise ValueError('Unsupported device value: %r' % device)
188 self._commands_installed = None 188 self._commands_installed = None
189 self._default_timeout = default_timeout 189 self._default_timeout = default_timeout
190 self._default_retries = default_retries 190 self._default_retries = default_retries
191 self._cache = {} 191 self._cache = {}
192 self._client_caches = {} 192 self._client_caches = {}
193 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) 193 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)
194 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) 194 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR)
195 195
196 self._ClearCache()
197
196 def __eq__(self, other): 198 def __eq__(self, other):
197 """Checks whether |other| refers to the same device as |self|. 199 """Checks whether |other| refers to the same device as |self|.
198 200
199 Args: 201 Args:
200 other: The object to compare to. This can be a basestring, an instance 202 other: The object to compare to. This can be a basestring, an instance
201 of adb_wrapper.AdbWrapper, or an instance of DeviceUtils. 203 of adb_wrapper.AdbWrapper, or an instance of DeviceUtils.
202 Returns: 204 Returns:
203 Whether |other| refers to the same device as |self|. 205 Whether |other| refers to the same device as |self|.
204 """ 206 """
205 return self.adb.GetDeviceSerial() == str(other) 207 return self.adb.GetDeviceSerial() == str(other)
(...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after
359 @decorators.WithTimeoutAndRetriesFromInstance() 361 @decorators.WithTimeoutAndRetriesFromInstance()
360 def GetApplicationPaths(self, package, timeout=None, retries=None): 362 def GetApplicationPaths(self, package, timeout=None, retries=None):
361 """Get the paths of the installed apks on the device for the given package. 363 """Get the paths of the installed apks on the device for the given package.
362 364
363 Args: 365 Args:
364 package: Name of the package. 366 package: Name of the package.
365 367
366 Returns: 368 Returns:
367 List of paths to the apks on the device for the given package. 369 List of paths to the apks on the device for the given package.
368 """ 370 """
371 return self._GetApplicationPathsInternal(package)
372
373 def _GetApplicationPathsInternal(self, package, skip_cache=False):
374 cached_result = self._cache['package_apk_paths'].get(package)
375 if cached_result is not None and not skip_cache:
376 return list(cached_result)
369 # 'pm path' is liable to incorrectly exit with a nonzero number starting 377 # 'pm path' is liable to incorrectly exit with a nonzero number starting
370 # in Lollipop. 378 # in Lollipop.
371 # TODO(jbudorick): Check if this is fixed as new Android versions are 379 # TODO(jbudorick): Check if this is fixed as new Android versions are
372 # released to put an upper bound on this. 380 # released to put an upper bound on this.
373 should_check_return = (self.build_version_sdk < 381 should_check_return = (self.build_version_sdk <
374 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) 382 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
375 output = self.RunShellCommand( 383 output = self.RunShellCommand(
376 ['pm', 'path', package], check_return=should_check_return) 384 ['pm', 'path', package], check_return=should_check_return)
377 apks = [] 385 apks = []
378 for line in output: 386 for line in output:
379 if not line.startswith('package:'): 387 if not line.startswith('package:'):
380 raise device_errors.CommandFailedError( 388 raise device_errors.CommandFailedError(
381 'pm path returned: %r' % '\n'.join(output), str(self)) 389 'pm path returned: %r' % '\n'.join(output), str(self))
382 apks.append(line[len('package:'):]) 390 apks.append(line[len('package:'):])
391 self._cache['package_apk_paths'][package] = list(apks)
383 return apks 392 return apks
384 393
385 @decorators.WithTimeoutAndRetriesFromInstance() 394 @decorators.WithTimeoutAndRetriesFromInstance()
386 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): 395 def GetApplicationDataDirectory(self, package, timeout=None, retries=None):
387 """Get the data directory on the device for the given package. 396 """Get the data directory on the device for the given package.
388 397
389 Args: 398 Args:
390 package: Name of the package. 399 package: Name of the package.
391 400
392 Returns: 401 Returns:
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
425 def sd_card_ready(): 434 def sd_card_ready():
426 try: 435 try:
427 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], 436 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()],
428 check_return=True) 437 check_return=True)
429 return True 438 return True
430 except device_errors.AdbCommandFailedError: 439 except device_errors.AdbCommandFailedError:
431 return False 440 return False
432 441
433 def pm_ready(): 442 def pm_ready():
434 try: 443 try:
435 return self.GetApplicationPaths('android') 444 return self._GetApplicationPathsInternal('android', skip_cache=True)
436 except device_errors.CommandFailedError: 445 except device_errors.CommandFailedError:
437 return False 446 return False
438 447
439 def boot_completed(): 448 def boot_completed():
440 return self.GetProp('sys.boot_completed') == '1' 449 return self.GetProp('sys.boot_completed') == '1'
441 450
442 def wifi_enabled(): 451 def wifi_enabled():
443 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], 452 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'],
444 check_return=False) 453 check_return=False)
445 454
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
495 reinstall: A boolean indicating if we should keep any existing app data. 504 reinstall: A boolean indicating if we should keep any existing app data.
496 timeout: timeout in seconds 505 timeout: timeout in seconds
497 retries: number of retries 506 retries: number of retries
498 507
499 Raises: 508 Raises:
500 CommandFailedError if the installation fails. 509 CommandFailedError if the installation fails.
501 CommandTimeoutError if the installation times out. 510 CommandTimeoutError if the installation times out.
502 DeviceUnreachableError on missing device. 511 DeviceUnreachableError on missing device.
503 """ 512 """
504 package_name = apk_helper.GetPackageName(apk_path) 513 package_name = apk_helper.GetPackageName(apk_path)
505 device_paths = self.GetApplicationPaths(package_name) 514 device_paths = self._GetApplicationPathsInternal(package_name)
506 if device_paths: 515 if device_paths:
507 if len(device_paths) > 1: 516 if len(device_paths) > 1:
508 logging.warning( 517 logging.warning(
509 'Installing single APK (%s) when split APKs (%s) are currently ' 518 'Installing single APK (%s) when split APKs (%s) are currently '
510 'installed.', apk_path, ' '.join(device_paths)) 519 'installed.', apk_path, ' '.join(device_paths))
511 (files_to_push, _) = self._GetChangedAndStaleFiles( 520 apks_to_install, host_checksums = (
512 apk_path, device_paths[0]) 521 self._ComputeStaleApks(package_name, [apk_path]))
513 should_install = bool(files_to_push) 522 should_install = bool(apks_to_install)
514 if should_install and not reinstall: 523 if should_install and not reinstall:
515 self.adb.Uninstall(package_name) 524 self.Uninstall(package_name)
516 else: 525 else:
517 should_install = True 526 should_install = True
527 host_checksums = None
518 if should_install: 528 if should_install:
jbudorick 2015/08/11 15:21:04 nit: I know this is how it was before, but can you
agrieve 2015/08/11 15:26:08 Done.
529 # We won't know the resulting device apk names.
530 self._cache['package_apk_paths'].pop(package_name, 0)
519 self.adb.Install(apk_path, reinstall=reinstall) 531 self.adb.Install(apk_path, reinstall=reinstall)
532 self._cache['package_apk_checksums'][package_name] = host_checksums
520 533
521 @decorators.WithTimeoutAndRetriesDefaults( 534 @decorators.WithTimeoutAndRetriesDefaults(
522 INSTALL_DEFAULT_TIMEOUT, 535 INSTALL_DEFAULT_TIMEOUT,
523 INSTALL_DEFAULT_RETRIES) 536 INSTALL_DEFAULT_RETRIES)
524 def InstallSplitApk(self, base_apk, split_apks, reinstall=False, 537 def InstallSplitApk(self, base_apk, split_apks, reinstall=False,
525 timeout=None, retries=None): 538 timeout=None, retries=None):
526 """Install a split APK. 539 """Install a split APK.
527 540
528 Noop if all of the APK splits are already installed. 541 Noop if all of the APK splits are already installed.
529 542
530 Args: 543 Args:
531 base_apk: A string of the path to the base APK. 544 base_apk: A string of the path to the base APK.
532 split_apks: A list of strings of paths of all of the APK splits. 545 split_apks: A list of strings of paths of all of the APK splits.
533 reinstall: A boolean indicating if we should keep any existing app data. 546 reinstall: A boolean indicating if we should keep any existing app data.
534 timeout: timeout in seconds 547 timeout: timeout in seconds
535 retries: number of retries 548 retries: number of retries
536 549
537 Raises: 550 Raises:
538 CommandFailedError if the installation fails. 551 CommandFailedError if the installation fails.
539 CommandTimeoutError if the installation times out. 552 CommandTimeoutError if the installation times out.
540 DeviceUnreachableError on missing device. 553 DeviceUnreachableError on missing device.
541 DeviceVersionError if device SDK is less than Android L. 554 DeviceVersionError if device SDK is less than Android L.
542 """ 555 """
543 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) 556 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
544 557
545 all_apks = [base_apk] + split_select.SelectSplits( 558 all_apks = [base_apk] + split_select.SelectSplits(
546 self, base_apk, split_apks) 559 self, base_apk, split_apks)
547 package_name = apk_helper.GetPackageName(base_apk) 560 package_name = apk_helper.GetPackageName(base_apk)
548 device_apk_paths = self.GetApplicationPaths(package_name) 561 device_apk_paths = self._GetApplicationPathsInternal(package_name)
549 562
550 if device_apk_paths: 563 if device_apk_paths:
551 partial_install_package = package_name 564 partial_install_package = package_name
552 device_checksums = md5sum.CalculateDeviceMd5Sums(device_apk_paths, self) 565 apks_to_install, host_checksums = (
553 host_checksums = md5sum.CalculateHostMd5Sums(all_apks) 566 self._ComputeStaleApks(package_name, all_apks))
554 apks_to_install = [k for (k, v) in host_checksums.iteritems()
555 if v not in device_checksums.values()]
556 if apks_to_install and not reinstall: 567 if apks_to_install and not reinstall:
557 self.adb.Uninstall(package_name) 568 self.Uninstall(package_name)
558 partial_install_package = None 569 partial_install_package = None
559 apks_to_install = all_apks 570 apks_to_install = all_apks
560 else: 571 else:
561 partial_install_package = None 572 partial_install_package = None
562 apks_to_install = all_apks 573 apks_to_install = all_apks
574 host_checksums = None
563 if apks_to_install: 575 if apks_to_install:
jbudorick 2015/08/11 15:21:04 nit: same
agrieve 2015/08/11 15:26:08 Done.
576 # We won't know the resulting device apk names.
577 self._cache['package_apk_paths'].pop(package_name, 0)
564 self.adb.InstallMultiple( 578 self.adb.InstallMultiple(
565 apks_to_install, partial=partial_install_package, reinstall=reinstall) 579 apks_to_install, partial=partial_install_package,
580 reinstall=reinstall)
581 self._cache['package_apk_checksums'][package_name] = host_checksums
582
583 @decorators.WithTimeoutAndRetriesFromInstance()
584 def Uninstall(self, package_name, keep_data=False, timeout=None,
585 retries=None):
586 """Remove the app |package_name| from the device.
587
588 Args:
589 package_name: The package to uninstall.
590 keep_data: (optional) Whether to keep the data and cache directories.
591 timeout: Timeout in seconds.
592 retries: Number of retries.
593
594 Raises:
595 CommandFailedError if the uninstallation fails.
596 CommandTimeoutError if the uninstallation times out.
597 DeviceUnreachableError on missing device.
598 """
599 try:
600 self.adb.Uninstall(package_name, keep_data)
601 self._cache['package_apk_paths'][package_name] = []
602 self._cache['package_apk_checksums'][package_name] = set()
603 except:
jbudorick 2015/08/11 15:21:04 Is there really a functional difference between th
agrieve 2015/08/11 15:26:08 There is. In the first case, we're caching the fac
604 # Clear cache since we can't be sure of the state.
605 self._cache['package_apk_paths'].pop(package_name, 0)
606 self._cache['package_apk_checksums'].pop(package_name, 0)
607 raise
566 608
567 def _CheckSdkLevel(self, required_sdk_level): 609 def _CheckSdkLevel(self, required_sdk_level):
568 """Raises an exception if the device does not have the required SDK level. 610 """Raises an exception if the device does not have the required SDK level.
569 """ 611 """
570 if self.build_version_sdk < required_sdk_level: 612 if self.build_version_sdk < required_sdk_level:
571 raise device_errors.DeviceVersionError( 613 raise device_errors.DeviceVersionError(
572 ('Requires SDK level %s, device is SDK level %s' % 614 ('Requires SDK level %s, device is SDK level %s' %
573 (required_sdk_level, self.build_version_sdk)), 615 (required_sdk_level, self.build_version_sdk)),
574 device_serial=self.adb.GetDeviceSerial()) 616 device_serial=self.adb.GetDeviceSerial())
575 617
(...skipping 331 matching lines...) Expand 10 before | Expand all | Expand 10 after
907 949
908 Raises: 950 Raises:
909 CommandTimeoutError on timeout. 951 CommandTimeoutError on timeout.
910 DeviceUnreachableError on missing device. 952 DeviceUnreachableError on missing device.
911 """ 953 """
912 # Check that the package exists before clearing it for android builds below 954 # Check that the package exists before clearing it for android builds below
913 # JB MR2. Necessary because calling pm clear on a package that doesn't exist 955 # JB MR2. Necessary because calling pm clear on a package that doesn't exist
914 # may never return. 956 # may never return.
915 if ((self.build_version_sdk >= 957 if ((self.build_version_sdk >=
916 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) 958 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2)
917 or self.GetApplicationPaths(package)): 959 or self._GetApplicationPathsInternal(package)):
918 self.RunShellCommand(['pm', 'clear', package], check_return=True) 960 self.RunShellCommand(['pm', 'clear', package], check_return=True)
919 961
920 @decorators.WithTimeoutAndRetriesFromInstance() 962 @decorators.WithTimeoutAndRetriesFromInstance()
921 def SendKeyEvent(self, keycode, timeout=None, retries=None): 963 def SendKeyEvent(self, keycode, timeout=None, retries=None):
922 """Sends a keycode to the device. 964 """Sends a keycode to the device.
923 965
924 See the pylib.constants.keyevent module for suitable keycode values. 966 See the pylib.constants.keyevent module for suitable keycode values.
925 967
926 Args: 968 Args:
927 keycode: A integer keycode to send to the device. 969 keycode: A integer keycode to send to the device.
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after
1031 to_push = [] 1073 to_push = []
1032 for host_abs_path, host_checksum in host_checksums.iteritems(): 1074 for host_abs_path, host_checksum in host_checksums.iteritems():
1033 device_abs_path = '%s/%s' % ( 1075 device_abs_path = '%s/%s' % (
1034 real_device_path, os.path.relpath(host_abs_path, real_host_path)) 1076 real_device_path, os.path.relpath(host_abs_path, real_host_path))
1035 device_checksum = device_checksums.pop(device_abs_path, None) 1077 device_checksum = device_checksums.pop(device_abs_path, None)
1036 if device_checksum != host_checksum: 1078 if device_checksum != host_checksum:
1037 to_push.append((host_abs_path, device_abs_path)) 1079 to_push.append((host_abs_path, device_abs_path))
1038 to_delete = device_checksums.keys() 1080 to_delete = device_checksums.keys()
1039 return (to_push, to_delete) 1081 return (to_push, to_delete)
1040 1082
1083 def _ComputeDeviceChecksumsForApks(self, package_name):
1084 ret = self._cache['package_apk_checksums'].get(package_name)
1085 if ret is None:
jbudorick 2015/08/11 15:21:04 Does this work in the Uninstall case if you've set
agrieve 2015/08/11 15:26:08 Yes. set() is None == False
jbudorick 2015/08/11 15:29:53 er... I'm pretty sure that's wrong. None is the si
1086 device_paths = self._GetApplicationPathsInternal(package_name)
1087 file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self)
1088 ret = set(file_to_checksums.values())
1089 self._cache['package_apk_checksums'][package_name] = ret
1090 return ret
1091
1092 def _ComputeStaleApks(self, package_name, host_apk_paths):
1093 host_checksums = md5sum.CalculateHostMd5Sums(host_apk_paths)
1094 device_checksums = self._ComputeDeviceChecksumsForApks(package_name)
1095 stale_apks = [k for (k, v) in host_checksums.iteritems()
1096 if v not in device_checksums]
1097 return stale_apks, set(host_checksums.values())
1098
1041 def _PushFilesImpl(self, host_device_tuples, files): 1099 def _PushFilesImpl(self, host_device_tuples, files):
1042 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) 1100 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files)
1043 file_count = len(files) 1101 file_count = len(files)
1044 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) 1102 dir_size = sum(host_utils.GetRecursiveDiskUsage(h)
1045 for h, _ in host_device_tuples) 1103 for h, _ in host_device_tuples)
1046 dir_file_count = 0 1104 dir_file_count = 0
1047 for h, _ in host_device_tuples: 1105 for h, _ in host_device_tuples:
1048 if os.path.isdir(h): 1106 if os.path.isdir(h):
1049 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) 1107 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h))
1050 else: 1108 else:
(...skipping 715 matching lines...) Expand 10 before | Expand all | Expand 10 after
1766 def GetClientCache(self, client_name): 1824 def GetClientCache(self, client_name):
1767 """Returns client cache.""" 1825 """Returns client cache."""
1768 if client_name not in self._client_caches: 1826 if client_name not in self._client_caches:
1769 self._client_caches[client_name] = {} 1827 self._client_caches[client_name] = {}
1770 return self._client_caches[client_name] 1828 return self._client_caches[client_name]
1771 1829
1772 def _ClearCache(self): 1830 def _ClearCache(self):
1773 """Clears all caches.""" 1831 """Clears all caches."""
1774 for client in self._client_caches: 1832 for client in self._client_caches:
1775 self._client_caches[client].clear() 1833 self._client_caches[client].clear()
1776 self._cache.clear() 1834 self._cache = {
1835 # Map of packageId -> list of on-device .apk paths
1836 'package_apk_paths': {},
1837 # Map of packageId -> set of on-device .apk checksums
1838 'package_apk_checksums': {},
1839 }
jbudorick 2015/08/11 15:21:04 nit: don't indent the closing brace
agrieve 2015/08/11 15:26:08 Done.
1777 1840
1778 @classmethod 1841 @classmethod
1779 def parallel(cls, devices=None, async=False): 1842 def parallel(cls, devices=None, async=False):
1780 """Creates a Parallelizer to operate over the provided list of devices. 1843 """Creates a Parallelizer to operate over the provided list of devices.
1781 1844
1782 If |devices| is either |None| or an empty list, the Parallelizer will 1845 If |devices| is either |None| or an empty list, the Parallelizer will
1783 operate over all attached devices that have not been blacklisted. 1846 operate over all attached devices that have not been blacklisted.
1784 1847
1785 Args: 1848 Args:
1786 devices: A list of either DeviceUtils instances or objects from 1849 devices: A list of either DeviceUtils instances or objects from
(...skipping 28 matching lines...) Expand all
1815 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() 1878 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices()
1816 if not blacklisted(adb)] 1879 if not blacklisted(adb)]
1817 1880
1818 @decorators.WithTimeoutAndRetriesFromInstance() 1881 @decorators.WithTimeoutAndRetriesFromInstance()
1819 def RestartAdbd(self, timeout=None, retries=None): 1882 def RestartAdbd(self, timeout=None, retries=None):
1820 logging.info('Restarting adbd on device.') 1883 logging.info('Restarting adbd on device.')
1821 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: 1884 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
1822 self.WriteFile(script.name, _RESTART_ADBD_SCRIPT) 1885 self.WriteFile(script.name, _RESTART_ADBD_SCRIPT)
1823 self.RunShellCommand(['source', script.name], as_root=True) 1886 self.RunShellCommand(['source', script.name], as_root=True)
1824 self.adb.WaitForDevice() 1887 self.adb.WaitForDevice()
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