[sans-io] playbook: fix multiple placeholders of same type

This commit is contained in:
Maximilian Hils 2020-11-22 15:22:14 +01:00
parent 9251f7820a
commit 553f102d6e
2 changed files with 69 additions and 88 deletions

View File

@ -3,7 +3,7 @@ from dataclasses import dataclass
import pytest
from mitmproxy.proxy2 import events, commands, layer
from mitmproxy.proxy2 import commands, events, layer
from . import tutils
@ -102,63 +102,6 @@ def test_placeholder_type_mismatch(tplaybook):
)
def test_fork(tplaybook):
"""Playbooks can be forked to test multiple execution streams."""
assert (
tplaybook
>> TEvent()
<< TCommand()
)
p2 = tplaybook.fork()
p3 = tplaybook.fork()
assert (
tplaybook
>> TEvent()
<< TCommand()
)
assert (
p2
>> TEvent()
<< TCommand()
)
assert len(tplaybook.actual) == len(tplaybook.expected) == 4
assert len(p2.actual) == len(p2.expected) == 4
assert len(p3.actual) == len(p3.expected) == 2
def test_fork_placeholder(tplaybook):
"""Forks require new placeholders."""
f = tutils.Placeholder()
flow = object()
assert (
tplaybook
>> TEvent([flow])
<< TCommand(f)
)
assert f() == flow
p2 = tplaybook.fork()
p2_flow = p2.expected[0].commands[0]
assert p2_flow != flow
# As we have forked, we need a new placeholder.
f2 = tutils.Placeholder()
assert (
p2
>> TEvent([p2_flow])
<< TCommand(f2)
)
assert f2() == p2_flow
# re-using the old placeholder does not work.
with pytest.raises(AssertionError, match="Playbook mismatch"):
assert (
p2
>> TEvent([p2_flow])
<< TCommand(f)
)
def test_unfinished(tplaybook):
"""We show a warning when playbooks aren't asserted."""
tplaybook >> TEvent()
@ -209,3 +152,28 @@ def test_eq_placeholder():
b.foo._obj = 44
assert not tutils._eq(a, b)
@pytest.mark.parametrize("swap", [False, True])
def test_command_multiple_replies(tplaybook, swap):
a = tutils.Placeholder(int)
b = tutils.Placeholder(int)
command1 = TCommand(a)
command2 = TCommand(b)
(tplaybook
>> TEvent([1])
<< command1
>> TEvent([2])
<< command2
)
if swap:
tplaybook >> tutils.reply(to=command1)
tplaybook >> tutils.reply(to=command2)
else:
tplaybook >> tutils.reply(to=command2)
tplaybook >> tutils.reply(to=command1)
assert tplaybook
assert a() == 1
assert b() == 2

View File

@ -1,5 +1,4 @@
import collections.abc
import copy
import difflib
import itertools
import re
@ -158,7 +157,18 @@ class Playbook:
if c is None:
return self
assert isinstance(c, commands.Command)
self.expected.append(c)
prev = self.expected[-1]
two_subsequent_sends_to_the_same_remote = (
isinstance(c, commands.SendData)
and isinstance(prev, commands.SendData)
and prev.connection is c.connection
)
if two_subsequent_sends_to_the_same_remote:
prev.data += c.data
else:
self.expected.append(c)
return self
def __bool__(self):
@ -191,17 +201,18 @@ class Playbook:
self.actual.append(_TracebackInPlaybook(traceback.format_exc()))
break
self.actual.extend(cmds)
pos = i
for cmd in cmds:
pos += 1
assert self.actual[pos] == cmd
if isinstance(cmd, commands.CloseConnection):
if cmd.half_close:
cmd.connection.state &= ~ConnectionState.CAN_WRITE
else:
cmd.connection.state = ConnectionState.CLOSED
if not self.logs:
for offset, cmd in enumerate(cmds):
pos = i + 1 + offset
elif isinstance(cmd, commands.Log):
need_to_emulate_log = (
isinstance(cmd, commands.Log) and
not self.logs and
cmd.level in ("debug", "info") and
(
pos >= len(self.expected)
@ -210,25 +221,34 @@ class Playbook:
)
if need_to_emulate_log:
self.expected.insert(pos, cmd)
if not self.hooks:
last_cmd = self.actual[-1]
pos = i + len(cmds)
need_to_emulate_hook = (
isinstance(last_cmd, commands.Hook) and
(
pos >= len(self.expected) or
(not (isinstance(self.expected[pos], commands.Hook)
and self.expected[pos].name == last_cmd.name))
)
)
if need_to_emulate_hook:
self.expected.insert(pos, last_cmd)
self.expected.insert(pos + 1, events.HookReply(last_cmd))
elif isinstance(cmd, commands.Hook) and not self.hooks:
need_to_emulate_hook = (
not self.hooks
and (
pos >= len(self.expected) or
(not (
isinstance(self.expected[pos], commands.Hook)
and self.expected[pos].name == cmd.name
))
)
)
if need_to_emulate_hook:
self.expected.insert(pos, cmd)
if cmd.blocking:
self.expected.insert(pos + 1, events.HookReply(cmd))
elif isinstance(cmd, commands.SendData):
prev = self.actual[pos - 1]
two_subsequent_sends_to_the_same_remote = (
isinstance(prev, commands.SendData) and
cmd.connection is prev.connection
)
if two_subsequent_sends_to_the_same_remote:
prev.data += cmd.data
self.actual.pop(pos)
pos -= 1
eq(self.expected[i:], self.actual[i:]) # compare now already to set placeholders
i += 1
self.actual = _merge_sends(self.actual)
self.expected = _merge_sends(self.expected)
if not eq(self.expected, self.actual):
self._errored = True
diff = "\n".join(difflib.ndiff(
@ -247,13 +267,6 @@ class Playbook:
if is_final_destruct or (not self._errored and len(self.actual) < len(self.expected)):
raise RuntimeError("Unfinished playbook!")
def fork(self):
"""
Fork the current playbook to assert a second execution stream from here on.
Returns a new playbook instance.
"""
return copy.deepcopy(self)
class reply(events.Event):
args: typing.Tuple[typing.Any, ...]