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