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