Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ merlinux GmbH, Germany, office at merlinux eu
Contributors include::

Aaron Coleman
Aathithya Arasu S
Abdeali JK
Abdelrahman Elbehery
Abhijeet Kasurde
Expand Down
1 change: 1 addition & 0 deletions changelog/15337.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed an ``INTERNALERROR`` crash when running pytest on Python 3.10.0 exactly: a slotted dataclass field was never initialized on that interpreter version (:issue:`15337`).
9 changes: 7 additions & 2 deletions src/_pytest/nodeid.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,13 @@ def parse(cls, nodeid: str) -> NodeId:
return self

def __str__(self) -> str:
if self._str_cache is not None:
return self._str_cache
# Read ``_str_cache`` defensively: on Python 3.10.0, slotted dataclass
# fields with ``init=False`` and a default value are never set
# (https://github.com/python/cpython/issues/88815), so the attribute
# may be missing entirely.
cache: str | None = getattr(self, "_str_cache", None)
if cache is not None:
return cache
s = "::".join((self.path, *self.names))
if self.params is not None:
s += f"[{self.params}]"
Expand Down
13 changes: 13 additions & 0 deletions testing/test_nodeid.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ def test_str_with_params(self) -> None:
node_id = NodeId(path="a/test_b.py", names=("test_c",), params="1-x")
assert str(node_id) == "a/test_b.py::test_c[1-x]"

def test_str_missing_str_cache(self) -> None:
# Simulate Python 3.10.0 (https://github.com/python/cpython/issues/88815):
# slotted dataclass fields with init=False and a default are never set,
# so _str_cache may be missing entirely (gh-15337).
node_id = NodeId(path="a/test_b.py", names=("test_c",))
# Ensure the attribute exists first so the deletion works on every
# interpreter version (on 3.10.0 it is never set in the first place).
object.__setattr__(node_id, "_str_cache", None)
object.__delattr__(node_id, "_str_cache")
assert str(node_id) == "a/test_b.py::test_c"
# The cache must be repopulated by the first __str__ call.
assert node_id._str_cache == "a/test_b.py::test_c"

# -- child() and leaf() --

def test_child(self) -> None:
Expand Down
Loading