|
26 | 26 | import struct |
27 | 27 | import tempfile |
28 | 28 | import operator |
| 29 | +import pathlib |
29 | 30 | import pickle |
30 | 31 | import weakref |
31 | 32 | import warnings |
@@ -6051,6 +6052,97 @@ def test_nested_startmethod(self): |
6051 | 6052 | # there is no synchronization in the test. |
6052 | 6053 | self.assertSetEqual(set(results), set([2, 1])) |
6053 | 6054 |
|
| 6055 | + @unittest.skipIf(os.name == "nt", "requires POSIX") |
| 6056 | + @support.requires_non_root_user |
| 6057 | + @support.subTests("mode", [ |
| 6058 | + os.R_OK, # read-only directory |
| 6059 | + os.R_OK | os.X_OK, # read-only directory |
| 6060 | + os.W_OK # write-only directory _without_ permissions for creating files |
| 6061 | + ]) |
| 6062 | + def test_forkserver_requires_writeable_tempdir(self, mode): |
| 6063 | + # Regression test to ensure that the defualt start method is |
| 6064 | + # not 'forkserver' when the temporary directory is not writeable. |
| 6065 | + # |
| 6066 | + # See https://github.com/python/cpython/issues/155717. |
| 6067 | + |
| 6068 | + cmd = '''if 1: |
| 6069 | + import os, tempfile |
| 6070 | + # We fake the read-onlyiness of /tmp (which is a fallback when |
| 6071 | + # the user-defined TMPDIR is not acceptable) by hardcoding the |
| 6072 | + # temporary directory for this specific test. |
| 6073 | + tempfile.tempdir = os.environ["TMPDIR"] |
| 6074 | +
|
| 6075 | + # Imported after patching 'tempfile' so that the default start |
| 6076 | + # method is deduced according to the permissions of TMPDIR. |
| 6077 | + import multiprocessing |
| 6078 | + if __name__ == "__main__": |
| 6079 | + print(multiprocessing.get_start_method()) |
| 6080 | + ''' |
| 6081 | + |
| 6082 | + with support.os_helper.temp_dir() as root: |
| 6083 | + TMPDIR = pathlib.Path(root, "TMPDIR") |
| 6084 | + TMPDIR.mkdir(mode=mode) |
| 6085 | + file = pathlib.Path(TMPDIR, "file") |
| 6086 | + self.assertRaises(OSError, file.touch) |
| 6087 | + _, out, err = script_helper.assert_python_ok('-c', cmd, TMPDIR=TMPDIR) |
| 6088 | + self.assertEqual(out.decode().strip(), "spawn") |
| 6089 | + |
| 6090 | + @unittest.skipIf(os.name == "nt", "requires POSIX") |
| 6091 | + @support.requires_non_root_user |
| 6092 | + def test_default_start_method_without_usable_tempdir(self): |
| 6093 | + # Neither importing multiprocessing nor determining the default |
| 6094 | + # start method may fail when tempfile finds no usable temporary |
| 6095 | + # directory at all, and the default start method must be 'spawn'. |
| 6096 | + # |
| 6097 | + # See https://github.com/python/cpython/issues/155717. |
| 6098 | + |
| 6099 | + cmd = '''if 1: |
| 6100 | + import os, tempfile |
| 6101 | + # Make TMPDIR the only candidate, so that tempfile.gettempdir() |
| 6102 | + # raises FileNotFoundError instead of falling back to /tmp. |
| 6103 | + tempfile._candidate_tempdir_list = lambda: [os.environ["TMPDIR"]] |
| 6104 | + try: |
| 6105 | + tempfile.gettempdir() |
| 6106 | + except FileNotFoundError: |
| 6107 | + pass |
| 6108 | + else: |
| 6109 | + raise AssertionError("TMPDIR is usable") |
| 6110 | +
|
| 6111 | + import multiprocessing |
| 6112 | + if __name__ == "__main__": |
| 6113 | + print(multiprocessing.get_start_method(), |
| 6114 | + multiprocessing.get_all_start_methods()[0]) |
| 6115 | + ''' |
| 6116 | + |
| 6117 | + with support.os_helper.temp_dir() as root: |
| 6118 | + TMPDIR = pathlib.Path(root, "TMPDIR") |
| 6119 | + TMPDIR.mkdir(mode=os.R_OK | os.X_OK) |
| 6120 | + _, out, err = script_helper.assert_python_ok('-c', cmd, TMPDIR=TMPDIR) |
| 6121 | + self.assertEqual(out.decode().split(), ["spawn", "spawn"]) |
| 6122 | + |
| 6123 | + def test_import_does_not_determine_default_start_method(self): |
| 6124 | + # Determining the default start method requires checking that the |
| 6125 | + # temporary directory is writeable. That must not happen, nor may |
| 6126 | + # the modules it needs be loaded, when importing multiprocessing. |
| 6127 | + import_helper.ensure_lazy_imports( |
| 6128 | + "multiprocessing", {"tempfile", "shutil", "subprocess"}) |
| 6129 | + |
| 6130 | + cmd = '''if 1: |
| 6131 | + import sys |
| 6132 | + import multiprocessing |
| 6133 | + print(multiprocessing.get_start_method(allow_none=True), |
| 6134 | + "tempfile" in sys.modules, |
| 6135 | + multiprocessing.get_start_method(), |
| 6136 | + multiprocessing.get_all_start_methods()[0]) |
| 6137 | + ''' |
| 6138 | + # -S: site customizations could import tempfile themselves. |
| 6139 | + _, out, err = script_helper.assert_python_ok('-S', '-c', cmd) |
| 6140 | + unset, probed, method, first = out.decode().split() |
| 6141 | + self.assertEqual(unset, "None") |
| 6142 | + self.assertEqual(probed, "False") |
| 6143 | + self.assertEqual(method, first) |
| 6144 | + self.assertIn(method, multiprocessing.get_all_start_methods()) |
| 6145 | + |
6054 | 6146 |
|
6055 | 6147 | @unittest.skipIf(sys.platform == "win32", |
6056 | 6148 | "test semantics don't make sense on Windows") |
|
0 commit comments