#! /usr/bin/env python # Copyright 2017 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import collections import os import unittest from devil import devil_env from devil.android import apk_helper from devil.android.ndk import abis from devil.utils import mock_calls with devil_env.SysPath(devil_env.PYMOCK_PATH): import mock # pylint: disable=import-error # pylint: disable=line-too-long _MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android E: manifest (line=1) A: android:versionCode(0x0101021b)=(type 0x10)0x166de1ea A: android:versionName(0x0101021c)="75.0.3763.0" (Raw: "75.0.3763.0") A: package="org.chromium.abc" (Raw: "org.chromium.abc") A: split="random_split" (Raw: "random_split") E: uses-sdk (line=2) A: android:minSdkVersion(0x0101020c)=(type 0x10)0x15 A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1c E: uses-permission (line=2) A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET") E: uses-permission (line=3) A: android:name(0x01010003)="android.permission.READ_EXTERNAL_STORAGE" (Raw: "android.permission.READ_EXTERNAL_STORAGE") E: uses-permission (line=4) A: android:name(0x01010003)="android.permission.ACCESS_FINE_LOCATION" (Raw: "android.permission.ACCESS_FINE_LOCATION") E: application (line=5) E: activity (line=6) A: android:name(0x01010003)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName") A: android:exported(0x01010010)=(type 0x12)0xffffffff E: service (line=7) A: android:name(0x01010001)="org.chromium.RandomService" (Raw: "org.chromium.RandomService") A: android:isolatedProcess(0x01010888)=(type 0x12)0xffffffff E: activity (line=173) A: android:name(0x01010003)=".MainActivity" (Raw: ".MainActivity") E: intent-filter (line=177) E: action (line=178) A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN") E: category (line=180) A: android:name(0x01010003)="android.intent.category.DEFAULT" (Raw: "android.intent.category.DEFAULT") E: category (line=181) A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER") E: activity-alias (line=173) A: android:name(0x01010003)="org.chromium.ViewActivity" (Raw: "org.chromium.ViewActivity") A: android:targetActivity(0x01010202)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName") E: intent-filter (line=191) E: action (line=192) A: android:name(0x01010003)="android.intent.action.VIEW" (Raw: "android.intent.action.VIEW") E: data (line=198) A: android:scheme(0x01010027)="http" (Raw: "http") E: data (line=199) A: android:scheme(0x01010027)="https" (Raw: "https") E: meta-data (line=43) A: android:name(0x01010003)="name1" (Raw: "name1") A: android:value(0x01010024)="value1" (Raw: "value1") E: meta-data (line=43) A: android:name(0x01010003)="name2" (Raw: "name2") A: android:value(0x01010024)="value2" (Raw: "value2") E: instrumentation (line=8) A: android:label(0x01010001)="abc" (Raw: "abc") A: android:name(0x01010003)="org.chromium.RandomJUnit4TestRunner" (Raw: "org.chromium.RandomJUnit4TestRunner") A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge") A: junit4=(type 0x12)0xffffffff (Raw: "true") E: instrumentation (line=9) A: android:label(0x01010001)="abc" (Raw: "abc") A: android:name(0x01010003)="org.chromium.RandomTestRunner" (Raw: "org.chromium.RandomTestRunner") A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge") """ _NO_ISOLATED_SERVICES = """N: android=http://schemas.android.com/apk/res/android E: manifest (line=1) A: package="org.chromium.abc" (Raw: "org.chromium.abc") E: application (line=5) E: activity (line=6) A: android:name(0x01010003)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName") A: android:exported(0x01010010)=(type 0x12)0xffffffff E: service (line=7) A: android:name(0x01010001)="org.chromium.RandomService" (Raw: "org.chromium.RandomService") """ _NO_SERVICES = """N: android=http://schemas.android.com/apk/res/android E: manifest (line=1) A: package="org.chromium.abc" (Raw: "org.chromium.abc") E: application (line=5) E: activity (line=6) A: android:name(0x01010003)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName") A: android:exported(0x01010010)=(type 0x12)0xffffffff """ _NO_APPLICATION = """N: android=http://schemas.android.com/apk/res/android E: manifest (line=1) A: package="org.chromium.abc" (Raw: "org.chromium.abc") """ _SINGLE_INSTRUMENTATION_MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android E: manifest (line=1) A: package="org.chromium.xyz" (Raw: "org.chromium.xyz") E: instrumentation (line=8) A: android:label(0x01010001)="xyz" (Raw: "xyz") A: android:name(0x01010003)="org.chromium.RandomTestRunner" (Raw: "org.chromium.RandomTestRunner") A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge") """ _SINGLE_J4_INSTRUMENTATION_MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android E: manifest (line=1) A: package="org.chromium.xyz" (Raw: "org.chromium.xyz") E: instrumentation (line=8) A: android:label(0x01010001)="xyz" (Raw: "xyz") A: android:name(0x01010003)="org.chromium.RandomJ4TestRunner" (Raw: "org.chromium.RandomJ4TestRunner") A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge") A: junit4=(type 0x12)0xffffffff (Raw: "true") """ _TARGETING_PRE_RELEASE_Q_MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android E: manifest (line=1) A: package="org.chromium.xyz" (Raw: "org.chromium.xyz") E: uses-sdk (line=2) A: android:minSdkVersion(0x0101020c)="Q" (Raw: "Q") A: android:targetSdkVersion(0x01010270)="Q" (Raw: "Q") """ _NO_NAMESPACE_MANIFEST_DUMP = """E: manifest (line=1) A: package="org.chromium.xyz" (Raw: "org.chromium.xyz") E: instrumentation (line=8) A: http://schemas.android.com/apk/res/android:label(0x01010001)="xyz" (Raw: "xyz") A: http://schemas.android.com/apk/res/android:name(0x01010003)="org.chromium.RandomTestRunner" (Raw: "org.chromium.RandomTestRunner") A: http://schemas.android.com/apk/res/android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge") """ # pylint: enable=line-too-long def _MockAaptDump(manifest_dump): return mock.patch( 'devil.android.sdk.aapt.Dump', mock.Mock(side_effect=None, return_value=manifest_dump.split('\n'))) def _MockListApkPaths(files): return mock.patch('devil.android.apk_helper.ApkHelper._ListApkPaths', mock.Mock(side_effect=None, return_value=files)) class _MockDeviceUtils(object): def __init__(self): self.product_cpu_abi = abis.ARM_64 self.product_cpu_abis = [abis.ARM_64, abis.ARM] self.pixel_density = 500 self.build_version_sdk = 28 def GetLocale(self): # pylint: disable=no-self-use return ('en', 'US') def GetFeatures(self): # pylint: disable=no-self-use return [ 'android.hardware.wifi', 'android.hardware.nfc', ] class ApkHelperTest(mock_calls.TestCase): def testToHelperApk(self): apk = apk_helper.ToHelper('abc.apk') self.assertTrue(isinstance(apk, apk_helper.ApkHelper)) def testToHelperApks(self): apk = apk_helper.ToHelper('abc.apks') self.assertTrue(isinstance(apk, apk_helper.ApksHelper)) def testToHelperBundleScript(self): apk = apk_helper.ToHelper('abc_bundle') self.assertTrue(isinstance(apk, apk_helper.BundleScriptHelper)) def testToHelperSplitApk(self): apk = apk_helper.ToSplitHelper('abc.apk', ['a.apk', 'b.apk']) self.assertTrue(isinstance(apk, apk_helper.SplitApkHelper)) def testToHelperSplitException(self): with self.assertRaises(apk_helper.ApkHelperError): apk_helper.ToSplitHelper( apk_helper.ToHelper('abc.apk'), ['a.apk', 'b.apk']) def testGetInstrumentationName(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') with self.assertRaises(apk_helper.ApkHelperError): helper.GetInstrumentationName() def testGetActivityName(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals(helper.GetActivityName(), 'org.chromium.abc.MainActivity') def testGetViewActivityName(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals(helper.GetViewActivityName(), 'org.chromium.ViewActivity') def testGetAllInstrumentations(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') all_instrumentations = helper.GetAllInstrumentations() self.assertEquals(len(all_instrumentations), 2) self.assertEquals(all_instrumentations[0]['android:name'], 'org.chromium.RandomJUnit4TestRunner') self.assertEquals(all_instrumentations[1]['android:name'], 'org.chromium.RandomTestRunner') def testGetPackageName(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals(helper.GetPackageName(), 'org.chromium.abc') def testGetPermssions(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') all_permissions = helper.GetPermissions() self.assertEquals(len(all_permissions), 3) self.assertTrue('android.permission.INTERNET' in all_permissions) self.assertTrue( 'android.permission.READ_EXTERNAL_STORAGE' in all_permissions) self.assertTrue( 'android.permission.ACCESS_FINE_LOCATION' in all_permissions) def testGetSplitName(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals(helper.GetSplitName(), 'random_split') def testHasIsolatedProcesses_noApplication(self): with _MockAaptDump(_NO_APPLICATION): helper = apk_helper.ApkHelper('') self.assertFalse(helper.HasIsolatedProcesses()) def testHasIsolatedProcesses_noServices(self): with _MockAaptDump(_NO_SERVICES): helper = apk_helper.ApkHelper('') self.assertFalse(helper.HasIsolatedProcesses()) def testHasIsolatedProcesses_oneNotIsolatedProcess(self): with _MockAaptDump(_NO_ISOLATED_SERVICES): helper = apk_helper.ApkHelper('') self.assertFalse(helper.HasIsolatedProcesses()) def testHasIsolatedProcesses_oneIsolatedProcess(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertTrue(helper.HasIsolatedProcesses()) def testGetSingleInstrumentationName(self): with _MockAaptDump(_SINGLE_INSTRUMENTATION_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals('org.chromium.RandomTestRunner', helper.GetInstrumentationName()) def testGetSingleJUnit4InstrumentationName(self): with _MockAaptDump(_SINGLE_J4_INSTRUMENTATION_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals('org.chromium.RandomJ4TestRunner', helper.GetInstrumentationName()) def testGetAllMetadata(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals([('name1', 'value1'), ('name2', 'value2')], helper.GetAllMetadata()) def testGetVersionCode(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals(376300010, helper.GetVersionCode()) def testGetVersionName(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals('75.0.3763.0', helper.GetVersionName()) def testGetMinSdkVersion_integerValue(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals('21', helper.GetMinSdkVersion()) def testGetMinSdkVersion_stringValue(self): with _MockAaptDump(_TARGETING_PRE_RELEASE_Q_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals('Q', helper.GetMinSdkVersion()) def testGetTargetSdkVersion_integerValue(self): with _MockAaptDump(_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals('28', helper.GetTargetSdkVersion()) def testGetTargetSdkVersion_stringValue(self): with _MockAaptDump(_TARGETING_PRE_RELEASE_Q_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals('Q', helper.GetTargetSdkVersion()) def testGetSingleInstrumentationName_strippedNamespaces(self): with _MockAaptDump(_NO_NAMESPACE_MANIFEST_DUMP): helper = apk_helper.ApkHelper('') self.assertEquals('org.chromium.RandomTestRunner', helper.GetInstrumentationName()) def testGetArchitectures(self): AbiPair = collections.namedtuple('AbiPair', ['abi32bit', 'abi64bit']) for abi_pair in [ AbiPair('lib/' + abis.ARM, 'lib/' + abis.ARM_64), AbiPair('lib/' + abis.X86, 'lib/' + abis.X86_64) ]: with _MockListApkPaths([abi_pair.abi32bit]): helper = apk_helper.ApkHelper('') self.assertEquals( set([ os.path.basename(abi_pair.abi32bit), os.path.basename(abi_pair.abi64bit) ]), set(helper.GetAbis())) with _MockListApkPaths([abi_pair.abi32bit, abi_pair.abi64bit]): helper = apk_helper.ApkHelper('') self.assertEquals( set([ os.path.basename(abi_pair.abi32bit), os.path.basename(abi_pair.abi64bit) ]), set(helper.GetAbis())) with _MockListApkPaths([abi_pair.abi64bit]): helper = apk_helper.ApkHelper('') self.assertEquals( set([os.path.basename(abi_pair.abi64bit)]), set(helper.GetAbis())) def testGetSplitsApk(self): apk = apk_helper.ToHelper('abc.apk') with apk.GetApkPaths(_MockDeviceUtils()) as apk_paths: self.assertEquals(apk_paths, ['abc.apk']) def testGetSplitsApkModulesException(self): apk = apk_helper.ToHelper('abc.apk') with self.assertRaises(apk_helper.ApkHelperError): apk.GetApkPaths(None, modules=['a']) def testGetSplitsApks(self): apk = apk_helper.ToHelper('abc.apks') with self.assertCalls( (mock.call.tempfile.mkdtemp(), '/tmp'), (mock.call.devil.android.sdk.bundletool.ExtractApks( '/tmp', 'abc.apks', ['arm64-v8a', 'armeabi-v7a'], [('en', 'US')], ['android.hardware.wifi', 'android.hardware.nfc'], 500, 28, None)), (mock.call.os.listdir('/tmp'), ['base-master.apk', 'foo-master.apk']), (mock.call.shutil.rmtree('/tmp')), ),\ apk.GetApkPaths(_MockDeviceUtils()) as apk_paths: self.assertEquals(apk_paths, ['/tmp/base-master.apk', '/tmp/foo-master.apk']) def testGetSplitsApksWithModules(self): apk = apk_helper.ToHelper('abc.apks') with self.assertCalls( (mock.call.tempfile.mkdtemp(), '/tmp'), (mock.call.devil.android.sdk.bundletool.ExtractApks( '/tmp', 'abc.apks', ['arm64-v8a', 'armeabi-v7a'], [('en', 'US')], ['android.hardware.wifi', 'android.hardware.nfc'], 500, 28, ['bar'])), (mock.call.os.listdir('/tmp'), ['base-master.apk', 'foo-master.apk', 'bar-master.apk']), (mock.call.shutil.rmtree('/tmp')), ),\ apk.GetApkPaths(_MockDeviceUtils(), ['bar']) as apk_paths: self.assertEquals(apk_paths, [ '/tmp/base-master.apk', '/tmp/foo-master.apk', '/tmp/bar-master.apk' ]) def testGetSplitsApksWithAdditionalLocales(self): apk = apk_helper.ToHelper('abc.apks') with self.assertCalls( (mock.call.tempfile.mkdtemp(), '/tmp'), (mock.call.devil.android.sdk.bundletool.ExtractApks( '/tmp', 'abc.apks', ['arm64-v8a', 'armeabi-v7a'], [('en', 'US'), ('es', 'ES'), ('fr', 'CA')], ['android.hardware.wifi', 'android.hardware.nfc'], 500, 28, None)), (mock.call.os.listdir('/tmp'), ['base-master.apk', 'base-es.apk', 'base-fr.apk']), (mock.call.shutil.rmtree('/tmp')), ),\ apk.GetApkPaths(_MockDeviceUtils(), additional_locales=['es-ES', 'fr-CA']) as apk_paths: self.assertEquals( apk_paths, ['/tmp/base-master.apk', '/tmp/base-es.apk', '/tmp/base-fr.apk']) def testGetSplitsApksWithAdditionalLocalesIncorrectFormat(self): apk = apk_helper.ToHelper('abc.apks') with self.assertRaises(apk_helper.ApkHelperError): apk.GetApkPaths(_MockDeviceUtils(), additional_locales=['es']) def testGetSplitsSplitApk(self): apk = apk_helper.ToSplitHelper('base.apk', ['split1.apk', 'split2.apk', 'split3.apk']) device = _MockDeviceUtils() with self.assertCalls( (mock.call.devil.android.sdk.split_select.SelectSplits( device, 'base.apk', ['split1.apk', 'split2.apk', 'split3.apk'], allow_cached_props=False), ['split2.apk'])),\ apk.GetApkPaths(device) as apk_paths: self.assertEquals(apk_paths, ['base.apk', 'split2.apk']) def testGetSplitsBundleScript(self): apk = apk_helper.ToHelper('abc_bundle') device = _MockDeviceUtils() with self.assertCalls( (mock.call.tempfile.mkstemp(suffix='.apks'), (0, '/tmp/abc.apks')), (mock.call.devil.utils.cmd_helper.GetCmdStatusOutputAndError([ 'abc_bundle', 'build-bundle-apks', '--output-apks', '/tmp/abc.apks' ]), (0, '', '')), (mock.call.tempfile.mkdtemp(), '/tmp2'), (mock.call.devil.android.sdk.bundletool.ExtractApks( '/tmp2', '/tmp/abc.apks', ['arm64-v8a', 'armeabi-v7a'], [('en', 'US')], ['android.hardware.wifi', 'android.hardware.nfc'], 500, 28, ['bar'])), (mock.call.os.listdir('/tmp2'), ['base-master.apk', 'bar-master.apk']), (mock.call.os.path.isfile('/tmp/abc.apks'), True), (mock.call.os.remove('/tmp/abc.apks')), (mock.call.os.path.isfile('/tmp2'), False), (mock.call.os.path.isdir('/tmp2'), True), (mock.call.shutil.rmtree('/tmp2')), ),\ apk.GetApkPaths(device, modules=['bar']) as apk_paths: self.assertEquals(apk_paths, ['/tmp2/base-master.apk', '/tmp2/bar-master.apk']) if __name__ == '__main__': unittest.main(verbosity=2)