Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Generalize PYTEST_CURRENT_TEST for threading #13837

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Liam-DeVoe wants to merge 3 commits into pytest-dev:main
base: main
Choose a base branch
Loading
from Liam-DeVoe:threading-current-test
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
2 changes: 1 addition & 1 deletion .gitignore
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ include/
*.class
*.orig
*~
.hypothesis/

Copy link
Contributor Author

@Liam-DeVoe Liam-DeVoe Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this ignore rule is duplicated farther down the file

# autogenerated
src/_pytest/_version.py
Expand Down Expand Up @@ -51,6 +50,7 @@ coverage.xml
.vscode
__pycache__/
.python-version
.claude

# generated by pip
pip-wheel-metadata/
Expand Down
1 change: 1 addition & 0 deletions changelog/13837.improvement.rst
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pytest setting :ref:`PYTEST_CURRENT_TEST <pytest current test env>` internally is now thread-safe.
20 changes: 20 additions & 0 deletions src/_pytest/main.py
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import os
from pathlib import Path
import sys
import threading
from threading import Thread
from typing import final
from typing import Literal
from typing import overload
Expand Down Expand Up @@ -581,6 +583,8 @@ def __init__(self, config: Config) -> None:
self._initial_parts: list[CollectionArgument] = []
self._collection_cache: dict[nodes.Collector, CollectReport] = {}
self.items: list[nodes.Item] = []
# track the thread in which each item started execution in
self._item_to_thread: dict[nodes.Item, Thread] = {}

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

Expand Down Expand Up @@ -643,6 +647,22 @@ def startpath(self) -> Path:
"""
return self.config.invocation_params.dir

def _readable_thread_id(self, thread: Thread) -> int:
# Returns a 0-indexed id of `thread`, corresponding to the order in
# which we saw that thread start executing tests. The main thread always
# has value 0, so non-main threads start at 1, even if the first thread
# we saw was a non-main thread, or even if we never saw the main thread
# execute tests.

# relying on item_to_thread to be sorted for stable ordering
threads = list(self._item_to_thread.values())
assert thread in threads

if thread is threading.main_thread():
return 0

return threads.index(thread) + 1
Copy link
Member

@RonnyPfannschmidt RonnyPfannschmidt Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not invent own thread numbering schemes

Copy link
Contributor Author

@Liam-DeVoe Liam-DeVoe Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the envvars be named PYTEST_CURRENT_TEST_THREAD_{thread.ident}instead, eg. PYTEST_CURRENT_TEST_THREAD_8422586432? My thinking was that a more human readable approach would be nice, and also that way someone could tell how many threads are active. Happy to just use thread.ident though.

Copy link
Member

@RonnyPfannschmidt RonnyPfannschmidt Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should require a "pytest thread" name for a thread run to be picked no guessing, no random long integers - the env names should be something people can deal with

Copy link
Member

@RonnyPfannschmidt RonnyPfannschmidt Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(i considered picking the threading thread name but their default is not ux friendly for env vars)


def _node_location_to_relpath(self, node_path: Path) -> str:
# bestrelpath is a quite slow function.
return self._bestrelpathcache[node_path]
Expand Down
9 changes: 9 additions & 0 deletions src/_pytest/runner.py
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import dataclasses
import os
import sys
import threading
import types
from typing import cast
from typing import final
Expand Down Expand Up @@ -127,6 +128,7 @@ def runtestprotocol(
# This only happens if the item is re-run, as is done by
# pytest-rerunfailures.
item._initrequest() # type: ignore[attr-defined]
item.session._item_to_thread[item] = threading.current_thread()
rep = call_and_report(item, "setup", log)
reports = [rep]
if rep.passed:
Expand Down Expand Up @@ -201,7 +203,14 @@ def _update_current_test_var(

If ``when`` is None, delete ``PYTEST_CURRENT_TEST`` from the environment.
"""
thread = item.session._item_to_thread[item]
readable_id = item.session._readable_thread_id(thread)
# main thread (aka humanid == 0) gets the plain var. Other threads
# append their id.
var_name = "PYTEST_CURRENT_TEST"
if readable_id != 0:
var_name += f"_THREAD_{readable_id}"

if when:
value = f"{item.nodeid} ({when})"
# don't allow null bytes on environment variables (see #2644, #2957)
Expand Down
Loading

AltStyle によって変換されたページ (->オリジナル) /