Skip to content

Commit

Permalink
support python 3.12.0b4
Browse files Browse the repository at this point in the history
  • Loading branch information
mmckerns committed Jul 13, 2023
1 parent 097ebbb commit 24faee8
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 8 deletions.
108 changes: 108 additions & 0 deletions py3.12/README_MODS
Original file line number Diff line number Diff line change
Expand Up @@ -887,3 +887,111 @@ diff Python-3.12.0a7/Lib/test/_test_multiprocessing.py Python-3.12.0b1/Lib/test/
< Temp = hashlib_helper.requires_hashdigest('md5')(Temp)
---
> Temp = hashlib_helper.requires_hashdigest('sha256')(Temp)
# ----------------------------------------------------------------------
diff Python-3.12.0b1/Lib/multiprocessing/spawn.py Python-3.12.0b4/Lib/multiprocessing/spawn.py
34c34
< WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")
---
> WINSERVICE = sys.executable and sys.executable.lower().endswith("pythonservice.exe")
38c38,40
< if sys.platform == 'win32':
---
> if exe is None:
> _python_exe = exe
> elif sys.platform == 'win32':
diff Python-3.12.0b1/Lib/test/_test_multiprocessing.py Python-3.12.0b4/Lib/test/_test_multiprocessing.py
15a16
> import functools
33a35
> from test.support import script_helper
173a176,228
> def only_run_in_spawn_testsuite(reason):
> """Returns a decorator: raises SkipTest when SM != spawn at test time.
>
> This can be useful to save overall Python test suite execution time.
> "spawn" is the universal mode available on all platforms so this limits the
> decorated test to only execute within test_multiprocessing_spawn.
>
> This would not be necessary if we refactored our test suite to split things
> into other test files when they are not start method specific to be rerun
> under all start methods.
> """
>
> def decorator(test_item):
>
> @functools.wraps(test_item)
> def spawn_check_wrapper(*args, **kwargs):
> if (start_method := multiprocessing.get_start_method()) != "spawn":
> raise unittest.SkipTest(f"{start_method=}, not 'spawn'; {reason}")
> return test_item(*args, **kwargs)
>
> return spawn_check_wrapper
>
> return decorator
>
>
> class TestInternalDecorators(unittest.TestCase):
> """Logic within a test suite that could errantly skip tests? Test it!"""
>
> @unittest.skipIf(sys.platform == "win32", "test requires that fork exists.")
> def test_only_run_in_spawn_testsuite(self):
> if multiprocessing.get_start_method() != "spawn":
> raise unittest.SkipTest("only run in test_multiprocessing_spawn.")
>
> try:
> @only_run_in_spawn_testsuite("testing this decorator")
> def return_four_if_spawn():
> return 4
> except Exception as err:
> self.fail(f"expected decorated `def` not to raise; caught {err}")
>
> orig_start_method = multiprocessing.get_start_method(allow_none=True)
> try:
> multiprocessing.set_start_method("spawn", force=True)
> self.assertEqual(return_four_if_spawn(), 4)
> multiprocessing.set_start_method("fork", force=True)
> with self.assertRaises(unittest.SkipTest) as ctx:
> return_four_if_spawn()
> self.assertIn("testing this decorator", str(ctx.exception))
> self.assertIn("start_method=", str(ctx.exception))
> finally:
> multiprocessing.set_start_method(orig_start_method, force=True)
>
>
5817a5873
> @only_run_in_spawn_testsuite("spawn specific test.")
5828d5883
<
5830d5884
<
5832d5885
<
5834d5886
<
5840c5892
< rc, out, err = test.support.script_helper.assert_python_ok(testfn)
---
> rc, out, err = script_helper.assert_python_ok(testfn)
5843c5895
< self.assertEqual(err, b'')
---
> self.assertFalse(err, msg=err.decode('utf-8'))
5851a5904,5921
> @only_run_in_spawn_testsuite("avoids redundant testing.")
> def test_spawn_sys_executable_none_allows_import(self):
> # Regression test for a bug introduced in
> # https://github.com/python/cpython/issues/90876 that caused an
> # ImportError in multiprocessing when sys.executable was None.
> # This can be true in embedded environments.
> rc, out, err = script_helper.assert_python_ok(
> "-c",
> """if 1:
> import sys
> sys.executable = None
> assert "multiprocessing" not in sys.modules, "already imported!"
> import multiprocessing
> import multiprocessing.spawn # This should not fail\n""",
> )
> self.assertEqual(rc, 0)
> self.assertFalse(err, msg=err.decode('utf-8'))
>
6 changes: 4 additions & 2 deletions py3.12/multiprocess/spawn.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
WINSERVICE = False
else:
WINEXE = getattr(sys, 'frozen', False)
WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")
WINSERVICE = sys.executable and sys.executable.lower().endswith("pythonservice.exe")

def set_executable(exe):
global _python_exe
if sys.platform == 'win32':
if exe is None:
_python_exe = exe
elif sys.platform == 'win32':
_python_exe = os.fsdecode(exe)
else:
_python_exe = os.fsencode(exe)
Expand Down
85 changes: 79 additions & 6 deletions py3.12/multiprocess/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import os
import gc
import errno
import functools
import signal
import array
import socket
Expand All @@ -31,6 +32,7 @@
from test.support import hashlib_helper
from test.support import import_helper
from test.support import os_helper
from test.support import script_helper
from test.support import socket_helper
from test.support import threading_helper
from test.support import warnings_helper
Expand Down Expand Up @@ -177,6 +179,59 @@ def check_enough_semaphores():
"to run the test (required: %d)." % nsems_min)


def only_run_in_spawn_testsuite(reason):
"""Returns a decorator: raises SkipTest when SM != spawn at test time.
This can be useful to save overall Python test suite execution time.
"spawn" is the universal mode available on all platforms so this limits the
decorated test to only execute within test_multiprocessing_spawn.
This would not be necessary if we refactored our test suite to split things
into other test files when they are not start method specific to be rerun
under all start methods.
"""

def decorator(test_item):

@functools.wraps(test_item)
def spawn_check_wrapper(*args, **kwargs):
if (start_method := multiprocessing.get_start_method()) != "spawn":
raise unittest.SkipTest(f"{start_method=}, not 'spawn'; {reason}")
return test_item(*args, **kwargs)

return spawn_check_wrapper

return decorator


class TestInternalDecorators(unittest.TestCase):
"""Logic within a test suite that could errantly skip tests? Test it!"""

@unittest.skipIf(sys.platform == "win32", "test requires that fork exists.")
def test_only_run_in_spawn_testsuite(self):
if multiprocessing.get_start_method() != "spawn":
raise unittest.SkipTest("only run in test_multiprocessing_spawn.")

try:
@only_run_in_spawn_testsuite("testing this decorator")
def return_four_if_spawn():
return 4
except Exception as err:
self.fail(f"expected decorated `def` not to raise; caught {err}")

orig_start_method = multiprocessing.get_start_method(allow_none=True)
try:
multiprocessing.set_start_method("spawn", force=True)
self.assertEqual(return_four_if_spawn(), 4)
multiprocessing.set_start_method("fork", force=True)
with self.assertRaises(unittest.SkipTest) as ctx:
return_four_if_spawn()
self.assertIn("testing this decorator", str(ctx.exception))
self.assertIn("start_method=", str(ctx.exception))
finally:
multiprocessing.set_start_method(orig_start_method, force=True)


#
# Creates a wrapper for a function which records the time it takes to finish
#
Expand Down Expand Up @@ -5825,6 +5880,8 @@ def test_namespace(self):


class TestNamedResource(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x30c00b4, "ModuleNotFoundError")
@only_run_in_spawn_testsuite("spawn specific test.")
def test_global_named_resource_spawn(self):
#
# gh-90549: Check that global named resources in main module
Expand All @@ -5835,22 +5892,18 @@ def test_global_named_resource_spawn(self):
with open(testfn, 'w', encoding='utf-8') as f:
f.write(textwrap.dedent('''\
import multiprocess as mp
ctx = mp.get_context('spawn')
global_resource = ctx.Semaphore()
def submain(): pass
if __name__ == '__main__':
p = ctx.Process(target=submain)
p.start()
p.join()
'''))
rc, out, err = test.support.script_helper.assert_python_ok(testfn, **ENV)
rc, out, err = script_helper.assert_python_ok(testfn)
# on error, err = 'UserWarning: resource_tracker: There appear to
# be 1 leaked semaphore objects to clean up at shutdown'
self.assertEqual(err, b'')
self.assertFalse(err, msg=err.decode('utf-8'))


class MiscTestCase(unittest.TestCase):
Expand All @@ -5861,6 +5914,26 @@ def test__all__(self):
'license', 'citation'])


@unittest.skipIf(sys.hexversion < 0x30c00b4, "ModuleNotFoundError")
@only_run_in_spawn_testsuite("avoids redundant testing.")
def test_spawn_sys_executable_none_allows_import(self):
# Regression test for a bug introduced in
# https://github.com/python/cpython/issues/90876 that caused an
# ImportError in multiprocessing when sys.executable was None.
# This can be true in embedded environments.
rc, out, err = script_helper.assert_python_ok(
"-c",
"""if 1:
import sys
sys.executable = None
assert "multiprocess" not in sys.modules, "already imported!"
import multiprocess as multiprocessing
import multiprocess.spawn # This should not fail\n""",
)
self.assertEqual(rc, 0)
self.assertFalse(err, msg=err.decode('utf-8'))


#
# Mixins
#
Expand Down

0 comments on commit 24faee8

Please sign in to comment.