@@ -1077,6 +1077,99 @@ def test(fix):
10771077 assert "PYTEST_CURRENT_TEST" not in os .environ
10781078
10791079
1080+ def test_current_test_env_var_thread_safety () -> None :
1081+ """Test thread-local PYTEST_CURRENT_TEST in multi-threaded scenarios."""
1082+ import threading
1083+
1084+ from _pytest .runner import _current_test_local
1085+ from _pytest .runner import _update_current_test_var
1086+
1087+ class MockItem :
1088+ def __init__ (self , nodeid : str ):
1089+ self .nodeid = nodeid
1090+
1091+ results = {}
1092+ errors = []
1093+
1094+ def worker (thread_id : int ):
1095+ try :
1096+ item = MockItem (f"test_file_{ thread_id } .py::test_func_{ thread_id } " )
1097+ expected = f"test_file_{ thread_id } .py::test_func_{ thread_id } (call)"
1098+ _update_current_test_var (item , "call" ) # type: ignore[arg-type]
1099+ assert (
1100+ os .environ ["PYTEST_CURRENT_TEST" ] == expected
1101+ ) # cover os.environ write path for Codecov
1102+
1103+ env_value = os .environ .get ("PYTEST_CURRENT_TEST" )
1104+ local_value = getattr (_current_test_local , "value" , None )
1105+
1106+ if local_value != expected :
1107+ errors .append (
1108+ f"Thread { thread_id } : local expected { expected !r} , got { local_value !r} "
1109+ )
1110+ if env_value is None :
1111+ errors .append (f"Thread { thread_id } : os.environ not set" )
1112+ elif expected not in env_value :
1113+ errors .append (f"Thread { thread_id } : expected substring in env_value" )
1114+
1115+ results [thread_id ] = local_value
1116+ _update_current_test_var (item , None ) # type: ignore[arg-type]
1117+ except Exception as e :
1118+ errors .append (f"Thread { thread_id } exception: { e } " )
1119+
1120+ threads = [threading .Thread (target = worker , args = (i ,)) for i in range (10 )]
1121+ for t in threads :
1122+ t .start ()
1123+ for t in threads :
1124+ t .join ()
1125+
1126+ assert not errors , "\n " .join (errors )
1127+ assert len (results ) == 10
1128+ for i in range (10 ):
1129+ assert f"test_func_{ i } (call)" in results [i ] # type: ignore[operator]
1130+ assert "PYTEST_CURRENT_TEST" not in os .environ
1131+
1132+
1133+ def test_current_test_env_var_cleanup () -> None :
1134+ """Test that thread-local and os.environ are cleaned up."""
1135+ from _pytest .runner import _current_test_local
1136+ from _pytest .runner import _update_current_test_var
1137+
1138+ class MockItem :
1139+ def __init__ (self ):
1140+ self .nodeid = "test_module.py::test_func"
1141+
1142+ item = MockItem ()
1143+ _update_current_test_var (item , "call" ) # type: ignore[arg-type]
1144+ assert hasattr (_current_test_local , "value" )
1145+ assert os .environ ["PYTEST_CURRENT_TEST" ] == "test_module.py::test_func (call)"
1146+
1147+ _update_current_test_var (item , None ) # type: ignore[arg-type]
1148+ assert not hasattr (_current_test_local , "value" )
1149+ assert "PYTEST_CURRENT_TEST" not in os .environ
1150+
1151+
1152+ def test_current_test_env_var_cleanup_when_not_set () -> None :
1153+ """Test cleanup when thread-local value was never set."""
1154+ from _pytest .runner import _current_test_local
1155+ from _pytest .runner import _update_current_test_var
1156+
1157+ class MockItem :
1158+ def __init__ (self ):
1159+ self .nodeid = "test_module.py::test_func"
1160+
1161+ # Ensure no thread-local value exists
1162+ if hasattr (_current_test_local , "value" ):
1163+ del _current_test_local .value
1164+ os .environ .pop ("PYTEST_CURRENT_TEST" , None )
1165+
1166+ item = MockItem ()
1167+ # Call cleanup without ever setting a value - covers the hasattr=False branch
1168+ _update_current_test_var (item , None ) # type: ignore[arg-type]
1169+ assert not hasattr (_current_test_local , "value" )
1170+ assert "PYTEST_CURRENT_TEST" not in os .environ
1171+
1172+
10801173class TestReportContents :
10811174 """Test user-level API of ``TestReport`` objects."""
10821175
0 commit comments