Skip to content

Commit 1a9d913

Browse files
committed
Capture and display warnings during collection
Fix #3251
1 parent 51e32cf commit 1a9d913

File tree

4 files changed

+51
-13
lines changed

4 files changed

+51
-13
lines changed

changelog/3251.feture.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Warnings are now captured and displayed during test collection.

src/_pytest/terminal.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,9 @@ def pytest_warning_captured(self, warning_message, item):
339339
fslocation = get_fslocation_from_item(item)
340340
message = warning_record_to_str(warning_message)
341341

342+
nodeid = item.nodeid if item is not None else ""
342343
warning_report = WarningReport(
343-
fslocation=fslocation, message=message, nodeid=item.nodeid
344+
fslocation=fslocation, message=message, nodeid=nodeid
344345
)
345346
warnings.append(warning_report)
346347

@@ -707,7 +708,8 @@ def summary_warnings(self):
707708

708709
self.write_sep("=", "warnings summary", yellow=True, bold=False)
709710
for location, warning_records in grouped:
710-
self._tw.line(str(location) if location else "<undetermined location>")
711+
if location:
712+
self._tw.line(str(location))
711713
for w in warning_records:
712714
lines = w.message.splitlines()
713715
indented = "\n".join(" " + x for x in lines)

src/_pytest/warnings.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,29 +58,32 @@ def pytest_configure(config):
5858

5959

6060
@contextmanager
61-
def catch_warnings_for_item(item):
61+
def catch_warnings_for_item(config, ihook, item):
6262
"""
63-
catches the warnings generated during setup/call/teardown execution
64-
of the given item and after it is done posts them as warnings to this
65-
item.
63+
Context manager that catches warnings generated in the contained execution block.
64+
65+
``item`` can be None if we are not in the context of an item execution.
66+
67+
Each warning captured triggers the ``pytest_warning_captured`` hook.
6668
"""
67-
args = item.config.getoption("pythonwarnings") or []
68-
inifilters = item.config.getini("filterwarnings")
69+
args = config.getoption("pythonwarnings") or []
70+
inifilters = config.getini("filterwarnings")
6971
with warnings.catch_warnings(record=True) as log:
7072
for arg in args:
7173
warnings._setoption(arg)
7274

7375
for arg in inifilters:
7476
_setoption(warnings, arg)
7577

76-
for mark in item.iter_markers(name="filterwarnings"):
77-
for arg in mark.args:
78-
warnings._setoption(arg)
78+
if item is not None:
79+
for mark in item.iter_markers(name="filterwarnings"):
80+
for arg in mark.args:
81+
warnings._setoption(arg)
7982

8083
yield
8184

8285
for warning_message in log:
83-
item.ihook.pytest_warning_captured.call_historic(
86+
ihook.pytest_warning_captured.call_historic(
8487
kwargs=dict(warning_message=warning_message, when="runtest", item=item)
8588
)
8689

@@ -119,5 +122,12 @@ def warning_record_to_str(warning_message):
119122

120123
@pytest.hookimpl(hookwrapper=True)
121124
def pytest_runtest_protocol(item):
122-
with catch_warnings_for_item(item):
125+
with catch_warnings_for_item(config=item.config, ihook=item.ihook, item=item):
126+
yield
127+
128+
129+
@pytest.hookimpl(hookwrapper=True)
130+
def pytest_collection(session):
131+
config = session.config
132+
with catch_warnings_for_item(config=config, ihook=config.hook, item=None):
123133
yield

testing/test_warnings.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,28 @@ def pytest_warning_captured(self, warning_message, when, item):
321321
(RuntimeWarning, "runtest", "test_func"),
322322
]
323323
assert collected == expected
324+
325+
326+
@pytest.mark.filterwarnings("always")
327+
def test_collection_warnings(testdir):
328+
"""
329+
"""
330+
testdir.makepyfile(
331+
"""
332+
import warnings
333+
334+
warnings.warn(UserWarning("collection warning"))
335+
336+
def test_foo():
337+
pass
338+
"""
339+
)
340+
result = testdir.runpytest()
341+
result.stdout.fnmatch_lines(
342+
[
343+
"*== %s ==*" % WARNINGS_SUMMARY_HEADER,
344+
"*collection_warnings.py:3: UserWarning: collection warning",
345+
' warnings.warn(UserWarning("collection warning"))',
346+
"* 1 passed, 1 warnings*",
347+
]
348+
)

0 commit comments

Comments
 (0)