diff --git a/OWNERS b/OWNERS index dbb491d8b..8355d1047 100644 --- a/OWNERS +++ b/OWNERS @@ -6,9 +6,8 @@ per-file * = jungjw@google.com per-file * = patricearruda@google.com per-file * = paulduffin@google.com -per-file ndk_*.go, *gen_stub_libs.py = danalbert@google.com +per-file ndk_*.go = danalbert@google.com per-file clang.go,global.go = srhines@google.com, chh@google.com, pirama@google.com, yikong@google.com per-file tidy.go = srhines@google.com, chh@google.com per-file lto.go,pgo.go = srhines@google.com, pirama@google.com, yikong@google.com per-file docs/map_files.md = danalbert@google.com, enh@google.com, jiyong@google.com -per-file *ndk_api_coverage_parser.py = sophiez@google.com \ No newline at end of file diff --git a/cc/ndk_api_coverage_parser/.gitignore b/cc/ndk_api_coverage_parser/.gitignore new file mode 100644 index 000000000..fd94eac3f --- /dev/null +++ b/cc/ndk_api_coverage_parser/.gitignore @@ -0,0 +1,140 @@ +# From https://github.com/github/gitignore/blob/master/Python.gitignore + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/cc/scriptlib/Android.bp b/cc/ndk_api_coverage_parser/Android.bp similarity index 73% rename from cc/scriptlib/Android.bp rename to cc/ndk_api_coverage_parser/Android.bp index ff9a2f0f3..8d9827ca6 100644 --- a/cc/scriptlib/Android.bp +++ b/cc/ndk_api_coverage_parser/Android.bp @@ -14,19 +14,33 @@ // limitations under the License. // +python_library_host { + name: "ndk_api_coverage_parser_lib", + pkg_path: "ndk_api_coverage_parser", + srcs: [ + "__init__.py", + ], +} + python_test_host { name: "test_ndk_api_coverage_parser", main: "test_ndk_api_coverage_parser.py", srcs: [ "test_ndk_api_coverage_parser.py", ], + libs: [ + "ndk_api_coverage_parser_lib", + "symbolfile", + ], } python_binary_host { name: "ndk_api_coverage_parser", - main: "ndk_api_coverage_parser.py", + main: "__init__.py", srcs: [ - "gen_stub_libs.py", - "ndk_api_coverage_parser.py", + "__init__.py", + ], + libs: [ + "symbolfile", ], } diff --git a/cc/ndk_api_coverage_parser/OWNERS b/cc/ndk_api_coverage_parser/OWNERS new file mode 100644 index 000000000..a90c48c15 --- /dev/null +++ b/cc/ndk_api_coverage_parser/OWNERS @@ -0,0 +1 @@ +sophiez@google.com diff --git a/cc/scriptlib/ndk_api_coverage_parser.py b/cc/ndk_api_coverage_parser/__init__.py similarity index 97% rename from cc/scriptlib/ndk_api_coverage_parser.py rename to cc/ndk_api_coverage_parser/__init__.py index d74035b2a..7817c7832 100755 --- a/cc/scriptlib/ndk_api_coverage_parser.py +++ b/cc/ndk_api_coverage_parser/__init__.py @@ -21,7 +21,7 @@ import os import sys from xml.etree.ElementTree import Element, SubElement, tostring -from gen_stub_libs import ALL_ARCHITECTURES, FUTURE_API_LEVEL, MultiplyDefinedSymbolError, SymbolFileParser +from symbolfile import ALL_ARCHITECTURES, FUTURE_API_LEVEL, MultiplyDefinedSymbolError, SymbolFileParser ROOT_ELEMENT_TAG = 'ndk-library' diff --git a/cc/scriptlib/test_ndk_api_coverage_parser.py b/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py similarity index 97% rename from cc/scriptlib/test_ndk_api_coverage_parser.py rename to cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py index a3c9b708c..f1c925ca9 100644 --- a/cc/scriptlib/test_ndk_api_coverage_parser.py +++ b/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py @@ -20,7 +20,7 @@ import textwrap import unittest from xml.etree.ElementTree import tostring -from gen_stub_libs import FUTURE_API_LEVEL, SymbolFileParser +from symbolfile import FUTURE_API_LEVEL, SymbolFileParser import ndk_api_coverage_parser as nparser diff --git a/cc/ndk_library.go b/cc/ndk_library.go index 6299b009e..4578fd329 100644 --- a/cc/ndk_library.go +++ b/cc/ndk_library.go @@ -26,17 +26,16 @@ import ( ) func init() { + pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen") pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser") } var ( - toolPath = pctx.SourcePathVariable("toolPath", "build/soong/cc/scriptlib/gen_stub_libs.py") - genStubSrc = pctx.AndroidStaticRule("genStubSrc", blueprint.RuleParams{ - Command: "$toolPath --arch $arch --api $apiLevel --api-map " + - "$apiMap $flags $in $out", - CommandDeps: []string{"$toolPath"}, + Command: "$ndkStubGenerator --arch $arch --api $apiLevel " + + "--api-map $apiMap $flags $in $out", + CommandDeps: []string{"$ndkStubGenerator"}, }, "arch", "apiLevel", "apiMap", "flags") parseNdkApiRule = pctx.AndroidStaticRule("parseNdkApiRule", @@ -78,9 +77,9 @@ type libraryProperties struct { // https://github.com/android-ndk/ndk/issues/265. Unversioned_until *string - // Private property for use by the mutator that splits per-API level. - // can be one of or or "current" - // passed to "gen_stub_libs.py" as it is + // Private property for use by the mutator that splits per-API level. Can be + // one of or or "current" passed to + // "ndkstubgen.py" as it is ApiLevel string `blueprint:"mutated"` // True if this API is not yet ready to be shipped in the NDK. It will be diff --git a/cc/ndkstubgen/.gitignore b/cc/ndkstubgen/.gitignore new file mode 100644 index 000000000..fd94eac3f --- /dev/null +++ b/cc/ndkstubgen/.gitignore @@ -0,0 +1,140 @@ +# From https://github.com/github/gitignore/blob/master/Python.gitignore + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/cc/ndkstubgen/Android.bp b/cc/ndkstubgen/Android.bp new file mode 100644 index 000000000..85dfaee18 --- /dev/null +++ b/cc/ndkstubgen/Android.bp @@ -0,0 +1,48 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +python_binary_host { + name: "ndkstubgen", + pkg_path: "ndkstubgen", + main: "__init__.py", + srcs: [ + "__init__.py", + ], + libs: [ + "symbolfile", + ], +} + +python_library_host { + name: "ndkstubgenlib", + pkg_path: "ndkstubgen", + srcs: [ + "__init__.py", + ], + libs: [ + "symbolfile", + ], +} + +python_test_host { + name: "test_ndkstubgen", + srcs: [ + "test_ndkstubgen.py", + ], + libs: [ + "ndkstubgenlib", + ], +} diff --git a/cc/ndkstubgen/OWNERS b/cc/ndkstubgen/OWNERS new file mode 100644 index 000000000..f0d873359 --- /dev/null +++ b/cc/ndkstubgen/OWNERS @@ -0,0 +1 @@ +danalbert@google.com diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py new file mode 100755 index 000000000..2f4326a9c --- /dev/null +++ b/cc/ndkstubgen/__init__.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Generates source for stub shared libraries for the NDK.""" +import argparse +import json +import logging +import os +import sys + +import symbolfile + + +class Generator: + """Output generator that writes stub source files and version scripts.""" + def __init__(self, src_file, version_script, arch, api, llndk, apex): + self.src_file = src_file + self.version_script = version_script + self.arch = arch + self.api = api + self.llndk = llndk + self.apex = apex + + def write(self, versions): + """Writes all symbol data to the output files.""" + for version in versions: + self.write_version(version) + + def write_version(self, version): + """Writes a single version block's data to the output files.""" + if symbolfile.should_omit_version(version, self.arch, self.api, + self.llndk, self.apex): + return + + section_versioned = symbolfile.symbol_versioned_in_api( + version.tags, self.api) + version_empty = True + pruned_symbols = [] + for symbol in version.symbols: + if symbolfile.should_omit_symbol(symbol, self.arch, self.api, + self.llndk, self.apex): + continue + + if symbolfile.symbol_versioned_in_api(symbol.tags, self.api): + version_empty = False + pruned_symbols.append(symbol) + + if len(pruned_symbols) > 0: + if not version_empty and section_versioned: + self.version_script.write(version.name + ' {\n') + self.version_script.write(' global:\n') + for symbol in pruned_symbols: + emit_version = symbolfile.symbol_versioned_in_api( + symbol.tags, self.api) + if section_versioned and emit_version: + self.version_script.write(' ' + symbol.name + ';\n') + + weak = '' + if 'weak' in symbol.tags: + weak = '__attribute__((weak)) ' + + if 'var' in symbol.tags: + self.src_file.write('{}int {} = 0;\n'.format( + weak, symbol.name)) + else: + self.src_file.write('{}void {}() {{}}\n'.format( + weak, symbol.name)) + + if not version_empty and section_versioned: + base = '' if version.base is None else ' ' + version.base + self.version_script.write('}' + base + ';\n') + + +def parse_args(): + """Parses and returns command line arguments.""" + parser = argparse.ArgumentParser() + + parser.add_argument('-v', '--verbose', action='count', default=0) + + parser.add_argument( + '--api', required=True, help='API level being targeted.') + parser.add_argument( + '--arch', choices=symbolfile.ALL_ARCHITECTURES, required=True, + help='Architecture being targeted.') + parser.add_argument( + '--llndk', action='store_true', help='Use the LLNDK variant.') + parser.add_argument( + '--apex', action='store_true', help='Use the APEX variant.') + + parser.add_argument( + '--api-map', type=os.path.realpath, required=True, + help='Path to the API level map JSON file.') + + parser.add_argument( + 'symbol_file', type=os.path.realpath, help='Path to symbol file.') + parser.add_argument( + 'stub_src', type=os.path.realpath, + help='Path to output stub source file.') + parser.add_argument( + 'version_script', type=os.path.realpath, + help='Path to output version script.') + + return parser.parse_args() + + +def main(): + """Program entry point.""" + args = parse_args() + + with open(args.api_map) as map_file: + api_map = json.load(map_file) + api = symbolfile.decode_api_level(args.api, api_map) + + verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG) + verbosity = args.verbose + if verbosity > 2: + verbosity = 2 + logging.basicConfig(level=verbose_map[verbosity]) + + with open(args.symbol_file) as symbol_file: + try: + versions = symbolfile.SymbolFileParser(symbol_file, api_map, + args.arch, api, args.llndk, + args.apex).parse() + except symbolfile.MultiplyDefinedSymbolError as ex: + sys.exit('{}: error: {}'.format(args.symbol_file, ex)) + + with open(args.stub_src, 'w') as src_file: + with open(args.version_script, 'w') as version_file: + generator = Generator(src_file, version_file, args.arch, api, + args.llndk, args.apex) + generator.write(versions) + + +if __name__ == '__main__': + main() diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py new file mode 100755 index 000000000..70bcf781c --- /dev/null +++ b/cc/ndkstubgen/test_ndkstubgen.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Tests for ndkstubgen.py.""" +import io +import textwrap +import unittest + +import ndkstubgen +import symbolfile + + +# pylint: disable=missing-docstring + + +class GeneratorTest(unittest.TestCase): + def test_omit_version(self): + # Thorough testing of the cases involved here is handled by + # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, + False, False) + + version = symbolfile.Version('VERSION_PRIVATE', None, [], [ + symbolfile.Symbol('foo', []), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + version = symbolfile.Version('VERSION', None, ['x86'], [ + symbolfile.Symbol('foo', []), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + version = symbolfile.Version('VERSION', None, ['introduced=14'], [ + symbolfile.Symbol('foo', []), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + def test_omit_symbol(self): + # Thorough testing of the cases involved here is handled by + # SymbolPresenceTest. + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, + False, False) + + version = symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', ['x86']), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + version = symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', ['introduced=14']), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + version = symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', ['llndk']), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + version = symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', ['apex']), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + def test_write(self): + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, + False, False) + + versions = [ + symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', []), + symbolfile.Symbol('bar', ['var']), + symbolfile.Symbol('woodly', ['weak']), + symbolfile.Symbol('doodly', ['weak', 'var']), + ]), + symbolfile.Version('VERSION_2', 'VERSION_1', [], [ + symbolfile.Symbol('baz', []), + ]), + symbolfile.Version('VERSION_3', 'VERSION_1', [], [ + symbolfile.Symbol('qux', ['versioned=14']), + ]), + ] + + generator.write(versions) + expected_src = textwrap.dedent("""\ + void foo() {} + int bar = 0; + __attribute__((weak)) void woodly() {} + __attribute__((weak)) int doodly = 0; + void baz() {} + void qux() {} + """) + self.assertEqual(expected_src, src_file.getvalue()) + + expected_version = textwrap.dedent("""\ + VERSION_1 { + global: + foo; + bar; + woodly; + doodly; + }; + VERSION_2 { + global: + baz; + } VERSION_1; + """) + self.assertEqual(expected_version, version_file.getvalue()) + + +class IntegrationTest(unittest.TestCase): + def test_integration(self): + api_map = { + 'O': 9000, + 'P': 9001, + } + + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + global: + foo; # var + bar; # x86 + fizz; # introduced=O + buzz; # introduced=P + local: + *; + }; + + VERSION_2 { # arm + baz; # introduced=9 + qux; # versioned=14 + } VERSION_1; + + VERSION_3 { # introduced=14 + woodly; + doodly; # var + } VERSION_2; + + VERSION_4 { # versioned=9 + wibble; + wizzes; # llndk + waggle; # apex + } VERSION_2; + + VERSION_5 { # versioned=14 + wobble; + } VERSION_4; + """)) + parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9, + False, False) + versions = parser.parse() + + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, + False, False) + generator.write(versions) + + expected_src = textwrap.dedent("""\ + int foo = 0; + void baz() {} + void qux() {} + void wibble() {} + void wobble() {} + """) + self.assertEqual(expected_src, src_file.getvalue()) + + expected_version = textwrap.dedent("""\ + VERSION_1 { + global: + foo; + }; + VERSION_2 { + global: + baz; + } VERSION_1; + VERSION_4 { + global: + wibble; + } VERSION_2; + """) + self.assertEqual(expected_version, version_file.getvalue()) + + def test_integration_future_api(self): + api_map = { + 'O': 9000, + 'P': 9001, + 'Q': 9002, + } + + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + global: + foo; # introduced=O + bar; # introduced=P + baz; # introduced=Q + local: + *; + }; + """)) + parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9001, + False, False) + versions = parser.parse() + + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9001, + False, False) + generator.write(versions) + + expected_src = textwrap.dedent("""\ + void foo() {} + void bar() {} + """) + self.assertEqual(expected_src, src_file.getvalue()) + + expected_version = textwrap.dedent("""\ + VERSION_1 { + global: + foo; + bar; + }; + """) + self.assertEqual(expected_version, version_file.getvalue()) + + def test_multiple_definition(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + global: + foo; + foo; + bar; + baz; + qux; # arm + local: + *; + }; + + VERSION_2 { + global: + bar; + qux; # arm64 + } VERSION_1; + + VERSION_PRIVATE { + global: + baz; + } VERSION_2; + + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, + False) + + with self.assertRaises( + symbolfile.MultiplyDefinedSymbolError) as ex_context: + parser.parse() + self.assertEqual(['bar', 'foo'], + ex_context.exception.multiply_defined_symbols) + + def test_integration_with_apex(self): + api_map = { + 'O': 9000, + 'P': 9001, + } + + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + global: + foo; # var + bar; # x86 + fizz; # introduced=O + buzz; # introduced=P + local: + *; + }; + + VERSION_2 { # arm + baz; # introduced=9 + qux; # versioned=14 + } VERSION_1; + + VERSION_3 { # introduced=14 + woodly; + doodly; # var + } VERSION_2; + + VERSION_4 { # versioned=9 + wibble; + wizzes; # llndk + waggle; # apex + bubble; # apex llndk + duddle; # llndk apex + } VERSION_2; + + VERSION_5 { # versioned=14 + wobble; + } VERSION_4; + """)) + parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9, + False, True) + versions = parser.parse() + + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, + False, True) + generator.write(versions) + + expected_src = textwrap.dedent("""\ + int foo = 0; + void baz() {} + void qux() {} + void wibble() {} + void waggle() {} + void bubble() {} + void duddle() {} + void wobble() {} + """) + self.assertEqual(expected_src, src_file.getvalue()) + + expected_version = textwrap.dedent("""\ + VERSION_1 { + global: + foo; + }; + VERSION_2 { + global: + baz; + } VERSION_1; + VERSION_4 { + global: + wibble; + waggle; + bubble; + duddle; + } VERSION_2; + """) + self.assertEqual(expected_version, version_file.getvalue()) + +def main(): + suite = unittest.TestLoader().loadTestsFromName(__name__) + unittest.TextTestRunner(verbosity=3).run(suite) + + +if __name__ == '__main__': + main() diff --git a/cc/scriptlib/__init__.py b/cc/scriptlib/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/cc/scriptlib/test_gen_stub_libs.py b/cc/scriptlib/test_gen_stub_libs.py deleted file mode 100755 index de8619966..000000000 --- a/cc/scriptlib/test_gen_stub_libs.py +++ /dev/null @@ -1,821 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2016 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -"""Tests for gen_stub_libs.py.""" -import io -import textwrap -import unittest - -import gen_stub_libs as gsl - - - -# pylint: disable=missing-docstring - - -class DecodeApiLevelTest(unittest.TestCase): - def test_decode_api_level(self): - self.assertEqual(9, gsl.decode_api_level('9', {})) - self.assertEqual(9000, gsl.decode_api_level('O', {'O': 9000})) - - with self.assertRaises(KeyError): - gsl.decode_api_level('O', {}) - - -class TagsTest(unittest.TestCase): - def test_get_tags_no_tags(self): - self.assertEqual([], gsl.get_tags('')) - self.assertEqual([], gsl.get_tags('foo bar baz')) - - def test_get_tags(self): - self.assertEqual(['foo', 'bar'], gsl.get_tags('# foo bar')) - self.assertEqual(['bar', 'baz'], gsl.get_tags('foo # bar baz')) - - def test_split_tag(self): - self.assertTupleEqual(('foo', 'bar'), gsl.split_tag('foo=bar')) - self.assertTupleEqual(('foo', 'bar=baz'), gsl.split_tag('foo=bar=baz')) - with self.assertRaises(ValueError): - gsl.split_tag('foo') - - def test_get_tag_value(self): - self.assertEqual('bar', gsl.get_tag_value('foo=bar')) - self.assertEqual('bar=baz', gsl.get_tag_value('foo=bar=baz')) - with self.assertRaises(ValueError): - gsl.get_tag_value('foo') - - def test_is_api_level_tag(self): - self.assertTrue(gsl.is_api_level_tag('introduced=24')) - self.assertTrue(gsl.is_api_level_tag('introduced-arm=24')) - self.assertTrue(gsl.is_api_level_tag('versioned=24')) - - # Shouldn't try to process things that aren't a key/value tag. - self.assertFalse(gsl.is_api_level_tag('arm')) - self.assertFalse(gsl.is_api_level_tag('introduced')) - self.assertFalse(gsl.is_api_level_tag('versioned')) - - # We don't support arch specific `versioned` tags. - self.assertFalse(gsl.is_api_level_tag('versioned-arm=24')) - - def test_decode_api_level_tags(self): - api_map = { - 'O': 9000, - 'P': 9001, - } - - tags = [ - 'introduced=9', - 'introduced-arm=14', - 'versioned=16', - 'arm', - 'introduced=O', - 'introduced=P', - ] - expected_tags = [ - 'introduced=9', - 'introduced-arm=14', - 'versioned=16', - 'arm', - 'introduced=9000', - 'introduced=9001', - ] - self.assertListEqual( - expected_tags, gsl.decode_api_level_tags(tags, api_map)) - - with self.assertRaises(gsl.ParseError): - gsl.decode_api_level_tags(['introduced=O'], {}) - - -class PrivateVersionTest(unittest.TestCase): - def test_version_is_private(self): - self.assertFalse(gsl.version_is_private('foo')) - self.assertFalse(gsl.version_is_private('PRIVATE')) - self.assertFalse(gsl.version_is_private('PLATFORM')) - self.assertFalse(gsl.version_is_private('foo_private')) - self.assertFalse(gsl.version_is_private('foo_platform')) - self.assertFalse(gsl.version_is_private('foo_PRIVATE_')) - self.assertFalse(gsl.version_is_private('foo_PLATFORM_')) - - self.assertTrue(gsl.version_is_private('foo_PRIVATE')) - self.assertTrue(gsl.version_is_private('foo_PLATFORM')) - - -class SymbolPresenceTest(unittest.TestCase): - def test_symbol_in_arch(self): - self.assertTrue(gsl.symbol_in_arch([], 'arm')) - self.assertTrue(gsl.symbol_in_arch(['arm'], 'arm')) - - self.assertFalse(gsl.symbol_in_arch(['x86'], 'arm')) - - def test_symbol_in_api(self): - self.assertTrue(gsl.symbol_in_api([], 'arm', 9)) - self.assertTrue(gsl.symbol_in_api(['introduced=9'], 'arm', 9)) - self.assertTrue(gsl.symbol_in_api(['introduced=9'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api(['introduced-arm=9'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api(['introduced-arm=9'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api(['introduced-x86=14'], 'arm', 9)) - self.assertTrue(gsl.symbol_in_api( - ['introduced-arm=9', 'introduced-x86=21'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api( - ['introduced=9', 'introduced-x86=21'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api( - ['introduced=21', 'introduced-arm=9'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api( - ['future'], 'arm', gsl.FUTURE_API_LEVEL)) - - self.assertFalse(gsl.symbol_in_api(['introduced=14'], 'arm', 9)) - self.assertFalse(gsl.symbol_in_api(['introduced-arm=14'], 'arm', 9)) - self.assertFalse(gsl.symbol_in_api(['future'], 'arm', 9)) - self.assertFalse(gsl.symbol_in_api( - ['introduced=9', 'future'], 'arm', 14)) - self.assertFalse(gsl.symbol_in_api( - ['introduced-arm=9', 'future'], 'arm', 14)) - self.assertFalse(gsl.symbol_in_api( - ['introduced-arm=21', 'introduced-x86=9'], 'arm', 14)) - self.assertFalse(gsl.symbol_in_api( - ['introduced=9', 'introduced-arm=21'], 'arm', 14)) - self.assertFalse(gsl.symbol_in_api( - ['introduced=21', 'introduced-x86=9'], 'arm', 14)) - - # Interesting edge case: this symbol should be omitted from the - # library, but this call should still return true because none of the - # tags indiciate that it's not present in this API level. - self.assertTrue(gsl.symbol_in_api(['x86'], 'arm', 9)) - - def test_verioned_in_api(self): - self.assertTrue(gsl.symbol_versioned_in_api([], 9)) - self.assertTrue(gsl.symbol_versioned_in_api(['versioned=9'], 9)) - self.assertTrue(gsl.symbol_versioned_in_api(['versioned=9'], 14)) - - self.assertFalse(gsl.symbol_versioned_in_api(['versioned=14'], 9)) - - -class OmitVersionTest(unittest.TestCase): - def test_omit_private(self): - self.assertFalse( - gsl.should_omit_version(gsl.Version('foo', None, [], []), 'arm', 9, - False, False)) - - self.assertTrue( - gsl.should_omit_version(gsl.Version('foo_PRIVATE', None, [], []), - 'arm', 9, False, False)) - self.assertTrue( - gsl.should_omit_version(gsl.Version('foo_PLATFORM', None, [], []), - 'arm', 9, False, False)) - - self.assertTrue( - gsl.should_omit_version( - gsl.Version('foo', None, ['platform-only'], []), 'arm', 9, - False, False)) - - def test_omit_llndk(self): - self.assertTrue( - gsl.should_omit_version(gsl.Version('foo', None, ['llndk'], []), - 'arm', 9, False, False)) - - self.assertFalse( - gsl.should_omit_version(gsl.Version('foo', None, [], []), 'arm', 9, - True, False)) - self.assertFalse( - gsl.should_omit_version(gsl.Version('foo', None, ['llndk'], []), - 'arm', 9, True, False)) - - def test_omit_apex(self): - self.assertTrue( - gsl.should_omit_version(gsl.Version('foo', None, ['apex'], []), - 'arm', 9, False, False)) - - self.assertFalse( - gsl.should_omit_version(gsl.Version('foo', None, [], []), 'arm', 9, - False, True)) - self.assertFalse( - gsl.should_omit_version(gsl.Version('foo', None, ['apex'], []), - 'arm', 9, False, True)) - - def test_omit_arch(self): - self.assertFalse( - gsl.should_omit_version(gsl.Version('foo', None, [], []), 'arm', 9, - False, False)) - self.assertFalse( - gsl.should_omit_version(gsl.Version('foo', None, ['arm'], []), - 'arm', 9, False, False)) - - self.assertTrue( - gsl.should_omit_version(gsl.Version('foo', None, ['x86'], []), - 'arm', 9, False, False)) - - def test_omit_api(self): - self.assertFalse( - gsl.should_omit_version(gsl.Version('foo', None, [], []), 'arm', 9, - False, False)) - self.assertFalse( - gsl.should_omit_version( - gsl.Version('foo', None, ['introduced=9'], []), 'arm', 9, - False, False)) - - self.assertTrue( - gsl.should_omit_version( - gsl.Version('foo', None, ['introduced=14'], []), 'arm', 9, - False, False)) - - -class OmitSymbolTest(unittest.TestCase): - def test_omit_llndk(self): - self.assertTrue( - gsl.should_omit_symbol(gsl.Symbol('foo', ['llndk']), 'arm', 9, - False, False)) - - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, True, - False)) - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', ['llndk']), 'arm', 9, - True, False)) - - def test_omit_apex(self): - self.assertTrue( - gsl.should_omit_symbol(gsl.Symbol('foo', ['apex']), 'arm', 9, - False, False)) - - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False, - True)) - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', ['apex']), 'arm', 9, - False, True)) - - def test_omit_arch(self): - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False, - False)) - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', ['arm']), 'arm', 9, False, - False)) - - self.assertTrue( - gsl.should_omit_symbol(gsl.Symbol('foo', ['x86']), 'arm', 9, False, - False)) - - def test_omit_api(self): - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False, - False)) - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', ['introduced=9']), 'arm', - 9, False, False)) - - self.assertTrue( - gsl.should_omit_symbol(gsl.Symbol('foo', ['introduced=14']), 'arm', - 9, False, False)) - - -class SymbolFileParseTest(unittest.TestCase): - def test_next_line(self): - input_file = io.StringIO(textwrap.dedent("""\ - foo - - bar - # baz - qux - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - self.assertIsNone(parser.current_line) - - self.assertEqual('foo', parser.next_line().strip()) - self.assertEqual('foo', parser.current_line.strip()) - - self.assertEqual('bar', parser.next_line().strip()) - self.assertEqual('bar', parser.current_line.strip()) - - self.assertEqual('qux', parser.next_line().strip()) - self.assertEqual('qux', parser.current_line.strip()) - - self.assertEqual('', parser.next_line()) - self.assertEqual('', parser.current_line) - - def test_parse_version(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { # foo bar - baz; - qux; # woodly doodly - }; - - VERSION_2 { - } VERSION_1; # asdf - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - - parser.next_line() - version = parser.parse_version() - self.assertEqual('VERSION_1', version.name) - self.assertIsNone(version.base) - self.assertEqual(['foo', 'bar'], version.tags) - - expected_symbols = [ - gsl.Symbol('baz', []), - gsl.Symbol('qux', ['woodly', 'doodly']), - ] - self.assertEqual(expected_symbols, version.symbols) - - parser.next_line() - version = parser.parse_version() - self.assertEqual('VERSION_2', version.name) - self.assertEqual('VERSION_1', version.base) - self.assertEqual([], version.tags) - - def test_parse_version_eof(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.next_line() - with self.assertRaises(gsl.ParseError): - parser.parse_version() - - def test_unknown_scope_label(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - foo: - } - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.next_line() - with self.assertRaises(gsl.ParseError): - parser.parse_version() - - def test_parse_symbol(self): - input_file = io.StringIO(textwrap.dedent("""\ - foo; - bar; # baz qux - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - - parser.next_line() - symbol = parser.parse_symbol() - self.assertEqual('foo', symbol.name) - self.assertEqual([], symbol.tags) - - parser.next_line() - symbol = parser.parse_symbol() - self.assertEqual('bar', symbol.name) - self.assertEqual(['baz', 'qux'], symbol.tags) - - def test_wildcard_symbol_global(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - *; - }; - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.next_line() - with self.assertRaises(gsl.ParseError): - parser.parse_version() - - def test_wildcard_symbol_local(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - local: - *; - }; - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.next_line() - version = parser.parse_version() - self.assertEqual([], version.symbols) - - def test_missing_semicolon(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - foo - }; - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.next_line() - with self.assertRaises(gsl.ParseError): - parser.parse_version() - - def test_parse_fails_invalid_input(self): - with self.assertRaises(gsl.ParseError): - input_file = io.StringIO('foo') - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, - False) - parser.parse() - - def test_parse(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - local: - hidden1; - global: - foo; - bar; # baz - }; - - VERSION_2 { # wasd - # Implicit global scope. - woodly; - doodly; # asdf - local: - qwerty; - } VERSION_1; - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - versions = parser.parse() - - expected = [ - gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', []), - gsl.Symbol('bar', ['baz']), - ]), - gsl.Version('VERSION_2', 'VERSION_1', ['wasd'], [ - gsl.Symbol('woodly', []), - gsl.Symbol('doodly', ['asdf']), - ]), - ] - - self.assertEqual(expected, versions) - - def test_parse_llndk_apex_symbol(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - foo; - bar; # llndk - baz; # llndk apex - qux; # apex - }; - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, True) - - parser.next_line() - version = parser.parse_version() - self.assertEqual('VERSION_1', version.name) - self.assertIsNone(version.base) - - expected_symbols = [ - gsl.Symbol('foo', []), - gsl.Symbol('bar', ['llndk']), - gsl.Symbol('baz', ['llndk', 'apex']), - gsl.Symbol('qux', ['apex']), - ] - self.assertEqual(expected_symbols, version.symbols) - - -class GeneratorTest(unittest.TestCase): - def test_omit_version(self): - # Thorough testing of the cases involved here is handled by - # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9, False, - False) - - version = gsl.Version('VERSION_PRIVATE', None, [], [ - gsl.Symbol('foo', []), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - version = gsl.Version('VERSION', None, ['x86'], [ - gsl.Symbol('foo', []), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - version = gsl.Version('VERSION', None, ['introduced=14'], [ - gsl.Symbol('foo', []), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - def test_omit_symbol(self): - # Thorough testing of the cases involved here is handled by - # SymbolPresenceTest. - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9, False, - False) - - version = gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', ['x86']), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - version = gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', ['introduced=14']), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - version = gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', ['llndk']), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - version = gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', ['apex']), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - def test_write(self): - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9, False, - False) - - versions = [ - gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', []), - gsl.Symbol('bar', ['var']), - gsl.Symbol('woodly', ['weak']), - gsl.Symbol('doodly', ['weak', 'var']), - ]), - gsl.Version('VERSION_2', 'VERSION_1', [], [ - gsl.Symbol('baz', []), - ]), - gsl.Version('VERSION_3', 'VERSION_1', [], [ - gsl.Symbol('qux', ['versioned=14']), - ]), - ] - - generator.write(versions) - expected_src = textwrap.dedent("""\ - void foo() {} - int bar = 0; - __attribute__((weak)) void woodly() {} - __attribute__((weak)) int doodly = 0; - void baz() {} - void qux() {} - """) - self.assertEqual(expected_src, src_file.getvalue()) - - expected_version = textwrap.dedent("""\ - VERSION_1 { - global: - foo; - bar; - woodly; - doodly; - }; - VERSION_2 { - global: - baz; - } VERSION_1; - """) - self.assertEqual(expected_version, version_file.getvalue()) - - -class IntegrationTest(unittest.TestCase): - def test_integration(self): - api_map = { - 'O': 9000, - 'P': 9001, - } - - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - global: - foo; # var - bar; # x86 - fizz; # introduced=O - buzz; # introduced=P - local: - *; - }; - - VERSION_2 { # arm - baz; # introduced=9 - qux; # versioned=14 - } VERSION_1; - - VERSION_3 { # introduced=14 - woodly; - doodly; # var - } VERSION_2; - - VERSION_4 { # versioned=9 - wibble; - wizzes; # llndk - waggle; # apex - } VERSION_2; - - VERSION_5 { # versioned=14 - wobble; - } VERSION_4; - """)) - parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9, False, - False) - versions = parser.parse() - - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9, False, - False) - generator.write(versions) - - expected_src = textwrap.dedent("""\ - int foo = 0; - void baz() {} - void qux() {} - void wibble() {} - void wobble() {} - """) - self.assertEqual(expected_src, src_file.getvalue()) - - expected_version = textwrap.dedent("""\ - VERSION_1 { - global: - foo; - }; - VERSION_2 { - global: - baz; - } VERSION_1; - VERSION_4 { - global: - wibble; - } VERSION_2; - """) - self.assertEqual(expected_version, version_file.getvalue()) - - def test_integration_future_api(self): - api_map = { - 'O': 9000, - 'P': 9001, - 'Q': 9002, - } - - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - global: - foo; # introduced=O - bar; # introduced=P - baz; # introduced=Q - local: - *; - }; - """)) - parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9001, False, - False) - versions = parser.parse() - - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9001, False, - False) - generator.write(versions) - - expected_src = textwrap.dedent("""\ - void foo() {} - void bar() {} - """) - self.assertEqual(expected_src, src_file.getvalue()) - - expected_version = textwrap.dedent("""\ - VERSION_1 { - global: - foo; - bar; - }; - """) - self.assertEqual(expected_version, version_file.getvalue()) - - def test_multiple_definition(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - global: - foo; - foo; - bar; - baz; - qux; # arm - local: - *; - }; - - VERSION_2 { - global: - bar; - qux; # arm64 - } VERSION_1; - - VERSION_PRIVATE { - global: - baz; - } VERSION_2; - - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - - with self.assertRaises(gsl.MultiplyDefinedSymbolError) as ex_context: - parser.parse() - self.assertEqual(['bar', 'foo'], - ex_context.exception.multiply_defined_symbols) - - def test_integration_with_apex(self): - api_map = { - 'O': 9000, - 'P': 9001, - } - - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - global: - foo; # var - bar; # x86 - fizz; # introduced=O - buzz; # introduced=P - local: - *; - }; - - VERSION_2 { # arm - baz; # introduced=9 - qux; # versioned=14 - } VERSION_1; - - VERSION_3 { # introduced=14 - woodly; - doodly; # var - } VERSION_2; - - VERSION_4 { # versioned=9 - wibble; - wizzes; # llndk - waggle; # apex - bubble; # apex llndk - duddle; # llndk apex - } VERSION_2; - - VERSION_5 { # versioned=14 - wobble; - } VERSION_4; - """)) - parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9, False, - True) - versions = parser.parse() - - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9, False, True) - generator.write(versions) - - expected_src = textwrap.dedent("""\ - int foo = 0; - void baz() {} - void qux() {} - void wibble() {} - void waggle() {} - void bubble() {} - void duddle() {} - void wobble() {} - """) - self.assertEqual(expected_src, src_file.getvalue()) - - expected_version = textwrap.dedent("""\ - VERSION_1 { - global: - foo; - }; - VERSION_2 { - global: - baz; - } VERSION_1; - VERSION_4 { - global: - wibble; - waggle; - bubble; - duddle; - } VERSION_2; - """) - self.assertEqual(expected_version, version_file.getvalue()) - -def main(): - suite = unittest.TestLoader().loadTestsFromName(__name__) - unittest.TextTestRunner(verbosity=3).run(suite) - - -if __name__ == '__main__': - main() diff --git a/cc/symbolfile/.gitignore b/cc/symbolfile/.gitignore new file mode 100644 index 000000000..fd94eac3f --- /dev/null +++ b/cc/symbolfile/.gitignore @@ -0,0 +1,140 @@ +# From https://github.com/github/gitignore/blob/master/Python.gitignore + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/cc/symbolfile/Android.bp b/cc/symbolfile/Android.bp new file mode 100644 index 000000000..5b4330916 --- /dev/null +++ b/cc/symbolfile/Android.bp @@ -0,0 +1,33 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +python_library_host { + name: "symbolfile", + pkg_path: "symbolfile", + srcs: [ + "__init__.py", + ], +} + +python_test_host { + name: "test_symbolfile", + srcs: [ + "test_symbolfile.py", + ], + libs: [ + "symbolfile", + ], +} diff --git a/cc/symbolfile/OWNERS b/cc/symbolfile/OWNERS new file mode 100644 index 000000000..f0d873359 --- /dev/null +++ b/cc/symbolfile/OWNERS @@ -0,0 +1 @@ +danalbert@google.com diff --git a/cc/scriptlib/gen_stub_libs.py b/cc/symbolfile/__init__.py old mode 100755 new mode 100644 similarity index 73% rename from cc/scriptlib/gen_stub_libs.py rename to cc/symbolfile/__init__.py index a9887f9fd..faa3823f3 --- a/cc/scriptlib/gen_stub_libs.py +++ b/cc/symbolfile/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # # Copyright (C) 2016 The Android Open Source Project # @@ -14,13 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # -"""Generates source for stub shared libraries for the NDK.""" -import argparse -import json +"""Parser for Android's version script information.""" import logging -import os import re -import sys ALL_ARCHITECTURES = ( @@ -57,6 +52,24 @@ def is_api_level_tag(tag): return False +def decode_api_level(api, api_map): + """Decodes the API level argument into the API level number. + + For the average case, this just decodes the integer value from the string, + but for unreleased APIs we need to translate from the API codename (like + "O") to the future API level for that codename. + """ + try: + return int(api) + except ValueError: + pass + + if api == "current": + return FUTURE_API_LEVEL + + return api_map[api] + + def decode_api_level_tags(tags, api_map): """Decodes API level code names in a list of tags. @@ -369,143 +382,3 @@ class SymbolFileParser: break self.current_line = line return self.current_line - - -class Generator: - """Output generator that writes stub source files and version scripts.""" - def __init__(self, src_file, version_script, arch, api, llndk, apex): - self.src_file = src_file - self.version_script = version_script - self.arch = arch - self.api = api - self.llndk = llndk - self.apex = apex - - def write(self, versions): - """Writes all symbol data to the output files.""" - for version in versions: - self.write_version(version) - - def write_version(self, version): - """Writes a single version block's data to the output files.""" - if should_omit_version(version, self.arch, self.api, self.llndk, - self.apex): - return - - section_versioned = symbol_versioned_in_api(version.tags, self.api) - version_empty = True - pruned_symbols = [] - for symbol in version.symbols: - if should_omit_symbol(symbol, self.arch, self.api, self.llndk, - self.apex): - continue - - if symbol_versioned_in_api(symbol.tags, self.api): - version_empty = False - pruned_symbols.append(symbol) - - if len(pruned_symbols) > 0: - if not version_empty and section_versioned: - self.version_script.write(version.name + ' {\n') - self.version_script.write(' global:\n') - for symbol in pruned_symbols: - emit_version = symbol_versioned_in_api(symbol.tags, self.api) - if section_versioned and emit_version: - self.version_script.write(' ' + symbol.name + ';\n') - - weak = '' - if 'weak' in symbol.tags: - weak = '__attribute__((weak)) ' - - if 'var' in symbol.tags: - self.src_file.write('{}int {} = 0;\n'.format( - weak, symbol.name)) - else: - self.src_file.write('{}void {}() {{}}\n'.format( - weak, symbol.name)) - - if not version_empty and section_versioned: - base = '' if version.base is None else ' ' + version.base - self.version_script.write('}' + base + ';\n') - - -def decode_api_level(api, api_map): - """Decodes the API level argument into the API level number. - - For the average case, this just decodes the integer value from the string, - but for unreleased APIs we need to translate from the API codename (like - "O") to the future API level for that codename. - """ - try: - return int(api) - except ValueError: - pass - - if api == "current": - return FUTURE_API_LEVEL - - return api_map[api] - - -def parse_args(): - """Parses and returns command line arguments.""" - parser = argparse.ArgumentParser() - - parser.add_argument('-v', '--verbose', action='count', default=0) - - parser.add_argument( - '--api', required=True, help='API level being targeted.') - parser.add_argument( - '--arch', choices=ALL_ARCHITECTURES, required=True, - help='Architecture being targeted.') - parser.add_argument( - '--llndk', action='store_true', help='Use the LLNDK variant.') - parser.add_argument( - '--apex', action='store_true', help='Use the APEX variant.') - - parser.add_argument( - '--api-map', type=os.path.realpath, required=True, - help='Path to the API level map JSON file.') - - parser.add_argument( - 'symbol_file', type=os.path.realpath, help='Path to symbol file.') - parser.add_argument( - 'stub_src', type=os.path.realpath, - help='Path to output stub source file.') - parser.add_argument( - 'version_script', type=os.path.realpath, - help='Path to output version script.') - - return parser.parse_args() - - -def main(): - """Program entry point.""" - args = parse_args() - - with open(args.api_map) as map_file: - api_map = json.load(map_file) - api = decode_api_level(args.api, api_map) - - verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG) - verbosity = args.verbose - if verbosity > 2: - verbosity = 2 - logging.basicConfig(level=verbose_map[verbosity]) - - with open(args.symbol_file) as symbol_file: - try: - versions = SymbolFileParser(symbol_file, api_map, args.arch, api, - args.llndk, args.apex).parse() - except MultiplyDefinedSymbolError as ex: - sys.exit('{}: error: {}'.format(args.symbol_file, ex)) - - with open(args.stub_src, 'w') as src_file: - with open(args.version_script, 'w') as version_file: - generator = Generator(src_file, version_file, args.arch, api, - args.llndk, args.apex) - generator.write(versions) - - -if __name__ == '__main__': - main() diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py new file mode 100644 index 000000000..c91131fee --- /dev/null +++ b/cc/symbolfile/test_symbolfile.py @@ -0,0 +1,493 @@ +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Tests for symbolfile.""" +import io +import textwrap +import unittest + +import symbolfile + +# pylint: disable=missing-docstring + + +class DecodeApiLevelTest(unittest.TestCase): + def test_decode_api_level(self): + self.assertEqual(9, symbolfile.decode_api_level('9', {})) + self.assertEqual(9000, symbolfile.decode_api_level('O', {'O': 9000})) + + with self.assertRaises(KeyError): + symbolfile.decode_api_level('O', {}) + + +class TagsTest(unittest.TestCase): + def test_get_tags_no_tags(self): + self.assertEqual([], symbolfile.get_tags('')) + self.assertEqual([], symbolfile.get_tags('foo bar baz')) + + def test_get_tags(self): + self.assertEqual(['foo', 'bar'], symbolfile.get_tags('# foo bar')) + self.assertEqual(['bar', 'baz'], symbolfile.get_tags('foo # bar baz')) + + def test_split_tag(self): + self.assertTupleEqual(('foo', 'bar'), symbolfile.split_tag('foo=bar')) + self.assertTupleEqual(('foo', 'bar=baz'), symbolfile.split_tag('foo=bar=baz')) + with self.assertRaises(ValueError): + symbolfile.split_tag('foo') + + def test_get_tag_value(self): + self.assertEqual('bar', symbolfile.get_tag_value('foo=bar')) + self.assertEqual('bar=baz', symbolfile.get_tag_value('foo=bar=baz')) + with self.assertRaises(ValueError): + symbolfile.get_tag_value('foo') + + def test_is_api_level_tag(self): + self.assertTrue(symbolfile.is_api_level_tag('introduced=24')) + self.assertTrue(symbolfile.is_api_level_tag('introduced-arm=24')) + self.assertTrue(symbolfile.is_api_level_tag('versioned=24')) + + # Shouldn't try to process things that aren't a key/value tag. + self.assertFalse(symbolfile.is_api_level_tag('arm')) + self.assertFalse(symbolfile.is_api_level_tag('introduced')) + self.assertFalse(symbolfile.is_api_level_tag('versioned')) + + # We don't support arch specific `versioned` tags. + self.assertFalse(symbolfile.is_api_level_tag('versioned-arm=24')) + + def test_decode_api_level_tags(self): + api_map = { + 'O': 9000, + 'P': 9001, + } + + tags = [ + 'introduced=9', + 'introduced-arm=14', + 'versioned=16', + 'arm', + 'introduced=O', + 'introduced=P', + ] + expected_tags = [ + 'introduced=9', + 'introduced-arm=14', + 'versioned=16', + 'arm', + 'introduced=9000', + 'introduced=9001', + ] + self.assertListEqual( + expected_tags, symbolfile.decode_api_level_tags(tags, api_map)) + + with self.assertRaises(symbolfile.ParseError): + symbolfile.decode_api_level_tags(['introduced=O'], {}) + + +class PrivateVersionTest(unittest.TestCase): + def test_version_is_private(self): + self.assertFalse(symbolfile.version_is_private('foo')) + self.assertFalse(symbolfile.version_is_private('PRIVATE')) + self.assertFalse(symbolfile.version_is_private('PLATFORM')) + self.assertFalse(symbolfile.version_is_private('foo_private')) + self.assertFalse(symbolfile.version_is_private('foo_platform')) + self.assertFalse(symbolfile.version_is_private('foo_PRIVATE_')) + self.assertFalse(symbolfile.version_is_private('foo_PLATFORM_')) + + self.assertTrue(symbolfile.version_is_private('foo_PRIVATE')) + self.assertTrue(symbolfile.version_is_private('foo_PLATFORM')) + + +class SymbolPresenceTest(unittest.TestCase): + def test_symbol_in_arch(self): + self.assertTrue(symbolfile.symbol_in_arch([], 'arm')) + self.assertTrue(symbolfile.symbol_in_arch(['arm'], 'arm')) + + self.assertFalse(symbolfile.symbol_in_arch(['x86'], 'arm')) + + def test_symbol_in_api(self): + self.assertTrue(symbolfile.symbol_in_api([], 'arm', 9)) + self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 9)) + self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api(['introduced-x86=14'], 'arm', 9)) + self.assertTrue(symbolfile.symbol_in_api( + ['introduced-arm=9', 'introduced-x86=21'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api( + ['introduced=9', 'introduced-x86=21'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api( + ['introduced=21', 'introduced-arm=9'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api( + ['future'], 'arm', symbolfile.FUTURE_API_LEVEL)) + + self.assertFalse(symbolfile.symbol_in_api(['introduced=14'], 'arm', 9)) + self.assertFalse(symbolfile.symbol_in_api(['introduced-arm=14'], 'arm', 9)) + self.assertFalse(symbolfile.symbol_in_api(['future'], 'arm', 9)) + self.assertFalse(symbolfile.symbol_in_api( + ['introduced=9', 'future'], 'arm', 14)) + self.assertFalse(symbolfile.symbol_in_api( + ['introduced-arm=9', 'future'], 'arm', 14)) + self.assertFalse(symbolfile.symbol_in_api( + ['introduced-arm=21', 'introduced-x86=9'], 'arm', 14)) + self.assertFalse(symbolfile.symbol_in_api( + ['introduced=9', 'introduced-arm=21'], 'arm', 14)) + self.assertFalse(symbolfile.symbol_in_api( + ['introduced=21', 'introduced-x86=9'], 'arm', 14)) + + # Interesting edge case: this symbol should be omitted from the + # library, but this call should still return true because none of the + # tags indiciate that it's not present in this API level. + self.assertTrue(symbolfile.symbol_in_api(['x86'], 'arm', 9)) + + def test_verioned_in_api(self): + self.assertTrue(symbolfile.symbol_versioned_in_api([], 9)) + self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 9)) + self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 14)) + + self.assertFalse(symbolfile.symbol_versioned_in_api(['versioned=14'], 9)) + + +class OmitVersionTest(unittest.TestCase): + def test_omit_private(self): + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, [], []), 'arm', 9, False, + False)) + + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo_PRIVATE', None, [], []), 'arm', 9, + False, False)) + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo_PLATFORM', None, [], []), 'arm', 9, + False, False)) + + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['platform-only'], []), 'arm', + 9, False, False)) + + def test_omit_llndk(self): + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9, + False, False)) + + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, [], []), 'arm', 9, True, + False)) + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9, True, + False)) + + def test_omit_apex(self): + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False, + False)) + + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, [], []), 'arm', 9, False, + True)) + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False, + True)) + + def test_omit_arch(self): + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, [], []), 'arm', 9, False, + False)) + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['arm'], []), 'arm', 9, False, + False)) + + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['x86'], []), 'arm', 9, False, + False)) + + def test_omit_api(self): + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, [], []), 'arm', 9, False, + False)) + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['introduced=9'], []), 'arm', + 9, False, False)) + + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['introduced=14'], []), 'arm', + 9, False, False)) + + +class OmitSymbolTest(unittest.TestCase): + def test_omit_llndk(self): + self.assertTrue( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']), + 'arm', 9, False, False)) + + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', + 9, True, False)) + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']), + 'arm', 9, True, False)) + + def test_omit_apex(self): + self.assertTrue( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['apex']), + 'arm', 9, False, False)) + + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', + 9, False, True)) + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['apex']), + 'arm', 9, False, True)) + + def test_omit_arch(self): + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', + 9, False, False)) + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['arm']), + 'arm', 9, False, False)) + + self.assertTrue( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['x86']), + 'arm', 9, False, False)) + + def test_omit_api(self): + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', + 9, False, False)) + self.assertFalse( + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', ['introduced=9']), 'arm', 9, False, + False)) + + self.assertTrue( + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', ['introduced=14']), 'arm', 9, False, + False)) + + +class SymbolFileParseTest(unittest.TestCase): + def test_next_line(self): + input_file = io.StringIO(textwrap.dedent("""\ + foo + + bar + # baz + qux + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + self.assertIsNone(parser.current_line) + + self.assertEqual('foo', parser.next_line().strip()) + self.assertEqual('foo', parser.current_line.strip()) + + self.assertEqual('bar', parser.next_line().strip()) + self.assertEqual('bar', parser.current_line.strip()) + + self.assertEqual('qux', parser.next_line().strip()) + self.assertEqual('qux', parser.current_line.strip()) + + self.assertEqual('', parser.next_line()) + self.assertEqual('', parser.current_line) + + def test_parse_version(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { # foo bar + baz; + qux; # woodly doodly + }; + + VERSION_2 { + } VERSION_1; # asdf + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + + parser.next_line() + version = parser.parse_version() + self.assertEqual('VERSION_1', version.name) + self.assertIsNone(version.base) + self.assertEqual(['foo', 'bar'], version.tags) + + expected_symbols = [ + symbolfile.Symbol('baz', []), + symbolfile.Symbol('qux', ['woodly', 'doodly']), + ] + self.assertEqual(expected_symbols, version.symbols) + + parser.next_line() + version = parser.parse_version() + self.assertEqual('VERSION_2', version.name) + self.assertEqual('VERSION_1', version.base) + self.assertEqual([], version.tags) + + def test_parse_version_eof(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser.next_line() + with self.assertRaises(symbolfile.ParseError): + parser.parse_version() + + def test_unknown_scope_label(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + foo: + } + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser.next_line() + with self.assertRaises(symbolfile.ParseError): + parser.parse_version() + + def test_parse_symbol(self): + input_file = io.StringIO(textwrap.dedent("""\ + foo; + bar; # baz qux + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + + parser.next_line() + symbol = parser.parse_symbol() + self.assertEqual('foo', symbol.name) + self.assertEqual([], symbol.tags) + + parser.next_line() + symbol = parser.parse_symbol() + self.assertEqual('bar', symbol.name) + self.assertEqual(['baz', 'qux'], symbol.tags) + + def test_wildcard_symbol_global(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + *; + }; + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser.next_line() + with self.assertRaises(symbolfile.ParseError): + parser.parse_version() + + def test_wildcard_symbol_local(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + local: + *; + }; + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser.next_line() + version = parser.parse_version() + self.assertEqual([], version.symbols) + + def test_missing_semicolon(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + foo + }; + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser.next_line() + with self.assertRaises(symbolfile.ParseError): + parser.parse_version() + + def test_parse_fails_invalid_input(self): + with self.assertRaises(symbolfile.ParseError): + input_file = io.StringIO('foo') + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, + False, False) + parser.parse() + + def test_parse(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + local: + hidden1; + global: + foo; + bar; # baz + }; + + VERSION_2 { # wasd + # Implicit global scope. + woodly; + doodly; # asdf + local: + qwerty; + } VERSION_1; + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + versions = parser.parse() + + expected = [ + symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', []), + symbolfile.Symbol('bar', ['baz']), + ]), + symbolfile.Version('VERSION_2', 'VERSION_1', ['wasd'], [ + symbolfile.Symbol('woodly', []), + symbolfile.Symbol('doodly', ['asdf']), + ]), + ] + + self.assertEqual(expected, versions) + + def test_parse_llndk_apex_symbol(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + foo; + bar; # llndk + baz; # llndk apex + qux; # apex + }; + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, True) + + parser.next_line() + version = parser.parse_version() + self.assertEqual('VERSION_1', version.name) + self.assertIsNone(version.base) + + expected_symbols = [ + symbolfile.Symbol('foo', []), + symbolfile.Symbol('bar', ['llndk']), + symbolfile.Symbol('baz', ['llndk', 'apex']), + symbolfile.Symbol('qux', ['apex']), + ] + self.assertEqual(expected_symbols, version.symbols) + + +def main(): + suite = unittest.TestLoader().loadTestsFromName(__name__) + unittest.TextTestRunner(verbosity=3).run(suite) + + +if __name__ == '__main__': + main()