diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py index 2f4326a9c..86bf6ff85 100755 --- a/cc/ndkstubgen/__init__.py +++ b/cc/ndkstubgen/__init__.py @@ -20,13 +20,16 @@ import json import logging import os import sys +from typing import Iterable, TextIO import symbolfile +from symbolfile import Arch, Version class Generator: """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.version_script = version_script self.arch = arch @@ -34,12 +37,12 @@ class Generator: self.llndk = llndk self.apex = apex - def write(self, versions): + def write(self, versions: Iterable[Version]) -> None: """Writes all symbol data to the output files.""" for version in versions: 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.""" if symbolfile.should_omit_version(version, self.arch, self.api, self.llndk, self.apex): @@ -84,7 +87,7 @@ class Generator: self.version_script.write('}' + base + ';\n') -def parse_args(): +def parse_args() -> argparse.Namespace: """Parses and returns command line arguments.""" parser = argparse.ArgumentParser() @@ -100,23 +103,31 @@ def parse_args(): parser.add_argument( '--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( - '--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.') 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( - 'stub_src', type=os.path.realpath, + 'stub_src', + type=os.path.realpath, # type: ignore help='Path to output stub source file.') parser.add_argument( - 'version_script', type=os.path.realpath, + 'version_script', + type=os.path.realpath, # type: ignore help='Path to output version script.') return parser.parse_args() -def main(): +def main() -> None: """Program entry point.""" args = parse_args() diff --git a/cc/ndkstubgen/mypy.ini b/cc/ndkstubgen/mypy.ini new file mode 100644 index 000000000..82aa7eb9d --- /dev/null +++ b/cc/ndkstubgen/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +disallow_untyped_defs = True diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py index 70bcf781c..6d2c9d673 100755 --- a/cc/ndkstubgen/test_ndkstubgen.py +++ b/cc/ndkstubgen/test_ndkstubgen.py @@ -21,19 +21,20 @@ import unittest import ndkstubgen import symbolfile +from symbolfile import Arch, Tag # pylint: disable=missing-docstring 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 # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, - False, False) + generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9, False, False) version = symbolfile.Version('VERSION_PRIVATE', None, [], [ symbolfile.Symbol('foo', []), @@ -42,74 +43,75 @@ class GeneratorTest(unittest.TestCase): self.assertEqual('', src_file.getvalue()) self.assertEqual('', version_file.getvalue()) - version = symbolfile.Version('VERSION', None, ['x86'], [ + version = symbolfile.Version('VERSION', None, [Tag('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'], [ + version = symbolfile.Version('VERSION', None, [Tag('introduced=14')], [ symbolfile.Symbol('foo', []), ]) generator.write_version(version) self.assertEqual('', src_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 # SymbolPresenceTest. src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, - False, False) + generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9, False, False) version = symbolfile.Version('VERSION_1', None, [], [ - symbolfile.Symbol('foo', ['x86']), + symbolfile.Symbol('foo', [Tag('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']), + symbolfile.Symbol('foo', [Tag('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']), + symbolfile.Symbol('foo', [Tag('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']), + symbolfile.Symbol('foo', [Tag('apex')]), ]) generator.write_version(version) self.assertEqual('', src_file.getvalue()) self.assertEqual('', version_file.getvalue()) - def test_write(self): + def test_write(self) -> None: src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, - False, False) + generator = ndkstubgen.Generator(src_file, version_file, Arch('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.Symbol('bar', [Tag('var')]), + symbolfile.Symbol('woodly', [Tag('weak')]), + symbolfile.Symbol('doodly', + [Tag('weak'), Tag('var')]), ]), symbolfile.Version('VERSION_2', 'VERSION_1', [], [ symbolfile.Symbol('baz', []), ]), 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): - def test_integration(self): + def test_integration(self) -> None: api_map = { 'O': 9000, 'P': 9001, @@ -178,14 +180,14 @@ class IntegrationTest(unittest.TestCase): wobble; } VERSION_4; """)) - parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9, - False, False) + parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('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 = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9, False, False) generator.write(versions) expected_src = textwrap.dedent("""\ @@ -213,7 +215,7 @@ class IntegrationTest(unittest.TestCase): """) self.assertEqual(expected_version, version_file.getvalue()) - def test_integration_future_api(self): + def test_integration_future_api(self) -> None: api_map = { 'O': 9000, 'P': 9001, @@ -230,14 +232,14 @@ class IntegrationTest(unittest.TestCase): *; }; """)) - parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9001, - False, False) + parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('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 = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9001, False, False) generator.write(versions) expected_src = textwrap.dedent("""\ @@ -255,7 +257,7 @@ class IntegrationTest(unittest.TestCase): """) self.assertEqual(expected_version, version_file.getvalue()) - def test_multiple_definition(self): + def test_multiple_definition(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { global: @@ -280,8 +282,8 @@ class IntegrationTest(unittest.TestCase): } VERSION_2; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, - False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) with self.assertRaises( symbolfile.MultiplyDefinedSymbolError) as ex_context: @@ -289,7 +291,7 @@ class IntegrationTest(unittest.TestCase): self.assertEqual(['bar', 'foo'], ex_context.exception.multiply_defined_symbols) - def test_integration_with_apex(self): + def test_integration_with_apex(self) -> None: api_map = { 'O': 9000, 'P': 9001, @@ -328,14 +330,14 @@ class IntegrationTest(unittest.TestCase): wobble; } VERSION_4; """)) - parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9, - False, True) + parser = symbolfile.SymbolFileParser(input_file, api_map, Arch('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 = ndkstubgen.Generator(src_file, version_file, Arch('arm'), + 9, False, True) generator.write(versions) expected_src = textwrap.dedent("""\ @@ -369,7 +371,8 @@ class IntegrationTest(unittest.TestCase): """) self.assertEqual(expected_version, version_file.getvalue()) -def main(): + +def main() -> None: suite = unittest.TestLoader().loadTestsFromName(__name__) unittest.TextTestRunner(verbosity=3).run(suite) diff --git a/cc/symbolfile/__init__.py b/cc/symbolfile/__init__.py index faa3823f3..5678e7d83 100644 --- a/cc/symbolfile/__init__.py +++ b/cc/symbolfile/__init__.py @@ -14,15 +14,31 @@ # limitations under the License. # """Parser for Android's version script information.""" +from dataclasses import dataclass import logging 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 = ( - 'arm', - 'arm64', - 'x86', - 'x86_64', + Arch('arm'), + Arch('arm64'), + Arch('x86'), + Arch('x86_64'), ) @@ -30,18 +46,36 @@ ALL_ARCHITECTURES = ( FUTURE_API_LEVEL = 10000 -def logger(): +def logger() -> logging.Logger: """Return the main logger for this module.""" 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.""" _, _, 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.""" if tag.startswith('introduced='): return True @@ -52,7 +86,7 @@ def is_api_level_tag(tag): 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. 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] -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. Raises: ParseError: An unknown version name was found in a tag. """ + decoded_tags = list(tags) for idx, tag in enumerate(tags): if not is_api_level_tag(tag): continue @@ -83,13 +118,13 @@ def decode_api_level_tags(tags, api_map): try: decoded = str(decode_api_level(value, api_map)) - tags[idx] = '='.join([name, decoded]) + decoded_tags[idx] = Tag('='.join([name, decoded])) except KeyError: - raise ParseError('Unknown version name in tag: {}'.format(tag)) - return tags + raise ParseError(f'Unknown version name in tag: {tag}') + return decoded_tags -def split_tag(tag): +def split_tag(tag: Tag) -> Tuple[str, str]: """Returns a key/value tuple of the tag. Raises: @@ -103,7 +138,7 @@ def split_tag(tag): return key, value -def get_tag_value(tag): +def get_tag_value(tag: Tag) -> str: """Returns the value of a key/value tag. Raises: @@ -114,12 +149,13 @@ def get_tag_value(tag): 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.""" 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. 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 -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.""" no_llndk_no_apex = 'llndk' not in symbol.tags and 'apex' not in symbol.tags keep = no_llndk_no_apex or \ @@ -160,7 +197,7 @@ def should_omit_symbol(symbol, arch, api, llndk, apex): 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.""" has_arch_tags = False for tag in tags: @@ -175,7 +212,7 @@ def symbol_in_arch(tags, arch): 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.""" introduced_tag = None arch_specific = False @@ -197,7 +234,7 @@ def symbol_in_api(tags, arch, api): 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. This models the `versioned=API` tag. This should be a very uncommonly @@ -223,68 +260,40 @@ class ParseError(RuntimeError): class MultiplyDefinedSymbolError(RuntimeError): """A symbol name was multiply defined.""" - def __init__(self, multiply_defined_symbols): - super(MultiplyDefinedSymbolError, self).__init__( + def __init__(self, multiply_defined_symbols: Iterable[str]) -> None: + super().__init__( 'Version script contains multiple definitions for: {}'.format( ', '.join(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: """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.api_map = api_map self.arch = arch self.api = api self.llndk = llndk 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.""" versions = [] while self.next_line() != '': + assert self.current_line is not None if '{' in self.current_line: versions.append(self.parse_version()) else: 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) 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. This situation is the normal case when symbol versioning is actually @@ -312,12 +321,13 @@ class SymbolFileParser: raise MultiplyDefinedSymbolError( sorted(list(multiply_defined_symbols))) - def parse_version(self): + def parse_version(self) -> Version: """Parses a single version section and returns a Version object.""" + assert self.current_line is not None name = self.current_line.split('{')[0].strip() tags = get_tags(self.current_line) tags = decode_api_level_tags(tags, self.api_map) - symbols = [] + symbols: List[Symbol] = [] global_scope = True cpp_symbols = False while self.next_line() != '': @@ -333,9 +343,7 @@ class SymbolFileParser: cpp_symbols = False else: base = base.rstrip(';').rstrip() - if base == '': - base = None - return Version(name, base, tags, symbols) + return Version(name, base or None, tags, symbols) elif 'extern "C++" {' in self.current_line: cpp_symbols = True elif not cpp_symbols and ':' in self.current_line: @@ -354,8 +362,9 @@ class SymbolFileParser: pass 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.""" + assert self.current_line is not None if ';' not in self.current_line: raise ParseError( 'Expected ; to terminate symbol: ' + self.current_line) @@ -368,7 +377,7 @@ class SymbolFileParser: tags = decode_api_level_tags(tags, self.api_map) return Symbol(name, tags) - def next_line(self): + def next_line(self) -> str: """Returns the next non-empty non-comment line. A return value of '' indicates EOF. diff --git a/cc/symbolfile/mypy.ini b/cc/symbolfile/mypy.ini new file mode 100644 index 000000000..82aa7eb9d --- /dev/null +++ b/cc/symbolfile/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +disallow_untyped_defs = True diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py index c91131fee..92b13999e 100644 --- a/cc/symbolfile/test_symbolfile.py +++ b/cc/symbolfile/test_symbolfile.py @@ -19,12 +19,13 @@ import textwrap import unittest import symbolfile +from symbolfile import Arch, Tag # pylint: disable=missing-docstring 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(9000, symbolfile.decode_api_level('O', {'O': 9000})) @@ -33,70 +34,73 @@ class DecodeApiLevelTest(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('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(['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')) + def test_split_tag(self) -> None: + self.assertTupleEqual(('foo', 'bar'), + symbolfile.split_tag(Tag('foo=bar'))) + self.assertTupleEqual(('foo', 'bar=baz'), + symbolfile.split_tag(Tag('foo=bar=baz'))) with self.assertRaises(ValueError): - symbolfile.split_tag('foo') + symbolfile.split_tag(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')) + def test_get_tag_value(self) -> None: + self.assertEqual('bar', symbolfile.get_tag_value(Tag('foo=bar'))) + self.assertEqual('bar=baz', + symbolfile.get_tag_value(Tag('foo=bar=baz'))) with self.assertRaises(ValueError): - symbolfile.get_tag_value('foo') + symbolfile.get_tag_value(Tag('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')) + def test_is_api_level_tag(self) -> None: + self.assertTrue(symbolfile.is_api_level_tag(Tag('introduced=24'))) + self.assertTrue(symbolfile.is_api_level_tag(Tag('introduced-arm=24'))) + self.assertTrue(symbolfile.is_api_level_tag(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')) + self.assertFalse(symbolfile.is_api_level_tag(Tag('arm'))) + self.assertFalse(symbolfile.is_api_level_tag(Tag('introduced'))) + self.assertFalse(symbolfile.is_api_level_tag(Tag('versioned'))) # 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 = { 'O': 9000, 'P': 9001, } tags = [ - 'introduced=9', - 'introduced-arm=14', - 'versioned=16', - 'arm', - 'introduced=O', - 'introduced=P', + Tag('introduced=9'), + Tag('introduced-arm=14'), + Tag('versioned=16'), + Tag('arm'), + Tag('introduced=O'), + Tag('introduced=P'), ] expected_tags = [ - 'introduced=9', - 'introduced-arm=14', - 'versioned=16', - 'arm', - 'introduced=9000', - 'introduced=9001', + Tag('introduced=9'), + Tag('introduced-arm=14'), + Tag('versioned=16'), + Tag('arm'), + Tag('introduced=9000'), + Tag('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'], {}) + symbolfile.decode_api_level_tags([Tag('introduced=O')], {}) 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('PRIVATE')) self.assertFalse(symbolfile.version_is_private('PLATFORM')) @@ -110,191 +114,227 @@ class PrivateVersionTest(unittest.TestCase): 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')) + def test_symbol_in_arch(self) -> None: + self.assertTrue(symbolfile.symbol_in_arch([], Arch('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): - 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)) + def test_symbol_in_api(self) -> None: + self.assertTrue(symbolfile.symbol_in_api([], Arch('arm'), 9)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('introduced=9')], Arch('arm'), 9)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('introduced=9')], Arch('arm'), 14)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('introduced-arm=9')], Arch('arm'), + 14)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('introduced-arm=9')], Arch('arm'), + 14)) + self.assertTrue( + symbolfile.symbol_in_api([Tag('introduced-x86=14')], Arch('arm'), + 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(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)) + self.assertFalse( + symbolfile.symbol_in_api([Tag('introduced=14')], Arch('arm'), 9)) + self.assertFalse( + symbolfile.symbol_in_api([Tag('introduced-arm=14')], Arch('arm'), + 9)) + self.assertFalse( + symbolfile.symbol_in_api([Tag('future')], Arch('arm'), 9)) + self.assertFalse( + symbolfile.symbol_in_api( + [Tag('introduced=9'), Tag('future')], Arch('arm'), 14)) + self.assertFalse( + symbolfile.symbol_in_api([Tag('introduced-arm=9'), + 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 # 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)) + 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(['versioned=9'], 9)) - self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 14)) + self.assertTrue( + 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): - def test_omit_private(self): + def test_omit_private(self) -> None: self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, [], []), 'arm', 9, False, + symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False, False)) self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo_PRIVATE', None, [], []), 'arm', 9, - False, False)) + symbolfile.Version('foo_PRIVATE', None, [], []), Arch('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', + symbolfile.Version('foo_PLATFORM', None, [], []), Arch('arm'), 9, False, False)) - def test_omit_llndk(self): self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9, - False, False)) + symbolfile.Version('foo', None, [Tag('platform-only')], []), + Arch('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): + def test_omit_llndk(self) -> None: self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False, - False)) + symbolfile.Version('foo', None, [Tag('llndk')], []), + Arch('arm'), 9, False, False)) self.assertFalse( 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)) self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False, - True)) + symbolfile.Version('foo', None, [Tag('apex')], []), + Arch('arm'), 9, False, True)) - def test_omit_arch(self): + def test_omit_arch(self) -> None: self.assertFalse( symbolfile.should_omit_version( - symbolfile.Version('foo', None, [], []), 'arm', 9, False, + symbolfile.Version('foo', None, [], []), Arch('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', + symbolfile.Version('foo', None, [Tag('arm')], []), Arch('arm'), 9, False, False)) self.assertTrue( symbolfile.should_omit_version( - symbolfile.Version('foo', None, ['introduced=14'], []), 'arm', + symbolfile.Version('foo', None, [Tag('x86')], []), Arch('arm'), 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): - def test_omit_llndk(self): + def test_omit_llndk(self) -> None: self.assertTrue( - symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']), - 'arm', 9, False, False)) + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', [Tag('llndk')]), Arch('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)) + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), + Arch('arm'), 9, True, False)) self.assertFalse( 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)) self.assertTrue( symbolfile.should_omit_symbol( - symbolfile.Symbol('foo', ['introduced=14']), 'arm', 9, False, + symbolfile.Symbol('foo', [Tag('x86')]), Arch('arm'), 9, 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): - def test_next_line(self): + def test_next_line(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ foo @@ -302,10 +342,12 @@ class SymbolFileParseTest(unittest.TestCase): # baz 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.assertEqual('foo', parser.next_line().strip()) + assert parser.current_line is not None self.assertEqual('foo', parser.current_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.current_line) - def test_parse_version(self): + def test_parse_version(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { # foo bar baz; @@ -327,7 +369,8 @@ class SymbolFileParseTest(unittest.TestCase): VERSION_2 { } 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() version = parser.parse_version() @@ -337,7 +380,7 @@ class SymbolFileParseTest(unittest.TestCase): expected_symbols = [ symbolfile.Symbol('baz', []), - symbolfile.Symbol('qux', ['woodly', 'doodly']), + symbolfile.Symbol('qux', [Tag('woodly'), Tag('doodly')]), ] self.assertEqual(expected_symbols, version.symbols) @@ -347,32 +390,35 @@ class SymbolFileParseTest(unittest.TestCase): self.assertEqual('VERSION_1', version.base) self.assertEqual([], version.tags) - def test_parse_version_eof(self): + def test_parse_version_eof(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() with self.assertRaises(symbolfile.ParseError): parser.parse_version() - def test_unknown_scope_label(self): + def test_unknown_scope_label(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { foo: } """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() with self.assertRaises(symbolfile.ParseError): parser.parse_version() - def test_parse_symbol(self): + def test_parse_symbol(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ foo; 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() symbol = parser.parse_symbol() @@ -384,48 +430,51 @@ class SymbolFileParseTest(unittest.TestCase): self.assertEqual('bar', symbol.name) 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("""\ VERSION_1 { *; }; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() with self.assertRaises(symbolfile.ParseError): parser.parse_version() - def test_wildcard_symbol_local(self): + def test_wildcard_symbol_local(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { local: *; }; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() version = parser.parse_version() self.assertEqual([], version.symbols) - def test_missing_semicolon(self): + def test_missing_semicolon(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { foo }; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) parser.next_line() with self.assertRaises(symbolfile.ParseError): parser.parse_version() - def test_parse_fails_invalid_input(self): + def test_parse_fails_invalid_input(self) -> None: with self.assertRaises(symbolfile.ParseError): input_file = io.StringIO('foo') - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, - False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), + 16, False, False) parser.parse() - def test_parse(self): + def test_parse(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { local: @@ -443,23 +492,24 @@ class SymbolFileParseTest(unittest.TestCase): qwerty; } VERSION_1; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, False) versions = parser.parse() expected = [ symbolfile.Version('VERSION_1', None, [], [ 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('doodly', ['asdf']), + symbolfile.Symbol('doodly', [Tag('asdf')]), ]), ] 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("""\ VERSION_1 { foo; @@ -468,7 +518,8 @@ class SymbolFileParseTest(unittest.TestCase): qux; # apex }; """)) - parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, True) + parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'), 16, + False, True) parser.next_line() version = parser.parse_version() @@ -477,14 +528,14 @@ class SymbolFileParseTest(unittest.TestCase): expected_symbols = [ symbolfile.Symbol('foo', []), - symbolfile.Symbol('bar', ['llndk']), - symbolfile.Symbol('baz', ['llndk', 'apex']), - symbolfile.Symbol('qux', ['apex']), + symbolfile.Symbol('bar', [Tag('llndk')]), + symbolfile.Symbol('baz', [Tag('llndk'), Tag('apex')]), + symbolfile.Symbol('qux', [Tag('apex')]), ] self.assertEqual(expected_symbols, version.symbols) -def main(): +def main() -> None: suite = unittest.TestLoader().loadTestsFromName(__name__) unittest.TextTestRunner(verbosity=3).run(suite)