xref: /aosp_15_r20/external/chromium-trace/catapult/devil/devil/android/apk_helper_test.py (revision 1fa4b3da657c0e9ad43c0220bacf9731820715a5)
1#! /usr/bin/env python
2# Copyright 2017 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import collections
7import os
8import unittest
9
10from devil import devil_env
11from devil.android import apk_helper
12from devil.android.ndk import abis
13from devil.utils import mock_calls
14
15with devil_env.SysPath(devil_env.PYMOCK_PATH):
16  import mock  # pylint: disable=import-error
17
18# pylint: disable=line-too-long
19_MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android
20  E: manifest (line=1)
21    A: android:versionCode(0x0101021b)=(type 0x10)0x166de1ea
22    A: android:versionName(0x0101021c)="75.0.3763.0" (Raw: "75.0.3763.0")
23    A: package="org.chromium.abc" (Raw: "org.chromium.abc")
24    A: split="random_split" (Raw: "random_split")
25    E: uses-sdk (line=2)
26      A: android:minSdkVersion(0x0101020c)=(type 0x10)0x15
27      A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1c
28    E: uses-permission (line=2)
29      A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
30    E: uses-permission (line=3)
31      A: android:name(0x01010003)="android.permission.READ_EXTERNAL_STORAGE" (Raw: "android.permission.READ_EXTERNAL_STORAGE")
32    E: uses-permission (line=4)
33      A: android:name(0x01010003)="android.permission.ACCESS_FINE_LOCATION" (Raw: "android.permission.ACCESS_FINE_LOCATION")
34    E: application (line=5)
35      E: activity (line=6)
36        A: android:name(0x01010003)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName")
37        A: android:exported(0x01010010)=(type 0x12)0xffffffff
38      E: service (line=7)
39        A: android:name(0x01010001)="org.chromium.RandomService" (Raw: "org.chromium.RandomService")
40        A: android:isolatedProcess(0x01010888)=(type 0x12)0xffffffff
41      E: activity (line=173)
42        A: android:name(0x01010003)=".MainActivity" (Raw: ".MainActivity")
43        E: intent-filter (line=177)
44          E: action (line=178)
45            A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
46          E: category (line=180)
47            A: android:name(0x01010003)="android.intent.category.DEFAULT" (Raw: "android.intent.category.DEFAULT")
48          E: category (line=181)
49            A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
50      E: activity-alias (line=173)
51        A: android:name(0x01010003)="org.chromium.ViewActivity" (Raw: "org.chromium.ViewActivity")
52        A: android:targetActivity(0x01010202)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName")
53        E: intent-filter (line=191)
54          E: action (line=192)
55            A: android:name(0x01010003)="android.intent.action.VIEW" (Raw: "android.intent.action.VIEW")
56          E: data (line=198)
57            A: android:scheme(0x01010027)="http" (Raw: "http")
58          E: data (line=199)
59            A: android:scheme(0x01010027)="https" (Raw: "https")
60      E: meta-data (line=43)
61        A: android:name(0x01010003)="name1" (Raw: "name1")
62        A: android:value(0x01010024)="value1" (Raw: "value1")
63      E: meta-data (line=43)
64        A: android:name(0x01010003)="name2" (Raw: "name2")
65        A: android:value(0x01010024)="value2" (Raw: "value2")
66    E: instrumentation (line=8)
67      A: android:label(0x01010001)="abc" (Raw: "abc")
68      A: android:name(0x01010003)="org.chromium.RandomJUnit4TestRunner" (Raw: "org.chromium.RandomJUnit4TestRunner")
69      A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge")
70      A: junit4=(type 0x12)0xffffffff (Raw: "true")
71    E: instrumentation (line=9)
72      A: android:label(0x01010001)="abc" (Raw: "abc")
73      A: android:name(0x01010003)="org.chromium.RandomTestRunner" (Raw: "org.chromium.RandomTestRunner")
74      A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge")
75"""
76
77_NO_ISOLATED_SERVICES = """N: android=http://schemas.android.com/apk/res/android
78  E: manifest (line=1)
79    A: package="org.chromium.abc" (Raw: "org.chromium.abc")
80    E: application (line=5)
81      E: activity (line=6)
82        A: android:name(0x01010003)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName")
83        A: android:exported(0x01010010)=(type 0x12)0xffffffff
84      E: service (line=7)
85        A: android:name(0x01010001)="org.chromium.RandomService" (Raw: "org.chromium.RandomService")
86"""
87
88_NO_SERVICES = """N: android=http://schemas.android.com/apk/res/android
89  E: manifest (line=1)
90    A: package="org.chromium.abc" (Raw: "org.chromium.abc")
91    E: application (line=5)
92      E: activity (line=6)
93        A: android:name(0x01010003)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName")
94        A: android:exported(0x01010010)=(type 0x12)0xffffffff
95"""
96
97_NO_APPLICATION = """N: android=http://schemas.android.com/apk/res/android
98  E: manifest (line=1)
99    A: package="org.chromium.abc" (Raw: "org.chromium.abc")
100"""
101
102_SINGLE_INSTRUMENTATION_MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android
103  E: manifest (line=1)
104    A: package="org.chromium.xyz" (Raw: "org.chromium.xyz")
105    E: instrumentation (line=8)
106      A: android:label(0x01010001)="xyz" (Raw: "xyz")
107      A: android:name(0x01010003)="org.chromium.RandomTestRunner" (Raw: "org.chromium.RandomTestRunner")
108      A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge")
109"""
110
111_SINGLE_J4_INSTRUMENTATION_MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android
112  E: manifest (line=1)
113    A: package="org.chromium.xyz" (Raw: "org.chromium.xyz")
114    E: instrumentation (line=8)
115      A: android:label(0x01010001)="xyz" (Raw: "xyz")
116      A: android:name(0x01010003)="org.chromium.RandomJ4TestRunner" (Raw: "org.chromium.RandomJ4TestRunner")
117      A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge")
118      A: junit4=(type 0x12)0xffffffff (Raw: "true")
119"""
120
121_TARGETING_PRE_RELEASE_Q_MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android
122  E: manifest (line=1)
123    A: package="org.chromium.xyz" (Raw: "org.chromium.xyz")
124    E: uses-sdk (line=2)
125      A: android:minSdkVersion(0x0101020c)="Q" (Raw: "Q")
126      A: android:targetSdkVersion(0x01010270)="Q" (Raw: "Q")
127"""
128
129_NO_NAMESPACE_MANIFEST_DUMP = """E: manifest (line=1)
130  A: package="org.chromium.xyz" (Raw: "org.chromium.xyz")
131  E: instrumentation (line=8)
132    A: http://schemas.android.com/apk/res/android:label(0x01010001)="xyz" (Raw: "xyz")
133    A: http://schemas.android.com/apk/res/android:name(0x01010003)="org.chromium.RandomTestRunner" (Raw: "org.chromium.RandomTestRunner")
134    A: http://schemas.android.com/apk/res/android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge")
135"""
136
137# pylint: enable=line-too-long
138
139
140def _MockAaptDump(manifest_dump):
141  return mock.patch(
142      'devil.android.sdk.aapt.Dump',
143      mock.Mock(side_effect=None, return_value=manifest_dump.split('\n')))
144
145
146def _MockListApkPaths(files):
147  return mock.patch('devil.android.apk_helper.ApkHelper._ListApkPaths',
148                    mock.Mock(side_effect=None, return_value=files))
149
150
151class _MockDeviceUtils(object):
152  def __init__(self):
153    self.product_cpu_abi = abis.ARM_64
154    self.product_cpu_abis = [abis.ARM_64, abis.ARM]
155    self.pixel_density = 500
156    self.build_version_sdk = 28
157
158  def GetLocale(self):
159    # pylint: disable=no-self-use
160    return ('en', 'US')
161
162  def GetFeatures(self):
163    # pylint: disable=no-self-use
164    return [
165        'android.hardware.wifi',
166        'android.hardware.nfc',
167    ]
168
169
170class ApkHelperTest(mock_calls.TestCase):
171  def testToHelperApk(self):
172    apk = apk_helper.ToHelper('abc.apk')
173    self.assertTrue(isinstance(apk, apk_helper.ApkHelper))
174
175  def testToHelperApks(self):
176    apk = apk_helper.ToHelper('abc.apks')
177    self.assertTrue(isinstance(apk, apk_helper.ApksHelper))
178
179  def testToHelperBundleScript(self):
180    apk = apk_helper.ToHelper('abc_bundle')
181    self.assertTrue(isinstance(apk, apk_helper.BundleScriptHelper))
182
183  def testToHelperSplitApk(self):
184    apk = apk_helper.ToSplitHelper('abc.apk', ['a.apk', 'b.apk'])
185    self.assertTrue(isinstance(apk, apk_helper.SplitApkHelper))
186
187  def testToHelperSplitException(self):
188    with self.assertRaises(apk_helper.ApkHelperError):
189      apk_helper.ToSplitHelper(
190          apk_helper.ToHelper('abc.apk'), ['a.apk', 'b.apk'])
191
192  def testGetInstrumentationName(self):
193    with _MockAaptDump(_MANIFEST_DUMP):
194      helper = apk_helper.ApkHelper('')
195      with self.assertRaises(apk_helper.ApkHelperError):
196        helper.GetInstrumentationName()
197
198  def testGetActivityName(self):
199    with _MockAaptDump(_MANIFEST_DUMP):
200      helper = apk_helper.ApkHelper('')
201      self.assertEquals(helper.GetActivityName(),
202                        'org.chromium.abc.MainActivity')
203
204  def testGetViewActivityName(self):
205    with _MockAaptDump(_MANIFEST_DUMP):
206      helper = apk_helper.ApkHelper('')
207      self.assertEquals(helper.GetViewActivityName(),
208                        'org.chromium.ViewActivity')
209
210  def testGetAllInstrumentations(self):
211    with _MockAaptDump(_MANIFEST_DUMP):
212      helper = apk_helper.ApkHelper('')
213      all_instrumentations = helper.GetAllInstrumentations()
214      self.assertEquals(len(all_instrumentations), 2)
215      self.assertEquals(all_instrumentations[0]['android:name'],
216                        'org.chromium.RandomJUnit4TestRunner')
217      self.assertEquals(all_instrumentations[1]['android:name'],
218                        'org.chromium.RandomTestRunner')
219
220  def testGetPackageName(self):
221    with _MockAaptDump(_MANIFEST_DUMP):
222      helper = apk_helper.ApkHelper('')
223      self.assertEquals(helper.GetPackageName(), 'org.chromium.abc')
224
225  def testGetPermssions(self):
226    with _MockAaptDump(_MANIFEST_DUMP):
227      helper = apk_helper.ApkHelper('')
228      all_permissions = helper.GetPermissions()
229      self.assertEquals(len(all_permissions), 3)
230      self.assertTrue('android.permission.INTERNET' in all_permissions)
231      self.assertTrue(
232          'android.permission.READ_EXTERNAL_STORAGE' in all_permissions)
233      self.assertTrue(
234          'android.permission.ACCESS_FINE_LOCATION' in all_permissions)
235
236  def testGetSplitName(self):
237    with _MockAaptDump(_MANIFEST_DUMP):
238      helper = apk_helper.ApkHelper('')
239      self.assertEquals(helper.GetSplitName(), 'random_split')
240
241  def testHasIsolatedProcesses_noApplication(self):
242    with _MockAaptDump(_NO_APPLICATION):
243      helper = apk_helper.ApkHelper('')
244      self.assertFalse(helper.HasIsolatedProcesses())
245
246  def testHasIsolatedProcesses_noServices(self):
247    with _MockAaptDump(_NO_SERVICES):
248      helper = apk_helper.ApkHelper('')
249      self.assertFalse(helper.HasIsolatedProcesses())
250
251  def testHasIsolatedProcesses_oneNotIsolatedProcess(self):
252    with _MockAaptDump(_NO_ISOLATED_SERVICES):
253      helper = apk_helper.ApkHelper('')
254      self.assertFalse(helper.HasIsolatedProcesses())
255
256  def testHasIsolatedProcesses_oneIsolatedProcess(self):
257    with _MockAaptDump(_MANIFEST_DUMP):
258      helper = apk_helper.ApkHelper('')
259      self.assertTrue(helper.HasIsolatedProcesses())
260
261  def testGetSingleInstrumentationName(self):
262    with _MockAaptDump(_SINGLE_INSTRUMENTATION_MANIFEST_DUMP):
263      helper = apk_helper.ApkHelper('')
264      self.assertEquals('org.chromium.RandomTestRunner',
265                        helper.GetInstrumentationName())
266
267  def testGetSingleJUnit4InstrumentationName(self):
268    with _MockAaptDump(_SINGLE_J4_INSTRUMENTATION_MANIFEST_DUMP):
269      helper = apk_helper.ApkHelper('')
270      self.assertEquals('org.chromium.RandomJ4TestRunner',
271                        helper.GetInstrumentationName())
272
273  def testGetAllMetadata(self):
274    with _MockAaptDump(_MANIFEST_DUMP):
275      helper = apk_helper.ApkHelper('')
276      self.assertEquals([('name1', 'value1'), ('name2', 'value2')],
277                        helper.GetAllMetadata())
278
279  def testGetVersionCode(self):
280    with _MockAaptDump(_MANIFEST_DUMP):
281      helper = apk_helper.ApkHelper('')
282      self.assertEquals(376300010, helper.GetVersionCode())
283
284  def testGetVersionName(self):
285    with _MockAaptDump(_MANIFEST_DUMP):
286      helper = apk_helper.ApkHelper('')
287      self.assertEquals('75.0.3763.0', helper.GetVersionName())
288
289  def testGetMinSdkVersion_integerValue(self):
290    with _MockAaptDump(_MANIFEST_DUMP):
291      helper = apk_helper.ApkHelper('')
292      self.assertEquals('21', helper.GetMinSdkVersion())
293
294  def testGetMinSdkVersion_stringValue(self):
295    with _MockAaptDump(_TARGETING_PRE_RELEASE_Q_MANIFEST_DUMP):
296      helper = apk_helper.ApkHelper('')
297      self.assertEquals('Q', helper.GetMinSdkVersion())
298
299  def testGetTargetSdkVersion_integerValue(self):
300    with _MockAaptDump(_MANIFEST_DUMP):
301      helper = apk_helper.ApkHelper('')
302      self.assertEquals('28', helper.GetTargetSdkVersion())
303
304  def testGetTargetSdkVersion_stringValue(self):
305    with _MockAaptDump(_TARGETING_PRE_RELEASE_Q_MANIFEST_DUMP):
306      helper = apk_helper.ApkHelper('')
307      self.assertEquals('Q', helper.GetTargetSdkVersion())
308
309  def testGetSingleInstrumentationName_strippedNamespaces(self):
310    with _MockAaptDump(_NO_NAMESPACE_MANIFEST_DUMP):
311      helper = apk_helper.ApkHelper('')
312      self.assertEquals('org.chromium.RandomTestRunner',
313                        helper.GetInstrumentationName())
314
315  def testGetArchitectures(self):
316    AbiPair = collections.namedtuple('AbiPair', ['abi32bit', 'abi64bit'])
317    for abi_pair in [
318        AbiPair('lib/' + abis.ARM, 'lib/' + abis.ARM_64),
319        AbiPair('lib/' + abis.X86, 'lib/' + abis.X86_64)
320    ]:
321      with _MockListApkPaths([abi_pair.abi32bit]):
322        helper = apk_helper.ApkHelper('')
323        self.assertEquals(
324            set([
325                os.path.basename(abi_pair.abi32bit),
326                os.path.basename(abi_pair.abi64bit)
327            ]), set(helper.GetAbis()))
328      with _MockListApkPaths([abi_pair.abi32bit, abi_pair.abi64bit]):
329        helper = apk_helper.ApkHelper('')
330        self.assertEquals(
331            set([
332                os.path.basename(abi_pair.abi32bit),
333                os.path.basename(abi_pair.abi64bit)
334            ]), set(helper.GetAbis()))
335      with _MockListApkPaths([abi_pair.abi64bit]):
336        helper = apk_helper.ApkHelper('')
337        self.assertEquals(
338            set([os.path.basename(abi_pair.abi64bit)]), set(helper.GetAbis()))
339
340  def testGetSplitsApk(self):
341    apk = apk_helper.ToHelper('abc.apk')
342    with apk.GetApkPaths(_MockDeviceUtils()) as apk_paths:
343      self.assertEquals(apk_paths, ['abc.apk'])
344
345  def testGetSplitsApkModulesException(self):
346    apk = apk_helper.ToHelper('abc.apk')
347    with self.assertRaises(apk_helper.ApkHelperError):
348      apk.GetApkPaths(None, modules=['a'])
349
350  def testGetSplitsApks(self):
351    apk = apk_helper.ToHelper('abc.apks')
352    with self.assertCalls(
353        (mock.call.tempfile.mkdtemp(),
354         '/tmp'),
355        (mock.call.devil.android.sdk.bundletool.ExtractApks(
356            '/tmp', 'abc.apks', ['arm64-v8a', 'armeabi-v7a'], [('en', 'US')],
357            ['android.hardware.wifi', 'android.hardware.nfc'], 500, 28, None)),
358        (mock.call.os.listdir('/tmp'), ['base-master.apk', 'foo-master.apk']),
359        (mock.call.shutil.rmtree('/tmp')),
360    ),\
361    apk.GetApkPaths(_MockDeviceUtils()) as apk_paths:
362      self.assertEquals(apk_paths,
363                        ['/tmp/base-master.apk', '/tmp/foo-master.apk'])
364
365  def testGetSplitsApksWithModules(self):
366    apk = apk_helper.ToHelper('abc.apks')
367    with self.assertCalls(
368        (mock.call.tempfile.mkdtemp(),
369         '/tmp'),
370        (mock.call.devil.android.sdk.bundletool.ExtractApks(
371            '/tmp', 'abc.apks', ['arm64-v8a', 'armeabi-v7a'], [('en', 'US')],
372            ['android.hardware.wifi', 'android.hardware.nfc'], 500, 28,
373            ['bar'])),
374        (mock.call.os.listdir('/tmp'),
375         ['base-master.apk', 'foo-master.apk', 'bar-master.apk']),
376        (mock.call.shutil.rmtree('/tmp')),
377    ),\
378    apk.GetApkPaths(_MockDeviceUtils(), ['bar']) as apk_paths:
379      self.assertEquals(apk_paths, [
380          '/tmp/base-master.apk', '/tmp/foo-master.apk', '/tmp/bar-master.apk'
381      ])
382
383  def testGetSplitsApksWithAdditionalLocales(self):
384    apk = apk_helper.ToHelper('abc.apks')
385    with self.assertCalls(
386        (mock.call.tempfile.mkdtemp(),
387         '/tmp'),
388        (mock.call.devil.android.sdk.bundletool.ExtractApks(
389            '/tmp', 'abc.apks', ['arm64-v8a', 'armeabi-v7a'],
390            [('en', 'US'), ('es', 'ES'), ('fr', 'CA')],
391            ['android.hardware.wifi', 'android.hardware.nfc'], 500, 28, None)),
392        (mock.call.os.listdir('/tmp'),
393         ['base-master.apk', 'base-es.apk', 'base-fr.apk']),
394        (mock.call.shutil.rmtree('/tmp')),
395    ),\
396        apk.GetApkPaths(_MockDeviceUtils(),
397                        additional_locales=['es-ES', 'fr-CA']) as apk_paths:
398      self.assertEquals(
399          apk_paths,
400          ['/tmp/base-master.apk', '/tmp/base-es.apk', '/tmp/base-fr.apk'])
401
402  def testGetSplitsApksWithAdditionalLocalesIncorrectFormat(self):
403    apk = apk_helper.ToHelper('abc.apks')
404    with self.assertRaises(apk_helper.ApkHelperError):
405      apk.GetApkPaths(_MockDeviceUtils(), additional_locales=['es'])
406
407  def testGetSplitsSplitApk(self):
408    apk = apk_helper.ToSplitHelper('base.apk',
409                                   ['split1.apk', 'split2.apk', 'split3.apk'])
410    device = _MockDeviceUtils()
411    with self.assertCalls(
412        (mock.call.devil.android.sdk.split_select.SelectSplits(
413            device,
414            'base.apk', ['split1.apk', 'split2.apk', 'split3.apk'],
415            allow_cached_props=False), ['split2.apk'])),\
416      apk.GetApkPaths(device) as apk_paths:
417      self.assertEquals(apk_paths, ['base.apk', 'split2.apk'])
418
419  def testGetSplitsBundleScript(self):
420    apk = apk_helper.ToHelper('abc_bundle')
421    device = _MockDeviceUtils()
422    with self.assertCalls(
423        (mock.call.tempfile.mkstemp(suffix='.apks'), (0, '/tmp/abc.apks')),
424        (mock.call.devil.utils.cmd_helper.GetCmdStatusOutputAndError([
425            'abc_bundle', 'build-bundle-apks', '--output-apks', '/tmp/abc.apks'
426        ]), (0, '', '')),
427        (mock.call.tempfile.mkdtemp(), '/tmp2'),
428        (mock.call.devil.android.sdk.bundletool.ExtractApks(
429            '/tmp2', '/tmp/abc.apks', ['arm64-v8a', 'armeabi-v7a'],
430            [('en', 'US')], ['android.hardware.wifi', 'android.hardware.nfc'],
431            500, 28, ['bar'])),
432        (mock.call.os.listdir('/tmp2'), ['base-master.apk', 'bar-master.apk']),
433        (mock.call.os.path.isfile('/tmp/abc.apks'), True),
434        (mock.call.os.remove('/tmp/abc.apks')),
435        (mock.call.os.path.isfile('/tmp2'), False),
436        (mock.call.os.path.isdir('/tmp2'), True),
437        (mock.call.shutil.rmtree('/tmp2')),
438    ),\
439    apk.GetApkPaths(device, modules=['bar']) as apk_paths:
440      self.assertEquals(apk_paths,
441                        ['/tmp2/base-master.apk', '/tmp2/bar-master.apk'])
442
443
444if __name__ == '__main__':
445  unittest.main(verbosity=2)
446