13
13
import os
14
14
import gc
15
15
import errno
16
+ import functools
16
17
import signal
17
18
import array
18
19
import socket
31
32
from test .support import hashlib_helper
32
33
from test .support import import_helper
33
34
from test .support import os_helper
35
+ from test .support import script_helper
34
36
from test .support import socket_helper
35
37
from test .support import threading_helper
36
38
from test .support import warnings_helper
@@ -171,6 +173,59 @@ def check_enough_semaphores():
171
173
"to run the test (required: %d)." % nsems_min )
172
174
173
175
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
+
174
229
#
175
230
# Creates a wrapper for a function which records the time it takes to finish
176
231
#
@@ -5815,6 +5870,7 @@ def test_namespace(self):
5815
5870
5816
5871
5817
5872
class TestNamedResource (unittest .TestCase ):
5873
+ @only_run_in_spawn_testsuite ("spawn specific test." )
5818
5874
def test_global_named_resource_spawn (self ):
5819
5875
#
5820
5876
# gh-90549: Check that global named resources in main module
@@ -5825,22 +5881,18 @@ def test_global_named_resource_spawn(self):
5825
5881
with open (testfn , 'w' , encoding = 'utf-8' ) as f :
5826
5882
f .write (textwrap .dedent ('''\
5827
5883
import multiprocessing as mp
5828
-
5829
5884
ctx = mp.get_context('spawn')
5830
-
5831
5885
global_resource = ctx.Semaphore()
5832
-
5833
5886
def submain(): pass
5834
-
5835
5887
if __name__ == '__main__':
5836
5888
p = ctx.Process(target=submain)
5837
5889
p.start()
5838
5890
p.join()
5839
5891
''' ))
5840
- rc , out , err = test . support . script_helper .assert_python_ok (testfn )
5892
+ rc , out , err = script_helper .assert_python_ok (testfn )
5841
5893
# on error, err = 'UserWarning: resource_tracker: There appear to
5842
5894
# be 1 leaked semaphore objects to clean up at shutdown'
5843
- self .assertEqual (err , b'' )
5895
+ self .assertFalse (err , msg = err . decode ( 'utf-8' ) )
5844
5896
5845
5897
5846
5898
class MiscTestCase (unittest .TestCase ):
@@ -5849,6 +5901,24 @@ def test__all__(self):
5849
5901
support .check__all__ (self , multiprocessing , extra = multiprocessing .__all__ ,
5850
5902
not_exported = ['SUBDEBUG' , 'SUBWARNING' ])
5851
5903
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
+
5852
5922
5853
5923
#
5854
5924
# Mixins
0 commit comments