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