xref: /aosp_15_r20/build/soong/cc/ndkstubgen/test_ndkstubgen.py (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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