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; 477 waggle; # llndk=202404 478 bubble; # llndk=202404 479 duddle; 480 duddle; # llndk=202504 481 } VERSION_34; 482 """)) 483 f = copy(self.filter) 484 f.llndk = True 485 f.api = 202404 486 parser = symbolfile.SymbolFileParser(input_file, {}, f) 487 versions = parser.parse() 488 489 src_file = io.StringIO() 490 version_file = io.StringIO() 491 symbol_list_file = io.StringIO() 492 493 generator = ndkstubgen.Generator(src_file, 494 version_file, symbol_list_file, f) 495 generator.write(versions) 496 497 expected_src = textwrap.dedent("""\ 498 void foo() {} 499 void bar() {} 500 void waggle() {} 501 void bubble() {} 502 """) 503 self.assertEqual(expected_src, src_file.getvalue()) 504 505 expected_version = textwrap.dedent("""\ 506 VERSION_34 { 507 global: 508 foo; 509 bar; 510 }; 511 VERSION_35 { 512 global: 513 waggle; 514 bubble; 515 } VERSION_34; 516 """) 517 self.assertEqual(expected_version, version_file.getvalue()) 518 519 def test_integration_with_llndk_with_single_version_block(self) -> None: 520 input_file = io.StringIO(textwrap.dedent("""\ 521 LIBANDROID { 522 global: 523 foo; # introduced=34 524 bar; # introduced=35 525 bar; # llndk=202404 526 baz; # introduced=35 527 }; 528 """)) 529 f = copy(self.filter) 530 f.llndk = True 531 f.api = 202404 532 parser = symbolfile.SymbolFileParser(input_file, {}, f) 533 versions = parser.parse() 534 535 src_file = io.StringIO() 536 version_file = io.StringIO() 537 symbol_list_file = io.StringIO() 538 539 generator = ndkstubgen.Generator(src_file, 540 version_file, symbol_list_file, f) 541 generator.write(versions) 542 543 expected_src = textwrap.dedent("""\ 544 void foo() {} 545 void bar() {} 546 """) 547 self.assertEqual(expected_src, src_file.getvalue()) 548 549 expected_version = textwrap.dedent("""\ 550 LIBANDROID { 551 global: 552 foo; 553 bar; 554 }; 555 """) 556 self.assertEqual(expected_version, version_file.getvalue()) 557 558 def test_empty_stub(self) -> None: 559 """Tests that empty stubs can be generated. 560 561 This is not a common case, but libraries whose only behavior is to 562 interpose symbols to alter existing behavior do not need to expose 563 their interposing symbols as API, so it's possible for the stub to be 564 empty while still needing a stub to link against. libsigchain is an 565 example of this. 566 """ 567 input_file = io.StringIO(textwrap.dedent("""\ 568 VERSION_1 { 569 local: 570 *; 571 }; 572 """)) 573 f = copy(self.filter) 574 f.apex = True 575 parser = symbolfile.SymbolFileParser(input_file, {}, f) 576 versions = parser.parse() 577 578 src_file = io.StringIO() 579 version_file = io.StringIO() 580 symbol_list_file = io.StringIO() 581 f = copy(self.filter) 582 f.apex = True 583 generator = ndkstubgen.Generator(src_file, 584 version_file, 585 symbol_list_file, f) 586 generator.write(versions) 587 588 self.assertEqual('', src_file.getvalue()) 589 self.assertEqual('', version_file.getvalue()) 590 591 592def main() -> None: 593 suite = unittest.TestLoader().loadTestsFromName(__name__) 594 unittest.TextTestRunner(verbosity=3).run(suite) 595 596 597if __name__ == '__main__': 598 main() 599