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_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_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_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}:" )
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)
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