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