| #!/usr/bin/env vpython3 |
| # Copyright 2018 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import unittest |
| |
| import list_class_verification_failures as list_verification |
| |
| import devil_chromium # pylint: disable=unused-import |
| from devil.android import device_errors |
| from devil.android import device_utils |
| from devil.android.ndk import abis |
| from devil.android.sdk import version_codes |
| |
| import mock # pylint: disable=import-error |
| |
| |
| def _CreateOdexLine(java_class_name, type_idx, verification_status): |
| """Create a rough approximation of a line of oatdump output.""" |
| return ('{type_idx}: L{java_class}; (offset=0xac) (type_idx={type_idx}) ' |
| '({verification}) ' |
| '(OatClassNoneCompiled)'.format(type_idx=type_idx, |
| java_class=java_class_name, |
| verification=verification_status)) |
| |
| |
| def _ClassForName(name, classes): |
| return next(c for c in classes if c.name == name) |
| |
| |
| class _DetermineDeviceToUseTest(unittest.TestCase): |
| |
| def testDetermineDeviceToUse_emptyListWithOneAttachedDevice(self): |
| fake_attached_devices = ['123'] |
| user_specified_devices = [] |
| device_utils.DeviceUtils.HealthyDevices = mock.MagicMock( |
| return_value=fake_attached_devices) |
| result = list_verification.DetermineDeviceToUse(user_specified_devices) |
| self.assertEqual(result, fake_attached_devices[0]) |
| # pylint: disable=no-member |
| device_utils.DeviceUtils.HealthyDevices.assert_called_with(device_arg=None) |
| # pylint: enable=no-member |
| |
| def testDetermineDeviceToUse_emptyListWithNoAttachedDevices(self): |
| user_specified_devices = [] |
| device_utils.DeviceUtils.HealthyDevices = mock.MagicMock( |
| side_effect=device_errors.NoDevicesError()) |
| with self.assertRaises(device_errors.NoDevicesError) as _: |
| list_verification.DetermineDeviceToUse(user_specified_devices) |
| # pylint: disable=no-member |
| device_utils.DeviceUtils.HealthyDevices.assert_called_with(device_arg=None) |
| # pylint: enable=no-member |
| |
| def testDetermineDeviceToUse_oneElementListWithOneAttachedDevice(self): |
| user_specified_devices = ['123'] |
| fake_attached_devices = ['123'] |
| device_utils.DeviceUtils.HealthyDevices = mock.MagicMock( |
| return_value=fake_attached_devices) |
| result = list_verification.DetermineDeviceToUse(user_specified_devices) |
| self.assertEqual(result, fake_attached_devices[0]) |
| # pylint: disable=no-member |
| device_utils.DeviceUtils.HealthyDevices.assert_called_with( |
| device_arg=user_specified_devices) |
| # pylint: enable=no-member |
| |
| |
| class _ListClassVerificationFailuresTest(unittest.TestCase): |
| |
| def testPathToDexForPlatformVersion_noPaths(self): |
| sdk_int = version_codes.LOLLIPOP |
| paths_to_apk = [] |
| package_name = 'package.name' |
| arch = abis.ARM_64 |
| |
| device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) |
| device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) |
| |
| with self.assertRaises(list_verification.DeviceOSError) as cm: |
| list_verification.FindOdexFiles(device, package_name) |
| message = str(cm.exception) |
| self.assertIn('Could not find data directory', message) |
| |
| def testPathToDexForPlatformVersion_multiplePaths(self): |
| sdk_int = version_codes.LOLLIPOP |
| paths_to_apk = ['/first/path', '/second/path'] |
| package_name = 'package.name' |
| arch = abis.ARM_64 |
| |
| device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) |
| device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) |
| |
| odex_files = list_verification.FindOdexFiles(device, package_name) |
| self.assertEqual(odex_files, [ |
| '/data/dalvik-cache/arm64/data@app@first@base.apk@classes.dex', |
| '/data/dalvik-cache/arm64/data@app@second@base.apk@classes.dex' |
| ]) |
| |
| def testPathToDexForPlatformVersion_dalvikApiLevel(self): |
| sdk_int = version_codes.KITKAT |
| paths_to_apk = ['/some/path'] |
| package_name = 'package.name' |
| arch = abis.ARM_64 |
| |
| device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) |
| device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) |
| |
| with self.assertRaises(list_verification.UnsupportedDeviceError) as _: |
| list_verification.FindOdexFiles(device, package_name) |
| |
| def testPathToDexForPlatformVersion_lollipopArm(self): |
| sdk_int = version_codes.LOLLIPOP |
| package_name = 'package.name' |
| paths_to_apk = ['/some/path/{}-1/base.apk'.format(package_name)] |
| arch = 'arm' |
| |
| device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) |
| device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) |
| device.FileExists = mock.MagicMock(return_value=True) |
| |
| odex_files = list_verification.FindOdexFiles(device, package_name) |
| self.assertEqual( |
| odex_files, |
| ['/data/dalvik-cache/arm/data@app@package.name-1@base.apk@classes.dex']) |
| |
| def testPathToDexForPlatformVersion_mashmallowArm(self): |
| sdk_int = version_codes.MARSHMALLOW |
| package_name = 'package.name' |
| paths_to_apk = ['/some/path/{}-1/base.apk'.format(package_name)] |
| arch = 'arm' |
| |
| device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) |
| device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) |
| device.FileExists = mock.MagicMock(return_value=True) |
| |
| odex_files = list_verification.FindOdexFiles(device, package_name) |
| self.assertEqual(odex_files, |
| ['/some/path/package.name-1/oat/arm/base.odex']) |
| |
| def testPathToDexForPlatformVersion_mashmallowArm64(self): |
| sdk_int = version_codes.MARSHMALLOW |
| package_name = 'package.name' |
| paths_to_apk = ['/some/path/{}-1/base.apk'.format(package_name)] |
| arch = abis.ARM_64 |
| |
| device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) |
| device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) |
| device.FileExists = mock.MagicMock(return_value=True) |
| |
| odex_files = list_verification.FindOdexFiles(device, package_name) |
| self.assertEqual(odex_files, |
| ['/some/path/package.name-1/oat/arm64/base.odex']) |
| |
| def testPathToDexForPlatformVersion_pieNoOdexFile(self): |
| sdk_int = version_codes.PIE |
| package_name = 'package.name' |
| paths_to_apk = ['/some/path/{}-1/base.apk'.format(package_name)] |
| arch = abis.ARM_64 |
| |
| device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) |
| device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) |
| device.FileExists = mock.MagicMock(return_value=False) |
| |
| with self.assertRaises(list_verification.DeviceOSError) as cm: |
| list_verification.FindOdexFiles(device, package_name) |
| message = str(cm.exception) |
| self.assertIn('you must run dex2oat on debuggable apps on >= P', message) |
| |
| def testPathToDexForPlatformVersion_lowerApiLevelNoOdexFile(self): |
| sdk_int = version_codes.MARSHMALLOW |
| package_name = 'package.name' |
| paths_to_apk = ['/some/path/{}-1/base.apk'.format(package_name)] |
| arch = abis.ARM_64 |
| |
| device = mock.Mock(build_version_sdk=sdk_int, product_cpu_abi=arch) |
| device.GetApplicationPaths = mock.MagicMock(return_value=paths_to_apk) |
| device.FileExists = mock.MagicMock(return_value=False) |
| |
| with self.assertRaises(list_verification.DeviceOSError) as _: |
| list_verification.FindOdexFiles(device, package_name) |
| |
| def testListClasses_noProguardMap(self): |
| oatdump_output = [ |
| _CreateOdexLine('a.b.JavaClass1', 6, 'StatusVerified'), |
| _CreateOdexLine('a.b.JavaClass2', 7, |
| 'StatusRetryVerificationAtRuntime'), |
| ] |
| |
| classes = list_verification.ParseOatdump(oatdump_output, None) |
| self.assertEqual(2, len(classes)) |
| java_class_1 = _ClassForName('a.b.JavaClass1', classes) |
| java_class_2 = _ClassForName('a.b.JavaClass2', classes) |
| self.assertEqual(java_class_1.verification_status, 'Verified') |
| self.assertEqual(java_class_2.verification_status, |
| 'RetryVerificationAtRuntime') |
| |
| def testListClasses_proguardMap(self): |
| oatdump_output = [ |
| _CreateOdexLine('a.b.ObfuscatedJavaClass1', 6, 'StatusVerified'), |
| _CreateOdexLine('a.b.ObfuscatedJavaClass2', 7, |
| 'StatusRetryVerificationAtRuntime'), |
| ] |
| |
| mapping = { |
| 'a.b.ObfuscatedJavaClass1': 'a.b.JavaClass1', |
| 'a.b.ObfuscatedJavaClass2': 'a.b.JavaClass2', |
| } |
| classes = list_verification.ParseOatdump(oatdump_output, mapping) |
| self.assertEqual(2, len(classes)) |
| java_class_1 = _ClassForName('a.b.JavaClass1', classes) |
| java_class_2 = _ClassForName('a.b.JavaClass2', classes) |
| self.assertEqual(java_class_1.verification_status, 'Verified') |
| self.assertEqual(java_class_2.verification_status, |
| 'RetryVerificationAtRuntime') |
| |
| def testListClasses_noStatusPrefix(self): |
| oatdump_output = [ |
| _CreateOdexLine('a.b.JavaClass1', 6, 'Verified'), |
| _CreateOdexLine('a.b.JavaClass2', 7, 'RetryVerificationAtRuntime'), |
| ] |
| |
| classes = list_verification.ParseOatdump(oatdump_output, None) |
| self.assertEqual(2, len(classes)) |
| java_class_1 = _ClassForName('a.b.JavaClass1', classes) |
| java_class_2 = _ClassForName('a.b.JavaClass2', classes) |
| self.assertEqual(java_class_1.verification_status, 'Verified') |
| self.assertEqual(java_class_2.verification_status, |
| 'RetryVerificationAtRuntime') |
| |
| if __name__ == '__main__': |
| # Suppress logging messages. |
| unittest.main(buffer=True) |