Add type information to symbolfile and ndkstubgen.

Test: mypy symbolfile
Test: pytest
Bug: None
Change-Id: I6b1045d315e5a10e699d31de9fafc084d82768b2
This commit is contained in:
Dan Albert
2020-06-23 11:21:21 -07:00
parent 8bd5095362
commit af7b36dea5
6 changed files with 378 additions and 300 deletions

View File

@@ -20,13 +20,16 @@ import json
import logging import logging
import os import os
import sys import sys
from typing import Iterable, TextIO
import symbolfile import symbolfile
from symbolfile import Arch, Version
class Generator: class Generator:
"""Output generator that writes stub source files and version scripts.""" """Output generator that writes stub source files and version scripts."""
def __init__(self, src_file, version_script, arch, api, llndk, apex): def __init__(self, src_file: TextIO, version_script: TextIO, arch: Arch,
api: int, llndk: bool, apex: bool) -> None:
self.src_file = src_file self.src_file = src_file
self.version_script = version_script self.version_script = version_script
self.arch = arch self.arch = arch
@@ -34,12 +37,12 @@ class Generator:
self.llndk = llndk self.llndk = llndk
self.apex = apex self.apex = apex
def write(self, versions): def write(self, versions: Iterable[Version]) -> None:
"""Writes all symbol data to the output files.""" """Writes all symbol data to the output files."""
for version in versions: for version in versions:
self.write_version(version) self.write_version(version)
def write_version(self, version): def write_version(self, version: Version) -> None:
"""Writes a single version block's data to the output files.""" """Writes a single version block's data to the output files."""
if symbolfile.should_omit_version(version, self.arch, self.api, if symbolfile.should_omit_version(version, self.arch, self.api,
self.llndk, self.apex): self.llndk, self.apex):
@@ -84,7 +87,7 @@ class Generator:
self.version_script.write('}' + base + ';\n') self.version_script.write('}' + base + ';\n')
def parse_args(): def parse_args() -> argparse.Namespace:
"""Parses and returns command line arguments.""" """Parses and returns command line arguments."""
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@@ -100,23 +103,31 @@ def parse_args():
parser.add_argument( parser.add_argument(
'--apex', action='store_true', help='Use the APEX variant.') '--apex', action='store_true', help='Use the APEX variant.')
# https://github.com/python/mypy/issues/1317
# mypy has issues with using os.path.realpath as an argument here.
parser.add_argument( parser.add_argument(
'--api-map', type=os.path.realpath, required=True, '--api-map',
type=os.path.realpath, # type: ignore
required=True,
help='Path to the API level map JSON file.') help='Path to the API level map JSON file.')
parser.add_argument( parser.add_argument(
'symbol_file', type=os.path.realpath, help='Path to symbol file.') 'symbol_file',
type=os.path.realpath, # type: ignore
help='Path to symbol file.')
parser.add_argument( parser.add_argument(
'stub_src', type=os.path.realpath, 'stub_src',
type=os.path.realpath, # type: ignore
help='Path to output stub source file.') help='Path to output stub source file.')
parser.add_argument( parser.add_argument(
'version_script', type=os.path.realpath, 'version_script',
type=os.path.realpath, # type: ignore
help='Path to output version script.') help='Path to output version script.')
return parser.parse_args() return parser.parse_args()
def main(): def main() -> None:
"""Program entry point.""" """Program entry point."""
args = parse_args() args = parse_args()

2
cc/ndkstubgen/mypy.ini Normal file
View File

@@ -0,0 +1,2 @@
[mypy]
disallow_untyped_defs = True

View File

@@ -21,19 +21,20 @@ import unittest
import ndkstubgen import ndkstubgen
import symbolfile import symbolfile
from symbolfile import Arch, Tag
# pylint: disable=missing-docstring # pylint: disable=missing-docstring
class GeneratorTest(unittest.TestCase): class GeneratorTest(unittest.TestCase):
def test_omit_version(self): def test_omit_version(self) -> None:
# Thorough testing of the cases involved here is handled by # Thorough testing of the cases involved here is handled by
# OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest.
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
False, False) 9, False, False)
version = symbolfile.Version('VERSION_PRIVATE', None, [], [ version = symbolfile.Version('VERSION_PRIVATE', None, [], [
symbolfile.Symbol('foo', []), symbolfile.Symbol('foo', []),
@@ -42,74 +43,75 @@ class GeneratorTest(unittest.TestCase):
self.assertEqual('', src_file.getvalue()) self.assertEqual('', src_file.getvalue())
self.assertEqual('', version_file.getvalue()) self.assertEqual('', version_file.getvalue())
version = symbolfile.Version('VERSION', None, ['x86'], [ version = symbolfile.Version('VERSION', None, [Tag('x86')], [
symbolfile.Symbol('foo', []), symbolfile.Symbol('foo', []),
]) ])
generator.write_version(version) generator.write_version(version)
self.assertEqual('', src_file.getvalue()) self.assertEqual('', src_file.getvalue())
self.assertEqual('', version_file.getvalue()) self.assertEqual('', version_file.getvalue())
version = symbolfile.Version('VERSION', None, ['introduced=14'], [ version = symbolfile.Version('VERSION', None, [Tag('introduced=14')], [
symbolfile.Symbol('foo', []), symbolfile.Symbol('foo', []),
]) ])
generator.write_version(version) generator.write_version(version)
self.assertEqual('', src_file.getvalue()) self.assertEqual('', src_file.getvalue())
self.assertEqual('', version_file.getvalue()) self.assertEqual('', version_file.getvalue())
def test_omit_symbol(self): def test_omit_symbol(self) -> None:
# Thorough testing of the cases involved here is handled by # Thorough testing of the cases involved here is handled by
# SymbolPresenceTest. # SymbolPresenceTest.
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
False, False) 9, False, False)
version = symbolfile.Version('VERSION_1', None, [], [ version = symbolfile.Version('VERSION_1', None, [], [
symbolfile.Symbol('foo', ['x86']), symbolfile.Symbol('foo', [Tag('x86')]),
]) ])
generator.write_version(version) generator.write_version(version)
self.assertEqual('', src_file.getvalue()) self.assertEqual('', src_file.getvalue())
self.assertEqual('', version_file.getvalue()) self.assertEqual('', version_file.getvalue())
version = symbolfile.Version('VERSION_1', None, [], [ version = symbolfile.Version('VERSION_1', None, [], [
symbolfile.Symbol('foo', ['introduced=14']), symbolfile.Symbol('foo', [Tag('introduced=14')]),
]) ])
generator.write_version(version) generator.write_version(version)
self.assertEqual('', src_file.getvalue()) self.assertEqual('', src_file.getvalue())
self.assertEqual('', version_file.getvalue()) self.assertEqual('', version_file.getvalue())
version = symbolfile.Version('VERSION_1', None, [], [ version = symbolfile.Version('VERSION_1', None, [], [
symbolfile.Symbol('foo', ['llndk']), symbolfile.Symbol('foo', [Tag('llndk')]),
]) ])
generator.write_version(version) generator.write_version(version)
self.assertEqual('', src_file.getvalue()) self.assertEqual('', src_file.getvalue())
self.assertEqual('', version_file.getvalue()) self.assertEqual('', version_file.getvalue())
version = symbolfile.Version('VERSION_1', None, [], [ version = symbolfile.Version('VERSION_1', None, [], [
symbolfile.Symbol('foo', ['apex']), symbolfile.Symbol('foo', [Tag('apex')]),
]) ])
generator.write_version(version) generator.write_version(version)
self.assertEqual('', src_file.getvalue()) self.assertEqual('', src_file.getvalue())
self.assertEqual('', version_file.getvalue()) self.assertEqual('', version_file.getvalue())
def test_write(self): def test_write(self) -> None:
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
False, False) 9, False, False)
versions = [ versions = [
symbolfile.Version('VERSION_1', None, [], [ symbolfile.Version('VERSION_1', None, [], [
symbolfile.Symbol('foo', []), symbolfile.Symbol('foo', []),
symbolfile.Symbol('bar', ['var']), symbolfile.Symbol('bar', [Tag('var')]),
symbolfile.Symbol('woodly', ['weak']), symbolfile.Symbol('woodly', [Tag('weak')]),
symbolfile.Symbol('doodly', ['weak', 'var']), symbolfile.Symbol('doodly',
[Tag('weak'), Tag('var')]),
]), ]),
symbolfile.Version('VERSION_2', 'VERSION_1', [], [ symbolfile.Version('VERSION_2', 'VERSION_1', [], [
symbolfile.Symbol('baz', []), symbolfile.Symbol('baz', []),
]), ]),
symbolfile.Version('VERSION_3', 'VERSION_1', [], [ symbolfile.Version('VERSION_3', 'VERSION_1', [], [
symbolfile.Symbol('qux', ['versioned=14']), symbolfile.Symbol('qux', [Tag('versioned=14')]),
]), ]),
] ]
@@ -141,7 +143,7 @@ class GeneratorTest(unittest.TestCase):
class IntegrationTest(unittest.TestCase): class IntegrationTest(unittest.TestCase):
def test_integration(self): def test_integration(self) -> None:
api_map = { api_map = {
'O': 9000, 'O': 9000,
'P': 9001, 'P': 9001,
@@ -178,14 +180,14 @@ class IntegrationTest(unittest.TestCase):
wobble; wobble;
} VERSION_4; } VERSION_4;
""")) """))
parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9, parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'),
False, False) 9, False, False)
versions = parser.parse() versions = parser.parse()
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
False, False) 9, False, False)
generator.write(versions) generator.write(versions)
expected_src = textwrap.dedent("""\ expected_src = textwrap.dedent("""\
@@ -213,7 +215,7 @@ class IntegrationTest(unittest.TestCase):
""") """)
self.assertEqual(expected_version, version_file.getvalue()) self.assertEqual(expected_version, version_file.getvalue())
def test_integration_future_api(self): def test_integration_future_api(self) -> None:
api_map = { api_map = {
'O': 9000, 'O': 9000,
'P': 9001, 'P': 9001,
@@ -230,14 +232,14 @@ class IntegrationTest(unittest.TestCase):
*; *;
}; };
""")) """))
parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9001, parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'),
False, False) 9001, False, False)
versions = parser.parse() versions = parser.parse()
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9001, generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
False, False) 9001, False, False)
generator.write(versions) generator.write(versions)
expected_src = textwrap.dedent("""\ expected_src = textwrap.dedent("""\
@@ -255,7 +257,7 @@ class IntegrationTest(unittest.TestCase):
""") """)
self.assertEqual(expected_version, version_file.getvalue()) self.assertEqual(expected_version, version_file.getvalue())
def test_multiple_definition(self): def test_multiple_definition(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
global: global:
@@ -280,8 +282,8 @@ class IntegrationTest(unittest.TestCase):
} VERSION_2; } VERSION_2;
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False) False, False)
with self.assertRaises( with self.assertRaises(
symbolfile.MultiplyDefinedSymbolError) as ex_context: symbolfile.MultiplyDefinedSymbolError) as ex_context:
@@ -289,7 +291,7 @@ class IntegrationTest(unittest.TestCase):
self.assertEqual(['bar', 'foo'], self.assertEqual(['bar', 'foo'],
ex_context.exception.multiply_defined_symbols) ex_context.exception.multiply_defined_symbols)
def test_integration_with_apex(self): def test_integration_with_apex(self) -> None:
api_map = { api_map = {
'O': 9000, 'O': 9000,
'P': 9001, 'P': 9001,
@@ -328,14 +330,14 @@ class IntegrationTest(unittest.TestCase):
wobble; wobble;
} VERSION_4; } VERSION_4;
""")) """))
parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9, parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('arm'),
False, True) 9, False, True)
versions = parser.parse() versions = parser.parse()
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
False, True) 9, False, True)
generator.write(versions) generator.write(versions)
expected_src = textwrap.dedent("""\ expected_src = textwrap.dedent("""\
@@ -369,7 +371,8 @@ class IntegrationTest(unittest.TestCase):
""") """)
self.assertEqual(expected_version, version_file.getvalue()) self.assertEqual(expected_version, version_file.getvalue())
def main():
def main() -> None:
suite = unittest.TestLoader().loadTestsFromName(__name__) suite = unittest.TestLoader().loadTestsFromName(__name__)
unittest.TextTestRunner(verbosity=3).run(suite) unittest.TextTestRunner(verbosity=3).run(suite)

View File

@@ -14,15 +14,31 @@
# limitations under the License. # limitations under the License.
# #
"""Parser for Android's version script information.""" """Parser for Android's version script information."""
from dataclasses import dataclass
import logging import logging
import re import re
from typing import (
Dict,
Iterable,
List,
Mapping,
NewType,
Optional,
TextIO,
Tuple,
)
ApiMap = Mapping[str, int]
Arch = NewType('Arch', str)
Tag = NewType('Tag', str)
ALL_ARCHITECTURES = ( ALL_ARCHITECTURES = (
'arm', Arch('arm'),
'arm64', Arch('arm64'),
'x86', Arch('x86'),
'x86_64', Arch('x86_64'),
) )
@@ -30,18 +46,36 @@ ALL_ARCHITECTURES = (
FUTURE_API_LEVEL = 10000 FUTURE_API_LEVEL = 10000
def logger(): def logger() -> logging.Logger:
"""Return the main logger for this module.""" """Return the main logger for this module."""
return logging.getLogger(__name__) return logging.getLogger(__name__)
def get_tags(line): @dataclass
class Symbol:
"""A symbol definition from a symbol file."""
name: str
tags: List[Tag]
@dataclass
class Version:
"""A version block of a symbol file."""
name: str
base: Optional[str]
tags: List[Tag]
symbols: List[Symbol]
def get_tags(line: str) -> List[Tag]:
"""Returns a list of all tags on this line.""" """Returns a list of all tags on this line."""
_, _, all_tags = line.strip().partition('#') _, _, all_tags = line.strip().partition('#')
return [e for e in re.split(r'\s+', all_tags) if e.strip()] return [Tag(e) for e in re.split(r'\s+', all_tags) if e.strip()]
def is_api_level_tag(tag): def is_api_level_tag(tag: Tag) -> bool:
"""Returns true if this tag has an API level that may need decoding.""" """Returns true if this tag has an API level that may need decoding."""
if tag.startswith('introduced='): if tag.startswith('introduced='):
return True return True
@@ -52,7 +86,7 @@ def is_api_level_tag(tag):
return False return False
def decode_api_level(api, api_map): def decode_api_level(api: str, api_map: ApiMap) -> int:
"""Decodes the API level argument into the API level number. """Decodes the API level argument into the API level number.
For the average case, this just decodes the integer value from the string, For the average case, this just decodes the integer value from the string,
@@ -70,12 +104,13 @@ def decode_api_level(api, api_map):
return api_map[api] return api_map[api]
def decode_api_level_tags(tags, api_map): def decode_api_level_tags(tags: Iterable[Tag], api_map: ApiMap) -> List[Tag]:
"""Decodes API level code names in a list of tags. """Decodes API level code names in a list of tags.
Raises: Raises:
ParseError: An unknown version name was found in a tag. ParseError: An unknown version name was found in a tag.
""" """
decoded_tags = list(tags)
for idx, tag in enumerate(tags): for idx, tag in enumerate(tags):
if not is_api_level_tag(tag): if not is_api_level_tag(tag):
continue continue
@@ -83,13 +118,13 @@ def decode_api_level_tags(tags, api_map):
try: try:
decoded = str(decode_api_level(value, api_map)) decoded = str(decode_api_level(value, api_map))
tags[idx] = '='.join([name, decoded]) decoded_tags[idx] = Tag('='.join([name, decoded]))
except KeyError: except KeyError:
raise ParseError('Unknown version name in tag: {}'.format(tag)) raise ParseError(f'Unknown version name in tag: {tag}')
return tags return decoded_tags
def split_tag(tag): def split_tag(tag: Tag) -> Tuple[str, str]:
"""Returns a key/value tuple of the tag. """Returns a key/value tuple of the tag.
Raises: Raises:
@@ -103,7 +138,7 @@ def split_tag(tag):
return key, value return key, value
def get_tag_value(tag): def get_tag_value(tag: Tag) -> str:
"""Returns the value of a key/value tag. """Returns the value of a key/value tag.
Raises: Raises:
@@ -114,12 +149,13 @@ def get_tag_value(tag):
return split_tag(tag)[1] return split_tag(tag)[1]
def version_is_private(version): def version_is_private(version: str) -> bool:
"""Returns True if the version name should be treated as private.""" """Returns True if the version name should be treated as private."""
return version.endswith('_PRIVATE') or version.endswith('_PLATFORM') return version.endswith('_PRIVATE') or version.endswith('_PLATFORM')
def should_omit_version(version, arch, api, llndk, apex): def should_omit_version(version: Version, arch: Arch, api: int, llndk: bool,
apex: bool) -> bool:
"""Returns True if the version section should be ommitted. """Returns True if the version section should be ommitted.
We want to omit any sections that do not have any symbols we'll have in the We want to omit any sections that do not have any symbols we'll have in the
@@ -145,7 +181,8 @@ def should_omit_version(version, arch, api, llndk, apex):
return False return False
def should_omit_symbol(symbol, arch, api, llndk, apex): def should_omit_symbol(symbol: Symbol, arch: Arch, api: int, llndk: bool,
apex: bool) -> bool:
"""Returns True if the symbol should be omitted.""" """Returns True if the symbol should be omitted."""
no_llndk_no_apex = 'llndk' not in symbol.tags and 'apex' not in symbol.tags no_llndk_no_apex = 'llndk' not in symbol.tags and 'apex' not in symbol.tags
keep = no_llndk_no_apex or \ keep = no_llndk_no_apex or \
@@ -160,7 +197,7 @@ def should_omit_symbol(symbol, arch, api, llndk, apex):
return False return False
def symbol_in_arch(tags, arch): def symbol_in_arch(tags: Iterable[Tag], arch: Arch) -> bool:
"""Returns true if the symbol is present for the given architecture.""" """Returns true if the symbol is present for the given architecture."""
has_arch_tags = False has_arch_tags = False
for tag in tags: for tag in tags:
@@ -175,7 +212,7 @@ def symbol_in_arch(tags, arch):
return not has_arch_tags return not has_arch_tags
def symbol_in_api(tags, arch, api): def symbol_in_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
"""Returns true if the symbol is present for the given API level.""" """Returns true if the symbol is present for the given API level."""
introduced_tag = None introduced_tag = None
arch_specific = False arch_specific = False
@@ -197,7 +234,7 @@ def symbol_in_api(tags, arch, api):
return api >= int(get_tag_value(introduced_tag)) return api >= int(get_tag_value(introduced_tag))
def symbol_versioned_in_api(tags, api): def symbol_versioned_in_api(tags: Iterable[Tag], api: int) -> bool:
"""Returns true if the symbol should be versioned for the given API. """Returns true if the symbol should be versioned for the given API.
This models the `versioned=API` tag. This should be a very uncommonly This models the `versioned=API` tag. This should be a very uncommonly
@@ -223,68 +260,40 @@ class ParseError(RuntimeError):
class MultiplyDefinedSymbolError(RuntimeError): class MultiplyDefinedSymbolError(RuntimeError):
"""A symbol name was multiply defined.""" """A symbol name was multiply defined."""
def __init__(self, multiply_defined_symbols): def __init__(self, multiply_defined_symbols: Iterable[str]) -> None:
super(MultiplyDefinedSymbolError, self).__init__( super().__init__(
'Version script contains multiple definitions for: {}'.format( 'Version script contains multiple definitions for: {}'.format(
', '.join(multiply_defined_symbols))) ', '.join(multiply_defined_symbols)))
self.multiply_defined_symbols = multiply_defined_symbols self.multiply_defined_symbols = multiply_defined_symbols
class Version:
"""A version block of a symbol file."""
def __init__(self, name, base, tags, symbols):
self.name = name
self.base = base
self.tags = tags
self.symbols = symbols
def __eq__(self, other):
if self.name != other.name:
return False
if self.base != other.base:
return False
if self.tags != other.tags:
return False
if self.symbols != other.symbols:
return False
return True
class Symbol:
"""A symbol definition from a symbol file."""
def __init__(self, name, tags):
self.name = name
self.tags = tags
def __eq__(self, other):
return self.name == other.name and set(self.tags) == set(other.tags)
class SymbolFileParser: 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: TextIO, api_map: ApiMap, arch: Arch,
api: int, llndk: bool, apex: bool) -> None:
self.input_file = input_file self.input_file = input_file
self.api_map = api_map self.api_map = api_map
self.arch = arch self.arch = arch
self.api = api self.api = api
self.llndk = llndk self.llndk = llndk
self.apex = apex self.apex = apex
self.current_line = None self.current_line: Optional[str] = None
def parse(self): def parse(self) -> List[Version]:
"""Parses the symbol file and returns a list of Version objects.""" """Parses the symbol file and returns a list of Version objects."""
versions = [] versions = []
while self.next_line() != '': while self.next_line() != '':
assert self.current_line is not None
if '{' in self.current_line: if '{' in self.current_line:
versions.append(self.parse_version()) versions.append(self.parse_version())
else: else:
raise ParseError( raise ParseError(
'Unexpected contents at top level: ' + self.current_line) f'Unexpected contents at top level: {self.current_line}')
self.check_no_duplicate_symbols(versions) self.check_no_duplicate_symbols(versions)
return versions return versions
def check_no_duplicate_symbols(self, versions): def check_no_duplicate_symbols(self, versions: Iterable[Version]) -> None:
"""Raises errors for multiply defined symbols. """Raises errors for multiply defined symbols.
This situation is the normal case when symbol versioning is actually This situation is the normal case when symbol versioning is actually
@@ -312,12 +321,13 @@ class SymbolFileParser:
raise MultiplyDefinedSymbolError( raise MultiplyDefinedSymbolError(
sorted(list(multiply_defined_symbols))) sorted(list(multiply_defined_symbols)))
def parse_version(self): def parse_version(self) -> Version:
"""Parses a single version section and returns a Version object.""" """Parses a single version section and returns a Version object."""
assert self.current_line is not None
name = self.current_line.split('{')[0].strip() name = self.current_line.split('{')[0].strip()
tags = get_tags(self.current_line) tags = get_tags(self.current_line)
tags = decode_api_level_tags(tags, self.api_map) tags = decode_api_level_tags(tags, self.api_map)
symbols = [] symbols: List[Symbol] = []
global_scope = True global_scope = True
cpp_symbols = False cpp_symbols = False
while self.next_line() != '': while self.next_line() != '':
@@ -333,9 +343,7 @@ class SymbolFileParser:
cpp_symbols = False cpp_symbols = False
else: else:
base = base.rstrip(';').rstrip() base = base.rstrip(';').rstrip()
if base == '': return Version(name, base or None, tags, symbols)
base = None
return Version(name, base, tags, symbols)
elif 'extern "C++" {' in self.current_line: elif 'extern "C++" {' in self.current_line:
cpp_symbols = True cpp_symbols = True
elif not cpp_symbols and ':' in self.current_line: elif not cpp_symbols and ':' in self.current_line:
@@ -354,8 +362,9 @@ class SymbolFileParser:
pass pass
raise ParseError('Unexpected EOF in version block.') raise ParseError('Unexpected EOF in version block.')
def parse_symbol(self): def parse_symbol(self) -> Symbol:
"""Parses a single symbol line and returns a Symbol object.""" """Parses a single symbol line and returns a Symbol object."""
assert self.current_line is not None
if ';' not in self.current_line: if ';' not in self.current_line:
raise ParseError( raise ParseError(
'Expected ; to terminate symbol: ' + self.current_line) 'Expected ; to terminate symbol: ' + self.current_line)
@@ -368,7 +377,7 @@ class SymbolFileParser:
tags = decode_api_level_tags(tags, self.api_map) tags = decode_api_level_tags(tags, self.api_map)
return Symbol(name, tags) return Symbol(name, tags)
def next_line(self): def next_line(self) -> str:
"""Returns the next non-empty non-comment line. """Returns the next non-empty non-comment line.
A return value of '' indicates EOF. A return value of '' indicates EOF.

2
cc/symbolfile/mypy.ini Normal file
View File

@@ -0,0 +1,2 @@
[mypy]
disallow_untyped_defs = True

View File

@@ -19,12 +19,13 @@ import textwrap
import unittest import unittest
import symbolfile import symbolfile
from symbolfile import Arch, Tag
# pylint: disable=missing-docstring # pylint: disable=missing-docstring
class DecodeApiLevelTest(unittest.TestCase): class DecodeApiLevelTest(unittest.TestCase):
def test_decode_api_level(self): def test_decode_api_level(self) -> None:
self.assertEqual(9, symbolfile.decode_api_level('9', {})) self.assertEqual(9, symbolfile.decode_api_level('9', {}))
self.assertEqual(9000, symbolfile.decode_api_level('O', {'O': 9000})) self.assertEqual(9000, symbolfile.decode_api_level('O', {'O': 9000}))
@@ -33,70 +34,73 @@ class DecodeApiLevelTest(unittest.TestCase):
class TagsTest(unittest.TestCase): class TagsTest(unittest.TestCase):
def test_get_tags_no_tags(self): def test_get_tags_no_tags(self) -> None:
self.assertEqual([], symbolfile.get_tags('')) self.assertEqual([], symbolfile.get_tags(''))
self.assertEqual([], symbolfile.get_tags('foo bar baz')) self.assertEqual([], symbolfile.get_tags('foo bar baz'))
def test_get_tags(self): def test_get_tags(self) -> None:
self.assertEqual(['foo', 'bar'], symbolfile.get_tags('# foo bar')) self.assertEqual(['foo', 'bar'], symbolfile.get_tags('# foo bar'))
self.assertEqual(['bar', 'baz'], symbolfile.get_tags('foo # bar baz')) self.assertEqual(['bar', 'baz'], symbolfile.get_tags('foo # bar baz'))
def test_split_tag(self): def test_split_tag(self) -> None:
self.assertTupleEqual(('foo', 'bar'), symbolfile.split_tag('foo=bar')) self.assertTupleEqual(('foo', 'bar'),
self.assertTupleEqual(('foo', 'bar=baz'), symbolfile.split_tag('foo=bar=baz')) symbolfile.split_tag(Tag('foo=bar')))
self.assertTupleEqual(('foo', 'bar=baz'),
symbolfile.split_tag(Tag('foo=bar=baz')))
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
symbolfile.split_tag('foo') symbolfile.split_tag(Tag('foo'))
def test_get_tag_value(self): def test_get_tag_value(self) -> None:
self.assertEqual('bar', symbolfile.get_tag_value('foo=bar')) self.assertEqual('bar', symbolfile.get_tag_value(Tag('foo=bar')))
self.assertEqual('bar=baz', symbolfile.get_tag_value('foo=bar=baz')) self.assertEqual('bar=baz',
symbolfile.get_tag_value(Tag('foo=bar=baz')))
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
symbolfile.get_tag_value('foo') symbolfile.get_tag_value(Tag('foo'))
def test_is_api_level_tag(self): def test_is_api_level_tag(self) -> None:
self.assertTrue(symbolfile.is_api_level_tag('introduced=24')) self.assertTrue(symbolfile.is_api_level_tag(Tag('introduced=24')))
self.assertTrue(symbolfile.is_api_level_tag('introduced-arm=24')) self.assertTrue(symbolfile.is_api_level_tag(Tag('introduced-arm=24')))
self.assertTrue(symbolfile.is_api_level_tag('versioned=24')) self.assertTrue(symbolfile.is_api_level_tag(Tag('versioned=24')))
# Shouldn't try to process things that aren't a key/value tag. # 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(Tag('arm')))
self.assertFalse(symbolfile.is_api_level_tag('introduced')) self.assertFalse(symbolfile.is_api_level_tag(Tag('introduced')))
self.assertFalse(symbolfile.is_api_level_tag('versioned')) self.assertFalse(symbolfile.is_api_level_tag(Tag('versioned')))
# We don't support arch specific `versioned` tags. # We don't support arch specific `versioned` tags.
self.assertFalse(symbolfile.is_api_level_tag('versioned-arm=24')) self.assertFalse(symbolfile.is_api_level_tag(Tag('versioned-arm=24')))
def test_decode_api_level_tags(self): def test_decode_api_level_tags(self) -> None:
api_map = { api_map = {
'O': 9000, 'O': 9000,
'P': 9001, 'P': 9001,
} }
tags = [ tags = [
'introduced=9', Tag('introduced=9'),
'introduced-arm=14', Tag('introduced-arm=14'),
'versioned=16', Tag('versioned=16'),
'arm', Tag('arm'),
'introduced=O', Tag('introduced=O'),
'introduced=P', Tag('introduced=P'),
] ]
expected_tags = [ expected_tags = [
'introduced=9', Tag('introduced=9'),
'introduced-arm=14', Tag('introduced-arm=14'),
'versioned=16', Tag('versioned=16'),
'arm', Tag('arm'),
'introduced=9000', Tag('introduced=9000'),
'introduced=9001', Tag('introduced=9001'),
] ]
self.assertListEqual( self.assertListEqual(
expected_tags, symbolfile.decode_api_level_tags(tags, api_map)) expected_tags, symbolfile.decode_api_level_tags(tags, api_map))
with self.assertRaises(symbolfile.ParseError): with self.assertRaises(symbolfile.ParseError):
symbolfile.decode_api_level_tags(['introduced=O'], {}) symbolfile.decode_api_level_tags([Tag('introduced=O')], {})
class PrivateVersionTest(unittest.TestCase): class PrivateVersionTest(unittest.TestCase):
def test_version_is_private(self): def test_version_is_private(self) -> None:
self.assertFalse(symbolfile.version_is_private('foo')) self.assertFalse(symbolfile.version_is_private('foo'))
self.assertFalse(symbolfile.version_is_private('PRIVATE')) self.assertFalse(symbolfile.version_is_private('PRIVATE'))
self.assertFalse(symbolfile.version_is_private('PLATFORM')) self.assertFalse(symbolfile.version_is_private('PLATFORM'))
@@ -110,191 +114,227 @@ class PrivateVersionTest(unittest.TestCase):
class SymbolPresenceTest(unittest.TestCase): class SymbolPresenceTest(unittest.TestCase):
def test_symbol_in_arch(self): def test_symbol_in_arch(self) -> None:
self.assertTrue(symbolfile.symbol_in_arch([], 'arm')) self.assertTrue(symbolfile.symbol_in_arch([], Arch('arm')))
self.assertTrue(symbolfile.symbol_in_arch(['arm'], 'arm')) self.assertTrue(symbolfile.symbol_in_arch([Tag('arm')], Arch('arm')))
self.assertFalse(symbolfile.symbol_in_arch(['x86'], 'arm')) self.assertFalse(symbolfile.symbol_in_arch([Tag('x86')], Arch('arm')))
def test_symbol_in_api(self): def test_symbol_in_api(self) -> None:
self.assertTrue(symbolfile.symbol_in_api([], 'arm', 9)) self.assertTrue(symbolfile.symbol_in_api([], Arch('arm'), 9))
self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 9)) self.assertTrue(
self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 14)) symbolfile.symbol_in_api([Tag('introduced=9')], Arch('arm'), 9))
self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14)) self.assertTrue(
self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14)) symbolfile.symbol_in_api([Tag('introduced=9')], Arch('arm'), 14))
self.assertTrue(symbolfile.symbol_in_api(['introduced-x86=14'], 'arm', 9)) self.assertTrue(
self.assertTrue(symbolfile.symbol_in_api( symbolfile.symbol_in_api([Tag('introduced-arm=9')], Arch('arm'),
['introduced-arm=9', 'introduced-x86=21'], 'arm', 14)) 14))
self.assertTrue(symbolfile.symbol_in_api( self.assertTrue(
['introduced=9', 'introduced-x86=21'], 'arm', 14)) symbolfile.symbol_in_api([Tag('introduced-arm=9')], Arch('arm'),
self.assertTrue(symbolfile.symbol_in_api( 14))
['introduced=21', 'introduced-arm=9'], 'arm', 14)) self.assertTrue(
self.assertTrue(symbolfile.symbol_in_api( symbolfile.symbol_in_api([Tag('introduced-x86=14')], Arch('arm'),
['future'], 'arm', symbolfile.FUTURE_API_LEVEL)) 9))
self.assertTrue(
symbolfile.symbol_in_api(
[Tag('introduced-arm=9'),
Tag('introduced-x86=21')], Arch('arm'), 14))
self.assertTrue(
symbolfile.symbol_in_api(
[Tag('introduced=9'),
Tag('introduced-x86=21')], Arch('arm'), 14))
self.assertTrue(
symbolfile.symbol_in_api(
[Tag('introduced=21'),
Tag('introduced-arm=9')], Arch('arm'), 14))
self.assertTrue(
symbolfile.symbol_in_api([Tag('future')], Arch('arm'),
symbolfile.FUTURE_API_LEVEL))
self.assertFalse(symbolfile.symbol_in_api(['introduced=14'], 'arm', 9)) self.assertFalse(
self.assertFalse(symbolfile.symbol_in_api(['introduced-arm=14'], 'arm', 9)) symbolfile.symbol_in_api([Tag('introduced=14')], Arch('arm'), 9))
self.assertFalse(symbolfile.symbol_in_api(['future'], 'arm', 9)) self.assertFalse(
self.assertFalse(symbolfile.symbol_in_api( symbolfile.symbol_in_api([Tag('introduced-arm=14')], Arch('arm'),
['introduced=9', 'future'], 'arm', 14)) 9))
self.assertFalse(symbolfile.symbol_in_api( self.assertFalse(
['introduced-arm=9', 'future'], 'arm', 14)) symbolfile.symbol_in_api([Tag('future')], Arch('arm'), 9))
self.assertFalse(symbolfile.symbol_in_api( self.assertFalse(
['introduced-arm=21', 'introduced-x86=9'], 'arm', 14)) symbolfile.symbol_in_api(
self.assertFalse(symbolfile.symbol_in_api( [Tag('introduced=9'), Tag('future')], Arch('arm'), 14))
['introduced=9', 'introduced-arm=21'], 'arm', 14)) self.assertFalse(
self.assertFalse(symbolfile.symbol_in_api( symbolfile.symbol_in_api([Tag('introduced-arm=9'),
['introduced=21', 'introduced-x86=9'], 'arm', 14)) Tag('future')], Arch('arm'), 14))
self.assertFalse(
symbolfile.symbol_in_api(
[Tag('introduced-arm=21'),
Tag('introduced-x86=9')], Arch('arm'), 14))
self.assertFalse(
symbolfile.symbol_in_api(
[Tag('introduced=9'),
Tag('introduced-arm=21')], Arch('arm'), 14))
self.assertFalse(
symbolfile.symbol_in_api(
[Tag('introduced=21'),
Tag('introduced-x86=9')], Arch('arm'), 14))
# Interesting edge case: this symbol should be omitted from the # Interesting edge case: this symbol should be omitted from the
# library, but this call should still return true because none of the # library, but this call should still return true because none of the
# tags indiciate that it's not present in this API level. # tags indiciate that it's not present in this API level.
self.assertTrue(symbolfile.symbol_in_api(['x86'], 'arm', 9)) self.assertTrue(symbolfile.symbol_in_api([Tag('x86')], Arch('arm'), 9))
def test_verioned_in_api(self): def test_verioned_in_api(self) -> None:
self.assertTrue(symbolfile.symbol_versioned_in_api([], 9)) self.assertTrue(symbolfile.symbol_versioned_in_api([], 9))
self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 9)) self.assertTrue(
self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 14)) symbolfile.symbol_versioned_in_api([Tag('versioned=9')], 9))
self.assertTrue(
symbolfile.symbol_versioned_in_api([Tag('versioned=9')], 14))
self.assertFalse(symbolfile.symbol_versioned_in_api(['versioned=14'], 9)) self.assertFalse(
symbolfile.symbol_versioned_in_api([Tag('versioned=14')], 9))
class OmitVersionTest(unittest.TestCase): class OmitVersionTest(unittest.TestCase):
def test_omit_private(self): def test_omit_private(self) -> None:
self.assertFalse( self.assertFalse(
symbolfile.should_omit_version( symbolfile.should_omit_version(
symbolfile.Version('foo', None, [], []), 'arm', 9, False, symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
False)) False))
self.assertTrue( self.assertTrue(
symbolfile.should_omit_version( symbolfile.should_omit_version(
symbolfile.Version('foo_PRIVATE', None, [], []), 'arm', 9, symbolfile.Version('foo_PRIVATE', None, [], []), Arch('arm'),
False, False)) 9, False, False))
self.assertTrue( self.assertTrue(
symbolfile.should_omit_version( symbolfile.should_omit_version(
symbolfile.Version('foo_PLATFORM', None, [], []), 'arm', 9, symbolfile.Version('foo_PLATFORM', None, [], []), Arch('arm'),
False, False))
self.assertTrue(
symbolfile.should_omit_version(
symbolfile.Version('foo', None, ['platform-only'], []), 'arm',
9, False, False)) 9, False, False))
def test_omit_llndk(self):
self.assertTrue( self.assertTrue(
symbolfile.should_omit_version( symbolfile.should_omit_version(
symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9, symbolfile.Version('foo', None, [Tag('platform-only')], []),
False, False)) Arch('arm'), 9, False, False))
self.assertFalse( def test_omit_llndk(self) -> None:
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( self.assertTrue(
symbolfile.should_omit_version( symbolfile.should_omit_version(
symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False, symbolfile.Version('foo', None, [Tag('llndk')], []),
False)) Arch('arm'), 9, False, False))
self.assertFalse( self.assertFalse(
symbolfile.should_omit_version( symbolfile.should_omit_version(
symbolfile.Version('foo', None, [], []), 'arm', 9, False, symbolfile.Version('foo', None, [], []), Arch('arm'), 9, True,
False))
self.assertFalse(
symbolfile.should_omit_version(
symbolfile.Version('foo', None, [Tag('llndk')], []),
Arch('arm'), 9, True, False))
def test_omit_apex(self) -> None:
self.assertTrue(
symbolfile.should_omit_version(
symbolfile.Version('foo', None, [Tag('apex')], []),
Arch('arm'), 9, False, False))
self.assertFalse(
symbolfile.should_omit_version(
symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
True)) True))
self.assertFalse( self.assertFalse(
symbolfile.should_omit_version( symbolfile.should_omit_version(
symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False, symbolfile.Version('foo', None, [Tag('apex')], []),
True)) Arch('arm'), 9, False, True))
def test_omit_arch(self): def test_omit_arch(self) -> None:
self.assertFalse( self.assertFalse(
symbolfile.should_omit_version( symbolfile.should_omit_version(
symbolfile.Version('foo', None, [], []), 'arm', 9, False, symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
False)) False))
self.assertFalse( self.assertFalse(
symbolfile.should_omit_version( symbolfile.should_omit_version(
symbolfile.Version('foo', None, ['arm'], []), 'arm', 9, False, symbolfile.Version('foo', None, [Tag('arm')], []), Arch('arm'),
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)) 9, False, False))
self.assertTrue( self.assertTrue(
symbolfile.should_omit_version( symbolfile.should_omit_version(
symbolfile.Version('foo', None, ['introduced=14'], []), 'arm', symbolfile.Version('foo', None, [Tag('x86')], []), Arch('arm'),
9, False, False)) 9, False, False))
def test_omit_api(self) -> None:
self.assertFalse(
symbolfile.should_omit_version(
symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
False))
self.assertFalse(
symbolfile.should_omit_version(
symbolfile.Version('foo', None, [Tag('introduced=9')], []),
Arch('arm'), 9, False, False))
self.assertTrue(
symbolfile.should_omit_version(
symbolfile.Version('foo', None, [Tag('introduced=14')], []),
Arch('arm'), 9, False, False))
class OmitSymbolTest(unittest.TestCase): class OmitSymbolTest(unittest.TestCase):
def test_omit_llndk(self): def test_omit_llndk(self) -> None:
self.assertTrue( self.assertTrue(
symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']), symbolfile.should_omit_symbol(
'arm', 9, False, False)) symbolfile.Symbol('foo', [Tag('llndk')]), Arch('arm'), 9,
False, False))
self.assertFalse( self.assertFalse(
symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
9, True, False)) Arch('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( self.assertFalse(
symbolfile.should_omit_symbol( symbolfile.should_omit_symbol(
symbolfile.Symbol('foo', ['introduced=9']), 'arm', 9, False, symbolfile.Symbol('foo', [Tag('llndk')]), Arch('arm'), 9, True,
False))
def test_omit_apex(self) -> None:
self.assertTrue(
symbolfile.should_omit_symbol(
symbolfile.Symbol('foo', [Tag('apex')]), Arch('arm'), 9, False,
False))
self.assertFalse(
symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
Arch('arm'), 9, False, True))
self.assertFalse(
symbolfile.should_omit_symbol(
symbolfile.Symbol('foo', [Tag('apex')]), Arch('arm'), 9, False,
True))
def test_omit_arch(self) -> None:
self.assertFalse(
symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
Arch('arm'), 9, False, False))
self.assertFalse(
symbolfile.should_omit_symbol(
symbolfile.Symbol('foo', [Tag('arm')]), Arch('arm'), 9, False,
False)) False))
self.assertTrue( self.assertTrue(
symbolfile.should_omit_symbol( symbolfile.should_omit_symbol(
symbolfile.Symbol('foo', ['introduced=14']), 'arm', 9, False, symbolfile.Symbol('foo', [Tag('x86')]), Arch('arm'), 9, False,
False)) False))
def test_omit_api(self) -> None:
self.assertFalse(
symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
Arch('arm'), 9, False, False))
self.assertFalse(
symbolfile.should_omit_symbol(
symbolfile.Symbol('foo', [Tag('introduced=9')]), Arch('arm'),
9, False, False))
self.assertTrue(
symbolfile.should_omit_symbol(
symbolfile.Symbol('foo', [Tag('introduced=14')]), Arch('arm'),
9, False, False))
class SymbolFileParseTest(unittest.TestCase): class SymbolFileParseTest(unittest.TestCase):
def test_next_line(self): def test_next_line(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
foo foo
@@ -302,10 +342,12 @@ class SymbolFileParseTest(unittest.TestCase):
# baz # baz
qux qux
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False, False)
self.assertIsNone(parser.current_line) self.assertIsNone(parser.current_line)
self.assertEqual('foo', parser.next_line().strip()) self.assertEqual('foo', parser.next_line().strip())
assert parser.current_line is not None
self.assertEqual('foo', parser.current_line.strip()) self.assertEqual('foo', parser.current_line.strip())
self.assertEqual('bar', parser.next_line().strip()) self.assertEqual('bar', parser.next_line().strip())
@@ -317,7 +359,7 @@ class SymbolFileParseTest(unittest.TestCase):
self.assertEqual('', parser.next_line()) self.assertEqual('', parser.next_line())
self.assertEqual('', parser.current_line) self.assertEqual('', parser.current_line)
def test_parse_version(self): def test_parse_version(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { # foo bar VERSION_1 { # foo bar
baz; baz;
@@ -327,7 +369,8 @@ class SymbolFileParseTest(unittest.TestCase):
VERSION_2 { VERSION_2 {
} VERSION_1; # asdf } VERSION_1; # asdf
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False, False)
parser.next_line() parser.next_line()
version = parser.parse_version() version = parser.parse_version()
@@ -337,7 +380,7 @@ class SymbolFileParseTest(unittest.TestCase):
expected_symbols = [ expected_symbols = [
symbolfile.Symbol('baz', []), symbolfile.Symbol('baz', []),
symbolfile.Symbol('qux', ['woodly', 'doodly']), symbolfile.Symbol('qux', [Tag('woodly'), Tag('doodly')]),
] ]
self.assertEqual(expected_symbols, version.symbols) self.assertEqual(expected_symbols, version.symbols)
@@ -347,32 +390,35 @@ class SymbolFileParseTest(unittest.TestCase):
self.assertEqual('VERSION_1', version.base) self.assertEqual('VERSION_1', version.base)
self.assertEqual([], version.tags) self.assertEqual([], version.tags)
def test_parse_version_eof(self): def test_parse_version_eof(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False, False)
parser.next_line() parser.next_line()
with self.assertRaises(symbolfile.ParseError): with self.assertRaises(symbolfile.ParseError):
parser.parse_version() parser.parse_version()
def test_unknown_scope_label(self): def test_unknown_scope_label(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
foo: foo:
} }
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False, False)
parser.next_line() parser.next_line()
with self.assertRaises(symbolfile.ParseError): with self.assertRaises(symbolfile.ParseError):
parser.parse_version() parser.parse_version()
def test_parse_symbol(self): def test_parse_symbol(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
foo; foo;
bar; # baz qux bar; # baz qux
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False, False)
parser.next_line() parser.next_line()
symbol = parser.parse_symbol() symbol = parser.parse_symbol()
@@ -384,48 +430,51 @@ class SymbolFileParseTest(unittest.TestCase):
self.assertEqual('bar', symbol.name) self.assertEqual('bar', symbol.name)
self.assertEqual(['baz', 'qux'], symbol.tags) self.assertEqual(['baz', 'qux'], symbol.tags)
def test_wildcard_symbol_global(self): def test_wildcard_symbol_global(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
*; *;
}; };
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False, False)
parser.next_line() parser.next_line()
with self.assertRaises(symbolfile.ParseError): with self.assertRaises(symbolfile.ParseError):
parser.parse_version() parser.parse_version()
def test_wildcard_symbol_local(self): def test_wildcard_symbol_local(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
local: local:
*; *;
}; };
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False, False)
parser.next_line() parser.next_line()
version = parser.parse_version() version = parser.parse_version()
self.assertEqual([], version.symbols) self.assertEqual([], version.symbols)
def test_missing_semicolon(self): def test_missing_semicolon(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
foo foo
}; };
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False, False)
parser.next_line() parser.next_line()
with self.assertRaises(symbolfile.ParseError): with self.assertRaises(symbolfile.ParseError):
parser.parse_version() parser.parse_version()
def test_parse_fails_invalid_input(self): def test_parse_fails_invalid_input(self) -> None:
with self.assertRaises(symbolfile.ParseError): with self.assertRaises(symbolfile.ParseError):
input_file = io.StringIO('foo') input_file = io.StringIO('foo')
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'),
False, False) 16, False, False)
parser.parse() parser.parse()
def test_parse(self): def test_parse(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
local: local:
@@ -443,23 +492,24 @@ class SymbolFileParseTest(unittest.TestCase):
qwerty; qwerty;
} VERSION_1; } VERSION_1;
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False, False)
versions = parser.parse() versions = parser.parse()
expected = [ expected = [
symbolfile.Version('VERSION_1', None, [], [ symbolfile.Version('VERSION_1', None, [], [
symbolfile.Symbol('foo', []), symbolfile.Symbol('foo', []),
symbolfile.Symbol('bar', ['baz']), symbolfile.Symbol('bar', [Tag('baz')]),
]), ]),
symbolfile.Version('VERSION_2', 'VERSION_1', ['wasd'], [ symbolfile.Version('VERSION_2', 'VERSION_1', [Tag('wasd')], [
symbolfile.Symbol('woodly', []), symbolfile.Symbol('woodly', []),
symbolfile.Symbol('doodly', ['asdf']), symbolfile.Symbol('doodly', [Tag('asdf')]),
]), ]),
] ]
self.assertEqual(expected, versions) self.assertEqual(expected, versions)
def test_parse_llndk_apex_symbol(self): def test_parse_llndk_apex_symbol(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
foo; foo;
@@ -468,7 +518,8 @@ class SymbolFileParseTest(unittest.TestCase):
qux; # apex qux; # apex
}; };
""")) """))
parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, True) parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16,
False, True)
parser.next_line() parser.next_line()
version = parser.parse_version() version = parser.parse_version()
@@ -477,14 +528,14 @@ class SymbolFileParseTest(unittest.TestCase):
expected_symbols = [ expected_symbols = [
symbolfile.Symbol('foo', []), symbolfile.Symbol('foo', []),
symbolfile.Symbol('bar', ['llndk']), symbolfile.Symbol('bar', [Tag('llndk')]),
symbolfile.Symbol('baz', ['llndk', 'apex']), symbolfile.Symbol('baz', [Tag('llndk'), Tag('apex')]),
symbolfile.Symbol('qux', ['apex']), symbolfile.Symbol('qux', [Tag('apex')]),
] ]
self.assertEqual(expected_symbols, version.symbols) self.assertEqual(expected_symbols, version.symbols)
def main(): def main() -> None:
suite = unittest.TestLoader().loadTestsFromName(__name__) suite = unittest.TestLoader().loadTestsFromName(__name__)
unittest.TextTestRunner(verbosity=3).run(suite) unittest.TextTestRunner(verbosity=3).run(suite)