1#!/usr/bin/env python 2# 3# Copyright (C) 2016 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"""Tests for ndkstubgen.py.""" 18import io 19import textwrap 20import unittest 21from copy import copy 22 23import symbolfile 24from symbolfile import Arch, Tags 25 26import ndkstubgen 27 28 29# pylint: disable=missing-docstring 30 31 32class GeneratorTest(unittest.TestCase): 33 def setUp(self) -> None: 34 self.filter = symbolfile.Filter(Arch('arm'), 9, False, False) 35 36 def test_omit_version(self) -> None: 37 # Thorough testing of the cases involved here is handled by 38 # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. 39 src_file = io.StringIO() 40 version_file = io.StringIO() 41 symbol_list_file = io.StringIO() 42 generator = ndkstubgen.Generator(src_file, 43 version_file, symbol_list_file, 44 self.filter) 45 46 version = symbolfile.Version('VERSION_PRIVATE', None, Tags(), [ 47 symbolfile.Symbol('foo', Tags()), 48 ]) 49 generator.write_version(version) 50 self.assertEqual('', src_file.getvalue()) 51 self.assertEqual('', version_file.getvalue()) 52 53 version = symbolfile.Version('VERSION', None, Tags.from_strs(['x86']), 54 [ 55 symbolfile.Symbol('foo', Tags()), 56 ]) 57 generator.write_version(version) 58 self.assertEqual('', src_file.getvalue()) 59 self.assertEqual('', version_file.getvalue()) 60 61 version = symbolfile.Version('VERSION', None, 62 Tags.from_strs(['introduced=14']), [ 63 symbolfile.Symbol('foo', Tags()), 64 ]) 65 generator.write_version(version) 66 self.assertEqual('', src_file.getvalue()) 67 self.assertEqual('', version_file.getvalue()) 68 69 def test_omit_symbol(self) -> None: 70 # Thorough testing of the cases involved here is handled by 71 # SymbolPresenceTest. 72 src_file = io.StringIO() 73 version_file = io.StringIO() 74 symbol_list_file = io.StringIO() 75 generator = ndkstubgen.Generator(src_file, 76 version_file, symbol_list_file, 77 self.filter) 78 79 version = symbolfile.Version('VERSION_1', None, Tags(), [ 80 symbolfile.Symbol('foo', Tags.from_strs(['x86'])), 81 ]) 82 generator.write_version(version) 83 self.assertEqual('', src_file.getvalue()) 84 self.assertEqual('', version_file.getvalue()) 85 86 version = symbolfile.Version('VERSION_1', None, Tags(), [ 87 symbolfile.Symbol('foo', Tags.from_strs(['introduced=14'])), 88 ]) 89 generator.write_version(version) 90 self.assertEqual('', src_file.getvalue()) 91 self.assertEqual('', version_file.getvalue()) 92 93 version = symbolfile.Version('VERSION_1', None, Tags(), [ 94 symbolfile.Symbol('foo', Tags.from_strs(['llndk'])), 95 ]) 96 generator.write_version(version) 97 self.assertEqual('', src_file.getvalue()) 98 self.assertEqual('', version_file.getvalue()) 99 100 version = symbolfile.Version('VERSION_1', None, Tags(), [ 101 symbolfile.Symbol('foo', Tags.from_strs(['apex'])), 102 ]) 103 generator.write_version(version) 104 self.assertEqual('', src_file.getvalue()) 105 self.assertEqual('', version_file.getvalue()) 106 107 def test_write(self) -> None: 108 src_file = io.StringIO() 109 version_file = io.StringIO() 110 symbol_list_file = io.StringIO() 111 generator = ndkstubgen.Generator(src_file, 112 version_file, symbol_list_file, 113 self.filter) 114 115 versions = [ 116 symbolfile.Version('VERSION_1', None, Tags(), [ 117 symbolfile.Symbol('foo', Tags()), 118 symbolfile.Symbol('bar', Tags.from_strs(['var'])), 119 symbolfile.Symbol('woodly', Tags.from_strs(['weak'])), 120 symbolfile.Symbol('doodly', Tags.from_strs(['weak', 'var'])), 121 ]), 122 symbolfile.Version('VERSION_2', 'VERSION_1', Tags(), [ 123 symbolfile.Symbol('baz', Tags()), 124 ]), 125 symbolfile.Version('VERSION_3', 'VERSION_1', Tags(), [ 126 symbolfile.Symbol('qux', Tags.from_strs(['versioned=14'])), 127 ]), 128 ] 129 130 generator.write(versions) 131 expected_src = textwrap.dedent("""\ 132 void foo() {} 133 int bar = 0; 134 __attribute__((weak)) void woodly() {} 135 __attribute__((weak)) int doodly = 0; 136 void baz() {} 137 void qux() {} 138 """) 139 self.assertEqual(expected_src, src_file.getvalue()) 140 141 expected_version = textwrap.dedent("""\ 142 VERSION_1 { 143 global: 144 foo; 145 bar; 146 woodly; 147 doodly; 148 }; 149 VERSION_2 { 150 global: 151 baz; 152 } VERSION_1; 153 """) 154 self.assertEqual(expected_version, version_file.getvalue()) 155 156 expected_allowlist = textwrap.dedent("""\ 157 [abi_symbol_list] 158 foo 159 bar 160 woodly 161 doodly 162 baz 163 qux 164 """) 165 self.assertEqual(expected_allowlist, symbol_list_file.getvalue()) 166 167 168class IntegrationTest(unittest.TestCase): 169 def setUp(self) -> None: 170 self.filter = symbolfile.Filter(Arch('arm'), 9, False, False) 171 172 def test_integration(self) -> None: 173 api_map = { 174 'O': 9000, 175 'P': 9001, 176 } 177 178 input_file = io.StringIO(textwrap.dedent("""\ 179 VERSION_1 { 180 global: 181 foo; # var 182 bar; # x86 183 fizz; # introduced=O 184 buzz; # introduced=P 185 local: 186 *; 187 }; 188 189 VERSION_2 { # arm 190 baz; # introduced=9 191 qux; # versioned=14 192 } VERSION_1; 193 194 VERSION_3 { # introduced=14 195 woodly; 196 doodly; # var 197 } VERSION_2; 198 199 VERSION_4 { # versioned=9 200 wibble; 201 wizzes; # llndk 202 waggle; # apex 203 } VERSION_2; 204 205 VERSION_5 { # versioned=14 206 wobble; 207 } VERSION_4; 208 """)) 209 parser = symbolfile.SymbolFileParser(input_file, api_map, self.filter) 210 versions = parser.parse() 211 212 src_file = io.StringIO() 213 version_file = io.StringIO() 214 symbol_list_file = io.StringIO() 215 generator = ndkstubgen.Generator(src_file, 216 version_file, symbol_list_file, 217 self.filter) 218 generator.write(versions) 219 220 expected_src = textwrap.dedent("""\ 221 int foo = 0; 222 void baz() {} 223 void qux() {} 224 void wibble() {} 225 void wobble() {} 226 """) 227 self.assertEqual(expected_src, src_file.getvalue()) 228 229 expected_version = textwrap.dedent("""\ 230 VERSION_1 { 231 global: 232 foo; 233 }; 234 VERSION_2 { 235 global: 236 baz; 237 } VERSION_1; 238 VERSION_4 { 239 global: 240 wibble; 241 } VERSION_2; 242 """) 243 self.assertEqual(expected_version, version_file.getvalue()) 244 245 expected_allowlist = textwrap.dedent("""\ 246 [abi_symbol_list] 247 foo 248 baz 249 qux 250 wibble 251 wobble 252 """) 253 self.assertEqual(expected_allowlist, symbol_list_file.getvalue()) 254 255 def test_integration_future_api(self) -> None: 256 api_map = { 257 'O': 9000, 258 'P': 9001, 259 'Q': 9002, 260 } 261 262 input_file = io.StringIO(textwrap.dedent("""\ 263 VERSION_1 { 264 global: 265 foo; # introduced=O 266 bar; # introduced=P 267 baz; # introduced=Q 268 local: 269 *; 270 }; 271 """)) 272 f = copy(self.filter) 273 f.api = 9001 274 parser = symbolfile.SymbolFileParser(input_file, api_map, f) 275 versions = parser.parse() 276 277 src_file = io.StringIO() 278 version_file = io.StringIO() 279 symbol_list_file = io.StringIO() 280 f = copy(self.filter) 281 f.api = 9001 282 generator = ndkstubgen.Generator(src_file, 283 version_file, symbol_list_file, f) 284 generator.write(versions) 285 286 expected_src = textwrap.dedent("""\ 287 void foo() {} 288 void bar() {} 289 """) 290 self.assertEqual(expected_src, src_file.getvalue()) 291 292 expected_version = textwrap.dedent("""\ 293 VERSION_1 { 294 global: 295 foo; 296 bar; 297 }; 298 """) 299 self.assertEqual(expected_version, version_file.getvalue()) 300 301 expected_allowlist = textwrap.dedent("""\ 302 [abi_symbol_list] 303 foo 304 bar 305 """) 306 self.assertEqual(expected_allowlist, symbol_list_file.getvalue()) 307 308 def test_multiple_definition(self) -> None: 309 input_file = io.StringIO(textwrap.dedent("""\ 310 VERSION_1 { 311 global: 312 foo; 313 foo; 314 bar; 315 baz; 316 qux; # arm 317 local: 318 *; 319 }; 320 321 VERSION_2 { 322 global: 323 bar; 324 qux; # arm64 325 } VERSION_1; 326 327 VERSION_PRIVATE { 328 global: 329 baz; 330 } VERSION_2; 331 332 """)) 333 f = copy(self.filter) 334 f.api = 16 335 parser = symbolfile.SymbolFileParser(input_file, {}, f) 336 337 with self.assertRaises( 338 symbolfile.MultiplyDefinedSymbolError) as ex_context: 339 parser.parse() 340 self.assertEqual(['bar', 'foo'], 341 ex_context.exception.multiply_defined_symbols) 342 343 def test_integration_with_apex(self) -> None: 344 api_map = { 345 'O': 9000, 346 'P': 9001, 347 } 348 349 input_file = io.StringIO(textwrap.dedent("""\ 350 VERSION_1 { 351 global: 352 foo; # var 353 bar; # x86 354 fizz; # introduced=O 355 buzz; # introduced=P 356 local: 357 *; 358 }; 359 360 VERSION_2 { # arm 361 baz; # introduced=9 362 qux; # versioned=14 363 } VERSION_1; 364 365 VERSION_3 { # introduced=14 366 woodly; 367 doodly; # var 368 } VERSION_2; 369 370 VERSION_4 { # versioned=9 371 wibble; 372 wizzes; # llndk 373 waggle; # apex 374 bubble; # apex llndk 375 duddle; # llndk apex 376 } VERSION_2; 377 378 VERSION_5 { # versioned=14 379 wobble; 380 } VERSION_4; 381 """)) 382 f = copy(self.filter) 383 f.apex = True 384 parser = symbolfile.SymbolFileParser(input_file, api_map, f) 385 versions = parser.parse() 386 387 src_file = io.StringIO() 388 version_file = io.StringIO() 389 symbol_list_file = io.StringIO() 390 f = copy(self.filter) 391 f.apex = True 392 generator = ndkstubgen.Generator(src_file, 393 version_file, symbol_list_file, f) 394 generator.write(versions) 395 396 expected_src = textwrap.dedent("""\ 397 int foo = 0; 398 void baz() {} 399 void qux() {} 400 void wibble() {} 401 void waggle() {} 402 void bubble() {} 403 void duddle() {} 404 void wobble() {} 405 """) 406 self.assertEqual(expected_src, src_file.getvalue()) 407 408 expected_version = textwrap.dedent("""\ 409 VERSION_1 { 410 global: 411 foo; 412 }; 413 VERSION_2 { 414 global: 415 baz; 416 } VERSION_1; 417 VERSION_4 { 418 global: 419 wibble; 420 waggle; 421 bubble; 422 duddle; 423 } VERSION_2; 424 """) 425 self.assertEqual(expected_version, version_file.getvalue()) 426 427 def test_integration_with_nondk(self) -> None: 428 input_file = io.StringIO(textwrap.dedent("""\ 429 VERSION_1 { 430 global: 431 foo; 432 bar; # apex 433 local: 434 *; 435 }; 436 """)) 437 f = copy(self.filter) 438 f.apex = True 439 f.ndk = False # ndk symbols should be excluded 440 parser = symbolfile.SymbolFileParser(input_file, {}, f) 441 versions = parser.parse() 442 443 src_file = io.StringIO() 444 version_file = io.StringIO() 445 symbol_list_file = io.StringIO() 446 f = copy(self.filter) 447 f.apex = True 448 f.ndk = False # ndk symbols should be excluded 449 generator = ndkstubgen.Generator(src_file, 450 version_file, symbol_list_file, f) 451 generator.write(versions) 452 453 expected_src = textwrap.dedent("""\ 454 void bar() {} 455 """) 456 self.assertEqual(expected_src, src_file.getvalue()) 457 458 expected_version = textwrap.dedent("""\ 459 VERSION_1 { 460 global: 461 bar; 462 }; 463 """) 464 self.assertEqual(expected_version, version_file.getvalue()) 465 466 def test_integration_with_llndk(self) -> None: 467 input_file = io.StringIO(textwrap.dedent("""\ 468 VERSION_34 { # introduced=34 469 global: 470 foo; 471 bar; # llndk 472 }; 473 VERSION_35 { # introduced=35 474 global: 475 wiggle; 476 waggle; # llndk 477 } VERSION_34; 478 VERSION_36 { # introduced=36 479 global: 480 abc; 481 xyz; # llndk 482 } VERSION_35; 483 """)) 484 f = copy(self.filter) 485 f.llndk = True 486 f.api = 35 487 parser = symbolfile.SymbolFileParser(input_file, {}, f) 488 versions = parser.parse() 489 490 src_file = io.StringIO() 491 version_file = io.StringIO() 492 symbol_list_file = io.StringIO() 493 494 generator = ndkstubgen.Generator(src_file, 495 version_file, symbol_list_file, f) 496 generator.write(versions) 497 498 expected_src = textwrap.dedent("""\ 499 void foo() {} 500 void bar() {} 501 void wiggle() {} 502 void waggle() {} 503 """) 504 self.assertEqual(expected_src, src_file.getvalue()) 505 506 expected_version = textwrap.dedent("""\ 507 VERSION_34 { 508 global: 509 foo; 510 bar; 511 }; 512 VERSION_35 { 513 global: 514 wiggle; 515 waggle; 516 } VERSION_34; 517 """) 518 self.assertEqual(expected_version, version_file.getvalue()) 519 520 def test_integration_with_llndk_with_single_version_block(self) -> None: 521 input_file = io.StringIO(textwrap.dedent("""\ 522 LIBANDROID { 523 global: 524 foo; # introduced=34 525 bar; # introduced=35 llndk 526 baz; # introduced=V 527 qux; # introduced=36 528 }; 529 """)) 530 f = copy(self.filter) 531 f.llndk = True 532 f.api = 35 533 parser = symbolfile.SymbolFileParser(input_file, {'V': 35}, f) 534 versions = parser.parse() 535 536 src_file = io.StringIO() 537 version_file = io.StringIO() 538 symbol_list_file = io.StringIO() 539 540 generator = ndkstubgen.Generator(src_file, 541 version_file, symbol_list_file, f) 542 generator.write(versions) 543 544 expected_src = textwrap.dedent("""\ 545 void foo() {} 546 void bar() {} 547 void baz() {} 548 """) 549 self.assertEqual(expected_src, src_file.getvalue()) 550 551 expected_version = textwrap.dedent("""\ 552 LIBANDROID { 553 global: 554 foo; 555 bar; 556 baz; 557 }; 558 """) 559 self.assertEqual(expected_version, version_file.getvalue()) 560 561 def test_empty_stub(self) -> None: 562 """Tests that empty stubs can be generated. 563 564 This is not a common case, but libraries whose only behavior is to 565 interpose symbols to alter existing behavior do not need to expose 566 their interposing symbols as API, so it's possible for the stub to be 567 empty while still needing a stub to link against. libsigchain is an 568 example of this. 569 """ 570 input_file = io.StringIO(textwrap.dedent("""\ 571 VERSION_1 { 572 local: 573 *; 574 }; 575 """)) 576 f = copy(self.filter) 577 f.apex = True 578 parser = symbolfile.SymbolFileParser(input_file, {}, f) 579 versions = parser.parse() 580 581 src_file = io.StringIO() 582 version_file = io.StringIO() 583 symbol_list_file = io.StringIO() 584 f = copy(self.filter) 585 f.apex = True 586 generator = ndkstubgen.Generator(src_file, 587 version_file, 588 symbol_list_file, f) 589 generator.write(versions) 590 591 self.assertEqual('', src_file.getvalue()) 592 self.assertEqual('', version_file.getvalue()) 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