Suppose a bit of code emits two warnings, and you want to check that both are generated. In #69110 rbcollins said "...there's little if any reason to add support for multiple regexes - just nest two context managers." This doesn't work, though, since the inner context manager swallows the warnings and the outer one doesn't see them.
In addition, the fact that non-matching warnings are swallowed means that a test that checks for a warning will hide any other warnings that the code generates, which is also a bug in the sense that the behavior is unexpected and can lead to tests that hide potential issues from the test author.
This results in a test failure for 'msg1'. if you remove one of the asserts, the test will pass, but the second warning will not appear on the console.
I'm not sure the warnings machinery has the facilities needed to actually fix the second issue, but the unittest issue should be fixable.
In my actual use case the outer assert is made by a wrapper used by multiple tests, while the inner is made by an individual test, and both are DeprecationWarnings, so using the tuple feature of assertWarnsRegex would not solve my issue for multiple reasons.
At a first glance, the context manager could simply replay all the caught warnings that do not match using something like warnings.warn_explicit. Here's the basic idea that makes your reproducer pass:
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index eba50839cd..bfc00b3c32 100644
--- a/Lib/unittest/case.py+++ b/Lib/unittest/case.py@@ -317,11 +317,13 @@ def __exit__(self, exc_type, exc_value, tb):
for m in self.warnings:
w = m.message
if not isinstance(w, self.expected):
+ self._reraise_warning(m)
continue
if first_matching is None:
first_matching = w
if (self.expected_regex is not None and
not self.expected_regex.search(str(w))):
+ self._reraise_warning(m)
continue
# store warning for later retrieval
self.warning = w
@@ -338,6 +340,15 @@ def __exit__(self, exc_type, exc_value, tb):
else:
self._raiseFailure("{} not triggered".format(exc_name))
+ def _reraise_warning(self, w):+ warnings.warn_explicit(+ message=w.message,+ category=w.category,+ filename=w.filename,+ lineno=w.lineno,+ source=w.source,+ )+
class _AssertNotWarnsContext(_AssertWarnsContext):
I'll have to think if there are some edge cases though I can try to put together a PR tomorrow.
I recently ran into the same behavior. In my actual use case, unrelated deprecation warnings that should have caused test errors were instead suppressed by assertWarnsRegex(DeprecationWarning, r"expected deprecation message").
I would expect assertWarns(category) to swallow all warnings of that category, but I would not expect assertWarnsRegex(category, regex) to swallow warnings that don't match the regex. (Even if some implementation happens to be shared with assertWarns().)
For my use case I adopted a variation on the stdlib test.support.warnings_helper._filterwarnings:
@contextmanager
def check_all_warnings(*filters):
"""Raise an error if the generated warnings to not exactly match filters."""
return _filterwarnings(filters)
It seems to me it might be possible to restrict that to checking warnings in just the module under test (or maybe even a list of modules), and thus make it useful outside the stdlib, but I haven't investigated that possibility yet.
The swallow-the-entire-category behavior still feels like a bug in assertWarnsRegex(). (Though there are probably a lot of existing tests that inadvertently depend on it.)
At a minimum, the docs should probably note this somewhat unexpected behavior.
It is not perfect. Since some information (like module name or registry) is not saved in WarningMessage, some external warning filters may not work or work incorrectly with the leaked warnings. This will only affect corner cases, like actions "once" and "module", and compiler warnings emitted by compile(), exec(), import, etc. Fixing these corner cases requires significant effort.
I think the improvement is worth it despite the corner cases, but I wonder, should the docs explain that the warnings are re-issued rather than not caught, possibly mentioning the corner cases?
Activity
tomasr8 commented on Dec 27, 2025
At a first glance, the context manager could simply replay all the caught warnings that do not match using something like warnings.warn_explicit. Here's the basic idea that makes your reproducer pass:
I'll have to think if there are some edge cases though I can try to put together a PR tomorrow.
serhiy-storchaka commented on Dec 28, 2025
I think this is rather a new feature.
serhiy-storchaka commented on Dec 28, 2025
For search: it is related not only to unittest.assertWarnsRegex(), but also to unittest.assertWarns().
medmunds commented on Apr 30, 2026
I recently ran into the same behavior. In my actual use case, unrelated deprecation warnings that should have caused test errors were instead suppressed by assertWarnsRegex(DeprecationWarning, r"expected deprecation message").
I would expect assertWarns(category) to swallow all warnings of that category, but I would not expect assertWarnsRegex(category, regex) to swallow warnings that don't match the regex. (Even if some implementation happens to be shared with assertWarns().)
bitdancer commented on Apr 30, 2026
For my use case I adopted a variation on the stdlib test.support.warnings_helper._filterwarnings:
@contextmanager def check_all_warnings(*filters): """Raise an error if the generated warnings to not exactly match filters.""" return _filterwarnings(filters)It seems to me it might be possible to restrict that to checking warnings in just the module under test (or maybe even a list of modules), and thus make it useful outside the stdlib, but I haven't investigated that possibility yet.
medmunds commented on Apr 30, 2026
That's helpful, thanks.
The swallow-the-entire-category behavior still feels like a bug in assertWarnsRegex(). (Though there are probably a lot of existing tests that inadvertently depend on it.)
At a minimum, the docs should probably note this somewhat unexpected behavior.
serhiy-storchaka commented on May 1, 2026
See #149229.
It is not perfect. Since some information (like module name or registry) is not saved in WarningMessage, some external warning filters may not work or work incorrectly with the leaked warnings. This will only affect corner cases, like actions "once" and "module", and compiler warnings emitted by compile(), exec(), import, etc. Fixing these corner cases requires significant effort.
bitdancer commented on May 1, 2026
I think the improvement is worth it despite the corner cases, but I wonder, should the docs explain that the warnings are re-issued rather than not caught, possibly mentioning the corner cases?
serhiy-storchaka commented on May 1, 2026
This is rather an implementation detail which will change in future.
I am going to do something with corner cases, but it requires larger changes in the warning module, so it may be a different issue.
bitdancer commented on May 1, 2026
Ok, so it's more like a known remaining bug we are going to fix. Makes sense.
gh-143231: Add the module attribute to warnings.WarningMessage (GH-14…
gh-143231: Do not swallow not matched warnings in assertWarns*() (GH-…