def test_prologue_evaluate_inner_line_span(mocker): """ Test use of line spanning using '\' to escape new line """ pro = Prologue() ctx = Context(pro) m_reg = mocker.patch.object(pro, "registry", autospec=True) mocker.patch.object(RegistryFile, "__init__", lambda x: None) m_con = mocker.patch.object(RegistryFile, "contents", new_callable=PropertyMock) # Create a fake file r_file = RegistryFile() r_file.path = Path(random_str(5, 10) + "." + random_str(5, 10)) m_reg.resolve.side_effect = [r_file] # Setup fake file contents intro = [random_str(10, 50, spaces=True) for _x in range(randint(5, 10))] span = [(random_str(10, 50, spaces=True) + "\\") for _x in range(randint(5, 10))] span += [random_str(10, 50, spaces=True)] outro = [random_str(10, 50, spaces=True) for _x in range(randint(5, 10))] m_con.return_value = intro + span + outro # Pull all lines out of the evaluate loop result = [x for x in pro.evaluate_inner(r_file.filename, ctx)] # Checks assert result == intro + ["".join([x.replace("\\", "") for x in span])] + outro m_reg.resolve.assert_has_calls([call(r_file.filename)]) assert ctx.stack == []
def test_prologue_evaluate_inner_break_loop(mocker): """ Check that an infinite include loop is detected """ pro = Prologue() m_reg = mocker.patch.object(pro, "registry", autospec=True) mocker.patch.object(RegistryFile, "__init__", lambda x: None) m_con = mocker.patch.object(RegistryFile, "contents", new_callable=PropertyMock) # Create a context with a bunch of mock files ctx = Context(pro) for _x in range(randint(10, 30)): ctx.stack_push(RegistryFile()) ctx.stack[-1].path = Path(random_str(5, 10) + "." + random_str(5, 10)) # Try evaluating files that are already on the stack for _x in range(100): r_file = choice(ctx.stack) m_reg.resolve.side_effect = [r_file] with pytest.raises(PrologueError) as excinfo: next(pro.evaluate_inner(r_file.filename, ctx)) assert ( f"Detected infinite recursion when including file '{r_file.filename}'" f" - file stack: {', '.join([x.filename for x in ctx.stack])}" ) == str(excinfo.value) m_reg.resolve.assert_has_calls([call(r_file.filename)]) m_reg.reset_mock() # Check a new file is pushed to the stack new_file = RegistryFile() new_file.path = Path(random_str(5, 10) + "." + random_str(5, 10)) m_reg.resolve.side_effect = [new_file] m_con.return_value = [random_str(5, 10), random_str(5, 10)] next(pro.evaluate_inner(new_file.filename, ctx)) assert ctx.stack[-1] == new_file
def test_prologue_add_file(mocker): """ Test that add file calls to the registry """ pro = Prologue() mocker.patch.object(pro, "registry", autospec=True) ignore_dup = choice((True, False)) pro.add_file("test_file_1234", ignore_duplicate=ignore_dup) pro.registry.add_file.assert_called_once_with( "test_file_1234", ignore_duplicate=ignore_dup, )
def test_prologue_bad_shared(): """ Try to setup Prologue with a bad shared value """ # Check a bad value doesn't work with pytest.raises(PrologueError) as excinfo: Prologue(shared_delimiter="banana") assert "Shared delimiter must be True or False: banana" in str( excinfo.value) # Check both sane values work for val in (True, False): assert Prologue(shared_delimiter=val).shared_delimiter == val
def test_prologue_evaluate_inner_block_floating(mocker): """ Test that floating block directives are flagged """ # Choose a delimiter delim = choice(("#", "@", "$", "%", "!")) # Create preprocessor, context, etc pro = Prologue(delimiter=delim) ctx = Context(pro) m_reg = mocker.patch.object(pro, "registry", autospec=True) mocker.patch.object(RegistryFile, "__init__", lambda x: None) m_con = mocker.patch.object(RegistryFile, "contents", new_callable=PropertyMock) # Create a block directive class BlockDirx(BlockDirective): def __init__(self, parent, src_file=None, src_line=0, callback=None): super().__init__( parent, yields=True, src_file=src_file, src_line=src_line, callback=callback, ) opening = [random_str(5, 10) for _x in range(randint(1, 5))] closing = [random_str(5, 10, avoid=opening) for _x in range(1, 5)] transit = [ random_str(5, 10, avoid=opening + closing) for _x in range(1, 5) ] pro.register_directive( DirectiveWrap(BlockDirx, opening, transition=transit, closing=closing)) # Create a fake file r_file = RegistryFile() r_file.path = Path(random_str(5, 10) + "." + random_str(5, 10)) m_reg.resolve.side_effect = [r_file] # Setup fake file contents contents = [] used_open = [] for idx in range(randint(50, 100)): if choice((True, False)): used_open.append(choice(opening)) contents.append( random_str(50, 100, spaces=True) + f" {delim}{used_open[-1]} {random_str(50, 100, spaces=True)}") else: contents.append(random_str(50, 100, spaces=True)) m_con.return_value = [ Line(x, r_file, i + 1) for i, x in enumerate(contents) ] # Catch the floating block error with pytest.raises(PrologueError) as excinfo: [x for x in pro.evaluate_inner(r_file.filename, ctx)] assert (f"The directive '{used_open[0].lower()}' can only be used with an " f"anchored delimiter as it is a block directive") == str( excinfo.value)
def test_prologue_resolve(): """ Test resolving input line number and file path from output line number """ pro = Prologue() # Before populating lookup, check for error with pytest.raises(PrologueError) as excinfo: pro.resolve([], randint(1, 10000)) assert "Lookup is either empty or not a list: []" == str(excinfo.value) # Populate the lookup with random entries num_before = randint(0, 5) num_after = randint(0, 5) def gen_random_entry(): fake_file = MagicMock() fake_file.contents = [ Line(random_str(20, 30), fake_file, x + 1) for x in range(randint(20, 50)) ] fake_file.snippet.return_value = "this is the snippet" fake_num = randint(1, len(fake_file.contents)) fake_before = min(num_before, fake_num - 1) fake_after = min(num_after, len(fake_file.contents) - fake_num) snippet = [ x.encase(f"{x.number:4d} {str(x)}") for x in (fake_file.contents[fake_num - 1 - fake_before:fake_num + fake_after]) ] snippet[fake_before] = snippet[fake_before].replace(" ", " >> ") return (fake_file, fake_num, [str(x) for x in snippet]) entries = [gen_random_entry() for _ in range(randint(100, 200))] lookup = [(x[0], x[1]) for x in entries] # Try using a non-integer for obj in (random_str(5, 10), {}, [], Prologue, MagicMock): with pytest.raises(PrologueError) as excinfo: pro.resolve(lookup, obj) assert f"Line number must be an integer - not '{obj}'" == str( excinfo.value) # Try out-of-range lines for _x in range(100): too_low = choice((True, False)) value = randint(-100, 0) if too_low else randint(len(entries) + 1, 300) with pytest.raises(PrologueError) as excinfo: pro.resolve(lookup, value) assert f"Line {value} is out of valid range 1-{len(entries)}" == str( excinfo.value) # Test random lines for _x in range(100): entry = choice(entries) out_line = entries.index(entry) + 1 # Resolve the input file, line number, and snippet r_file, line_no, snippet = pro.resolve(lookup, out_line, before=num_before, after=num_after) # Check file, line number, and snippet match those in the fake lookup assert entry[0] == r_file assert entry[1] == line_no assert snippet == "this is the snippet" r_file.snippet.assert_has_calls([call(line_no, num_before, num_after)]) r_file.snippet.reset_mock()
def test_prologue_bad_new_shared(): """ Try to change Prologue's shared delimiter value """ pro = Prologue() # Check a bad value doesn't work with pytest.raises(PrologueError) as excinfo: pro.shared_delimiter = "banana" assert "Shared delimiter should be True or False" in str(excinfo.value) # Check both sane values work for val in (True, False): pro.shared_delimiter = val assert pro.shared_delimiter == val
def test_prologue_evaluate_inner_bad_file(mocker): """ Check that an error is raised trying to evaluate a non-existent file """ pro = Prologue() m_reg = mocker.patch.object(pro, "registry", autospec=True) # Try a bunch of random file names for _x in range(100): m_reg.resolve.side_effect = [None] f_name = random_str(5, 10) + "." + random_str(5, 10) with pytest.raises(PrologueError) as excinfo: [x for x in pro.evaluate_inner(f_name, Context(None))] assert f"Failed to find file {f_name}" == str(excinfo.value) m_reg.resolve.assert_has_calls([call(f_name)]) m_reg.reset_mock()
def test_prologue_add_folder(mocker): """ Test that add folder calls to the registry """ pro = Prologue() mocker.patch.object(pro, "registry", autospec=True) ignore_dup = choice((True, False)) pro.add_folder("test_folder_1234", ".txt", True, ignore_duplicate=ignore_dup) pro.registry.add_folder.assert_called_once_with( "test_folder_1234", search_for=".txt", recursive=True, ignore_duplicate=ignore_dup, )
def test_prologue_bad_delimiter(): """ Try to setup Prologue with a bad delimiter """ # Use an empty delimiter with pytest.raises(PrologueError) as excinfo: Prologue(delimiter="") assert "Delimiter should be at least one character" in str(excinfo.value) # Use just whitespace with pytest.raises(PrologueError) as excinfo: Prologue(delimiter=" ") assert "Delimiter should be at least one character" in str(excinfo.value) # Use mix of whitespace and characters with pytest.raises(PrologueError) as excinfo: Prologue(delimiter=" / /") assert "Delimiter should not contain whitespace" in str(excinfo.value) # Check a sane value works assert Prologue(delimiter="//").delimiter == "//"
def test_prologue_messages(mocker): """ Test that debug messages are logged using 'print' or callback """ pro = Prologue() mock_print = mocker.patch("builtins.print") for func, cb, mtype in [ (pro.debug_message, "callback_debug", "DEBUG"), (pro.info_message, "callback_info", "INFO"), (pro.warning_message, "callback_warning", "WARN"), (pro.error_message, "callback_error", "ERROR"), ]: # First test logging via 'print' (default behaviour) if mtype == "ERROR": with pytest.raises(PrologueError) as excinfo: func("Hello 1234!") assert "Hello 1234!" in str(excinfo.value) else: func("Hello 1234!") mock_print.assert_called_once_with( f"[PROLOGUE:{mtype}] Hello 1234!") # Now test logging via callback setattr(pro, cb, MagicMock()) func("Goodbye 9876?") getattr(pro, cb).assert_called_once_with("Goodbye 9876?") # Reset mocks mock_print.reset_mock() getattr(pro, cb).reset_mock()
def test_prologue_bad_arguments(): """ Try bad arguments when initialising Prologue """ # Use a bad comment with pytest.raises(PrologueError) as excinfo: Prologue(comment=choice((123, True, {}))) assert str(excinfo.value).startswith("Comment sequence must be a string") # Use a bad delimiter with pytest.raises(PrologueError) as excinfo: Prologue(delimiter=choice((123, True, {}))) assert str(excinfo.value).startswith("Delimiter sequence must be a string") # Use a bad shared delimiter setting with pytest.raises(PrologueError) as excinfo: Prologue(shared_delimiter=choice((123, "Hello", -4, 5.03))) assert str( excinfo.value).startswith("Shared delimiter must be True or False") # Use a bad implicit substitution setting with pytest.raises(PrologueError) as excinfo: Prologue(implicit_sub=choice((123, "Hello", -4, 5.03))) assert str(excinfo.value).startswith( "Implicit substitution must be True or False") # Use a bad explicit style with pytest.raises(PrologueError) as excinfo: Prologue(explicit_style=choice((123, True, {}, "hello"))) assert str(excinfo.value).startswith("Explicit style must be a tuple") # Use a bad allow redefinition setting with pytest.raises(PrologueError) as excinfo: Prologue(allow_redefine=choice((123, "Hello", -4, 5.03))) assert str( excinfo.value).startswith("Allow redefinition must be True or False") # Use a bad register prime setting with pytest.raises(PrologueError) as excinfo: Prologue(register_prime=choice((123, "Hello", -4, 5.03))) assert str( excinfo.value).startswith("Register prime must be True or False")
def test_prologue_evaluate_inner_bad_context(mocker): """ Check that an error is raised when evaluating with a non-Context object """ pro = Prologue() m_reg = mocker.patch.object(pro, "registry", autospec=True) # Try a bunch of random file names for _x in range(100): m_reg.resolve.side_effect = [MagicMock()] f_name = random_str(5, 10) + "." + random_str(5, 10) bad_ctx = choice( (True, False, {}, random_str(5, 10), randint(1, 10000), [], MagicMock())) with pytest.raises(PrologueError) as excinfo: next(pro.evaluate_inner(f_name, bad_ctx)) assert f"An invalid context was provided: {bad_ctx}" == str( excinfo.value) m_reg.resolve.assert_has_calls([call(f_name)]) m_reg.reset_mock()
def test_prologue_evaluate_inner_plain(mocker): """ Check that a plain sequence of lines is reproduced within alteration """ pro = Prologue() ctx = Context(pro) m_reg = mocker.patch.object(pro, "registry", autospec=True) mocker.patch.object(RegistryFile, "__init__", lambda x: None) m_con = mocker.patch.object(RegistryFile, "contents", new_callable=PropertyMock) # Create a fake file r_file = RegistryFile() r_file.path = Path(random_str(5, 10) + "." + random_str(5, 10)) m_reg.resolve.side_effect = [r_file] # Setup fake file contents contents = [ random_str(10, 50, spaces=True) for _x in range(randint(50, 100)) ] m_con.return_value = contents # Pull all lines out of the evaluate loop result = [x for x in pro.evaluate_inner(r_file.filename, ctx)] # Checks assert result == contents m_reg.resolve.assert_has_calls([call(r_file.filename)]) assert ctx.stack == []
def test_prologue_bad_new_delimiter(): """ Try to change the Prologue delimiter to a bad value """ pro = Prologue(delimiter="#") # Read back the delimiter assert pro.delimiter == "#" # Use an empty delimiter with pytest.raises(PrologueError) as excinfo: pro.delimiter = "" assert "Delimiter should be at least one character" in str(excinfo.value) # Use just whitespace with pytest.raises(PrologueError) as excinfo: pro.delimiter = " " assert "Delimiter should be at least one character" in str(excinfo.value) # Use mix of whitespace and characters with pytest.raises(PrologueError) as excinfo: pro.delimiter = " / /" assert "Delimiter should not contain whitespace" in str(excinfo.value) # Check a sane value works pro.delimiter = "//" assert pro.delimiter == "//"
def test_prologue_evaluate_inner_block_confused(mocker): """ Check that one block can't be closed by another's tags """ # Choose a delimiter delim = choice(("#", "@", "$", "%", "!")) # Create a pair of block directives class BlockDirA(BlockDirective): def __init__(self, parent, src_file=None, src_line=0, callback=None): super().__init__( parent, yields=True, src_file=src_file, src_line=src_line, callback=callback, ) class BlockDirB(BlockDirective): def __init__(self, parent, src_file=None, src_line=0, callback=None): super().__init__( parent, yields=True, src_file=src_file, src_line=src_line, callback=callback, ) all_tags = [] opening_a = [ random_str(5, 10, avoid=all_tags) for _x in range(randint(1, 5)) ] all_tags += opening_a closing_a = [ random_str(5, 10, avoid=all_tags) for _x in range(randint(1, 5)) ] all_tags += closing_a transit_a = [ random_str(5, 10, avoid=all_tags) for _x in range(randint(1, 5)) ] all_tags += transit_a opening_b = [ random_str(5, 10, avoid=all_tags) for _x in range(randint(1, 5)) ] all_tags += opening_b closing_b = [ random_str(5, 10, avoid=all_tags) for _x in range(randint(1, 5)) ] all_tags += closing_b transit_b = [ random_str(5, 10, avoid=all_tags) for _x in range(randint(1, 5)) ] # Create a fake file mocker.patch.object(RegistryFile, "__init__", lambda x: None) r_file = RegistryFile() r_file.path = Path(random_str(5, 10) + "." + random_str(5, 10)) # Create preprocessor, context, etc for _x in range(100): pro = Prologue(delimiter=delim) ctx = Context(pro) m_reg = mocker.patch.object(pro, "registry", autospec=True) m_reg.resolve.side_effect = [r_file] m_con = mocker.patch.object(RegistryFile, "contents", new_callable=PropertyMock) pro.register_directive( DirectiveWrap(BlockDirA, opening_a, transition=transit_a, closing=closing_a)) pro.register_directive( DirectiveWrap(BlockDirB, opening_b, transition=transit_b, closing=closing_b)) # Setup fake file contents bad_tag = choice(transit_b + closing_b) contents = [] contents += [ random_str(50, 100, spaces=True) for _x in range(randint(5, 10)) ] contents += [ f"{delim}{choice(opening_a)} {random_str(50, 100, spaces=True)}" ] contents += [ random_str(50, 100, spaces=True) for _x in range(randint(5, 10)) ] contents += [f"{delim}{bad_tag} {random_str(50, 100, spaces=True)}"] contents += [ random_str(50, 100, spaces=True) for _x in range(randint(5, 10)) ] m_con.return_value = [ Line(x, r_file, i + 1) for i, x in enumerate(contents) ] # Expect an unexpected transition tag with pytest.raises(PrologueError) as excinfo: [x for x in pro.evaluate_inner(r_file.filename, ctx)] if bad_tag in transit_b: assert ( f"Transition tag '{bad_tag.lower()}' was not expected") == str( excinfo.value) else: assert ( f"Closing tag '{bad_tag.lower()}' was not expected") == str( excinfo.value)
import sys from prologue import Prologue # Check for right number of arguments, then extract them if len(sys.argv) != 4: print("SYNTAX: python3 verilog.py <INPUTDIR> <TOPFILE> <OUTPUT>") sys.exit(1) in_dir = Path(sys.argv[1]) top = Path(sys.argv[2]) output = Path(sys.argv[3]) # Setup preprocessor instance pro = Prologue( comment ="//", delimiter ="`", shared_delimiter=True, # Verilog uses "`" to signal a defined variable implicit_sub =False, # Every substitution must be prefixed with "`" explicit_style =("`", ""), ) # Find all files within the input directory and add them to the registry for f_path in in_dir.glob("*.*"): pro.add_file(f_path) # Kick off evaluate using the top-level file with open(output, "w") as fh: for line in pro.evaluate(top.parts[-1]): fh.write(line + "\n")
def test_prologue_register_directive(): """ Test registration of block and line directives """ pro = Prologue() class LineDirx(LineDirective): pass class BlockDirx(BlockDirective): pass wrap_line = DirectiveWrap(LineDirx, [random_str(5, 10)]) wrap_block = DirectiveWrap(BlockDirx, [random_str(5, 10)], closing=[random_str(5, 10)]) # Try registration pro.register_directive(wrap_line) pro.register_directive(wrap_block) # Check registrations assert pro.get_directive(wrap_line.opening[0]) == wrap_line assert pro.get_directive(wrap_block.opening[0]) == wrap_block # Try registration a bad directive for obj in (random_str(5, 10), randint(1, 1000), Prologue): with pytest.raises(PrologueError) as excinfo: pro.register_directive(obj) assert "Directive type is not known, is it decorated?" == str( excinfo.value) # Try overriding existing directive class OtherLineDirx(LineDirective): pass class OtherBlockDirx(BlockDirective): pass other_line = DirectiveWrap(OtherLineDirx, wrap_line.opening) other_block = DirectiveWrap(OtherBlockDirx, wrap_block.opening, closing=wrap_block.closing) with pytest.raises(PrologueError) as excinfo: pro.register_directive(other_line) assert f"Directive already registered for tag '{wrap_line.opening[0]}'" == str( excinfo.value) with pytest.raises(PrologueError) as excinfo: pro.register_directive(other_block) assert f"Directive already registered for tag '{wrap_block.opening[0]}'" == str( excinfo.value)
def test_prologue_evaluate_inner_block(mocker, should_yield): """ Check that a block directive is detected """ # Choose a delimiter delim = choice(("#", "@", "$", "%", "!")) # Create preprocessor, context, etc pro = Prologue(delimiter=delim) ctx = Context(pro) m_reg = mocker.patch.object(pro, "registry", autospec=True) mocker.patch.object(RegistryFile, "__init__", lambda x: None) m_con = mocker.patch.object(RegistryFile, "contents", new_callable=PropertyMock) # Create a line directive dirx_inst = [] class BlockDirx(BlockDirective): def __init__(self, parent, src_file=None, src_line=0, callback=None): super().__init__( parent, yields=should_yield, src_file=src_file, src_line=src_line, callback=callback, ) dirx_inst.append(self) mocker.patch.object(BlockDirx, "open", autospec=True) mocker.patch.object(BlockDirx, "transition", autospec=True) mocker.patch.object(BlockDirx, "close", autospec=True) mocker.patch.object(BlockDirx, "evaluate", autospec=True) def do_open(self, tag, arguments): self._BlockDirective__opened = True def do_close(self, tag, arguments): self._BlockDirective__closed = True BlockDirx.open.side_effect = do_open BlockDirx.close.side_effect = do_close dirx_text = [] for _x in range(randint(5, 10)): dirx_text.append(random_str(20, 30, spaces=True)) def block_eval(self, context): for line in dirx_text: yield Line(line, None, randint(1, 10000)) BlockDirx.evaluate.side_effect = block_eval opening = [random_str(5, 10) for _x in range(randint(1, 5))] closing = [random_str(5, 10, avoid=opening) for _x in range(1, 5)] transit = [ random_str(5, 10, avoid=opening + closing) for _x in range(1, 5) ] pro.register_directive( DirectiveWrap(BlockDirx, opening, transition=transit, closing=closing)) # Create a fake file r_file = RegistryFile() r_file.path = Path(random_str(5, 10) + "." + random_str(5, 10)) m_reg.resolve.side_effect = [r_file] # Setup fake file contents contents = [] output = [] open_calls = [] tran_calls = [] close_calls = [] for idx in range(randint(50, 100)): use_dirx = choice((True, False)) open_arg = random_str(50, 100, spaces=True) tran_args = [ random_str(50, 100, spaces=True) for _x in range(randint(0, 3)) ] close_arg = random_str(50, 100, spaces=True) open_tag = choice(opening) close_tag = choice(closing) tran_tag = choice(transit) if use_dirx: contents.append(f"{delim}{open_tag} {open_arg}") else: contents.append(random_str(50, 100, spaces=True)) # If this is a directive, generate transitions and closing if use_dirx: # Opening block contents for _x in range(randint(5, 10)): contents.append(random_str(20, 30, spaces=True)) # Transitions for arg in tran_args: contents.append(f"{delim}{tran_tag} {arg}") for _x in range(5, 10): contents.append(random_str(20, 30, spaces=True)) contents.append(f"{delim}{close_tag} {close_arg}") # Setup expected output if use_dirx and should_yield: output += dirx_text elif not use_dirx: output.append(contents[-1]) # Accumulate calls if use_dirx: open_calls.append(call(ANY, open_tag.lower(), open_arg)) for arg in tran_args: tran_calls.append(call(ANY, tran_tag.lower(), arg)) close_calls.append(call(ANY, close_tag.lower(), close_arg)) m_con.return_value = [ Line(x, r_file, i + 1) for i, x in enumerate(contents) ] # Create a dummy callback def dummy_cb(): pass # Pull all lines out of the evaluate loop result = [ x for x in pro.evaluate_inner(r_file.filename, ctx, callback=dummy_cb) ] # Checks assert len(result) == len(output) assert ctx.stack == [] m_reg.resolve.assert_has_calls([call(r_file.filename)]) for got_out, exp_out in zip(result, output): assert str(got_out) == exp_out.rstrip(" ") BlockDirx.open.assert_has_calls(open_calls) BlockDirx.transition.assert_has_calls(tran_calls) BlockDirx.close.assert_has_calls(close_calls) for dirx in dirx_inst: assert dirx.callback == dummy_cb
def test_prologue_evaluate_inner_line(mocker, should_yield): """ Check that a line directive is detected """ # Choose a delimiter delim = choice(("#", "@", "$", "%", "!")) # Create preprocessor, context, etc pro = Prologue(delimiter=delim) ctx = Context(pro) m_reg = mocker.patch.object(pro, "registry", autospec=True) mocker.patch.object(RegistryFile, "__init__", lambda x: None) m_con = mocker.patch.object(RegistryFile, "contents", new_callable=PropertyMock) # Create a line directive dirx_inst = [] class LineDirx(LineDirective): def __init__(self, parent, src_file=None, src_line=0, callback=None): super().__init__( parent, yields=should_yield, src_file=src_file, src_line=src_line, callback=callback, ) dirx_inst.append(self) mocker.patch.object(LineDirx, "invoke", autospec=True) mocker.patch.object(LineDirx, "evaluate", autospec=True) dirx_text = "LINE DIRX " + random_str(20, 30, spaces=True) + " END LINE" def line_eval(self, context): yield Line(dirx_text, None, randint(1, 10000)) LineDirx.evaluate.side_effect = line_eval opening = [random_str(5, 10) for _x in range(randint(1, 5))] pro.register_directive(DirectiveWrap(LineDirx, opening)) # Create a fake file r_file = RegistryFile() r_file.path = Path(random_str(5, 10) + "." + random_str(5, 10)) m_reg.resolve.side_effect = [r_file] # Setup fake file contents contents = [] output = [] dirx_calls = [] for idx in range(randint(50, 100)): use_dirx = choice((True, False)) anchor = choice((True, False)) argument = random_str(50, 100, spaces=True) use_tag = choice(opening) line_txt = "" if use_dirx: if not anchor: line_txt += random_str(50, 100, spaces=True) + " " line_txt += f"{delim}{use_tag} {argument}" else: line_txt += random_str(50, 100, spaces=True) # Accumulate the data to push into evaluate contents.append(line_txt) # Accumulate expected outputs if not (use_dirx and anchor): output.append(line_txt.split(delim)[0]) if should_yield: if use_dirx and anchor: output.append(dirx_text) if use_dirx and not anchor: output.append(dirx_text) # Accumulate calls if use_dirx: dirx_calls.append(call(ANY, use_tag.lower(), argument)) m_con.return_value = [ Line(x, r_file, i + 1) for i, x in enumerate(contents) ] # Create a dummy callback def dummy_cb(): pass # Pull all lines out of the evaluate loop result = [ x for x in pro.evaluate_inner(r_file.filename, ctx, callback=dummy_cb) ] # Checks assert len(result) == len(output) assert ctx.stack == [] m_reg.resolve.assert_has_calls([call(r_file.filename)]) for got_out, exp_out in zip(result, output): assert str(got_out) == exp_out.rstrip(" ") LineDirx.invoke.assert_has_calls(dirx_calls) for dirx in dirx_inst: assert dirx.callback == dummy_cb
def test_prologue_get_directive(): """ Request registered and unregistered directives """ pro = Prologue() # Register a bunch of directives class LineDirx(LineDirective): pass class BlockDirx(BlockDirective): pass line_opens = [random_str(3, 10) for _x in range(5)] block_opens = [random_str(3, 10, avoid=line_opens) for _x in range(5)] block_close = [ random_str(3, 10, avoid=(line_opens + block_opens)) for _x in range(5) ] wrap_line = DirectiveWrap(LineDirx, line_opens) wrap_block = DirectiveWrap(BlockDirx, block_opens, closing=block_close) for opening in line_opens: assert not pro.has_directive(opening) pro.register_directive(wrap_line) for tag in line_opens: assert pro.has_directive(tag) for tag in (block_opens + block_close): assert not pro.has_directive(tag) pro.register_directive(wrap_block) for tag in (block_opens + block_close): assert pro.has_directive(tag) # List all of the registered directives reg_dirx = pro.list_directives() assert wrap_line in reg_dirx assert wrap_block in reg_dirx # Test that correct directive is returned each time all_tags = line_opens + block_opens + block_close for use_shared in (False, True): pro.shared_delimiter = use_shared for _x in range(100): use_bad = choice((True, False)) tag = random_str(3, 10, avoid=all_tags) if use_bad else choice(all_tags) # If using a bad directive without shared delimiters, expect an exception if use_bad and not use_shared: with pytest.raises(PrologueError) as excinfo: pro.get_directive(tag) assert f"No directive known for tag '{tag}'" == str( excinfo.value) # If using a bad directive with shared delimiters, expect None elif use_bad and use_shared: assert pro.get_directive(tag) == None # Otherwise, expect the correct directive to be returned else: if tag in line_opens: assert pro.get_directive(tag) == wrap_line else: assert pro.get_directive(tag) == wrap_block # Test de-registering directives pro.deregister_directive(choice(line_opens)) for tag in line_opens: assert not pro.has_directive(tag) pro.deregister_directive(choice(block_opens + block_close)) for tag in (block_opens + block_close): assert not pro.has_directive(tag) # Test deregistering directives again for tags in (line_opens, block_opens + block_close): use_tag = choice(tags) with pytest.raises(PrologueError) as excinfo: pro.deregister_directive(use_tag) assert str( excinfo.value) == f"No directive registered for tag '{use_tag}'"
def test_prologue_evaluate(mocker): """ Test evaluation of a Prologue instance """ # Patch Context mock_ctx_cls = mocker.patch("prologue.Context", autospec=True) mock_ctx_inst = [] def create_context(*args, **kwargs): mock_ctx = MagicMock() def fake_sub(line): print(f"Called fake_sub with {type(line)} {line}") return line.encase("start sub " + str(line) + " end sub") mock_ctx.substitute.side_effect = fake_sub mock_ctx_inst.append(mock_ctx) return mock_ctx mock_ctx_cls.side_effect = create_context # Create a Prologue instance, and patch 'evaluate_inner' pro = Prologue() mocker.patch.object(pro, "evaluate_inner", autospec=True) # Setup some fake lines to be produced by 'evaluate_inner' l_file = random_str(20, 30) lines = [ Line(random_str(30, 50, spaces=True), l_file, idx + 1) for idx in range(randint(20, 30)) ] def gen_lines(file, ctx, callback=None): for line in lines: yield line pro.evaluate_inner.side_effect = gen_lines # Create a dummy callback routine def dummy_cb(): print(f"Hit the dummy callback routine") # Call evaluate initial = {random_str(5, 10): random_str(5, 10) for _ in range(5)} lookup = [] result = [ x for x in pro.evaluate( l_file, defines=initial, callback=dummy_cb, lookup=lookup, ) ] # Check that the initial state reached the context mock_ctx_cls.assert_has_calls([ call( pro, implicit_sub=True, explicit_style=("$(", ")"), allow_redefine=False, initial_state=initial, ) ]) # Check that only 'str' objects were yielded assert len([x for x in result if not isinstance(x, str)]) == 0 # Check that every line contains substitutions assert result == [f"start sub {str(x)} end sub" for x in lines] # Check that the lookup has been populated correctly assert lookup == [(x.file, x.number) for x in lines] # Check calls to 'evaluate_inner' pro.evaluate_inner.assert_has_calls( [call( l_file, mock_ctx_inst[0], callback=dummy_cb, )]) # Check calls to 'substitute' mock_ctx_inst[0].substitute.assert_has_calls([call(x) for x in lines])
# distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from pathlib import Path import sys from prologue import Prologue # Check for right number of arguments, then extract them if len(sys.argv) != 4: print("SYNTAX: python3 c_style.py <INPUTDIR> <TOPFILE> <OUTPUT>") sys.exit(1) in_dir = Path(sys.argv[1]) top = Path(sys.argv[2]) output = Path(sys.argv[3]) # Setup preprocessor instance pro = Prologue(delimiter="#", shared_delimiter=False) # Find all files within the input directory and add them to the registry for f_path in in_dir.glob("*.*"): pro.add_file(f_path) # Kick off evaluate using the top-level file with open(output, "w") as fh: for line in pro.evaluate(top.parts[-1]): fh.write(line + "\n")
def test_prologue_evaluate_inner_block_trailing(mocker): """ Check that unclosed blocks at the end of the file are detected """ # Choose a delimiter delim = choice(("#", "@", "$", "%", "!")) # Create a pair of block directives dirx_inst = [] class BlockDirx(BlockDirective): def __init__(self, parent, src_file, src_line, callback=None): super().__init__( parent, yields=True, src_file=src_file, src_line=src_line, callback=callback, ) dirx_inst.append(self) opening = [random_str(5, 10) for _x in range(randint(1, 5))] closing = [random_str(5, 10, avoid=opening) for _x in range(randint(1, 5))] transit = [ random_str(5, 10, avoid=opening + closing) for _x in range(randint(1, 5)) ] BlockDirx.OPENING = opening # Create a fake file mocker.patch.object(RegistryFile, "__init__", lambda x: None) r_file = RegistryFile() r_file.path = Path(random_str(5, 10) + "." + random_str(5, 10)) # Create preprocessor, context, etc for _x in range(100): pro = Prologue(delimiter=delim) ctx = Context(pro) m_reg = mocker.patch.object(pro, "registry", autospec=True) m_reg.resolve.side_effect = [r_file] m_con = mocker.patch.object(RegistryFile, "contents", new_callable=PropertyMock) pro.register_directive( DirectiveWrap(BlockDirx, opening, transition=transit, closing=closing)) # Setup fake file contents contents = [] contents += [ random_str(50, 100, spaces=True) for _x in range(randint(5, 10)) ] open_idx = len(contents) contents += [ f"{delim}{choice(opening)} {random_str(50, 100, spaces=True)}" ] contents += [ random_str(50, 100, spaces=True) for _x in range(randint(5, 10)) ] for _y in range(randint(0, 3)): contents += [ f"{delim}{choice(transit)} {random_str(50, 100, spaces=True)}" ] contents += [ random_str(50, 100, spaces=True) for _x in range(randint(5, 10)) ] m_con.return_value = [ Line(x, r_file, i + 1) for i, x in enumerate(contents) ] # Expected an unclosed directive with pytest.raises(PrologueError) as excinfo: [x for x in pro.evaluate_inner(r_file.filename, ctx)] assert str(excinfo.value).startswith( f"Unmatched BlockDirx block directive in {r_file.path}:{open_idx+1}:" )
# distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from pathlib import Path import sys from prologue import Prologue # Check for right number of arguments, then extract them if len(sys.argv) != 4: print("SYNTAX: python3 demo.py <INPUTDIR> <TOPFILE> <OUTPUT>") sys.exit(1) in_dir = Path(sys.argv[1]) top = Path(sys.argv[2]) output = Path(sys.argv[3]) # Setup preprocessor instance (use defaults) pro = Prologue() # Find all files within the input directory and add them to the registry for f_path in in_dir.glob("*.*"): pro.add_file(f_path) # Kick off evaluate using the top-level file with open(output, "w") as fh: for line in pro.evaluate(top.parts[-1]): fh.write(line + "\n")