From 22e032a2ec8180a1a88784bf89fdd1398972a4ea Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Sun, 9 Apr 2017 22:03:49 +0200 Subject: [PATCH 1/7] bpo-30028: make test.support.temp_cwd() fork-safe Improve the context manager test.support.temp_cwd() to not remove the temporary directory, if a forked child terminates. --- Lib/test/support/__init__.py | 4 +++- Lib/test/test_regrtest.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 6658ece735cfe9..46796e788743cd 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -960,10 +960,12 @@ def temp_dir(path=None, quiet=False): warnings.warn(f'tests may fail, unable to create ' f'temporary directory {path!r}: {exc}', RuntimeWarning, stacklevel=3) + if dir_created: + pid = os.getpid() try: yield path finally: - if dir_created: + if dir_created and pid == os.getpid(): rmtree(path) @contextlib.contextmanager diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index 0bd62985d9c21d..908ab2c7506cfc 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -821,5 +821,20 @@ def test_crashed(self): randomize=True) +class TempCwdTestCase(unittest.TestCase): + @unittest.skipUnless(hasattr(os, "fork"), "test requires os.fork") + def test_forked_child(self): + import sys + with support.temp_cwd() as cwd: + pid = os.fork() + if pid != 0: + # parent + os.waitpid(pid, 0) + self.assertTrue(os.path.isdir(cwd), "directory was removed "+cwd) + if pid == 0: + # terminate the child in order to not confuse the test runner + os._exit(0) + + if __name__ == '__main__': unittest.main() From 7b96cf72703eab2932158cbb3ec1dc0757e60134 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Mon, 10 Apr 2017 10:00:14 +0200 Subject: [PATCH 2/7] Update test_regrtest.py Remove an unused "import sys". --- Lib/test/test_regrtest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index 908ab2c7506cfc..287576619e17e2 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -824,7 +824,6 @@ def test_crashed(self): class TempCwdTestCase(unittest.TestCase): @unittest.skipUnless(hasattr(os, "fork"), "test requires os.fork") def test_forked_child(self): - import sys with support.temp_cwd() as cwd: pid = os.fork() if pid != 0: From 9ca966554cab0decb4b555701148fef5be2b3875 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Mon, 10 Apr 2017 10:49:47 +0200 Subject: [PATCH 3/7] bpo-30028: Add comments, move the test to test_support.py Added an explanatory comment and moved the test to test_support.py. I also modified the test to be in line with the other tests of temp_cwd. --- Lib/test/support/__init__.py | 2 ++ Lib/test/test_regrtest.py | 14 -------------- Lib/test/test_support.py | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 46796e788743cd..ed3738d949f56f 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -965,6 +965,8 @@ def temp_dir(path=None, quiet=False): try: yield path finally: + # In case the process forks, let only the parent remove the + # directory. The child has a diffent process id. (bpo-30028) if dir_created and pid == os.getpid(): rmtree(path) diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index 287576619e17e2..0bd62985d9c21d 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -821,19 +821,5 @@ def test_crashed(self): randomize=True) -class TempCwdTestCase(unittest.TestCase): - @unittest.skipUnless(hasattr(os, "fork"), "test requires os.fork") - def test_forked_child(self): - with support.temp_cwd() as cwd: - pid = os.fork() - if pid != 0: - # parent - os.waitpid(pid, 0) - self.assertTrue(os.path.isdir(cwd), "directory was removed "+cwd) - if pid == 0: - # terminate the child in order to not confuse the test runner - os._exit(0) - - if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 0dbe02eeb388b6..e3a25dfe391084 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -161,6 +161,20 @@ def test_temp_dir__existing_dir__quiet_true(self): f'temporary directory {path!r}: '), warn) + @unittest.skipUnless(hasattr(os, "fork"), "test requires os.fork") + def test_temp_dir__forked_child(self): + """Test that a forked child process does not remove the directory.""" + with support.temp_cwd() as temp_path: + pid = os.fork() + if pid != 0: + # parent process + os.waitpid(pid, 0) # wait for the child to terminate + # make sure that temp_path is still present + self.assertTrue(os.path.isdir(temp_path)) + if pid == 0: + # terminate the child in order to not confuse the test runner + os._exit(0) + # Tests for change_cwd() def test_change_cwd(self): From 1a0364d4bf0427df69f0e560664ab80e7389ac86 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Mon, 10 Apr 2017 11:55:17 +0200 Subject: [PATCH 4/7] bpo-30028: Run the fork test as an external script. --- Lib/test/test_support.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index e3a25dfe391084..b7710beda33708 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -7,7 +7,9 @@ import socket import tempfile import errno +import textwrap from test import support +from test.support import script_helper TESTFN = support.TESTFN @@ -164,16 +166,18 @@ def test_temp_dir__existing_dir__quiet_true(self): @unittest.skipUnless(hasattr(os, "fork"), "test requires os.fork") def test_temp_dir__forked_child(self): """Test that a forked child process does not remove the directory.""" - with support.temp_cwd() as temp_path: - pid = os.fork() - if pid != 0: - # parent process - os.waitpid(pid, 0) # wait for the child to terminate - # make sure that temp_path is still present - self.assertTrue(os.path.isdir(temp_path)) - if pid == 0: - # terminate the child in order to not confuse the test runner - os._exit(0) + # Run the test as an external script, because it uses fork. + script_helper.assert_python_ok("-c", textwrap.dedent(""" + import os + from test import support + with support.temp_cwd() as temp_path: + pid = os.fork() + if pid != 0: + # parent process + os.waitpid(pid, 0) # wait for the child to terminate + # make sure that temp_path is still present + assert os.path.isdir(temp_path), "Child removed temp_path." + """)) # Tests for change_cwd() From 0f480d95bffc511e4f73221ab5127394aa3a6ad9 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Sat, 15 Apr 2017 14:38:52 +0200 Subject: [PATCH 5/7] bpo-30028: replace assert by if ...: raise AssertionError An assert might removed depending on Python options. --- Lib/test/test_support.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index b7710beda33708..a71e32d7436915 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -176,7 +176,8 @@ def test_temp_dir__forked_child(self): # parent process os.waitpid(pid, 0) # wait for the child to terminate # make sure that temp_path is still present - assert os.path.isdir(temp_path), "Child removed temp_path." + if not os.path.isdir(temp_path): + raise AssertionError("Child removed temp_path.") """)) # Tests for change_cwd() From 8cebac881d4689f2f7ed8e2dfda823298d8da4f9 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Sat, 22 Apr 2017 23:10:19 +0100 Subject: [PATCH 6/7] bpo-30028: improve the test case - better comments - make sure, that the child exits successfully --- Lib/test/test_support.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index a71e32d7436915..cceb2360fb80df 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -166,6 +166,7 @@ def test_temp_dir__existing_dir__quiet_true(self): @unittest.skipUnless(hasattr(os, "fork"), "test requires os.fork") def test_temp_dir__forked_child(self): """Test that a forked child process does not remove the directory.""" + # See bpo-30028 for details. # Run the test as an external script, because it uses fork. script_helper.assert_python_ok("-c", textwrap.dedent(""" import os @@ -173,9 +174,18 @@ def test_temp_dir__forked_child(self): with support.temp_cwd() as temp_path: pid = os.fork() if pid != 0: - # parent process - os.waitpid(pid, 0) # wait for the child to terminate - # make sure that temp_path is still present + # parent process (child has pid == 0) + + # wait for the child to terminate + (pid, status) = os.waitpid(pid, 0) + if status != 0: + raise AssertionError(f"Child process failed with exit " + f"status indication 0x{status:x}.") + + # Make sure that temp_path is still present. When the child + # process leaves the 'temp_cwd'-context, the __exit__()- + # method of the context must not remove the temporary + # directory. if not os.path.isdir(temp_path): raise AssertionError("Child removed temp_path.") """)) From ee34a5b025742c0ac188e82bdc6fb6c90699ac50 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Thu, 22 Feb 2018 15:44:23 +0100 Subject: [PATCH 7/7] bpo-30028: Add name to Misc/ACKS Proposed by Serhiy Storchaka. --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index b15b20e6c1c02f..4e43581869b2bc 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -856,6 +856,7 @@ Pedro Kroger Hannu Krosing Andrej Krpic Ivan Krstić +Anselm Kruis Steven Kryskalla Andrew Kuchling Dave Kuhlman