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