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