Merge changes I2ac22f7c,I7fc9d55a

* changes:
  Get NDK python script tests running.
  Fix pylint issues in gen_stub_libs.
This commit is contained in:
Dan Albert 2020-07-16 23:06:12 +00:00 committed by Gerrit Code Review
commit 4fea65086f
20 changed files with 1580 additions and 1243 deletions

3
OWNERS
View file

@ -6,9 +6,8 @@ per-file * = jungjw@google.com
per-file * = patricearruda@google.com per-file * = patricearruda@google.com
per-file * = paulduffin@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 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 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 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 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. // 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 { python_test_host {
name: "test_ndk_api_coverage_parser", name: "test_ndk_api_coverage_parser",
main: "test_ndk_api_coverage_parser.py", main: "test_ndk_api_coverage_parser.py",
srcs: [ srcs: [
"test_ndk_api_coverage_parser.py", "test_ndk_api_coverage_parser.py",
], ],
libs: [
"ndk_api_coverage_parser_lib",
"symbolfile",
],
} }
python_binary_host { python_binary_host {
name: "ndk_api_coverage_parser", name: "ndk_api_coverage_parser",
main: "ndk_api_coverage_parser.py", main: "__init__.py",
srcs: [ srcs: [
"gen_stub_libs.py", "__init__.py",
"ndk_api_coverage_parser.py", ],
libs: [
"symbolfile",
], ],
} }

View file

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

View file

@ -21,7 +21,7 @@ import os
import sys import sys
from xml.etree.ElementTree import Element, SubElement, tostring 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' ROOT_ELEMENT_TAG = 'ndk-library'

View file

@ -20,7 +20,7 @@ import textwrap
import unittest import unittest
from xml.etree.ElementTree import tostring 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 import ndk_api_coverage_parser as nparser

View file

@ -26,17 +26,16 @@ import (
) )
func init() { func init() {
pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen")
pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser") pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser")
} }
var ( var (
toolPath = pctx.SourcePathVariable("toolPath", "build/soong/cc/scriptlib/gen_stub_libs.py")
genStubSrc = pctx.AndroidStaticRule("genStubSrc", genStubSrc = pctx.AndroidStaticRule("genStubSrc",
blueprint.RuleParams{ blueprint.RuleParams{
Command: "$toolPath --arch $arch --api $apiLevel --api-map " + Command: "$ndkStubGenerator --arch $arch --api $apiLevel " +
"$apiMap $flags $in $out", "--api-map $apiMap $flags $in $out",
CommandDeps: []string{"$toolPath"}, CommandDeps: []string{"$ndkStubGenerator"},
}, "arch", "apiLevel", "apiMap", "flags") }, "arch", "apiLevel", "apiMap", "flags")
parseNdkApiRule = pctx.AndroidStaticRule("parseNdkApiRule", parseNdkApiRule = pctx.AndroidStaticRule("parseNdkApiRule",
@ -78,9 +77,9 @@ type libraryProperties struct {
// https://github.com/android-ndk/ndk/issues/265. // https://github.com/android-ndk/ndk/issues/265.
Unversioned_until *string Unversioned_until *string
// Private property for use by the mutator that splits per-API level. // Private property for use by the mutator that splits per-API level. Can be
// can be one of <number:sdk_version> or <codename> or "current" // one of <number:sdk_version> or <codename> or "current" passed to
// passed to "gen_stub_libs.py" as it is // "ndkstubgen.py" as it is
ApiLevel string `blueprint:"mutated"` ApiLevel string `blueprint:"mutated"`
// True if this API is not yet ready to be shipped in the NDK. It will be // 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,280 +1,11 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL] [MESSAGES CONTROL]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=design,fixme disable=design,fixme
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[BASIC] [BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct attribute names in class
# bodies
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_ good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[SIMILARITIES] [SIMILARITIES]
ignore-imports=yes
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[VARIABLES] [VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the beginning of the name of dummy variables
# (i.e. not used).
dummy-variables-rgx=_|dummy dummy-variables-rgx=_|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception

View file

@ -1,807 +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 cm:
parser.parse()
self.assertEquals(['bar', 'foo'],
cm.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 # Copyright (C) 2016 The Android Open Source Project
# #
@ -14,13 +13,9 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
"""Generates source for stub shared libraries for the NDK.""" """Parser for Android's version script information."""
import argparse
import json
import logging import logging
import os
import re import re
import sys
ALL_ARCHITECTURES = ( ALL_ARCHITECTURES = (
@ -57,6 +52,24 @@ def is_api_level_tag(tag):
return False 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): def decode_api_level_tags(tags, api_map):
"""Decodes API level code names in a list of tags. """Decodes API level code names in a list of tags.
@ -118,7 +131,8 @@ def should_omit_version(version, arch, api, llndk, apex):
if 'platform-only' in version.tags: if 'platform-only' in version.tags:
return True return True
no_llndk_no_apex = 'llndk' not in version.tags and 'apex' not in version.tags no_llndk_no_apex = ('llndk' not in version.tags
and 'apex' not in version.tags)
keep = no_llndk_no_apex or \ keep = no_llndk_no_apex or \
('llndk' in version.tags and llndk) or \ ('llndk' in version.tags and llndk) or \
('apex' in version.tags and apex) ('apex' in version.tags and apex)
@ -205,7 +219,6 @@ def symbol_versioned_in_api(tags, api):
class ParseError(RuntimeError): class ParseError(RuntimeError):
"""An error that occurred while parsing a symbol file.""" """An error that occurred while parsing a symbol file."""
pass
class MultiplyDefinedSymbolError(RuntimeError): class MultiplyDefinedSymbolError(RuntimeError):
@ -217,7 +230,7 @@ class MultiplyDefinedSymbolError(RuntimeError):
self.multiply_defined_symbols = multiply_defined_symbols self.multiply_defined_symbols = multiply_defined_symbols
class Version(object): class Version:
"""A version block of a symbol file.""" """A version block of a symbol file."""
def __init__(self, name, base, tags, symbols): def __init__(self, name, base, tags, symbols):
self.name = name self.name = name
@ -237,7 +250,7 @@ class Version(object):
return True return True
class Symbol(object): class Symbol:
"""A symbol definition from a symbol file.""" """A symbol definition from a symbol file."""
def __init__(self, name, tags): def __init__(self, name, tags):
self.name = name self.name = name
@ -247,7 +260,7 @@ class Symbol(object):
return self.name == other.name and set(self.tags) == set(other.tags) return self.name == other.name and set(self.tags) == set(other.tags)
class SymbolFileParser(object): class SymbolFileParser:
"""Parses NDK symbol files.""" """Parses NDK symbol files."""
def __init__(self, input_file, api_map, arch, api, llndk, apex): def __init__(self, input_file, api_map, arch, api, llndk, apex):
self.input_file = input_file self.input_file = input_file
@ -283,11 +296,13 @@ class SymbolFileParser(object):
symbol_names = set() symbol_names = set()
multiply_defined_symbols = set() multiply_defined_symbols = set()
for version in versions: for version in versions:
if should_omit_version(version, self.arch, self.api, self.llndk, self.apex): if should_omit_version(version, self.arch, self.api, self.llndk,
self.apex):
continue continue
for symbol in version.symbols: for symbol in version.symbols:
if should_omit_symbol(symbol, self.arch, self.api, self.llndk, self.apex): if should_omit_symbol(symbol, self.arch, self.api, self.llndk,
self.apex):
continue continue
if symbol.name in symbol_names: if symbol.name in symbol_names:
@ -367,141 +382,3 @@ class SymbolFileParser(object):
break break
self.current_line = line self.current_line = line
return self.current_line return self.current_line
class Generator(object):
"""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()