Expand warnings output for ResourceWarning (#9682)

Fix #9644
This commit is contained in:
Bruno Oliveira 2022-02-15 10:00:50 -03:00 committed by GitHub
parent bdbad91493
commit 9af3e23695
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 0 deletions

View File

@ -0,0 +1,4 @@
More information about the location of resources that led Python to raise :class:`ResourceWarning` can now
be obtained by enabling :mod:`tracemalloc`.
See :ref:`resource-warnings` for more information.

View File

@ -441,3 +441,18 @@ Please read our :ref:`backwards-compatibility` to learn how we proceed about dep
features.
The full list of warnings is listed in :ref:`the reference documentation <warnings ref>`.
.. _`resource-warnings`:
Resource Warnings
-----------------
Additional information of the source of a :class:`ResourceWarning` can be obtained when captured by pytest if
:mod:`tracemalloc` module is enabled.
One convenient way to enable :mod:`tracemalloc` when running tests is to set the :envvar:`PYTHONTRACEMALLOC` to a large
enough number of frames (say ``20``, but that number is application dependent).
For more information, consult the `Python Development Mode <https://docs.python.org/3/library/devmode.html>`__
section in the Python documentation.

View File

@ -81,6 +81,23 @@ def warning_record_to_str(warning_message: warnings.WarningMessage) -> str:
warning_message.lineno,
warning_message.line,
)
if warning_message.source is not None:
try:
import tracemalloc
except ImportError:
pass
else:
tb = tracemalloc.get_object_traceback(warning_message.source)
if tb is not None:
formatted_tb = "\n".join(tb.format())
# Use a leading new line to better separate the (large) output
# from the traceback to the previous warning text.
msg += f"\nObject allocated at:\n{formatted_tb}"
else:
# No need for a leading new line.
url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings"
msg += "Enable tracemalloc to get traceback where the object was allocated.\n"
msg += f"See {url} for more info."
return msg

View File

@ -1,4 +1,5 @@
import os
import sys
import warnings
from typing import List
from typing import Optional
@ -774,3 +775,57 @@ class TestStackLevel:
"*Unknown pytest.mark.unknown*",
]
)
def test_resource_warning(pytester: Pytester, monkeypatch: pytest.MonkeyPatch) -> None:
# Some platforms (notably PyPy) don't have tracemalloc.
# We choose to explicitly not skip this in case tracemalloc is not
# available, using `importorskip("tracemalloc")` for example,
# because we want to ensure the same code path does not break in those platforms.
try:
import tracemalloc # noqa
has_tracemalloc = True
except ImportError:
has_tracemalloc = False
# Explicitly disable PYTHONTRACEMALLOC in case pytest's test suite is running
# with it enabled.
monkeypatch.delenv("PYTHONTRACEMALLOC", raising=False)
pytester.makepyfile(
"""
def open_file(p):
f = p.open("r")
assert p.read_text() == "hello"
def test_resource_warning(tmp_path):
p = tmp_path.joinpath("foo.txt")
p.write_text("hello")
open_file(p)
"""
)
result = pytester.run(sys.executable, "-Xdev", "-m", "pytest")
expected_extra = (
[
"*ResourceWarning* unclosed file*",
"*Enable tracemalloc to get traceback where the object was allocated*",
"*See https* for more info.",
]
if has_tracemalloc
else []
)
result.stdout.fnmatch_lines([*expected_extra, "*1 passed*"])
monkeypatch.setenv("PYTHONTRACEMALLOC", "20")
result = pytester.run(sys.executable, "-Xdev", "-m", "pytest")
expected_extra = (
[
"*ResourceWarning* unclosed file*",
"*Object allocated at*",
]
if has_tracemalloc
else []
)
result.stdout.fnmatch_lines([*expected_extra, "*1 passed*"])