1313import os
1414import gc
1515import errno
16+ import functools
1617import signal
1718import array
1819import socket
3132from test .support import hashlib_helper
3233from test .support import import_helper
3334from test .support import os_helper
35+ from test .support import script_helper
3436from test .support import socket_helper
3537from test .support import threading_helper
3638from test .support import warnings_helper
@@ -171,6 +173,59 @@ def check_enough_semaphores():
171173 "to run the test (required: %d)." % nsems_min )
172174
173175
176+ def only_run_in_spawn_testsuite (reason ):
177+ """Returns a decorator: raises SkipTest when SM != spawn at test time.
178+
179+ This can be useful to save overall Python test suite execution time.
180+ "spawn" is the universal mode available on all platforms so this limits the
181+ decorated test to only execute within test_multiprocessing_spawn.
182+
183+ This would not be necessary if we refactored our test suite to split things
184+ into other test files when they are not start method specific to be rerun
185+ under all start methods.
186+ """
187+
188+ def decorator (test_item ):
189+
190+ @functools .wraps (test_item )
191+ def spawn_check_wrapper (* args , ** kwargs ):
192+ if (start_method := multiprocessing .get_start_method ()) != "spawn" :
193+ raise unittest .SkipTest (f"{ start_method = } , not 'spawn'; { reason } " )
194+ return test_item (* args , ** kwargs )
195+
196+ return spawn_check_wrapper
197+
198+ return decorator
199+
200+
201+ class TestInternalDecorators (unittest .TestCase ):
202+ """Logic within a test suite that could errantly skip tests? Test it!"""
203+
204+ @unittest .skipIf (sys .platform == "win32" , "test requires that fork exists." )
205+ def test_only_run_in_spawn_testsuite (self ):
206+ if multiprocessing .get_start_method () != "spawn" :
207+ raise unittest .SkipTest ("only run in test_multiprocessing_spawn." )
208+
209+ try :
210+ @only_run_in_spawn_testsuite ("testing this decorator" )
211+ def return_four_if_spawn ():
212+ return 4
213+ except Exception as err :
214+ self .fail (f"expected decorated `def` not to raise; caught { err } " )
215+
216+ orig_start_method = multiprocessing .get_start_method (allow_none = True )
217+ try :
218+ multiprocessing .set_start_method ("spawn" , force = True )
219+ self .assertEqual (return_four_if_spawn (), 4 )
220+ multiprocessing .set_start_method ("fork" , force = True )
221+ with self .assertRaises (unittest .SkipTest ) as ctx :
222+ return_four_if_spawn ()
223+ self .assertIn ("testing this decorator" , str (ctx .exception ))
224+ self .assertIn ("start_method=" , str (ctx .exception ))
225+ finally :
226+ multiprocessing .set_start_method (orig_start_method , force = True )
227+
228+
174229#
175230# Creates a wrapper for a function which records the time it takes to finish
176231#
@@ -5815,6 +5870,7 @@ def test_namespace(self):
58155870
58165871
58175872class TestNamedResource (unittest .TestCase ):
5873+ @only_run_in_spawn_testsuite ("spawn specific test." )
58185874 def test_global_named_resource_spawn (self ):
58195875 #
58205876 # gh-90549: Check that global named resources in main module
@@ -5825,22 +5881,18 @@ def test_global_named_resource_spawn(self):
58255881 with open (testfn , 'w' , encoding = 'utf-8' ) as f :
58265882 f .write (textwrap .dedent ('''\
58275883 import multiprocessing as mp
5828-
58295884 ctx = mp.get_context('spawn')
5830-
58315885 global_resource = ctx.Semaphore()
5832-
58335886 def submain(): pass
5834-
58355887 if __name__ == '__main__':
58365888 p = ctx.Process(target=submain)
58375889 p.start()
58385890 p.join()
58395891 ''' ))
5840- rc , out , err = test . support . script_helper .assert_python_ok (testfn )
5892+ rc , out , err = script_helper .assert_python_ok (testfn )
58415893 # on error, err = 'UserWarning: resource_tracker: There appear to
58425894 # be 1 leaked semaphore objects to clean up at shutdown'
5843- self .assertEqual (err , b'' )
5895+ self .assertFalse (err , msg = err . decode ( 'utf-8' ) )
58445896
58455897
58465898class MiscTestCase (unittest .TestCase ):
@@ -5849,6 +5901,24 @@ def test__all__(self):
58495901 support .check__all__ (self , multiprocessing , extra = multiprocessing .__all__ ,
58505902 not_exported = ['SUBDEBUG' , 'SUBWARNING' ])
58515903
5904+ @only_run_in_spawn_testsuite ("avoids redundant testing." )
5905+ def test_spawn_sys_executable_none_allows_import (self ):
5906+ # Regression test for a bug introduced in
5907+ # https://github.com/python/cpython/issues/90876 that caused an
5908+ # ImportError in multiprocessing when sys.executable was None.
5909+ # This can be true in embedded environments.
5910+ rc , out , err = script_helper .assert_python_ok (
5911+ "-c" ,
5912+ """if 1:
5913+ import sys
5914+ sys.executable = None
5915+ assert "multiprocessing" not in sys.modules, "already imported!"
5916+ import multiprocessing
5917+ import multiprocessing.spawn # This should not fail\n """ ,
5918+ )
5919+ self .assertEqual (rc , 0 )
5920+ self .assertFalse (err , msg = err .decode ('utf-8' ))
5921+
58525922
58535923#
58545924# Mixins
0 commit comments