1# 2# Copyright (C) 2016 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16"""Tests for symbolfile.""" 17import io 18import textwrap 19import unittest 20 21import symbolfile 22from symbolfile import Arch, Tag, Tags, Version 23 24# pylint: disable=missing-docstring 25 26 27class DecodeApiLevelTest(unittest.TestCase): 28 def test_decode_api_level(self) -> None: 29 self.assertEqual(9, symbolfile.decode_api_level('9', {})) 30 self.assertEqual(9000, symbolfile.decode_api_level('O', {'O': 9000})) 31 32 with self.assertRaises(KeyError): 33 symbolfile.decode_api_level('O', {}) 34 35 36class TagsTest(unittest.TestCase): 37 def test_get_tags_no_tags(self) -> None: 38 self.assertEqual(Tags(), symbolfile.get_tags('', {})) 39 self.assertEqual(Tags(), symbolfile.get_tags('foo bar baz', {})) 40 41 def test_get_tags(self) -> None: 42 self.assertEqual(Tags.from_strs(['foo', 'bar']), 43 symbolfile.get_tags('# foo bar', {})) 44 self.assertEqual(Tags.from_strs(['bar', 'baz']), 45 symbolfile.get_tags('foo # bar baz', {})) 46 47 def test_split_tag(self) -> None: 48 self.assertTupleEqual(('foo', 'bar'), 49 symbolfile.split_tag(Tag('foo=bar'))) 50 self.assertTupleEqual(('foo', 'bar=baz'), 51 symbolfile.split_tag(Tag('foo=bar=baz'))) 52 with self.assertRaises(ValueError): 53 symbolfile.split_tag(Tag('foo')) 54 55 def test_get_tag_value(self) -> None: 56 self.assertEqual('bar', symbolfile.get_tag_value(Tag('foo=bar'))) 57 self.assertEqual('bar=baz', 58 symbolfile.get_tag_value(Tag('foo=bar=baz'))) 59 with self.assertRaises(ValueError): 60 symbolfile.get_tag_value(Tag('foo')) 61 62 def test_is_api_level_tag(self) -> None: 63 self.assertTrue(symbolfile.is_api_level_tag(Tag('introduced=24'))) 64 self.assertTrue(symbolfile.is_api_level_tag(Tag('introduced-arm=24'))) 65 self.assertTrue(symbolfile.is_api_level_tag(Tag('versioned=24'))) 66 67 # Shouldn't try to process things that aren't a key/value tag. 68 self.assertFalse(symbolfile.is_api_level_tag(Tag('arm'))) 69 self.assertFalse(symbolfile.is_api_level_tag(Tag('introduced'))) 70 self.assertFalse(symbolfile.is_api_level_tag(Tag('versioned'))) 71 72 # We don't support arch specific `versioned` tags. 73 self.assertFalse(symbolfile.is_api_level_tag(Tag('versioned-arm=24'))) 74 75 def test_decode_api_level_tags(self) -> None: 76 api_map = { 77 'O': 9000, 78 'P': 9001, 79 } 80 81 tags = [ 82 symbolfile.decode_api_level_tag(t, api_map) for t in ( 83 Tag('introduced=9'), 84 Tag('introduced-arm=14'), 85 Tag('versioned=16'), 86 Tag('arm'), 87 Tag('introduced=O'), 88 Tag('introduced=P'), 89 ) 90 ] 91 expected_tags = [ 92 Tag('introduced=9'), 93 Tag('introduced-arm=14'), 94 Tag('versioned=16'), 95 Tag('arm'), 96 Tag('introduced=9000'), 97 Tag('introduced=9001'), 98 ] 99 self.assertListEqual(expected_tags, tags) 100 101 with self.assertRaises(symbolfile.ParseError): 102 symbolfile.decode_api_level_tag(Tag('introduced=O'), {}) 103 104 105class PrivateVersionTest(unittest.TestCase): 106 def test_version_is_private(self) -> None: 107 def mock_version(name: str) -> Version: 108 return Version(name, base=None, tags=Tags(), symbols=[]) 109 110 self.assertFalse(mock_version('foo').is_private) 111 self.assertFalse(mock_version('PRIVATE').is_private) 112 self.assertFalse(mock_version('PLATFORM').is_private) 113 self.assertFalse(mock_version('foo_private').is_private) 114 self.assertFalse(mock_version('foo_platform').is_private) 115 self.assertFalse(mock_version('foo_PRIVATE_').is_private) 116 self.assertFalse(mock_version('foo_PLATFORM_').is_private) 117 118 self.assertTrue(mock_version('foo_PRIVATE').is_private) 119 self.assertTrue(mock_version('foo_PLATFORM').is_private) 120 121 122class SymbolPresenceTest(unittest.TestCase): 123 def test_symbol_in_arch(self) -> None: 124 self.assertTrue(symbolfile.symbol_in_arch(Tags(), Arch('arm'))) 125 self.assertTrue( 126 symbolfile.symbol_in_arch(Tags.from_strs(['arm']), Arch('arm'))) 127 128 self.assertFalse( 129 symbolfile.symbol_in_arch(Tags.from_strs(['x86']), Arch('arm'))) 130 131 def test_symbol_in_api(self) -> None: 132 self.assertTrue(symbolfile.symbol_in_api([], Arch('arm'), 9)) 133 self.assertTrue( 134 symbolfile.symbol_in_api([Tag('introduced=9')], Arch('arm'), 9)) 135 self.assertTrue( 136 symbolfile.symbol_in_api([Tag('introduced=9')], Arch('arm'), 14)) 137 self.assertTrue( 138 symbolfile.symbol_in_api([Tag('introduced-arm=9')], Arch('arm'), 139 14)) 140 self.assertTrue( 141 symbolfile.symbol_in_api([Tag('introduced-arm=9')], Arch('arm'), 142 14)) 143 self.assertTrue( 144 symbolfile.symbol_in_api([Tag('introduced-x86=14')], Arch('arm'), 145 9)) 146 self.assertTrue( 147 symbolfile.symbol_in_api( 148 [Tag('introduced-arm=9'), 149 Tag('introduced-x86=21')], Arch('arm'), 14)) 150 self.assertTrue( 151 symbolfile.symbol_in_api( 152 [Tag('introduced=9'), 153 Tag('introduced-x86=21')], Arch('arm'), 14)) 154 self.assertTrue( 155 symbolfile.symbol_in_api( 156 [Tag('introduced=21'), 157 Tag('introduced-arm=9')], Arch('arm'), 14)) 158 self.assertTrue( 159 symbolfile.symbol_in_api([Tag('future')], Arch('arm'), 160 symbolfile.FUTURE_API_LEVEL)) 161 162 self.assertFalse( 163 symbolfile.symbol_in_api([Tag('introduced=14')], Arch('arm'), 9)) 164 self.assertFalse( 165 symbolfile.symbol_in_api([Tag('introduced-arm=14')], Arch('arm'), 166 9)) 167 self.assertFalse( 168 symbolfile.symbol_in_api([Tag('future')], Arch('arm'), 9)) 169 self.assertFalse( 170 symbolfile.symbol_in_api( 171 [Tag('introduced=9'), Tag('future')], Arch('arm'), 14)) 172 self.assertFalse( 173 symbolfile.symbol_in_api([Tag('introduced-arm=9'), 174 Tag('future')], Arch('arm'), 14)) 175 self.assertFalse( 176 symbolfile.symbol_in_api( 177 [Tag('introduced-arm=21'), 178 Tag('introduced-x86=9')], Arch('arm'), 14)) 179 self.assertFalse( 180 symbolfile.symbol_in_api( 181 [Tag('introduced=9'), 182 Tag('introduced-arm=21')], Arch('arm'), 14)) 183 self.assertFalse( 184 symbolfile.symbol_in_api( 185 [Tag('introduced=21'), 186 Tag('introduced-x86=9')], Arch('arm'), 14)) 187 188 # Interesting edge case: this symbol should be omitted from the 189 # library, but this call should still return true because none of the 190 # tags indiciate that it's not present in this API level. 191 self.assertTrue(symbolfile.symbol_in_api([Tag('x86')], Arch('arm'), 9)) 192 193 def test_verioned_in_api(self) -> None: 194 self.assertTrue(symbolfile.symbol_versioned_in_api([], 9)) 195 self.assertTrue( 196 symbolfile.symbol_versioned_in_api([Tag('versioned=9')], 9)) 197 self.assertTrue( 198 symbolfile.symbol_versioned_in_api([Tag('versioned=9')], 14)) 199 200 self.assertFalse( 201 symbolfile.symbol_versioned_in_api([Tag('versioned=14')], 9)) 202 203 204class OmitVersionTest(unittest.TestCase): 205 def test_omit_private(self) -> None: 206 self.assertFalse( 207 symbolfile.should_omit_version( 208 symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9, 209 False, False)) 210 211 self.assertTrue( 212 symbolfile.should_omit_version( 213 symbolfile.Version('foo_PRIVATE', None, Tags(), []), 214 Arch('arm'), 9, False, False)) 215 self.assertTrue( 216 symbolfile.should_omit_version( 217 symbolfile.Version('foo_PLATFORM', None, Tags(), []), 218 Arch('arm'), 9, False, False)) 219 220 self.assertTrue( 221 symbolfile.should_omit_version( 222 symbolfile.Version('foo', None, 223 Tags.from_strs(['platform-only']), []), 224 Arch('arm'), 9, False, False)) 225 226 def test_omit_llndk(self) -> None: 227 self.assertTrue( 228 symbolfile.should_omit_version( 229 symbolfile.Version('foo', None, Tags.from_strs(['llndk']), []), 230 Arch('arm'), 9, False, False)) 231 232 self.assertFalse( 233 symbolfile.should_omit_version( 234 symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9, 235 True, False)) 236 self.assertFalse( 237 symbolfile.should_omit_version( 238 symbolfile.Version('foo', None, Tags.from_strs(['llndk']), []), 239 Arch('arm'), 9, True, False)) 240 241 def test_omit_apex(self) -> None: 242 self.assertTrue( 243 symbolfile.should_omit_version( 244 symbolfile.Version('foo', None, Tags.from_strs(['apex']), []), 245 Arch('arm'), 9, False, False)) 246 247 self.assertFalse( 248 symbolfile.should_omit_version( 249 symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9, 250 False, True)) 251 self.assertFalse( 252 symbolfile.should_omit_version( 253 symbolfile.Version('foo', None, Tags.from_strs(['apex']), []), 254 Arch('arm'), 9, False, True)) 255 256 def test_omit_systemapi(self) -> None: 257 self.assertTrue( 258 symbolfile.should_omit_version( 259 symbolfile.Version('foo', None, Tags.from_strs(['systemapi']), 260 []), Arch('arm'), 9, False, False)) 261 262 self.assertFalse( 263 symbolfile.should_omit_version( 264 symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9, 265 False, True)) 266 self.assertFalse( 267 symbolfile.should_omit_version( 268 symbolfile.Version('foo', None, Tags.from_strs(['systemapi']), 269 []), Arch('arm'), 9, False, True)) 270 271 def test_omit_arch(self) -> None: 272 self.assertFalse( 273 symbolfile.should_omit_version( 274 symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9, 275 False, False)) 276 self.assertFalse( 277 symbolfile.should_omit_version( 278 symbolfile.Version('foo', None, Tags.from_strs(['arm']), []), 279 Arch('arm'), 9, False, False)) 280 281 self.assertTrue( 282 symbolfile.should_omit_version( 283 symbolfile.Version('foo', None, Tags.from_strs(['x86']), []), 284 Arch('arm'), 9, False, False)) 285 286 def test_omit_api(self) -> None: 287 self.assertFalse( 288 symbolfile.should_omit_version( 289 symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9, 290 False, False)) 291 self.assertFalse( 292 symbolfile.should_omit_version( 293 symbolfile.Version('foo', None, 294 Tags.from_strs(['introduced=9']), []), 295 Arch('arm'), 9, False, False)) 296 297 self.assertTrue( 298 symbolfile.should_omit_version( 299 symbolfile.Version('foo', None, 300 Tags.from_strs(['introduced=14']), []), 301 Arch('arm'), 9, False, False)) 302 303 304class OmitSymbolTest(unittest.TestCase): 305 def test_omit_llndk(self) -> None: 306 self.assertTrue( 307 symbolfile.should_omit_symbol( 308 symbolfile.Symbol('foo', Tags.from_strs(['llndk'])), 309 Arch('arm'), 9, False, False)) 310 311 self.assertFalse( 312 symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()), 313 Arch('arm'), 9, True, False)) 314 self.assertFalse( 315 symbolfile.should_omit_symbol( 316 symbolfile.Symbol('foo', Tags.from_strs(['llndk'])), 317 Arch('arm'), 9, True, False)) 318 319 def test_omit_apex(self) -> None: 320 self.assertTrue( 321 symbolfile.should_omit_symbol( 322 symbolfile.Symbol('foo', Tags.from_strs(['apex'])), 323 Arch('arm'), 9, False, False)) 324 325 self.assertFalse( 326 symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()), 327 Arch('arm'), 9, False, True)) 328 self.assertFalse( 329 symbolfile.should_omit_symbol( 330 symbolfile.Symbol('foo', Tags.from_strs(['apex'])), 331 Arch('arm'), 9, False, True)) 332 333 def test_omit_systemapi(self) -> None: 334 self.assertTrue( 335 symbolfile.should_omit_symbol( 336 symbolfile.Symbol('foo', Tags.from_strs(['systemapi'])), 337 Arch('arm'), 9, False, False)) 338 339 self.assertFalse( 340 symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()), 341 Arch('arm'), 9, False, True)) 342 self.assertFalse( 343 symbolfile.should_omit_symbol( 344 symbolfile.Symbol('foo', Tags.from_strs(['systemapi'])), 345 Arch('arm'), 9, False, True)) 346 347 def test_omit_arch(self) -> None: 348 self.assertFalse( 349 symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()), 350 Arch('arm'), 9, False, False)) 351 self.assertFalse( 352 symbolfile.should_omit_symbol( 353 symbolfile.Symbol('foo', Tags.from_strs(['arm'])), Arch('arm'), 354 9, False, False)) 355 356 self.assertTrue( 357 symbolfile.should_omit_symbol( 358 symbolfile.Symbol('foo', Tags.from_strs(['x86'])), Arch('arm'), 359 9, False, False)) 360 361 def test_omit_api(self) -> None: 362 self.assertFalse( 363 symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()), 364 Arch('arm'), 9, False, False)) 365 self.assertFalse( 366 symbolfile.should_omit_symbol( 367 symbolfile.Symbol('foo', Tags.from_strs(['introduced=9'])), 368 Arch('arm'), 9, False, False)) 369 370 self.assertTrue( 371 symbolfile.should_omit_symbol( 372 symbolfile.Symbol('foo', Tags.from_strs(['introduced=14'])), 373 Arch('arm'), 9, False, False)) 374 375 376class SymbolFileParseTest(unittest.TestCase): 377 def test_next_line(self) -> None: 378 input_file = io.StringIO(textwrap.dedent("""\ 379 foo 380 381 bar 382 # baz 383 qux 384 """)) 385 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, 386 False, False) 387 self.assertIsNone(parser.current_line) 388 389 self.assertEqual('foo', parser.next_line().strip()) 390 assert parser.current_line is not None 391 self.assertEqual('foo', parser.current_line.strip()) 392 393 self.assertEqual('bar', parser.next_line().strip()) 394 self.assertEqual('bar', parser.current_line.strip()) 395 396 self.assertEqual('qux', parser.next_line().strip()) 397 self.assertEqual('qux', parser.current_line.strip()) 398 399 self.assertEqual('', parser.next_line()) 400 self.assertEqual('', parser.current_line) 401 402 def test_parse_version(self) -> None: 403 input_file = io.StringIO(textwrap.dedent("""\ 404 VERSION_1 { # foo bar 405 baz; 406 qux; # woodly doodly 407 }; 408 409 VERSION_2 { 410 } VERSION_1; # asdf 411 """)) 412 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, 413 False, False) 414 415 parser.next_line() 416 version = parser.parse_version() 417 self.assertEqual('VERSION_1', version.name) 418 self.assertIsNone(version.base) 419 self.assertEqual(Tags.from_strs(['foo', 'bar']), version.tags) 420 421 expected_symbols = [ 422 symbolfile.Symbol('baz', Tags()), 423 symbolfile.Symbol('qux', Tags.from_strs(['woodly', 'doodly'])), 424 ] 425 self.assertEqual(expected_symbols, version.symbols) 426 427 parser.next_line() 428 version = parser.parse_version() 429 self.assertEqual('VERSION_2', version.name) 430 self.assertEqual('VERSION_1', version.base) 431 self.assertEqual(Tags(), version.tags) 432 433 def test_parse_version_eof(self) -> None: 434 input_file = io.StringIO(textwrap.dedent("""\ 435 VERSION_1 { 436 """)) 437 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, 438 False, False) 439 parser.next_line() 440 with self.assertRaises(symbolfile.ParseError): 441 parser.parse_version() 442 443 def test_unknown_scope_label(self) -> None: 444 input_file = io.StringIO(textwrap.dedent("""\ 445 VERSION_1 { 446 foo: 447 } 448 """)) 449 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, 450 False, False) 451 parser.next_line() 452 with self.assertRaises(symbolfile.ParseError): 453 parser.parse_version() 454 455 def test_parse_symbol(self) -> None: 456 input_file = io.StringIO(textwrap.dedent("""\ 457 foo; 458 bar; # baz qux 459 """)) 460 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, 461 False, False) 462 463 parser.next_line() 464 symbol = parser.parse_symbol() 465 self.assertEqual('foo', symbol.name) 466 self.assertEqual(Tags(), symbol.tags) 467 468 parser.next_line() 469 symbol = parser.parse_symbol() 470 self.assertEqual('bar', symbol.name) 471 self.assertEqual(Tags.from_strs(['baz', 'qux']), symbol.tags) 472 473 def test_wildcard_symbol_global(self) -> None: 474 input_file = io.StringIO(textwrap.dedent("""\ 475 VERSION_1 { 476 *; 477 }; 478 """)) 479 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, 480 False, False) 481 parser.next_line() 482 with self.assertRaises(symbolfile.ParseError): 483 parser.parse_version() 484 485 def test_wildcard_symbol_local(self) -> None: 486 input_file = io.StringIO(textwrap.dedent("""\ 487 VERSION_1 { 488 local: 489 *; 490 }; 491 """)) 492 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, 493 False, False) 494 parser.next_line() 495 version = parser.parse_version() 496 self.assertEqual([], version.symbols) 497 498 def test_missing_semicolon(self) -> None: 499 input_file = io.StringIO(textwrap.dedent("""\ 500 VERSION_1 { 501 foo 502 }; 503 """)) 504 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, 505 False, False) 506 parser.next_line() 507 with self.assertRaises(symbolfile.ParseError): 508 parser.parse_version() 509 510 def test_parse_fails_invalid_input(self) -> None: 511 with self.assertRaises(symbolfile.ParseError): 512 input_file = io.StringIO('foo') 513 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 514 16, False, False) 515 parser.parse() 516 517 def test_parse(self) -> None: 518 input_file = io.StringIO(textwrap.dedent("""\ 519 VERSION_1 { 520 local: 521 hidden1; 522 global: 523 foo; 524 bar; # baz 525 }; 526 527 VERSION_2 { # wasd 528 # Implicit global scope. 529 woodly; 530 doodly; # asdf 531 local: 532 qwerty; 533 } VERSION_1; 534 """)) 535 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, 536 False, False) 537 versions = parser.parse() 538 539 expected = [ 540 symbolfile.Version('VERSION_1', None, Tags(), [ 541 symbolfile.Symbol('foo', Tags()), 542 symbolfile.Symbol('bar', Tags.from_strs(['baz'])), 543 ]), 544 symbolfile.Version( 545 'VERSION_2', 'VERSION_1', Tags.from_strs(['wasd']), [ 546 symbolfile.Symbol('woodly', Tags()), 547 symbolfile.Symbol('doodly', Tags.from_strs(['asdf'])), 548 ]), 549 ] 550 551 self.assertEqual(expected, versions) 552 553 def test_parse_llndk_apex_symbol(self) -> None: 554 input_file = io.StringIO(textwrap.dedent("""\ 555 VERSION_1 { 556 foo; 557 bar; # llndk 558 baz; # llndk apex 559 qux; # apex 560 }; 561 """)) 562 parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, 563 False, True) 564 565 parser.next_line() 566 version = parser.parse_version() 567 self.assertEqual('VERSION_1', version.name) 568 self.assertIsNone(version.base) 569 570 expected_symbols = [ 571 symbolfile.Symbol('foo', Tags()), 572 symbolfile.Symbol('bar', Tags.from_strs(['llndk'])), 573 symbolfile.Symbol('baz', Tags.from_strs(['llndk', 'apex'])), 574 symbolfile.Symbol('qux', Tags.from_strs(['apex'])), 575 ] 576 self.assertEqual(expected_symbols, version.symbols) 577 578 579def main() -> None: 580 suite = unittest.TestLoader().loadTestsFromName(__name__) 581 unittest.TextTestRunner(verbosity=3).run(suite) 582 583 584if __name__ == '__main__': 585 main() 586