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
@@ -170,6 +172,59 @@ def check_enough_semaphores():
170
172
"to run the test (required: %d)." % nsems_min )
171
173
172
174
175
+ def only_run_in_spawn_testsuite (reason ):
176
+ """Returns a decorator: raises SkipTest when SM != spawn at test time.
177
+
178
+ This can be useful to save overall Python test suite execution time.
179
+ "spawn" is the universal mode available on all platforms so this limits the
180
+ decorated test to only execute within test_multiprocessing_spawn.
181
+
182
+ This would not be necessary if we refactored our test suite to split things
183
+ into other test files when they are not start method specific to be rerun
184
+ under all start methods.
185
+ """
186
+
187
+ def decorator (test_item ):
188
+
189
+ @functools .wraps (test_item )
190
+ def spawn_check_wrapper (* args , ** kwargs ):
191
+ if (start_method := multiprocessing .get_start_method ()) != "spawn" :
192
+ raise unittest .SkipTest (f"{ start_method = } , not 'spawn'; { reason } " )
193
+ return test_item (* args , ** kwargs )
194
+
195
+ return spawn_check_wrapper
196
+
197
+ return decorator
198
+
199
+
200
+ class TestInternalDecorators (unittest .TestCase ):
201
+ """Logic within a test suite that could errantly skip tests? Test it!"""
202
+
203
+ @unittest .skipIf (sys .platform == "win32" , "test requires that fork exists." )
204
+ def test_only_run_in_spawn_testsuite (self ):
205
+ if multiprocessing .get_start_method () != "spawn" :
206
+ raise unittest .SkipTest ("only run in test_multiprocessing_spawn." )
207
+
208
+ try :
209
+ @only_run_in_spawn_testsuite ("testing this decorator" )
210
+ def return_four_if_spawn ():
211
+ return 4
212
+ except Exception as err :
213
+ self .fail (f"expected decorated `def` not to raise; caught { err } " )
214
+
215
+ orig_start_method = multiprocessing .get_start_method (allow_none = True )
216
+ try :
217
+ multiprocessing .set_start_method ("spawn" , force = True )
218
+ self .assertEqual (return_four_if_spawn (), 4 )
219
+ multiprocessing .set_start_method ("fork" , force = True )
220
+ with self .assertRaises (unittest .SkipTest ) as ctx :
221
+ return_four_if_spawn ()
222
+ self .assertIn ("testing this decorator" , str (ctx .exception ))
223
+ self .assertIn ("start_method=" , str (ctx .exception ))
224
+ finally :
225
+ multiprocessing .set_start_method (orig_start_method , force = True )
226
+
227
+
173
228
#
174
229
# Creates a wrapper for a function which records the time it takes to finish
175
230
#
@@ -5775,6 +5830,7 @@ def test_namespace(self):
5775
5830
5776
5831
5777
5832
class TestNamedResource (unittest .TestCase ):
5833
+ @only_run_in_spawn_testsuite ("spawn specific test." )
5778
5834
def test_global_named_resource_spawn (self ):
5779
5835
#
5780
5836
# gh-90549: Check that global named resources in main module
@@ -5785,22 +5841,18 @@ def test_global_named_resource_spawn(self):
5785
5841
with open (testfn , 'w' , encoding = 'utf-8' ) as f :
5786
5842
f .write (textwrap .dedent ('''\
5787
5843
import multiprocessing as mp
5788
-
5789
5844
ctx = mp.get_context('spawn')
5790
-
5791
5845
global_resource = ctx.Semaphore()
5792
-
5793
5846
def submain(): pass
5794
-
5795
5847
if __name__ == '__main__':
5796
5848
p = ctx.Process(target=submain)
5797
5849
p.start()
5798
5850
p.join()
5799
5851
''' ))
5800
- rc , out , err = test . support . script_helper .assert_python_ok (testfn )
5852
+ rc , out , err = script_helper .assert_python_ok (testfn )
5801
5853
# on error, err = 'UserWarning: resource_tracker: There appear to
5802
5854
# be 1 leaked semaphore objects to clean up at shutdown'
5803
- self .assertEqual (err , b'' )
5855
+ self .assertFalse (err , msg = err . decode ( 'utf-8' ) )
5804
5856
5805
5857
5806
5858
class MiscTestCase (unittest .TestCase ):
@@ -5809,6 +5861,24 @@ def test__all__(self):
5809
5861
support .check__all__ (self , multiprocessing , extra = multiprocessing .__all__ ,
5810
5862
not_exported = ['SUBDEBUG' , 'SUBWARNING' ])
5811
5863
5864
+ @only_run_in_spawn_testsuite ("avoids redundant testing." )
5865
+ def test_spawn_sys_executable_none_allows_import (self ):
5866
+ # Regression test for a bug introduced in
5867
+ # https://github.com/python/cpython/issues/90876 that caused an
5868
+ # ImportError in multiprocessing when sys.executable was None.
5869
+ # This can be true in embedded environments.
5870
+ rc , out , err = script_helper .assert_python_ok (
5871
+ "-c" ,
5872
+ """if 1:
5873
+ import sys
5874
+ sys.executable = None
5875
+ assert "multiprocessing" not in sys.modules, "already imported!"
5876
+ import multiprocessing
5877
+ import multiprocessing.spawn # This should not fail\n """ ,
5878
+ )
5879
+ self .assertEqual (rc , 0 )
5880
+ self .assertFalse (err , msg = err .decode ('utf-8' ))
5881
+
5812
5882
5813
5883
#
5814
5884
# Mixins
0 commit comments