Skip to content

Commit 05367e7

Browse files
committed
Refactor warns() exit logic
1 parent 34d71b1 commit 05367e7

File tree

2 files changed

+43
-55
lines changed

2 files changed

+43
-55
lines changed

src/_pytest/recwarn.py

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,11 @@ def __init__(
281281
self.expected_warning = expected_warning_tup
282282
self.match_expr = match_expr
283283

284+
def matches(self, warning) -> bool:
285+
return issubclass(warning.category, self.expected_warning) and (
286+
self.match_expr is None or re.search(self.match_expr, str(warning.message))
287+
)
288+
284289
def __exit__(
285290
self,
286291
exc_type: Optional[Type[BaseException]],
@@ -291,56 +296,39 @@ def __exit__(
291296

292297
__tracebackhide__ = True
293298

294-
def found_str():
295-
return pformat([record.message for record in self], indent=2)
299+
if self.expected_warning is None:
300+
# nothing to do in this deprecated case, see WARNS_NONE_ARG above
301+
return
296302

297-
def re_emit() -> None:
298-
for r in self:
299-
if matches(r):
300-
continue
303+
if not (exc_type is None and exc_val is None and exc_tb is None):
304+
# We currently ignore missing warnings if an exception is active.
305+
# TODO: fix this, because it means things are surprisingly order-sensitive.
306+
return
301307

302-
assert issubclass(r.message.__class__, Warning)
308+
def found_str():
309+
return pformat([record.message for record in self], indent=2)
303310

304-
warnings.warn_explicit(
305-
str(r.message),
306-
r.message.__class__,
307-
r.filename,
308-
r.lineno,
309-
module=r.__module__,
310-
source=r.source,
311+
try:
312+
if not any(issubclass(w.category, self.expected_warning) for w in self):
313+
fail(
314+
f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n"
315+
f" Emitted warnings: {found_str()}."
311316
)
312-
313-
def matches(warning) -> bool:
314-
if self.expected_warning is not None:
315-
if issubclass(warning.category, self.expected_warning):
316-
if self.match_expr is not None:
317-
if re.compile(self.match_expr).search(str(warning.message)):
318-
return True
319-
return False
320-
return True
321-
return False
322-
323-
# only check if we're not currently handling an exception
324-
if exc_type is None and exc_val is None and exc_tb is None:
325-
if self.expected_warning is not None:
326-
if not any(issubclass(r.category, self.expected_warning) for r in self):
327-
__tracebackhide__ = True
328-
fail(
329-
f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n"
330-
f"The list of emitted warnings is: {found_str()}."
317+
elif not any(self.matches(w) for w in self):
318+
fail(
319+
f"DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted.\n"
320+
f" Regex: {self.match_expr}\n"
321+
f" Emitted warnings: {found_str()}."
322+
)
323+
finally:
324+
# Whether or not any warnings matched, we want to re-emit all unmatched warnings.
325+
for w in self:
326+
if not self.matches(w):
327+
warnings.warn_explicit(
328+
str(w.message),
329+
w.message.__class__,
330+
w.filename,
331+
w.lineno,
332+
module=w.__module__,
333+
source=w.source,
331334
)
332-
elif self.match_expr is not None:
333-
for r in self:
334-
if issubclass(r.category, self.expected_warning):
335-
if re.compile(self.match_expr).search(str(r.message)):
336-
re_emit()
337-
break
338-
else:
339-
fail(
340-
f"""\
341-
DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted.
342-
Regex: {self.match_expr}
343-
Emitted warnings: {found_str()}"""
344-
)
345-
else:
346-
re_emit()

testing/test_recwarn.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -376,15 +376,15 @@ def test_match_regex(self) -> None:
376376
warnings.warn("value must be 42", UserWarning)
377377

378378
def test_one_from_multiple_warns(self) -> None:
379-
with pytest.raises(pytest.fail.Exception):
379+
with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
380380
with pytest.warns(UserWarning, match=r"aaa"):
381381
with pytest.warns(UserWarning, match=r"aaa"):
382382
warnings.warn("cccccccccc", UserWarning)
383383
warnings.warn("bbbbbbbbbb", UserWarning)
384384
warnings.warn("aaaaaaaaaa", UserWarning)
385385

386386
def test_none_of_multiple_warns(self) -> None:
387-
with pytest.raises(pytest.fail.Exception):
387+
with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
388388
with pytest.warns(UserWarning, match=r"aaa"):
389389
warnings.warn("bbbbbbbbbb", UserWarning)
390390
warnings.warn("cccccccccc", UserWarning)
@@ -424,13 +424,13 @@ def test_re_emit_match_single(self) -> None:
424424
warnings.warn("some deprecation warning", DeprecationWarning)
425425

426426
def test_re_emit_match_multiple(self) -> None:
427-
# with pytest.warns(UserWarning):
428-
with pytest.warns(UserWarning, match="user warning"):
429-
warnings.warn("first user warning", UserWarning)
430-
warnings.warn("second user warning", UserWarning)
427+
with warnings.catch_warnings():
428+
warnings.simplefilter("error") # if anything is re-emitted
429+
with pytest.warns(UserWarning, match="user warning"):
430+
warnings.warn("first user warning", UserWarning)
431+
warnings.warn("second user warning", UserWarning)
431432

432433
def test_re_emit_non_match_single(self) -> None:
433-
# with pytest.warns(UserWarning):
434434
with pytest.warns(UserWarning, match="v2 warning"):
435435
with pytest.warns(UserWarning, match="v1 warning"):
436436
warnings.warn("v1 warning", UserWarning)

0 commit comments

Comments
 (0)