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

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: some style nits 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
528
518 if should_install: 529 if should_install:
530 # We won't know the resulting device apk names.
531 self._cache['package_apk_paths'].pop(package_name, 0)
519 self.adb.Install(apk_path, reinstall=reinstall) 532 self.adb.Install(apk_path, reinstall=reinstall)
533 self._cache['package_apk_checksums'][package_name] = host_checksums
520 534
521 @decorators.WithTimeoutAndRetriesDefaults( 535 @decorators.WithTimeoutAndRetriesDefaults(
522 INSTALL_DEFAULT_TIMEOUT, 536 INSTALL_DEFAULT_TIMEOUT,
523 INSTALL_DEFAULT_RETRIES) 537 INSTALL_DEFAULT_RETRIES)
524 def InstallSplitApk(self, base_apk, split_apks, reinstall=False, 538 def InstallSplitApk(self, base_apk, split_apks, reinstall=False,
525 timeout=None, retries=None): 539 timeout=None, retries=None):
526 """Install a split APK. 540 """Install a split APK.
527 541
528 Noop if all of the APK splits are already installed. 542 Noop if all of the APK splits are already installed.
529 543
530 Args: 544 Args:
531 base_apk: A string of the path to the base APK. 545 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. 546 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. 547 reinstall: A boolean indicating if we should keep any existing app data.
534 timeout: timeout in seconds 548 timeout: timeout in seconds
535 retries: number of retries 549 retries: number of retries
536 550
537 Raises: 551 Raises:
538 CommandFailedError if the installation fails. 552 CommandFailedError if the installation fails.
539 CommandTimeoutError if the installation times out. 553 CommandTimeoutError if the installation times out.
540 DeviceUnreachableError on missing device. 554 DeviceUnreachableError on missing device.
541 DeviceVersionError if device SDK is less than Android L. 555 DeviceVersionError if device SDK is less than Android L.
542 """ 556 """
543 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) 557 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP)
544 558
545 all_apks = [base_apk] + split_select.SelectSplits( 559 all_apks = [base_apk] + split_select.SelectSplits(
546 self, base_apk, split_apks) 560 self, base_apk, split_apks)
547 package_name = apk_helper.GetPackageName(base_apk) 561 package_name = apk_helper.GetPackageName(base_apk)
548 device_apk_paths = self.GetApplicationPaths(package_name) 562 device_apk_paths = self._GetApplicationPathsInternal(package_name)
549 563
550 if device_apk_paths: 564 if device_apk_paths:
551 partial_install_package = package_name 565 partial_install_package = package_name
552 device_checksums = md5sum.CalculateDeviceMd5Sums(device_apk_paths, self) 566 apks_to_install, host_checksums = (
553 host_checksums = md5sum.CalculateHostMd5Sums(all_apks) 567 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: 568 if apks_to_install and not reinstall:
557 self.adb.Uninstall(package_name) 569 self.Uninstall(package_name)
558 partial_install_package = None 570 partial_install_package = None
559 apks_to_install = all_apks 571 apks_to_install = all_apks
560 else: 572 else:
561 partial_install_package = None 573 partial_install_package = None
562 apks_to_install = all_apks 574 apks_to_install = all_apks
575 host_checksums = None
576
563 if apks_to_install: 577 if apks_to_install:
578 # We won't know the resulting device apk names.
579 self._cache['package_apk_paths'].pop(package_name, 0)
564 self.adb.InstallMultiple( 580 self.adb.InstallMultiple(
565 apks_to_install, partial=partial_install_package, reinstall=reinstall) 581 apks_to_install, partial=partial_install_package,
582 reinstall=reinstall)
583 self._cache['package_apk_checksums'][package_name] = host_checksums
584
585 @decorators.WithTimeoutAndRetriesFromInstance()
586 def Uninstall(self, package_name, keep_data=False, timeout=None,
587 retries=None):
588 """Remove the app |package_name| from the device.
589
590 Args:
591 package_name: The package to uninstall.
592 keep_data: (optional) Whether to keep the data and cache directories.
593 timeout: Timeout in seconds.
594 retries: Number of retries.
595
596 Raises:
597 CommandFailedError if the uninstallation fails.
598 CommandTimeoutError if the uninstallation times out.
599 DeviceUnreachableError on missing device.
600 """
601 try:
602 self.adb.Uninstall(package_name, keep_data)
603 self._cache['package_apk_paths'][package_name] = []
604 self._cache['package_apk_checksums'][package_name] = set()
605 except:
606 # Clear cache since we can't be sure of the state.
607 self._cache['package_apk_paths'].pop(package_name, 0)
608 self._cache['package_apk_checksums'].pop(package_name, 0)
609 raise
566 610
567 def _CheckSdkLevel(self, required_sdk_level): 611 def _CheckSdkLevel(self, required_sdk_level):
568 """Raises an exception if the device does not have the required SDK level. 612 """Raises an exception if the device does not have the required SDK level.
569 """ 613 """
570 if self.build_version_sdk < required_sdk_level: 614 if self.build_version_sdk < required_sdk_level:
571 raise device_errors.DeviceVersionError( 615 raise device_errors.DeviceVersionError(
572 ('Requires SDK level %s, device is SDK level %s' % 616 ('Requires SDK level %s, device is SDK level %s' %
573 (required_sdk_level, self.build_version_sdk)), 617 (required_sdk_level, self.build_version_sdk)),
574 device_serial=self.adb.GetDeviceSerial()) 618 device_serial=self.adb.GetDeviceSerial())
575 619
(...skipping 331 matching lines...) Expand 10 before | Expand all | Expand 10 after
907 951
908 Raises: 952 Raises:
909 CommandTimeoutError on timeout. 953 CommandTimeoutError on timeout.
910 DeviceUnreachableError on missing device. 954 DeviceUnreachableError on missing device.
911 """ 955 """
912 # Check that the package exists before clearing it for android builds below 956 # 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 957 # JB MR2. Necessary because calling pm clear on a package that doesn't exist
914 # may never return. 958 # may never return.
915 if ((self.build_version_sdk >= 959 if ((self.build_version_sdk >=
916 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) 960 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2)
917 or self.GetApplicationPaths(package)): 961 or self._GetApplicationPathsInternal(package)):
918 self.RunShellCommand(['pm', 'clear', package], check_return=True) 962 self.RunShellCommand(['pm', 'clear', package], check_return=True)
919 963
920 @decorators.WithTimeoutAndRetriesFromInstance() 964 @decorators.WithTimeoutAndRetriesFromInstance()
921 def SendKeyEvent(self, keycode, timeout=None, retries=None): 965 def SendKeyEvent(self, keycode, timeout=None, retries=None):
922 """Sends a keycode to the device. 966 """Sends a keycode to the device.
923 967
924 See the pylib.constants.keyevent module for suitable keycode values. 968 See the pylib.constants.keyevent module for suitable keycode values.
925 969
926 Args: 970 Args:
927 keycode: A integer keycode to send to the device. 971 keycode: A integer keycode to send to the device.
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after
1031 to_push = [] 1075 to_push = []
1032 for host_abs_path, host_checksum in host_checksums.iteritems(): 1076 for host_abs_path, host_checksum in host_checksums.iteritems():
1033 device_abs_path = '%s/%s' % ( 1077 device_abs_path = '%s/%s' % (
1034 real_device_path, os.path.relpath(host_abs_path, real_host_path)) 1078 real_device_path, os.path.relpath(host_abs_path, real_host_path))
1035 device_checksum = device_checksums.pop(device_abs_path, None) 1079 device_checksum = device_checksums.pop(device_abs_path, None)
1036 if device_checksum != host_checksum: 1080 if device_checksum != host_checksum:
1037 to_push.append((host_abs_path, device_abs_path)) 1081 to_push.append((host_abs_path, device_abs_path))
1038 to_delete = device_checksums.keys() 1082 to_delete = device_checksums.keys()
1039 return (to_push, to_delete) 1083 return (to_push, to_delete)
1040 1084
1085 def _ComputeDeviceChecksumsForApks(self, package_name):
1086 ret = self._cache['package_apk_checksums'].get(package_name)
1087 if ret is None:
1088 device_paths = self._GetApplicationPathsInternal(package_name)
1089 file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self)
1090 ret = set(file_to_checksums.values())
1091 self._cache['package_apk_checksums'][package_name] = ret
1092 return ret
1093
1094 def _ComputeStaleApks(self, package_name, host_apk_paths):
1095 host_checksums = md5sum.CalculateHostMd5Sums(host_apk_paths)
1096 device_checksums = self._ComputeDeviceChecksumsForApks(package_name)
1097 stale_apks = [k for (k, v) in host_checksums.iteritems()
1098 if v not in device_checksums]
1099 return stale_apks, set(host_checksums.values())
1100
1041 def _PushFilesImpl(self, host_device_tuples, files): 1101 def _PushFilesImpl(self, host_device_tuples, files):
1042 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) 1102 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files)
1043 file_count = len(files) 1103 file_count = len(files)
1044 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) 1104 dir_size = sum(host_utils.GetRecursiveDiskUsage(h)
1045 for h, _ in host_device_tuples) 1105 for h, _ in host_device_tuples)
1046 dir_file_count = 0 1106 dir_file_count = 0
1047 for h, _ in host_device_tuples: 1107 for h, _ in host_device_tuples:
1048 if os.path.isdir(h): 1108 if os.path.isdir(h):
1049 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) 1109 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h))
1050 else: 1110 else:
(...skipping 715 matching lines...) Expand 10 before | Expand all | Expand 10 after
1766 def GetClientCache(self, client_name): 1826 def GetClientCache(self, client_name):
1767 """Returns client cache.""" 1827 """Returns client cache."""
1768 if client_name not in self._client_caches: 1828 if client_name not in self._client_caches:
1769 self._client_caches[client_name] = {} 1829 self._client_caches[client_name] = {}
1770 return self._client_caches[client_name] 1830 return self._client_caches[client_name]
1771 1831
1772 def _ClearCache(self): 1832 def _ClearCache(self):
1773 """Clears all caches.""" 1833 """Clears all caches."""
1774 for client in self._client_caches: 1834 for client in self._client_caches:
1775 self._client_caches[client].clear() 1835 self._client_caches[client].clear()
1776 self._cache.clear() 1836 self._cache = {
1837 # Map of packageId -> list of on-device .apk paths
1838 'package_apk_paths': {},
1839 # Map of packageId -> set of on-device .apk checksums
1840 'package_apk_checksums': {},
1841 }
1777 1842
1778 @classmethod 1843 @classmethod
1779 def parallel(cls, devices=None, async=False): 1844 def parallel(cls, devices=None, async=False):
1780 """Creates a Parallelizer to operate over the provided list of devices. 1845 """Creates a Parallelizer to operate over the provided list of devices.
1781 1846
1782 If |devices| is either |None| or an empty list, the Parallelizer will 1847 If |devices| is either |None| or an empty list, the Parallelizer will
1783 operate over all attached devices that have not been blacklisted. 1848 operate over all attached devices that have not been blacklisted.
1784 1849
1785 Args: 1850 Args:
1786 devices: A list of either DeviceUtils instances or objects from 1851 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() 1880 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices()
1816 if not blacklisted(adb)] 1881 if not blacklisted(adb)]
1817 1882
1818 @decorators.WithTimeoutAndRetriesFromInstance() 1883 @decorators.WithTimeoutAndRetriesFromInstance()
1819 def RestartAdbd(self, timeout=None, retries=None): 1884 def RestartAdbd(self, timeout=None, retries=None):
1820 logging.info('Restarting adbd on device.') 1885 logging.info('Restarting adbd on device.')
1821 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: 1886 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
1822 self.WriteFile(script.name, _RESTART_ADBD_SCRIPT) 1887 self.WriteFile(script.name, _RESTART_ADBD_SCRIPT)
1823 self.RunShellCommand(['source', script.name], as_root=True) 1888 self.RunShellCommand(['source', script.name], as_root=True)
1824 self.adb.WaitForDevice() 1889 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