1#!/usr/bin/env python
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17"""Unit tests for manifest_fixer.py."""
18
19import StringIO
20import sys
21import unittest
22from xml.dom import minidom
23
24import manifest_fixer
25
26sys.dont_write_bytecode = True
27
28
29class CompareVersionGtTest(unittest.TestCase):
30  """Unit tests for compare_version_gt function."""
31
32  def test_sdk(self):
33    """Test comparing sdk versions."""
34    self.assertTrue(manifest_fixer.compare_version_gt('28', '27'))
35    self.assertFalse(manifest_fixer.compare_version_gt('27', '28'))
36    self.assertFalse(manifest_fixer.compare_version_gt('28', '28'))
37
38  def test_codename(self):
39    """Test comparing codenames."""
40    self.assertTrue(manifest_fixer.compare_version_gt('Q', 'P'))
41    self.assertFalse(manifest_fixer.compare_version_gt('P', 'Q'))
42    self.assertFalse(manifest_fixer.compare_version_gt('Q', 'Q'))
43
44  def test_sdk_codename(self):
45    """Test comparing sdk versions with codenames."""
46    self.assertTrue(manifest_fixer.compare_version_gt('Q', '28'))
47    self.assertFalse(manifest_fixer.compare_version_gt('28', 'Q'))
48
49  def test_compare_numeric(self):
50    """Test that numbers are compared in numeric and not lexicographic order."""
51    self.assertTrue(manifest_fixer.compare_version_gt('18', '8'))
52
53
54class RaiseMinSdkVersionTest(unittest.TestCase):
55  """Unit tests for raise_min_sdk_version function."""
56
57  def raise_min_sdk_version_test(self, input_manifest, min_sdk_version,
58                                 target_sdk_version, library):
59    doc = minidom.parseString(input_manifest)
60    manifest_fixer.raise_min_sdk_version(doc, min_sdk_version,
61                                         target_sdk_version, library)
62    output = StringIO.StringIO()
63    manifest_fixer.write_xml(output, doc)
64    return output.getvalue()
65
66  manifest_tmpl = (
67      '<?xml version="1.0" encoding="utf-8"?>\n'
68      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
69      '%s'
70      '</manifest>\n')
71
72  # pylint: disable=redefined-builtin
73  def uses_sdk(self, min=None, target=None, extra=''):
74    attrs = ''
75    if min:
76      attrs += ' android:minSdkVersion="%s"' % (min)
77    if target:
78      attrs += ' android:targetSdkVersion="%s"' % (target)
79    if extra:
80      attrs += ' ' + extra
81    return '    <uses-sdk%s/>\n' % (attrs)
82
83  def test_no_uses_sdk(self):
84    """Tests inserting a uses-sdk element into a manifest."""
85
86    manifest_input = self.manifest_tmpl % ''
87    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
88    output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
89    self.assertEqual(output, expected)
90
91  def test_no_min(self):
92    """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
93
94    manifest_input = self.manifest_tmpl % '    <uses-sdk extra="foo"/>\n'
95    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28',
96                                                  extra='extra="foo"')
97    output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
98    self.assertEqual(output, expected)
99
100  def test_raise_min(self):
101    """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
102
103    manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
104    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
105    output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
106    self.assertEqual(output, expected)
107
108  def test_raise(self):
109    """Tests raising a minSdkVersion attribute."""
110
111    manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
112    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
113    output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
114    self.assertEqual(output, expected)
115
116  def test_no_raise_min(self):
117    """Tests a minSdkVersion that doesn't need raising."""
118
119    manifest_input = self.manifest_tmpl % self.uses_sdk(min='28')
120    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
121    output = self.raise_min_sdk_version_test(manifest_input, '27', '27', False)
122    self.assertEqual(output, expected)
123
124  def test_raise_codename(self):
125    """Tests raising a minSdkVersion attribute to a codename."""
126
127    manifest_input = self.manifest_tmpl % self.uses_sdk(min='28')
128    expected = self.manifest_tmpl % self.uses_sdk(min='P', target='P')
129    output = self.raise_min_sdk_version_test(manifest_input, 'P', 'P', False)
130    self.assertEqual(output, expected)
131
132  def test_no_raise_codename(self):
133    """Tests a minSdkVersion codename that doesn't need raising."""
134
135    manifest_input = self.manifest_tmpl % self.uses_sdk(min='P')
136    expected = self.manifest_tmpl % self.uses_sdk(min='P', target='28')
137    output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
138    self.assertEqual(output, expected)
139
140  def test_target(self):
141    """Tests an existing targetSdkVersion is preserved."""
142
143    manifest_input = self.manifest_tmpl % self.uses_sdk(min='26', target='27')
144    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
145    output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
146    self.assertEqual(output, expected)
147
148  def test_no_target(self):
149    """Tests inserting targetSdkVersion when minSdkVersion exists."""
150
151    manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
152    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='29')
153    output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
154    self.assertEqual(output, expected)
155
156  def test_target_no_min(self):
157    """"Tests inserting targetSdkVersion when minSdkVersion exists."""
158
159    manifest_input = self.manifest_tmpl % self.uses_sdk(target='27')
160    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
161    output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
162    self.assertEqual(output, expected)
163
164  def test_no_target_no_min(self):
165    """Tests inserting targetSdkVersion when minSdkVersion does not exist."""
166
167    manifest_input = self.manifest_tmpl % ''
168    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='29')
169    output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
170    self.assertEqual(output, expected)
171
172  def test_library_no_target(self):
173    """Tests inserting targetSdkVersion when minSdkVersion exists."""
174
175    manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
176    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='15')
177    output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
178    self.assertEqual(output, expected)
179
180  def test_library_target_no_min(self):
181    """Tests inserting targetSdkVersion when minSdkVersion exists."""
182
183    manifest_input = self.manifest_tmpl % self.uses_sdk(target='27')
184    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
185    output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
186    self.assertEqual(output, expected)
187
188  def test_library_no_target_no_min(self):
189    """Tests inserting targetSdkVersion when minSdkVersion does not exist."""
190
191    manifest_input = self.manifest_tmpl % ''
192    expected = self.manifest_tmpl % self.uses_sdk(min='28', target='15')
193    output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
194    self.assertEqual(output, expected)
195
196  def test_extra(self):
197    """Tests that extra attributes and elements are maintained."""
198
199    manifest_input = self.manifest_tmpl % (
200        '    <!-- comment -->\n'
201        '    <uses-sdk android:minSdkVersion="27" extra="foo"/>\n'
202        '    <application/>\n')
203
204    # pylint: disable=line-too-long
205    expected = self.manifest_tmpl % (
206        '    <!-- comment -->\n'
207        '    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29" extra="foo"/>\n'
208        '    <application/>\n')
209
210    output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
211
212    self.assertEqual(output, expected)
213
214  def test_indent(self):
215    """Tests that an inserted element copies the existing indentation."""
216
217    manifest_input = self.manifest_tmpl % '  <!-- comment -->\n'
218
219    # pylint: disable=line-too-long
220    expected = self.manifest_tmpl % (
221        '  <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/>\n'
222        '  <!-- comment -->\n')
223
224    output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
225
226    self.assertEqual(output, expected)
227
228
229class AddLoggingParentTest(unittest.TestCase):
230  """Unit tests for add_logging_parent function."""
231
232  def add_logging_parent_test(self, input_manifest, logging_parent=None):
233    doc = minidom.parseString(input_manifest)
234    if logging_parent:
235      manifest_fixer.add_logging_parent(doc, logging_parent)
236    output = StringIO.StringIO()
237    manifest_fixer.write_xml(output, doc)
238    return output.getvalue()
239
240  manifest_tmpl = (
241      '<?xml version="1.0" encoding="utf-8"?>\n'
242      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
243      '%s'
244      '</manifest>\n')
245
246  def uses_logging_parent(self, logging_parent=None):
247    attrs = ''
248    if logging_parent:
249      meta_text = ('<meta-data android:name="android.content.pm.LOGGING_PARENT" '
250                   'android:value="%s"/>\n') % (logging_parent)
251      attrs += '    <application>\n        %s    </application>\n' % (meta_text)
252
253    return attrs
254
255  def test_no_logging_parent(self):
256    """Tests manifest_fixer with no logging_parent."""
257    manifest_input = self.manifest_tmpl % ''
258    expected = self.manifest_tmpl % self.uses_logging_parent()
259    output = self.add_logging_parent_test(manifest_input)
260    self.assertEqual(output, expected)
261
262  def test_logging_parent(self):
263    """Tests manifest_fixer with no logging_parent."""
264    manifest_input = self.manifest_tmpl % ''
265    expected = self.manifest_tmpl % self.uses_logging_parent('FOO')
266    output = self.add_logging_parent_test(manifest_input, 'FOO')
267    self.assertEqual(output, expected)
268
269
270class AddUsesLibrariesTest(unittest.TestCase):
271  """Unit tests for add_uses_libraries function."""
272
273  def run_test(self, input_manifest, new_uses_libraries):
274    doc = minidom.parseString(input_manifest)
275    manifest_fixer.add_uses_libraries(doc, new_uses_libraries, True)
276    output = StringIO.StringIO()
277    manifest_fixer.write_xml(output, doc)
278    return output.getvalue()
279
280  manifest_tmpl = (
281      '<?xml version="1.0" encoding="utf-8"?>\n'
282      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
283      '    <application>\n'
284      '%s'
285      '    </application>\n'
286      '</manifest>\n')
287
288  def uses_libraries(self, name_required_pairs):
289    ret = ''
290    for name, required in name_required_pairs:
291      ret += (
292          '        <uses-library android:name="%s" android:required="%s"/>\n'
293      ) % (name, required)
294
295    return ret
296
297  def test_empty(self):
298    """Empty new_uses_libraries must not touch the manifest."""
299    manifest_input = self.manifest_tmpl % self.uses_libraries([
300        ('foo', 'true'),
301        ('bar', 'false')])
302    expected = manifest_input
303    output = self.run_test(manifest_input, [])
304    self.assertEqual(output, expected)
305
306  def test_not_overwrite(self):
307    """new_uses_libraries must not overwrite existing tags."""
308    manifest_input = self.manifest_tmpl % self.uses_libraries([
309        ('foo', 'true'),
310        ('bar', 'false')])
311    expected = manifest_input
312    output = self.run_test(manifest_input, ['foo', 'bar'])
313    self.assertEqual(output, expected)
314
315  def test_add(self):
316    """New names are added with 'required:true'."""
317    manifest_input = self.manifest_tmpl % self.uses_libraries([
318        ('foo', 'true'),
319        ('bar', 'false')])
320    expected = self.manifest_tmpl % self.uses_libraries([
321        ('foo', 'true'),
322        ('bar', 'false'),
323        ('baz', 'true'),
324        ('qux', 'true')])
325    output = self.run_test(manifest_input, ['bar', 'baz', 'qux'])
326    self.assertEqual(output, expected)
327
328  def test_no_application(self):
329    """When there is no <application> tag, the tag is added."""
330    manifest_input = (
331        '<?xml version="1.0" encoding="utf-8"?>\n'
332        '<manifest xmlns:android='
333        '"http://schemas.android.com/apk/res/android">\n'
334        '</manifest>\n')
335    expected = self.manifest_tmpl % self.uses_libraries([
336        ('foo', 'true'),
337        ('bar', 'true')])
338    output = self.run_test(manifest_input, ['foo', 'bar'])
339    self.assertEqual(output, expected)
340
341  def test_empty_application(self):
342    """Even when here is an empty <application/> tag, the libs are added."""
343    manifest_input = (
344        '<?xml version="1.0" encoding="utf-8"?>\n'
345        '<manifest xmlns:android='
346        '"http://schemas.android.com/apk/res/android">\n'
347        '    <application/>\n'
348        '</manifest>\n')
349    expected = self.manifest_tmpl % self.uses_libraries([
350        ('foo', 'true'),
351        ('bar', 'true')])
352    output = self.run_test(manifest_input, ['foo', 'bar'])
353    self.assertEqual(output, expected)
354
355
356class AddUsesNonSdkApiTest(unittest.TestCase):
357  """Unit tests for add_uses_libraries function."""
358
359  def run_test(self, input_manifest):
360    doc = minidom.parseString(input_manifest)
361    manifest_fixer.add_uses_non_sdk_api(doc)
362    output = StringIO.StringIO()
363    manifest_fixer.write_xml(output, doc)
364    return output.getvalue()
365
366  manifest_tmpl = (
367      '<?xml version="1.0" encoding="utf-8"?>\n'
368      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
369      '    <application%s/>\n'
370      '</manifest>\n')
371
372  def uses_non_sdk_api(self, value):
373    return ' android:usesNonSdkApi="true"' if value else ''
374
375  def test_set_true(self):
376    """Empty new_uses_libraries must not touch the manifest."""
377    manifest_input = self.manifest_tmpl % self.uses_non_sdk_api(False)
378    expected = self.manifest_tmpl % self.uses_non_sdk_api(True)
379    output = self.run_test(manifest_input)
380    self.assertEqual(output, expected)
381
382  def test_already_set(self):
383    """new_uses_libraries must not overwrite existing tags."""
384    manifest_input = self.manifest_tmpl % self.uses_non_sdk_api(True)
385    expected = manifest_input
386    output = self.run_test(manifest_input)
387    self.assertEqual(output, expected)
388
389
390class UseEmbeddedDexTest(unittest.TestCase):
391  """Unit tests for add_use_embedded_dex function."""
392
393  def run_test(self, input_manifest):
394    doc = minidom.parseString(input_manifest)
395    manifest_fixer.add_use_embedded_dex(doc)
396    output = StringIO.StringIO()
397    manifest_fixer.write_xml(output, doc)
398    return output.getvalue()
399
400  manifest_tmpl = (
401      '<?xml version="1.0" encoding="utf-8"?>\n'
402      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
403      '    <application%s/>\n'
404      '</manifest>\n')
405
406  def use_embedded_dex(self, value):
407    return ' android:useEmbeddedDex="%s"' % value
408
409  def test_manifest_with_undeclared_preference(self):
410    manifest_input = self.manifest_tmpl % ''
411    expected = self.manifest_tmpl % self.use_embedded_dex('true')
412    output = self.run_test(manifest_input)
413    self.assertEqual(output, expected)
414
415  def test_manifest_with_use_embedded_dex(self):
416    manifest_input = self.manifest_tmpl % self.use_embedded_dex('true')
417    expected = manifest_input
418    output = self.run_test(manifest_input)
419    self.assertEqual(output, expected)
420
421  def test_manifest_with_not_use_embedded_dex(self):
422    manifest_input = self.manifest_tmpl % self.use_embedded_dex('false')
423    self.assertRaises(RuntimeError, self.run_test, manifest_input)
424
425
426class AddExtractNativeLibsTest(unittest.TestCase):
427  """Unit tests for add_extract_native_libs function."""
428
429  def run_test(self, input_manifest, value):
430    doc = minidom.parseString(input_manifest)
431    manifest_fixer.add_extract_native_libs(doc, value)
432    output = StringIO.StringIO()
433    manifest_fixer.write_xml(output, doc)
434    return output.getvalue()
435
436  manifest_tmpl = (
437      '<?xml version="1.0" encoding="utf-8"?>\n'
438      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
439      '    <application%s/>\n'
440      '</manifest>\n')
441
442  def extract_native_libs(self, value):
443    return ' android:extractNativeLibs="%s"' % value
444
445  def test_set_true(self):
446    manifest_input = self.manifest_tmpl % ''
447    expected = self.manifest_tmpl % self.extract_native_libs('true')
448    output = self.run_test(manifest_input, True)
449    self.assertEqual(output, expected)
450
451  def test_set_false(self):
452    manifest_input = self.manifest_tmpl % ''
453    expected = self.manifest_tmpl % self.extract_native_libs('false')
454    output = self.run_test(manifest_input, False)
455    self.assertEqual(output, expected)
456
457  def test_match(self):
458    manifest_input = self.manifest_tmpl % self.extract_native_libs('true')
459    expected = manifest_input
460    output = self.run_test(manifest_input, True)
461    self.assertEqual(output, expected)
462
463  def test_conflict(self):
464    manifest_input = self.manifest_tmpl % self.extract_native_libs('true')
465    self.assertRaises(RuntimeError, self.run_test, manifest_input, False)
466
467
468class AddNoCodeApplicationTest(unittest.TestCase):
469  """Unit tests for set_has_code_to_false function."""
470
471  def run_test(self, input_manifest):
472    doc = minidom.parseString(input_manifest)
473    manifest_fixer.set_has_code_to_false(doc)
474    output = StringIO.StringIO()
475    manifest_fixer.write_xml(output, doc)
476    return output.getvalue()
477
478  manifest_tmpl = (
479      '<?xml version="1.0" encoding="utf-8"?>\n'
480      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
481      '%s'
482      '</manifest>\n')
483
484  def test_no_application(self):
485    manifest_input = self.manifest_tmpl % ''
486    expected = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
487    output = self.run_test(manifest_input)
488    self.assertEqual(output, expected)
489
490  def test_has_application_no_has_code(self):
491    manifest_input = self.manifest_tmpl % '    <application/>\n'
492    expected = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
493    output = self.run_test(manifest_input)
494    self.assertEqual(output, expected)
495
496  def test_has_application_has_code_false(self):
497    """ Do nothing if there's already an application elemeent. """
498    manifest_input = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
499    output = self.run_test(manifest_input)
500    self.assertEqual(output, manifest_input)
501
502  def test_has_application_has_code_true(self):
503    """ Do nothing if there's already an application elemeent even if its
504     hasCode attribute is true. """
505    manifest_input = self.manifest_tmpl % '    <application android:hasCode="true"/>\n'
506    output = self.run_test(manifest_input)
507    self.assertEqual(output, manifest_input)
508
509
510if __name__ == '__main__':
511  unittest.main(verbosity=2)
512