Skip to content

Commit 093a055

Browse files
committed
track item: thread mapping for PYTEST_CURRENT_TEST
1 parent dff5232 commit 093a055

File tree

2 files changed

+30
-7
lines changed

2 files changed

+30
-7
lines changed

src/_pytest/main.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import os
1717
from pathlib import Path
1818
import sys
19+
import threading
20+
from threading import Thread
1921
from typing import final
2022
from typing import Literal
2123
from typing import overload
@@ -581,6 +583,8 @@ def __init__(self, config: Config) -> None:
581583
self._initial_parts: list[CollectionArgument] = []
582584
self._collection_cache: dict[nodes.Collector, CollectReport] = {}
583585
self.items: list[nodes.Item] = []
586+
# track the thread in which each item started execution in
587+
self._item_to_thread: dict[nodes.Item, Thread] = {}
584588

585589
self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath)
586590

@@ -643,6 +647,22 @@ def startpath(self) -> Path:
643647
"""
644648
return self.config.invocation_params.dir
645649

650+
def _readable_thread_id(self, thread: Thread) -> int:
651+
# Returns a 0-indexed id of `thread`, corresponding to the order in
652+
# which we saw that thread start executing tests. The main thread always
653+
# has value 0, so non-main threads start at 1, even if the first thread
654+
# we saw was a non-main thread, or even if we never saw the main thread
655+
# execute tests.
656+
657+
# relying on item_to_thread to be sorted for stable ordering
658+
threads = list(self._item_to_thread.values())
659+
assert thread in threads
660+
661+
if thread is threading.main_thread():
662+
return 0
663+
664+
return threads.index(thread) + 1
665+
646666
def _node_location_to_relpath(self, node_path: Path) -> str:
647667
# bestrelpath is a quite slow function.
648668
return self._bestrelpathcache[node_path]

src/_pytest/runner.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import dataclasses
99
import os
1010
import sys
11+
import threading
1112
import types
1213
from typing import cast
1314
from typing import final
@@ -127,6 +128,7 @@ def runtestprotocol(
127128
# This only happens if the item is re-run, as is done by
128129
# pytest-rerunfailures.
129130
item._initrequest() # type: ignore[attr-defined]
131+
item.session._item_to_thread[item] = threading.current_thread()
130132
rep = call_and_report(item, "setup", log)
131133
reports = [rep]
132134
if rep.passed:
@@ -201,20 +203,21 @@ def _update_current_test_var(
201203
202204
If ``when`` is None, delete ``PYTEST_CURRENT_TEST`` from the environment.
203205
"""
206+
thread = item.session._item_to_thread[item]
207+
readable_id = item.session._readable_thread_id(thread)
208+
# main thread (aka humanid == 0) gets the plain var. Other threads
209+
# append their id.
204210
var_name = "PYTEST_CURRENT_TEST"
211+
if readable_id != 0:
212+
var_name += f"_THREAD_{readable_id}"
213+
205214
if when:
206215
value = f"{item.nodeid} ({when})"
207216
# don't allow null bytes on environment variables (see #2644, #2957)
208217
value = value.replace("\x00", "(null)")
209218
os.environ[var_name] = value
210219
else:
211-
# under multithreading, this may have already been popped by another thread.
212-
# Note that os.environ inherits from MutableMapping and therefore .pop(var_name, None)
213-
# is not atomic or thread-safe, unlike e.g. popping from a builtin dict.
214-
try:
215-
os.environ.pop(var_name)
216-
except KeyError: # pragma: no cover # can be removed when #13768 is farther along
217-
pass
220+
os.environ.pop(var_name)
218221

219222

220223
def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None:

0 commit comments

Comments
 (0)