diff --git a/reccmp/isledecomp/parser/codebase.py b/reccmp/isledecomp/parser/codebase.py index b300f0d6..17497bf3 100644 --- a/reccmp/isledecomp/parser/codebase.py +++ b/reccmp/isledecomp/parser/codebase.py @@ -19,7 +19,7 @@ def __init__(self, filenames: Iterable[str], module: str) -> None: for filename in filenames: parser.reset() with open(filename, "r", encoding="utf-8") as f: - parser.read_lines(f) + parser.read(f.read()) for sym in parser.iter_symbols(module): sym.filename = filename diff --git a/reccmp/isledecomp/parser/linter.py b/reccmp/isledecomp/parser/linter.py index b44487df..53f9c1c4 100644 --- a/reccmp/isledecomp/parser/linter.py +++ b/reccmp/isledecomp/parser/linter.py @@ -115,15 +115,12 @@ def _check_byname_allowed(self): ) ) - def check_lines(self, lines, filename, module=None): - """`lines` is a generic iterable to allow for testing with a list of strings. - We assume lines has the entire contents of the compilation unit.""" - + def read(self, code: str, filename: str, module=None) -> bool: self.reset(False) self._filename = filename self._module = module - self._parser.read_lines(lines) + self._parser.read(code) self._parser.finish() self.alerts = self._parser.alerts[::] @@ -138,7 +135,7 @@ def check_lines(self, lines, filename, module=None): return len(self.alerts) == 0 - def check_file(self, filename, module=None): + def check_file(self, filename: str, module=None): """Convenience method for decomplint cli tool""" with open(filename, "r", encoding="utf-8") as f: - return self.check_lines(f, filename, module) + return self.read(f.read(), filename, module) diff --git a/reccmp/isledecomp/parser/parser.py b/reccmp/isledecomp/parser/parser.py index 92e41dd3..2d4af22e 100644 --- a/reccmp/isledecomp/parser/parser.py +++ b/reccmp/isledecomp/parser/parser.py @@ -1,6 +1,7 @@ # C++ file parser -from typing import List, Iterable, Iterator, Optional +import io +from typing import List, Iterator, Optional from enum import Enum from .util import ( get_class_name, @@ -545,8 +546,8 @@ def read_line(self, line: str): if vtable_class is not None: self._vtable_done(class_name=vtable_class) - def read_lines(self, lines: Iterable): - for line in lines: + def read(self, text: str): + for line in io.StringIO(text, newline=None): self.read_line(line) def finish(self): diff --git a/tests/test_linter.py b/tests/test_linter.py index e82ebac7..4d4b7fd9 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -9,27 +9,27 @@ def fixture_linter(): def test_simple_in_order(linter): - lines = [ - "// FUNCTION: TEST 0x1000", - "void function1() {}", - "// FUNCTION: TEST 0x2000", - "void function2() {}", - "// FUNCTION: TEST 0x3000", - "void function3() {}", - ] - assert linter.check_lines(lines, "test.cpp", "TEST") is True + code = """\ + // FUNCTION: TEST 0x1000 + void function1() {} + // FUNCTION: TEST 0x2000 + void function2() {} + // FUNCTION: TEST 0x3000 + void function3() {} + """ + assert linter.read(code, "test.cpp", "TEST") is True def test_simple_not_in_order(linter): - lines = [ - "// FUNCTION: TEST 0x1000", - "void function1() {}", - "// FUNCTION: TEST 0x3000", - "void function3() {}", - "// FUNCTION: TEST 0x2000", - "void function2() {}", - ] - assert linter.check_lines(lines, "test.cpp", "TEST") is False + code = """\ + // FUNCTION: TEST 0x1000 + void function1() {} + // FUNCTION: TEST 0x3000 + void function3() {} + // FUNCTION: TEST 0x2000 + void function2() {} + """ + assert linter.read(code, "test.cpp", "TEST") is False assert len(linter.alerts) == 1 assert linter.alerts[0].code == ParserError.FUNCTION_OUT_OF_ORDER @@ -39,16 +39,16 @@ def test_simple_not_in_order(linter): def test_byname_ignored(linter): """Should ignore lookup-by-name markers when checking order.""" - lines = [ - "// FUNCTION: TEST 0x1000", - "void function1() {}", - "// FUNCTION: TEST 0x3000", - "// MyClass::MyMethod", - "// FUNCTION: TEST 0x2000", - "void function2() {}", - ] + code = """\ + // FUNCTION: TEST 0x1000 + void function1() {} + // FUNCTION: TEST 0x3000 + // MyClass::MyMethod + // FUNCTION: TEST 0x2000 + void function2() {} + """ # This will fail because byname lookup does not belong in the cpp file - assert linter.check_lines(lines, "test.cpp", "TEST") is False + assert linter.read(code, "test.cpp", "TEST") is False # but it should not fail for function order. assert all( alert.code != ParserError.FUNCTION_OUT_OF_ORDER for alert in linter.alerts @@ -57,49 +57,49 @@ def test_byname_ignored(linter): def test_module_isolation(linter): """Should check the order of markers from a single module only.""" - lines = [ - "// FUNCTION: ALPHA 0x0001", - "// FUNCTION: TEST 0x1000", - "void function1() {}", - "// FUNCTION: ALPHA 0x0002", - "// FUNCTION: TEST 0x2000", - "void function2() {}", - "// FUNCTION: ALPHA 0x0003", - "// FUNCTION: TEST 0x3000", - "void function3() {}", - ] - - assert linter.check_lines(lines, "test.cpp", "TEST") is True + code = """\ + // FUNCTION: ALPHA 0x0001 + // FUNCTION: TEST 0x1000 + void function1() {} + // FUNCTION: ALPHA 0x0002 + // FUNCTION: TEST 0x2000 + void function2() {} + // FUNCTION: ALPHA 0x0003 + // FUNCTION: TEST 0x3000 + void function3() {} + """ + + assert linter.read(code, "test.cpp", "TEST") is True linter.reset(True) - assert linter.check_lines(lines, "test.cpp", "ALPHA") is True + assert linter.read(code, "test.cpp", "ALPHA") is True def test_byname_headers_only(linter): """Markers that ar referenced by name with cvdump belong in header files only.""" - lines = [ - "// FUNCTION: TEST 0x1000", - "// MyClass::~MyClass", - ] + code = """\ + // FUNCTION: TEST 0x1000 + // MyClass::~MyClass + """ - assert linter.check_lines(lines, "test.h", "TEST") is True + assert linter.read(code, "test.h", "TEST") is True linter.reset(True) - assert linter.check_lines(lines, "test.cpp", "TEST") is False + assert linter.read(code, "test.cpp", "TEST") is False assert linter.alerts[0].code == ParserError.BYNAME_FUNCTION_IN_CPP def test_duplicate_offsets(linter): """The linter will retain module/offset pairs found until we do a full reset.""" - lines = [ - "// FUNCTION: TEST 0x1000", - "// FUNCTION: HELLO 0x1000", - "// MyClass::~MyClass", - ] + code = """\ + // FUNCTION: TEST 0x1000 + // FUNCTION: HELLO 0x1000 + // MyClass::~MyClass + """ # Should not fail for duplicate offset 0x1000 because the modules are unique. - assert linter.check_lines(lines, "test.h", "TEST") is True + assert linter.read(code, "test.h", "TEST") is True # Simulate a failure by reading the same file twice. - assert linter.check_lines(lines, "test.h", "TEST") is False + assert linter.read(code, "test.h", "TEST") is False # Two errors because offsets from both modules are duplicated assert len(linter.alerts) == 2 @@ -107,38 +107,38 @@ def test_duplicate_offsets(linter): # Partial reset will retain the list of seen offsets. linter.reset(False) - assert linter.check_lines(lines, "test.h", "TEST") is False + assert linter.read(code, "test.h", "TEST") is False # Full reset will forget seen offsets. linter.reset(True) - assert linter.check_lines(lines, "test.h", "TEST") is True + assert linter.read(code, "test.h", "TEST") is True def test_duplicate_strings(linter): """Duplicate string markers are okay if the string value is the same.""" - string_lines = [ - "// STRING: TEST 0x1000", - 'return "hello world";', - ] + string_lines = """\ + // STRING: TEST 0x1000 + return "hello world"; + """ # No problem to use this marker twice. - assert linter.check_lines(string_lines, "test.h", "TEST") is True - assert linter.check_lines(string_lines, "test.h", "TEST") is True + assert linter.read(string_lines, "test.h", "TEST") is True + assert linter.read(string_lines, "test.h", "TEST") is True - different_string = [ - "// STRING: TEST 0x1000", - 'return "hi there";', - ] + different_string = """\ + // STRING: TEST 0x1000 + return "hi there"; + """ # Same address but the string is different - assert linter.check_lines(different_string, "greeting.h", "TEST") is False + assert linter.read(different_string, "greeting.h", "TEST") is False assert len(linter.alerts) == 1 assert linter.alerts[0].code == ParserError.WRONG_STRING - same_addr_reused = [ - "// GLOBAL:TEXT 0x1000", - "int g_test = 123;", - ] + same_addr_reused = """\ + // GLOBAL:TEXT 0x1000 + int g_test = 123; + """ # This will fail like any other offset reuse. - assert linter.check_lines(same_addr_reused, "other.h", "TEST") is False + assert linter.read(same_addr_reused, "other.h", "TEST") is False diff --git a/tests/test_parser.py b/tests/test_parser.py index 0abf3a4e..5080b81f 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,3 +1,4 @@ +from textwrap import dedent import pytest from reccmp.isledecomp.parser.parser import ( ReaderState, @@ -14,13 +15,15 @@ def fixture_parser(): def test_missing_sig(parser): """In the hopefully rare scenario that the function signature and marker are swapped, we still have enough to match witch reccmp""" - parser.read_lines( - [ - "void my_function()", - "// FUNCTION: TEST 0x1234", - "{", - "}", - ] + parser.read( + dedent( + """\ + void my_function() + // FUNCTION: TEST 0x1234 + { + } + """ + ) ) assert parser.state == ReaderState.SEARCH assert len(parser.functions) == 1 @@ -33,14 +36,14 @@ def test_missing_sig(parser): def test_not_exact_syntax(parser): """Alert to inexact syntax right here in the parser instead of kicking it downstream. Doing this means we don't have to save the actual text.""" - parser.read_line("// function: test 0x1234") + parser.read("// function: test 0x1234") assert len(parser.alerts) == 1 assert parser.alerts[0].code == ParserError.BAD_DECOMP_MARKER def test_invalid_marker(parser): """We matched a decomp marker, but it's not one we care about""" - parser.read_line("// BANANA: TEST 0x1234") + parser.read("// BANANA: TEST 0x1234") assert parser.state == ReaderState.SEARCH assert len(parser.alerts) == 1 @@ -49,11 +52,11 @@ def test_invalid_marker(parser): def test_incompatible_marker(parser): """The marker we just read cannot be handled in the current parser state""" - parser.read_lines( - [ - "// FUNCTION: TEST 0x1234", - "// GLOBAL: TEST 0x5000", - ] + parser.read( + """\ + // FUNCTION: TEST 0x1234 + // GLOBAL: TEST 0x5000 + """ ) assert parser.state == ReaderState.SEARCH assert len(parser.alerts) == 1 @@ -62,11 +65,11 @@ def test_incompatible_marker(parser): def test_variable(parser): """Should identify a global variable""" - parser.read_lines( - [ - "// GLOBAL: HELLO 0x1234", - "int g_value = 5;", - ] + parser.read( + """\ + // GLOBAL: HELLO 0x1234 + int g_value = 5; + """ ) assert len(parser.variables) == 1 @@ -74,11 +77,11 @@ def test_variable(parser): def test_synthetic_plus_marker(parser): """Marker tracking preempts synthetic name detection. Should fail with error and not log the synthetic""" - parser.read_lines( - [ - "// SYNTHETIC: HEY 0x555", - "// FUNCTION: HOWDY 0x1234", - ] + parser.read( + """\ + // SYNTHETIC: HEY 0x555 + // FUNCTION: HOWDY 0x1234 + """ ) assert len(parser.functions) == 0 assert len(parser.alerts) == 1 @@ -88,13 +91,15 @@ def test_synthetic_plus_marker(parser): def test_different_markers_different_module(parser): """Does it make any sense for a function to be a stub in one module, but not in another? I don't know. But it's no problem for us.""" - parser.read_lines( - [ - "// FUNCTION: HOWDY 0x1234", - "// STUB: SUP 0x5555", - "void interesting_function() {", - "}", - ] + parser.read( + dedent( + """\ + // FUNCTION: HOWDY 0x1234 + // STUB: SUP 0x5555 + void interesting_function() { + } + """ + ) ) assert len(parser.alerts) == 0 @@ -104,13 +109,15 @@ def test_different_markers_different_module(parser): def test_different_markers_same_module(parser): """Now, if something is a regular function but then a stub, what do we say about that?""" - parser.read_lines( - [ - "// FUNCTION: HOWDY 0x1234", - "// STUB: HOWDY 0x5555", - "void interesting_function() {", - "}", - ] + parser.read( + dedent( + """\ + // FUNCTION: HOWDY 0x1234 + // STUB: HOWDY 0x5555 + void interesting_function() { + } + """ + ) ) # Use first marker declaration, don't replace @@ -124,13 +131,13 @@ def test_different_markers_same_module(parser): def test_unexpected_synthetic(parser): """FUNCTION then SYNTHETIC should fail to report either one""" - parser.read_lines( - [ - "// FUNCTION: HOWDY 0x1234", - "// SYNTHETIC: HOWDY 0x5555", - "void interesting_function() {", - "}", - ] + parser.read( + """\ + // FUNCTION: HOWDY 0x1234 + // SYNTHETIC: HOWDY 0x5555 + void interesting_function() { + } + """ ) assert parser.state == ReaderState.SEARCH @@ -142,13 +149,13 @@ def test_unexpected_synthetic(parser): @pytest.mark.skip(reason="not implemented yet") def test_duplicate_offset(parser): """Repeating the same module/offset in the same file is probably a typo""" - parser.read_lines( - [ - "// GLOBAL: HELLO 0x1234", - "int x = 1;", - "// GLOBAL: HELLO 0x1234", - "int y = 2;", - ] + parser.read( + """\ + // GLOBAL: HELLO 0x1234 + int x = 1; + // GLOBAL: HELLO 0x1234 + int y = 2; + """ ) assert len(parser.alerts) == 1 @@ -157,12 +164,12 @@ def test_duplicate_offset(parser): def test_multiple_variables(parser): """Theoretically the same global variable can appear in multiple modules""" - parser.read_lines( - [ - "// GLOBAL: HELLO 0x1234", - "// GLOBAL: WUZZUP 0x555", - "const char *g_greeting;", - ] + parser.read( + """\ + // GLOBAL: HELLO 0x1234 + // GLOBAL: WUZZUP 0x555 + const char *g_greeting; + """ ) assert len(parser.alerts) == 0 assert len(parser.variables) == 2 @@ -170,12 +177,12 @@ def test_multiple_variables(parser): def test_multiple_variables_same_module(parser): """Should not overwrite offset""" - parser.read_lines( - [ - "// GLOBAL: HELLO 0x1234", - "// GLOBAL: HELLO 0x555", - "const char *g_greeting;", - ] + parser.read( + """\ + // GLOBAL: HELLO 0x1234 + // GLOBAL: HELLO 0x555 + const char *g_greeting; + """ ) assert len(parser.alerts) == 1 assert parser.alerts[0].code == ParserError.DUPLICATE_MODULE @@ -184,12 +191,12 @@ def test_multiple_variables_same_module(parser): def test_multiple_vtables(parser): - parser.read_lines( - [ - "// VTABLE: HELLO 0x1234", - "// VTABLE: TEST 0x5432", - "class MxString : public MxCore {", - ] + parser.read( + """\ + // VTABLE: HELLO 0x1234 + // VTABLE: TEST 0x5432 + class MxString : public MxCore { + """ ) assert len(parser.alerts) == 0 assert len(parser.vtables) == 2 @@ -198,12 +205,12 @@ def test_multiple_vtables(parser): def test_multiple_vtables_same_module(parser): """Should not overwrite offset""" - parser.read_lines( - [ - "// VTABLE: HELLO 0x1234", - "// VTABLE: HELLO 0x5432", - "class MxString : public MxCore {", - ] + parser.read( + """\ + // VTABLE: HELLO 0x1234 + // VTABLE: HELLO 0x5432 + class MxString : public MxCore { + """ ) assert len(parser.alerts) == 1 assert parser.alerts[0].code == ParserError.DUPLICATE_MODULE @@ -212,11 +219,11 @@ def test_multiple_vtables_same_module(parser): def test_synthetic(parser): - parser.read_lines( - [ - "// SYNTHETIC: TEST 0x1234", - "// TestClass::TestMethod", - ] + parser.read( + """\ + // SYNTHETIC: TEST 0x1234 + // TestClass::TestMethod + """ ) assert len(parser.functions) == 1 assert parser.functions[0].lookup_by_name is True @@ -224,12 +231,12 @@ def test_synthetic(parser): def test_synthetic_same_module(parser): - parser.read_lines( - [ - "// SYNTHETIC: TEST 0x1234", - "// SYNTHETIC: TEST 0x555", - "// TestClass::TestMethod", - ] + parser.read( + """\ + // SYNTHETIC: TEST 0x1234 + // SYNTHETIC: TEST 0x555 + // TestClass::TestMethod + """ ) assert len(parser.alerts) == 1 assert parser.alerts[0].code == ParserError.DUPLICATE_MODULE @@ -239,11 +246,11 @@ def test_synthetic_same_module(parser): def test_synthetic_no_comment(parser): """Synthetic marker followed by a code line (i.e. non-comment)""" - parser.read_lines( - [ - "// SYNTHETIC: TEST 0x1234", - "int x = 123;", - ] + parser.read( + """\ + // SYNTHETIC: TEST 0x1234 + int x = 123; + """ ) assert len(parser.functions) == 0 assert len(parser.alerts) == 1 @@ -252,11 +259,11 @@ def test_synthetic_no_comment(parser): def test_single_line_function(parser): - parser.read_lines( - [ - "// FUNCTION: TEST 0x1234", - "int hello() { return 1234; }", - ] + parser.read( + """\ + // FUNCTION: TEST 0x1234 + int hello() { return 1234; } + """ ) assert len(parser.functions) == 1 assert parser.functions[0].line_number == 2 @@ -267,15 +274,17 @@ def test_indented_function(parser): """Track the number of whitespace characters when we begin the function and check that against each closing curly brace we read. Should not report a syntax warning if the function is indented""" - parser.read_lines( - [ - " // FUNCTION: TEST 0x1234", - " void indented()", - " {", - " // TODO", - " }", - " // FUNCTION: NEXT 0x555", - ] + parser.read( + dedent( + """\ + // FUNCTION: TEST 0x1234 + void indented() + { + // TODO + } + // FUNCTION: NEXT 0x555 + """ + ) ) assert len(parser.alerts) == 0 @@ -285,13 +294,15 @@ def test_indented_no_curly_hint(parser): """Same as above, but opening curly brace is on the same line. Without the hint of how many whitespace characters to check, can we still identify the end of the function?""" - parser.read_lines( - [ - " // FUNCTION: TEST 0x1234", - " void indented() {", - " }", - " // FUNCTION: NEXT 0x555", - ] + parser.read( + dedent( + """\ + // FUNCTION: TEST 0x1234 + void indented() { + } + // FUNCTION: NEXT 0x555 + """ + ) ) assert len(parser.alerts) == 0 @@ -300,11 +311,11 @@ def test_implicit_lookup_by_name(parser): """FUNCTION (or STUB) offsets must directly precede the function signature. If we detect a comment instead, we assume that this is a lookup-by-name function and end here.""" - parser.read_lines( - [ - "// FUNCTION: TEST 0x1234", - "// TestClass::TestMethod()", - ] + parser.read( + """\ + // FUNCTION: TEST 0x1234 + // TestClass::TestMethod() + """ ) assert parser.state == ReaderState.SEARCH assert len(parser.functions) == 1 @@ -316,12 +327,12 @@ def test_function_with_spaces(parser): """There should not be any spaces between the end of FUNCTION markers and the start or name of the function. If it's a blank line, we can safely ignore but should alert to this.""" - parser.read_lines( - [ - "// FUNCTION: TEST 0x1234", - " ", - "inline void test_function() { };", - ] + parser.read( + """\ + // FUNCTION: TEST 0x1234 + + inline void test_function() { }; + """ ) assert len(parser.functions) == 1 assert len(parser.alerts) == 1 @@ -330,12 +341,12 @@ def test_function_with_spaces(parser): def test_function_with_spaces_implicit(parser): """Same as above, but for implicit lookup-by-name""" - parser.read_lines( - [ - "// FUNCTION: TEST 0x1234", - " ", - "// Implicit::Method", - ] + parser.read( + """\ + // FUNCTION: TEST 0x1234 + + // Implicit::Method + """ ) assert len(parser.functions) == 1 assert len(parser.alerts) == 1 @@ -347,14 +358,14 @@ def test_function_is_commented(parser): """In an ideal world, we would recognize that there is no code here. Some editors (or users) might comment the function on each line like this but hopefully it is rare.""" - parser.read_lines( - [ - "// FUNCTION: TEST 0x1234", - "// int my_function()", - "// {", - "// return 5;", - "// }", - ] + parser.read( + """\ + // FUNCTION: TEST 0x1234 + // int my_function() + // { + // return 5; + // } + """ ) assert len(parser.functions) == 0 @@ -363,12 +374,11 @@ def test_function_is_commented(parser): def test_unexpected_eof(parser): """If a decomp marker finds its way to the last line of the file, report that we could not get anything from it.""" - parser.read_lines( - [ - "// FUNCTION: TEST 0x1234", - "// Cls::Method", - "// FUNCTION: TEST 0x5555", - ] + parser.read( + """\ + // FUNCTION: TEST 0x1234 + // Cls::Method + // FUNCTION: TEST 0x5555""" ) parser.finish() @@ -380,20 +390,20 @@ def test_unexpected_eof(parser): @pytest.mark.xfail(reason="no longer applies") def test_global_variable_prefix(parser): """Global and static variables should have the g_ prefix.""" - parser.read_lines( - [ - "// GLOBAL: TEST 0x1234", - 'const char* g_msg = "hello";', - ] + parser.read( + """\ + // GLOBAL: TEST 0x1234 + const char* g_msg = "hello"; + """ ) assert len(parser.variables) == 1 assert len(parser.alerts) == 0 - parser.read_lines( - [ - "// GLOBAL: TEXT 0x5555", - "int test = 5;", - ] + parser.read( + """\ + // GLOBAL: TEXT 0x5555 + int test = 5; + """ ) assert len(parser.alerts) == 1 assert parser.alerts[0].code == ParserError.GLOBAL_MISSING_PREFIX @@ -405,11 +415,11 @@ def test_global_nomatch(parser): """We do our best to grab the variable name, even without the g_ prefix but this (by design) will not match everything.""" - parser.read_lines( - [ - "// GLOBAL: TEST 0x1234", - "FunctionCall();", - ] + parser.read( + """\ + // GLOBAL: TEST 0x1234 + FunctionCall(); + """ ) assert len(parser.variables) == 0 assert len(parser.alerts) == 1 @@ -422,23 +432,23 @@ def test_static_variable(parser): Checking for the word `static` alone is not a good test. Static class variables are filed as S_GDATA32, same as regular globals.""" - parser.read_lines( - [ - "// GLOBAL: TEST 0x1234", - "int g_test = 1234;", - ] + parser.read( + """\ + // GLOBAL: TEST 0x1234 + int g_test = 1234; + """ ) assert len(parser.variables) == 1 assert parser.variables[0].is_static is False - parser.read_lines( - [ - "// FUNCTION: TEST 0x5555", - "void test_function() {", - "// GLOBAL: TEST 0x8888", - "static int g_internal = 0;", - "}", - ] + parser.read( + """\ + // FUNCTION: TEST 0x5555 + void test_function() { + // GLOBAL: TEST 0x8888 + static int g_internal = 0; + } + """ ) assert len(parser.variables) == 2 assert parser.variables[1].is_static is True @@ -449,14 +459,14 @@ def test_reject_global_return(parser): For example: if a function returned a string. We now want these to be annotated with the STRING marker.""" - parser.read_lines( - [ - "// FUNCTION: TEST 0x5555", - "void test_function() {", - " // GLOBAL: TEST 0x8888", - ' return "test";', - "}", - ] + parser.read( + """\ + // FUNCTION: TEST 0x5555 + void test_function() { + // GLOBAL: TEST 0x8888 + return "test"; + } + """ ) assert len(parser.variables) == 0 assert len(parser.alerts) == 1 @@ -466,12 +476,12 @@ def test_reject_global_return(parser): def test_global_string(parser): """We now allow GLOBAL and STRING markers for the same item.""" - parser.read_lines( - [ - "// GLOBAL: TEST 0x1234", - "// STRING: TEXT 0x5555", - 'char* g_test = "hello";', - ] + parser.read( + """\ + // GLOBAL: TEST 0x1234 + // STRING: TEXT 0x5555 + char* g_test = "hello"; + """ ) assert len(parser.variables) == 1 assert len(parser.strings) == 1 @@ -484,11 +494,11 @@ def test_global_string(parser): def test_comment_variables(parser): """Match on hidden variables from libraries.""" - parser.read_lines( - [ - "// GLOBAL: TEST 0x1234", - "// g_test", - ] + parser.read( + """\ + // GLOBAL: TEST 0x1234 + // g_test + """ ) assert len(parser.variables) == 1 assert parser.variables[0].name == "g_test" @@ -498,11 +508,11 @@ def test_flexible_variable_prefix(parser): """Don't alert to library variables that lack the g_ prefix. This is out of our control.""" - parser.read_lines( - [ - "// GLOBAL: TEST 0x1234", - "// some_other_variable", - ] + parser.read( + """\ + // GLOBAL: TEST 0x1234 + // some_other_variable + """ ) assert len(parser.variables) == 1 assert len(parser.alerts) == 0 @@ -513,11 +523,11 @@ def test_string_ignore_g_prefix(parser): """String annotations above a regular variable should not alert to the missing g_ prefix. This is only required for GLOBAL markers.""" - parser.read_lines( - [ - "// STRING: TEST 0x1234", - 'const char* value = "";', - ] + parser.read( + """\ + // STRING: TEST 0x1234 + const char* value = ""; + """ ) assert len(parser.strings) == 1 assert len(parser.alerts) == 0 @@ -526,14 +536,14 @@ def test_string_ignore_g_prefix(parser): def test_class_variable(parser): """We should accurately name static variables that are class members.""" - parser.read_lines( - [ - "class Test {", - "protected:", - " // GLOBAL: TEST 0x1234", - " static int g_test;", - "};", - ] + parser.read( + """\ + class Test { + protected: + // GLOBAL: TEST 0x1234 + static int g_test; + }; + """ ) assert len(parser.variables) == 1 @@ -543,15 +553,15 @@ def test_class_variable(parser): def test_namespace_variable(parser): """We should identify a namespace surrounding any global variables""" - parser.read_lines( - [ - "namespace Test {", - "// GLOBAL: TEST 0x1234", - "int g_test = 1234;", - "}", - "// GLOBAL: TEST 0x5555", - "int g_second = 2;", - ] + parser.read( + """\ + namespace Test { + // GLOBAL: TEST 0x1234 + int g_test = 1234; + } + // GLOBAL: TEST 0x5555 + int g_second = 2; + """ ) assert len(parser.variables) == 2 @@ -560,16 +570,16 @@ def test_namespace_variable(parser): def test_namespace_vtable(parser): - parser.read_lines( - [ - "namespace Tgl {", - "// VTABLE: TEST 0x1234", - "class Renderer {", - "};", - "}", - "// VTABLE: TEST 0x5555", - "class Hello { };", - ] + parser.read( + """\ + namespace Tgl { + // VTABLE: TEST 0x1234 + class Renderer { + }; + } + // VTABLE: TEST 0x5555 + class Hello { }; + """ ) assert len(parser.vtables) == 2 @@ -581,15 +591,15 @@ def test_namespace_vtable(parser): def test_global_prefix_namespace(parser): """Should correctly identify namespaces before checking for the g_ prefix""" - parser.read_lines( - [ - "class Test {", - " // GLOBAL: TEST 0x1234", - " static int g_count = 0;", - " // GLOBAL: TEST 0x5555", - " static int count = 0;", - "};", - ] + parser.read( + """\ + class Test { + // GLOBAL: TEST 0x1234 + static int g_count = 0; + // GLOBAL: TEST 0x5555 + static int count = 0; + }; + """ ) assert len(parser.variables) == 2 @@ -601,15 +611,15 @@ def test_global_prefix_namespace(parser): def test_nested_namespace(parser): - parser.read_lines( - [ - "namespace Tgl {", - "class Renderer {", - " // GLOBAL: TEST 0x1234", - " static int g_count = 0;", - "};", - "};", - ] + parser.read( + """\ + namespace Tgl { + class Renderer { + // GLOBAL: TEST 0x1234 + static int g_count = 0; + }; + }; + """ ) assert len(parser.variables) == 1 @@ -620,11 +630,11 @@ def test_match_qualified_variable(parser): """If a variable belongs to a scope and we use a fully qualified reference below a GLOBAL marker, make sure we capture the full name.""" - parser.read_lines( - [ - "// GLOBAL: TEST 0x1234", - "int MxTest::g_count = 0;", - ] + parser.read( + """\ + // GLOBAL: TEST 0x1234 + int MxTest::g_count = 0; + """ ) assert len(parser.variables) == 1 @@ -635,15 +645,15 @@ def test_match_qualified_variable(parser): def test_static_variable_parent(parser): """Report the address of the parent function that contains a static variable.""" - parser.read_lines( - [ - "// FUNCTION: TEST 0x1234", - "void test()", - "{", - " // GLOBAL: TEST 0x5555", - " static int g_count = 0;", - "}", - ] + parser.read( + """\ + // FUNCTION: TEST 0x1234 + void test() + { + // GLOBAL: TEST 0x5555 + static int g_count = 0; + } + """ ) assert len(parser.variables) == 1 @@ -659,14 +669,14 @@ def test_static_variable_no_parent(parser): """If the function that contains a static variable is not marked, we cannot match it with cvdump so we should skip it and report an error.""" - parser.read_lines( - [ - "void test()", - "{", - " // GLOBAL: TEST 0x5555", - " static int g_count = 0;", - "}", - ] + parser.read( + """\ + void test() + { + // GLOBAL: TEST 0x5555 + static int g_count = 0; + } + """ ) # No way to match this variable so don't report it @@ -679,16 +689,16 @@ def test_static_variable_incomplete_coverage(parser): """If the function that contains a static variable is marked, but not for each module used for the variable itself, this is an error.""" - parser.read_lines( - [ - "// FUNCTION: HELLO 0x1234", - "void test()", - "{", - " // GLOBAL: HELLO 0x5555", - " // GLOBAL: TEST 0x5555", - " static int g_count = 0;", - "}", - ] + parser.read( + """\ + // FUNCTION: HELLO 0x1234 + void test() + { + // GLOBAL: HELLO 0x5555 + // GLOBAL: TEST 0x5555 + static int g_count = 0; + } + """ ) # Match for HELLO module @@ -704,11 +714,11 @@ def test_header_function_declaration(parser): Meaning: The implementation is not here. This is not the correct place for the FUNCTION marker and it will probably not match anything.""" - parser.read_lines( - [ - "// FUNCTION: HELLO 0x1234", - "void sample_function(int);", - ] + parser.read( + """\ + // FUNCTION: HELLO 0x1234 + void sample_function(int); + """ ) assert len(parser.alerts) == 1 @@ -722,15 +732,15 @@ def test_extra(parser): # Intentionally using non-vtable markers here. # We might want to emit a parser warning for unnecessary extra info. - parser.read_lines( - [ - "// GLOBAL: TEST 0x5555 Haha", - "int g_variable = 0;", - "// FUNCTION: TEST 0x1234 Something", - "void Test() { g_variable++; }", - "// LIBRARY: TEST 0x8080 Printf", - "// _printf", - ] + parser.read( + """\ + // GLOBAL: TEST 0x5555 Haha + int g_variable = 0; + // FUNCTION: TEST 0x1234 Something + void Test() { g_variable++; } + // LIBRARY: TEST 0x8080 Printf + // _printf + """ ) # We don't use this information (yet) but this is all fine. @@ -740,14 +750,14 @@ def test_extra(parser): def test_virtual_inheritance(parser): """Indicate the base class for a vtable where the class uses virtual inheritance.""" - parser.read_lines( - [ - "// VTABLE: HELLO 0x1234", - "// VTABLE: HELLO 0x1238 Greetings", - "// VTABLE: HELLO 0x123c Howdy", - "class HiThere : public virtual Greetings {", - "};", - ] + parser.read( + """\ + // VTABLE: HELLO 0x1234 + // VTABLE: HELLO 0x1238 Greetings + // VTABLE: HELLO 0x123c Howdy + class HiThere : public virtual Greetings { + }; + """ ) assert len(parser.alerts) == 0 @@ -759,13 +769,13 @@ def test_virtual_inheritance(parser): def test_namespace_in_comment(parser): - parser.read_lines( - [ - "// VTABLE: HELLO 0x1234", - "// class Tgl::Object", - "// VTABLE: HELLO 0x5555", - "// class TglImpl::RendererImpl", - ] + parser.read( + """\ + // VTABLE: HELLO 0x1234 + // class Tgl::Object + // VTABLE: HELLO 0x5555 + // class TglImpl::RendererImpl + """ ) assert len(parser.vtables) == 2 diff --git a/tests/test_parser_samples.py b/tests/test_parser_samples.py index 9832c46f..ccb0b5ef 100644 --- a/tests/test_parser_samples.py +++ b/tests/test_parser_samples.py @@ -31,7 +31,7 @@ def fixture_parser(): def test_sanity(parser): """Read a very basic file""" with sample_file("basic_file.cpp") as f: - parser.read_lines(f) + parser.read(f.read()) assert len(parser.functions) == 3 assert code_blocks_are_sorted(parser.functions) is True @@ -45,7 +45,7 @@ def test_oneline(parser): """(Assuming clang-format permits this) This sample has a function on a single line. This will test the end-of-function detection""" with sample_file("oneline_function.cpp") as f: - parser.read_lines(f) + parser.read(f.read()) assert len(parser.functions) == 2 assert parser.functions[0].line_number == 5 @@ -55,7 +55,7 @@ def test_oneline(parser): def test_missing_offset(parser): """What if the function doesn't have an offset comment?""" with sample_file("missing_offset.cpp") as f: - parser.read_lines(f) + parser.read(f.read()) # TODO: For now, the function without the offset will just be ignored. # Would be the same outcome if the comment was present but mangled and @@ -68,7 +68,7 @@ def test_jumbled_case(parser): the downstream tools to do something about a jumbled file. Just verify that we are reading it correctly.""" with sample_file("out_of_order.cpp") as f: - parser.read_lines(f) + parser.read(f.read()) assert len(parser.functions) == 3 assert code_blocks_are_sorted(parser.functions) is False @@ -76,7 +76,7 @@ def test_jumbled_case(parser): def test_bad_file(parser): with sample_file("poorly_formatted.cpp") as f: - parser.read_lines(f) + parser.read(f.read()) assert len(parser.functions) == 3 @@ -84,7 +84,7 @@ def test_bad_file(parser): def test_indented(parser): """Offsets for functions inside of a class will probably be indented.""" with sample_file("basic_class.cpp") as f: - parser.read_lines(f) + parser.read(f.read()) # TODO: We don't properly detect the end of these functions # because the closing brace is indented. However... knowing where each @@ -103,7 +103,7 @@ def test_indented(parser): def test_inline(parser): with sample_file("inline.cpp") as f: - parser.read_lines(f) + parser.read(f.read()) assert len(parser.functions) == 2 for fun in parser.functions: @@ -116,7 +116,7 @@ def test_multiple_offsets(parser): all but ensure module name (case-insensitive) is distinct. Use first module occurrence in case of duplicates.""" with sample_file("multiple_offsets.cpp") as f: - parser.read_lines(f) + parser.read(f.read()) assert len(parser.functions) == 4 assert parser.functions[0].module == "TEST" @@ -135,7 +135,7 @@ def test_multiple_offsets(parser): def test_variables(parser): with sample_file("global_variables.cpp") as f: - parser.read_lines(f) + parser.read(f.read()) assert len(parser.functions) == 1 assert len(parser.variables) == 2