diff --git a/changelog/3251.feture.rst b/changelog/3251.feture.rst new file mode 100644 index 000000000..3ade3093d --- /dev/null +++ b/changelog/3251.feture.rst @@ -0,0 +1 @@ +Warnings are now captured and displayed during test collection. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 14549ddf7..41b0e755c 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -339,8 +339,9 @@ class TerminalReporter(object): fslocation = get_fslocation_from_item(item) message = warning_record_to_str(warning_message) + nodeid = item.nodeid if item is not None else "" warning_report = WarningReport( - fslocation=fslocation, message=message, nodeid=item.nodeid + fslocation=fslocation, message=message, nodeid=nodeid ) warnings.append(warning_report) @@ -707,7 +708,8 @@ class TerminalReporter(object): self.write_sep("=", "warnings summary", yellow=True, bold=False) for location, warning_records in grouped: - self._tw.line(str(location) if location else "") + if location: + self._tw.line(str(location)) for w in warning_records: lines = w.message.splitlines() indented = "\n".join(" " + x for x in lines) diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 7c772f7c4..6562d11a3 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -58,14 +58,16 @@ def pytest_configure(config): @contextmanager -def catch_warnings_for_item(item): +def catch_warnings_for_item(config, ihook, item): """ - catches the warnings generated during setup/call/teardown execution - of the given item and after it is done posts them as warnings to this - item. + Context manager that catches warnings generated in the contained execution block. + + ``item`` can be None if we are not in the context of an item execution. + + Each warning captured triggers the ``pytest_warning_captured`` hook. """ - args = item.config.getoption("pythonwarnings") or [] - inifilters = item.config.getini("filterwarnings") + args = config.getoption("pythonwarnings") or [] + inifilters = config.getini("filterwarnings") with warnings.catch_warnings(record=True) as log: for arg in args: warnings._setoption(arg) @@ -73,14 +75,15 @@ def catch_warnings_for_item(item): for arg in inifilters: _setoption(warnings, arg) - for mark in item.iter_markers(name="filterwarnings"): - for arg in mark.args: - warnings._setoption(arg) + if item is not None: + for mark in item.iter_markers(name="filterwarnings"): + for arg in mark.args: + warnings._setoption(arg) yield for warning_message in log: - item.ihook.pytest_warning_captured.call_historic( + ihook.pytest_warning_captured.call_historic( kwargs=dict(warning_message=warning_message, when="runtest", item=item) ) @@ -119,5 +122,12 @@ def warning_record_to_str(warning_message): @pytest.hookimpl(hookwrapper=True) def pytest_runtest_protocol(item): - with catch_warnings_for_item(item): + with catch_warnings_for_item(config=item.config, ihook=item.ihook, item=item): + yield + + +@pytest.hookimpl(hookwrapper=True) +def pytest_collection(session): + config = session.config + with catch_warnings_for_item(config=config, ihook=config.hook, item=None): yield diff --git a/testing/test_warnings.py b/testing/test_warnings.py index efb974905..3bd7bb52e 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -321,3 +321,28 @@ def test_warning_captured_hook(testdir, pyfile_with_warnings): (RuntimeWarning, "runtest", "test_func"), ] assert collected == expected + + +@pytest.mark.filterwarnings("always") +def test_collection_warnings(testdir): + """ + """ + testdir.makepyfile( + """ + import warnings + + warnings.warn(UserWarning("collection warning")) + + def test_foo(): + pass + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + [ + "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + "*collection_warnings.py:3: UserWarning: collection warning", + ' warnings.warn(UserWarning("collection warning"))', + "* 1 passed, 1 warnings*", + ] + )