summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mocker.py46
-rwxr-xr-xtest.py90
2 files changed, 115 insertions, 21 deletions
diff --git a/mocker.py b/mocker.py
index 813c0b8..da411ec 100644
--- a/mocker.py
+++ b/mocker.py
@@ -684,17 +684,24 @@ class MockerBase(object):
recorder(self, event)
return Mock(self, path)
else:
- # First run unsatisfied events, then ones not previously run. We
- # put the index in the ordering tuple instead of the actual event
- # because we want a stable sort (ordering between 2 events is
- # undefined).
+ # First run events that may run, then run unsatisfied events, then
+ # ones not previously run. We put the index in the ordering tuple
+ # instead of the actual event because we want a stable sort
+ # (ordering between 2 events is undefined).
events = self._events
order = [(events[i].satisfied()*2 + events[i].has_run(), i)
for i in range(len(events))]
order.sort()
+ postponed = None
for weight, i in order:
- if events[i].matches(path):
- return events[i].run(path)
+ event = events[i]
+ if event.matches(path):
+ if event.may_run(path):
+ return event.run(path)
+ elif postponed is None:
+ postponed = event
+ if postponed is not None:
+ return postponed.run(path)
raise MatchError(ERROR_PREFIX + "Unexpected expression: %s" % path)
def get_recorders(cls, self):
@@ -1483,9 +1490,22 @@ class Event(object):
def has_run(self):
return self._has_run
+ def may_run(self, path):
+ """Verify if any task would certainly raise an error if run.
+
+ This will call the C{may_run()} method on each task and return
+ false if any of them returns false.
+ """
+ for task in self._tasks:
+ if not task.may_run(path):
+ return False
+ return True
+
def run(self, path):
"""Run all tasks with the given action.
+ @param path: The path of the expression run.
+
Running an event means running all of its tasks individually and in
order. An event should only ever be run if all of its tasks claim to
match the given action.
@@ -1587,6 +1607,10 @@ class Task(object):
"""
return True
+ def may_run(self, path):
+ """Return false if running this task would certainly raise an error."""
+ return True
+
def run(self, path):
"""Perform the task item, considering that the given action happened.
"""
@@ -1658,6 +1682,9 @@ class RunCounter(Task):
def replay(self):
self._runs = 0
+ def may_run(self, path):
+ return self._runs < self.max
+
def run(self, path):
self._runs += 1
if self._runs > self.max:
@@ -1822,6 +1849,13 @@ class SpecChecker(Task):
if not self._method:
raise AssertionError("Method not found in real specification")
+ def may_run(self, path):
+ try:
+ self.run(path)
+ except AssertionError:
+ return False
+ return True
+
def run(self, path):
if not self._method:
raise AssertionError("Method not found in real specification")
diff --git a/test.py b/test.py
index ca6e097..9318633 100755
--- a/test.py
+++ b/test.py
@@ -1370,21 +1370,36 @@ class MockerTest(TestCase):
self.assertRaises(AssertionError, self.mocker.act, self.path)
self.assertEquals(calls, ["matches"])
- def test_replaying_order_not_satisfied_first_then_not_run(self):
- class MyTask1(Task):
- def run(self, path):
- return "result1"
- class MyTask2(Task):
+ def test_replay_order(self):
+ """
+ When playing back, the precedence of events is as follows:
+
+ 1. Events with may_run() true
+ 2. Events with satisfied() false
+ 3. Events with has_run() false
+
+ """
+ class MyTaskBase(Task):
+ postpone = 2
+ def may_run(self, path):
+ if not self.postpone:
+ return True
+ self.postpone -= 1
def run(self, path):
- return "result2"
- class MyTask3(Task):
+ return self.__class__.__name__
+ class MyTask1(MyTaskBase): pass
+ class MyTask2(MyTaskBase): pass
+ class MyTask3(MyTaskBase):
raised = False
def verify(self):
- if not self.raised:
+ if not self.postpone and not self.raised:
self.raised = True
raise AssertionError("An error")
- def run(self, path):
- return "result3"
+ class MyTask4(MyTaskBase):
+ postpone = 0
+ class MyTask5(MyTaskBase):
+ postpone = 1
+
event1 = self.mocker.add_event(Event())
event1.add_task(MyTask1())
event2 = self.mocker.add_event(Event())
@@ -1392,11 +1407,27 @@ class MockerTest(TestCase):
event3 = self.mocker.add_event(Event())
event3.add_task(MyTask3())
event4 = self.mocker.add_event(Event())
- event4.add_task(MyTask1())
+ event4.add_task(MyTask4())
+ event5 = self.mocker.add_event(Event())
+ event5.add_task(MyTask5())
self.mocker.replay()
- self.assertEquals(self.mocker.act(self.path), "result3")
- self.assertEquals(self.mocker.act(self.path), "result1")
- self.assertEquals(self.mocker.act(self.path), "result2")
+
+ # Labels: [M]ay run, [S]atisfied, [H]as run
+
+ # State: 1=S 2=S 3= 4=MS 5=S
+ self.assertEquals(self.mocker.act(self.path), "MyTask4")
+ # State: 1=S 2=S 3= 4=MSH 5=S
+ self.assertEquals(self.mocker.act(self.path), "MyTask4")
+ # State: 1=MS 2=MS 3=M 4=MSH 5=MS
+ self.assertEquals(self.mocker.act(self.path), "MyTask3")
+ # State: 1=MS 2=MS 3=MSH 4=MSH 5=MS
+ self.assertEquals(self.mocker.act(self.path), "MyTask1")
+ # State: 1=MSH 2=MS 3=MSH 4=MSH 5=MS
+ self.assertEquals(self.mocker.act(self.path), "MyTask2")
+ # State: 1=MSH 2=MSH 3=MSH 4=MSH 5=MS
+ self.assertEquals(self.mocker.act(self.path), "MyTask5")
+ # State: 1=MSH 2=MSH 3=MSH 4=MSH 5=MSH
+ self.assertEquals(self.mocker.act(self.path), "MyTask1")
def test_recorder_decorator(self):
result = recorder(42)
@@ -2624,7 +2655,6 @@ class EventTest(TestCase):
self.assertEquals(calls, [42, 42, 42])
def test_run_errors(self):
- """When the path representation isn't the same it's shown up."""
class MyTask(object):
def __init__(self, id, failed):
self.id = id
@@ -2689,6 +2719,25 @@ class EventTest(TestCase):
self.event.replay()
self.assertFalse(self.event.has_run())
+ def test_may_run(self):
+ calls = []
+ task1 = Task()
+ task1.may_run = lambda path: calls.append((1, path)) or True
+ task2 = Task()
+ task2.may_run = lambda path: calls.append((2, path))
+
+ self.assertEquals(self.event.may_run(42), True)
+
+ self.event.add_task(task1)
+ self.assertEquals(self.event.may_run(42), True)
+ self.assertEquals(calls, [(1, 42)])
+
+ del calls[:]
+ self.event.add_task(task2)
+ self.event.add_task(task1) # Should return on first false.
+ self.assertEquals(self.event.may_run(42), False)
+ self.assertEquals(calls, [(1, 42), (2, 42)])
+
def test_satisfied_false(self):
def raise_error():
raise AssertionError("An error")
@@ -2773,6 +2822,9 @@ class TaskTest(TestCase):
def test_default_matches(self):
self.assertEquals(self.task.matches(None), True)
+ def test_default_may_run(self):
+ self.assertEquals(self.task.may_run(None), True)
+
def test_default_run(self):
self.assertEquals(self.task.run(None), None)
@@ -2878,6 +2930,12 @@ class RunCounterTest(TestCase):
task.run(self.path)
self.assertRaises(AssertionError, task.run, self.path)
+ def test_may_run(self):
+ task = RunCounter(1)
+ self.assertEquals(task.may_run(None), True)
+ task.run(self.path)
+ self.assertEquals(task.may_run(None), False)
+
def test_verify(self):
task = RunCounter(2)
self.assertRaises(AssertionError, task.verify)
@@ -3205,6 +3263,7 @@ class SpecCheckerTest(TestCase):
for method_name in method_names:
task = SpecChecker(getattr(self.cls, method_name, None))
path = eval("self.path(%s)" % args_expr)
+ self.assertEquals(task.may_run(path), True)
try:
task.run(path)
except AssertionError:
@@ -3217,6 +3276,7 @@ class SpecCheckerTest(TestCase):
for method_name in method_names:
task = SpecChecker(getattr(self.cls, method_name, None))
path = eval("self.path(%s)" % args_expr)
+ self.assertEquals(task.may_run(path), False)
try:
task.run(path)
except AssertionError: