Skip to content

Commit 33dddac

Browse files
Anselm Kruisgpshead
Anselm Kruis
authored andcommitted
bpo-30028: make test.support.temp_cwd() fork-safe (GH-1066)
Make test.support.temp_cwd() fork-safe. The context manager test.support.temp_cwd() no longer removes the temporary directory when executing in a process other than the parent it entered from. If a forked child exits the context manager it won't do the cleanup.
1 parent 520b7ae commit 33dddac

File tree

3 files changed

+35
-1
lines changed

3 files changed

+35
-1
lines changed

Lib/test/support/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -948,10 +948,14 @@ def temp_dir(path=None, quiet=False):
948948
warnings.warn(f'tests may fail, unable to create '
949949
f'temporary directory {path!r}: {exc}',
950950
RuntimeWarning, stacklevel=3)
951+
if dir_created:
952+
pid = os.getpid()
951953
try:
952954
yield path
953955
finally:
954-
if dir_created:
956+
# In case the process forks, let only the parent remove the
957+
# directory. The child has a diffent process id. (bpo-30028)
958+
if dir_created and pid == os.getpid():
955959
rmtree(path)
956960

957961
@contextlib.contextmanager

Lib/test/test_support.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
import subprocess
1010
import sys
1111
import tempfile
12+
import textwrap
1213
import time
1314
import unittest
1415
from test import support
16+
from test.support import script_helper
1517

1618
TESTFN = support.TESTFN
1719

@@ -165,6 +167,33 @@ def test_temp_dir__existing_dir__quiet_true(self):
165167
f'temporary directory {path!r}: '),
166168
warn)
167169

170+
@unittest.skipUnless(hasattr(os, "fork"), "test requires os.fork")
171+
def test_temp_dir__forked_child(self):
172+
"""Test that a forked child process does not remove the directory."""
173+
# See bpo-30028 for details.
174+
# Run the test as an external script, because it uses fork.
175+
script_helper.assert_python_ok("-c", textwrap.dedent("""
176+
import os
177+
from test import support
178+
with support.temp_cwd() as temp_path:
179+
pid = os.fork()
180+
if pid != 0:
181+
# parent process (child has pid == 0)
182+
183+
# wait for the child to terminate
184+
(pid, status) = os.waitpid(pid, 0)
185+
if status != 0:
186+
raise AssertionError(f"Child process failed with exit "
187+
f"status indication 0x{status:x}.")
188+
189+
# Make sure that temp_path is still present. When the child
190+
# process leaves the 'temp_cwd'-context, the __exit__()-
191+
# method of the context must not remove the temporary
192+
# directory.
193+
if not os.path.isdir(temp_path):
194+
raise AssertionError("Child removed temp_path.")
195+
"""))
196+
168197
# Tests for change_cwd()
169198

170199
def test_change_cwd(self):

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ Pedro Kroger
857857
Hannu Krosing
858858
Andrej Krpic
859859
Ivan Krstić
860+
Anselm Kruis
860861
Steven Kryskalla
861862
Andrew Kuchling
862863
Dave Kuhlman

0 commit comments

Comments
 (0)