Get NDK python script tests running.

Imports weren't working in tests because the package had been created.
The Python "binaries" built by Soong don't seem to take their own
pkg_path into account, so I split the separate pieces of code here out
into their own packages.

Note that the ndk_api_coverage_parser tests do not actually pass
before or after this change (seems like it might be a
non-deterministic ordering issue in the attributes of the generated
output?), but they can at least be run now.

Test: pytest ndkstubgen
Test: pytest symbolfile
Test: pytest ndk_api_coverage_parser
Test: out/host/linux-x86/nativetest64/test_ndkstubgen/test_ndkstubgen
Test: out/host/linux-x86/nativetest64/test_symbolfile/test_symbolfile
Test: out/host/linux-x86/nativetest64/test_ndk_api_coverage_parser/test_ndk_api_coverage_parser
Bug: None
Change-Id: I2ac22f7ced7566e4808070f2f72fd04355846e0b
This commit is contained in:
Dan Albert 2020-06-22 15:10:31 -07:00
parent 802cc82af6
commit 06f58afd81
19 changed files with 1570 additions and 982 deletions

3
OWNERS
View file

@ -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

140
cc/ndk_api_coverage_parser/.gitignore vendored Normal file
View file

@ -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/

View file

@ -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",
],
}

View file

@ -0,0 +1 @@
sophiez@google.com

View file

@ -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'

View file

@ -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

View file

@ -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 <number:sdk_version> or <codename> 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 <number:sdk_version> or <codename> 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

140
cc/ndkstubgen/.gitignore vendored Normal file
View file

@ -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/

48
cc/ndkstubgen/Android.bp Normal file
View file

@ -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",
],
}

1
cc/ndkstubgen/OWNERS Normal file
View file

@ -0,0 +1 @@
danalbert@google.com

149
cc/ndkstubgen/__init__.py Executable file
View file

@ -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()

378
cc/ndkstubgen/test_ndkstubgen.py Executable file
View file

@ -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()

View file

@ -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()

140
cc/symbolfile/.gitignore vendored Normal file
View file

@ -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/

33
cc/symbolfile/Android.bp Normal file
View file

@ -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",
],
}

1
cc/symbolfile/OWNERS Normal file
View file

@ -0,0 +1 @@
danalbert@google.com

View file

@ -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()

View file

@ -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()