Skip to content

Commit 929df14

Browse files
authored
fix(warnings): don't raise MiscalledStubWarning on verified calls (#42)
Closes #41
1 parent cf08794 commit 929df14

File tree

3 files changed

+64
-36
lines changed

3 files changed

+64
-36
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ jobs:
2626

2727
- name: "Set up dependency cache"
2828
uses: actions/cache@v2
29-
if: ${{ matrix.os != 'Windows' || matrix.python-version != 3.6 }}
29+
# poetry venv restore is buggy on windows
30+
# https://github.com/python-poetry/poetry/issues/2629
31+
if: ${{ matrix.os != 'Windows' }}
3032
with:
3133
key: deps-${{ secrets.GH_CACHE }}-${{ runner.os }}-python${{ matrix.python-version }}-${{ hashFiles('**/*.lock') }}
3234
path: |

decoy/warning_checker.py

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,37 +26,33 @@ def _check_no_miscalled_stubs(all_calls: Sequence[BaseSpyCall]) -> None:
2626
all_calls_by_id[spy_id] = spy_calls + [call]
2727

2828
for spy_id, spy_calls in all_calls_by_id.items():
29-
rehearsals: List[WhenRehearsal] = []
3029
unmatched: List[SpyCall] = []
3130

32-
for call in spy_calls:
33-
next_rehearsals = rehearsals
34-
next_unmatched = unmatched
31+
for index, call in enumerate(spy_calls):
32+
past_stubs = [
33+
wr for wr in spy_calls[0:index] if isinstance(wr, WhenRehearsal)
34+
]
3535

36-
if isinstance(call, WhenRehearsal):
37-
next_rehearsals = rehearsals + [call]
36+
matched_past_stubs = [wr for wr in past_stubs if wr == call]
3837

39-
if len(unmatched) > 0:
40-
next_unmatched = []
38+
matched_future_verifies = [
39+
vr
40+
for vr in spy_calls[index + 1 :]
41+
if isinstance(vr, VerifyRehearsal) and vr == call
42+
]
4143

42-
warn(
43-
MiscalledStubWarning(
44-
calls=unmatched,
45-
rehearsals=rehearsals,
46-
)
47-
)
48-
elif (
44+
if (
4945
isinstance(call, SpyCall)
50-
and len(rehearsals) > 0
51-
and not any(rh == call for rh in rehearsals)
46+
and len(past_stubs) > 0
47+
and len(matched_past_stubs) == 0
48+
and len(matched_future_verifies) == 0
5249
):
53-
next_unmatched = unmatched + [call]
54-
55-
rehearsals = next_rehearsals
56-
unmatched = next_unmatched
57-
58-
if len(unmatched) > 0:
59-
warn(MiscalledStubWarning(calls=unmatched, rehearsals=rehearsals))
50+
unmatched = unmatched + [call]
51+
if index == len(spy_calls) - 1:
52+
warn(MiscalledStubWarning(calls=unmatched, rehearsals=past_stubs))
53+
elif isinstance(call, WhenRehearsal) and len(unmatched) > 0:
54+
warn(MiscalledStubWarning(calls=unmatched, rehearsals=past_stubs))
55+
unmatched = []
6056

6157

6258
def _check_no_redundant_verify(all_calls: Sequence[BaseSpyCall]) -> None:

tests/test_warning_checker.py

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
"""Tests for spy call verification."""
1+
"""Tests for the WarningChecker API."""
22
import pytest
3-
from typing import Any, List, NamedTuple, Sequence
3+
from typing import List, NamedTuple, Sequence
44

55
from decoy import matchers
66
from decoy.spy_calls import BaseSpyCall, SpyCall, WhenRehearsal, VerifyRehearsal
@@ -172,6 +172,34 @@ class WarningCheckerSpec(NamedTuple):
172172
),
173173
],
174174
),
175+
# it should not warn if a call misses a stubbing but is later verified
176+
WarningCheckerSpec(
177+
all_calls=[
178+
WhenRehearsal(spy_id=1, spy_name="spy", args=(1,), kwargs={}),
179+
SpyCall(spy_id=1, spy_name="spy", args=(2,), kwargs={}),
180+
VerifyRehearsal(spy_id=1, spy_name="spy", args=(2,), kwargs={}),
181+
],
182+
expected_warnings=[],
183+
),
184+
# it should warn if a call misses a stubbing after it is verified
185+
WarningCheckerSpec(
186+
all_calls=[
187+
SpyCall(spy_id=1, spy_name="spy", args=(2,), kwargs={}),
188+
VerifyRehearsal(spy_id=1, spy_name="spy", args=(2,), kwargs={}),
189+
WhenRehearsal(spy_id=1, spy_name="spy", args=(1,), kwargs={}),
190+
SpyCall(spy_id=1, spy_name="spy", args=(2,), kwargs={}),
191+
],
192+
expected_warnings=[
193+
MiscalledStubWarning(
194+
rehearsals=[
195+
WhenRehearsal(spy_id=1, spy_name="spy", args=(1,), kwargs={}),
196+
],
197+
calls=[
198+
SpyCall(spy_id=1, spy_name="spy", args=(2,), kwargs={}),
199+
],
200+
),
201+
],
202+
),
175203
# it should issue a redundant verify warning if a call has a when and a verify
176204
WarningCheckerSpec(
177205
all_calls=[
@@ -210,14 +238,16 @@ def test_verify_no_misscalled_stubs(
210238
subject = WarningChecker()
211239
subject.check(all_calls)
212240

213-
assert len(recwarn) == len(expected_warnings)
214-
for expected in expected_warnings:
215-
result: Any = recwarn.pop(DecoyWarning).message
216-
expected_attr = {}
241+
warning_matchers = []
242+
243+
for warning in expected_warnings:
244+
if isinstance(warning, MiscalledStubWarning):
245+
warning_attr = {"rehearsals": warning.rehearsals, "calls": warning.calls}
246+
elif isinstance(warning, RedundantVerifyWarning):
247+
warning_attr = {"rehearsal": warning.rehearsal}
248+
249+
warning_matchers.append(matchers.IsA(type(warning), warning_attr))
217250

218-
if isinstance(expected, MiscalledStubWarning):
219-
expected_attr = {"rehearsals": expected.rehearsals, "calls": expected.calls}
220-
elif isinstance(expected, RedundantVerifyWarning):
221-
expected_attr = {"rehearsal": expected.rehearsal}
251+
actual_warnings = [record.message for record in recwarn]
222252

223-
assert result == matchers.IsA(type(expected), expected_attr)
253+
assert actual_warnings == warning_matchers

0 commit comments

Comments
 (0)