parent
bdbad91493
commit
9af3e23695
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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*"])
|
||||
|
|
Loading…
Reference in New Issue