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