Skip to content

Commit b4f3318

Browse files
miss-islingtonCAM-GerlachJelleZijlstra
authored
gh-75729: Fix os.spawn tests not handling spaces on Windows (GH-99150)
* Quote paths in os.spawn tests on Windows so they work with spaces * Add NEWS entry for os spawn test fix * Fix code style to avoid double negative in os.spawn tests Co-authored-by: Jelle Zijlstra <[email protected]> --------- (cherry picked from commit a34c796) Co-authored-by: C.A.M. Gerlach <[email protected]> Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent 29a1e89 commit b4f3318

File tree

2 files changed

+59
-45
lines changed

2 files changed

+59
-45
lines changed

Lib/test/test_os.py

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3109,6 +3109,14 @@ def kill_process(pid):
31093109

31103110
@support.requires_subprocess()
31113111
class SpawnTests(unittest.TestCase):
3112+
@staticmethod
3113+
def quote_args(args):
3114+
# On Windows, os.spawn* simply joins arguments with spaces:
3115+
# arguments need to be quoted
3116+
if os.name != 'nt':
3117+
return args
3118+
return [f'"{arg}"' if " " in arg.strip() else arg for arg in args]
3119+
31123120
def create_args(self, *, with_env=False, use_bytes=False):
31133121
self.exitcode = 17
31143122

@@ -3129,115 +3137,118 @@ def create_args(self, *, with_env=False, use_bytes=False):
31293137
with open(filename, "w", encoding="utf-8") as fp:
31303138
fp.write(code)
31313139

3132-
args = [sys.executable, filename]
3140+
program = sys.executable
3141+
args = self.quote_args([program, filename])
31333142
if use_bytes:
3143+
program = os.fsencode(program)
31343144
args = [os.fsencode(a) for a in args]
31353145
self.env = {os.fsencode(k): os.fsencode(v)
31363146
for k, v in self.env.items()}
31373147

3138-
return args
3148+
return program, args
31393149

31403150
@requires_os_func('spawnl')
31413151
def test_spawnl(self):
3142-
args = self.create_args()
3143-
exitcode = os.spawnl(os.P_WAIT, args[0], *args)
3152+
program, args = self.create_args()
3153+
exitcode = os.spawnl(os.P_WAIT, program, *args)
31443154
self.assertEqual(exitcode, self.exitcode)
31453155

31463156
@requires_os_func('spawnle')
31473157
def test_spawnle(self):
3148-
args = self.create_args(with_env=True)
3149-
exitcode = os.spawnle(os.P_WAIT, args[0], *args, self.env)
3158+
program, args = self.create_args(with_env=True)
3159+
exitcode = os.spawnle(os.P_WAIT, program, *args, self.env)
31503160
self.assertEqual(exitcode, self.exitcode)
31513161

31523162
@requires_os_func('spawnlp')
31533163
def test_spawnlp(self):
3154-
args = self.create_args()
3155-
exitcode = os.spawnlp(os.P_WAIT, args[0], *args)
3164+
program, args = self.create_args()
3165+
exitcode = os.spawnlp(os.P_WAIT, program, *args)
31563166
self.assertEqual(exitcode, self.exitcode)
31573167

31583168
@requires_os_func('spawnlpe')
31593169
def test_spawnlpe(self):
3160-
args = self.create_args(with_env=True)
3161-
exitcode = os.spawnlpe(os.P_WAIT, args[0], *args, self.env)
3170+
program, args = self.create_args(with_env=True)
3171+
exitcode = os.spawnlpe(os.P_WAIT, program, *args, self.env)
31623172
self.assertEqual(exitcode, self.exitcode)
31633173

31643174
@requires_os_func('spawnv')
31653175
def test_spawnv(self):
3166-
args = self.create_args()
3167-
exitcode = os.spawnv(os.P_WAIT, args[0], args)
3176+
program, args = self.create_args()
3177+
exitcode = os.spawnv(os.P_WAIT, program, args)
31683178
self.assertEqual(exitcode, self.exitcode)
31693179

31703180
# Test for PyUnicode_FSConverter()
3171-
exitcode = os.spawnv(os.P_WAIT, FakePath(args[0]), args)
3181+
exitcode = os.spawnv(os.P_WAIT, FakePath(program), args)
31723182
self.assertEqual(exitcode, self.exitcode)
31733183

31743184
@requires_os_func('spawnve')
31753185
def test_spawnve(self):
3176-
args = self.create_args(with_env=True)
3177-
exitcode = os.spawnve(os.P_WAIT, args[0], args, self.env)
3186+
program, args = self.create_args(with_env=True)
3187+
exitcode = os.spawnve(os.P_WAIT, program, args, self.env)
31783188
self.assertEqual(exitcode, self.exitcode)
31793189

31803190
@requires_os_func('spawnvp')
31813191
def test_spawnvp(self):
3182-
args = self.create_args()
3183-
exitcode = os.spawnvp(os.P_WAIT, args[0], args)
3192+
program, args = self.create_args()
3193+
exitcode = os.spawnvp(os.P_WAIT, program, args)
31843194
self.assertEqual(exitcode, self.exitcode)
31853195

31863196
@requires_os_func('spawnvpe')
31873197
def test_spawnvpe(self):
3188-
args = self.create_args(with_env=True)
3189-
exitcode = os.spawnvpe(os.P_WAIT, args[0], args, self.env)
3198+
program, args = self.create_args(with_env=True)
3199+
exitcode = os.spawnvpe(os.P_WAIT, program, args, self.env)
31903200
self.assertEqual(exitcode, self.exitcode)
31913201

31923202
@requires_os_func('spawnv')
31933203
def test_nowait(self):
3194-
args = self.create_args()
3195-
pid = os.spawnv(os.P_NOWAIT, args[0], args)
3204+
program, args = self.create_args()
3205+
pid = os.spawnv(os.P_NOWAIT, program, args)
31963206
support.wait_process(pid, exitcode=self.exitcode)
31973207

31983208
@requires_os_func('spawnve')
31993209
def test_spawnve_bytes(self):
32003210
# Test bytes handling in parse_arglist and parse_envlist (#28114)
3201-
args = self.create_args(with_env=True, use_bytes=True)
3202-
exitcode = os.spawnve(os.P_WAIT, args[0], args, self.env)
3211+
program, args = self.create_args(with_env=True, use_bytes=True)
3212+
exitcode = os.spawnve(os.P_WAIT, program, args, self.env)
32033213
self.assertEqual(exitcode, self.exitcode)
32043214

32053215
@requires_os_func('spawnl')
32063216
def test_spawnl_noargs(self):
3207-
args = self.create_args()
3208-
self.assertRaises(ValueError, os.spawnl, os.P_NOWAIT, args[0])
3209-
self.assertRaises(ValueError, os.spawnl, os.P_NOWAIT, args[0], '')
3217+
program, __ = self.create_args()
3218+
self.assertRaises(ValueError, os.spawnl, os.P_NOWAIT, program)
3219+
self.assertRaises(ValueError, os.spawnl, os.P_NOWAIT, program, '')
32103220

32113221
@requires_os_func('spawnle')
32123222
def test_spawnle_noargs(self):
3213-
args = self.create_args()
3214-
self.assertRaises(ValueError, os.spawnle, os.P_NOWAIT, args[0], {})
3215-
self.assertRaises(ValueError, os.spawnle, os.P_NOWAIT, args[0], '', {})
3223+
program, __ = self.create_args()
3224+
self.assertRaises(ValueError, os.spawnle, os.P_NOWAIT, program, {})
3225+
self.assertRaises(ValueError, os.spawnle, os.P_NOWAIT, program, '', {})
32163226

32173227
@requires_os_func('spawnv')
32183228
def test_spawnv_noargs(self):
3219-
args = self.create_args()
3220-
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, args[0], ())
3221-
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, args[0], [])
3222-
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, args[0], ('',))
3223-
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, args[0], [''])
3229+
program, __ = self.create_args()
3230+
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, ())
3231+
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, [])
3232+
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, ('',))
3233+
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, [''])
32243234

32253235
@requires_os_func('spawnve')
32263236
def test_spawnve_noargs(self):
3227-
args = self.create_args()
3228-
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, args[0], (), {})
3229-
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, args[0], [], {})
3230-
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, args[0], ('',), {})
3231-
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, args[0], [''], {})
3237+
program, __ = self.create_args()
3238+
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, (), {})
3239+
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, [], {})
3240+
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, ('',), {})
3241+
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, [''], {})
32323242

32333243
def _test_invalid_env(self, spawn):
3234-
args = [sys.executable, '-c', 'pass']
3244+
program = sys.executable
3245+
args = self.quote_args([program, '-c', 'pass'])
32353246

32363247
# null character in the environment variable name
32373248
newenv = os.environ.copy()
32383249
newenv["FRUIT\0VEGETABLE"] = "cabbage"
32393250
try:
3240-
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
3251+
exitcode = spawn(os.P_WAIT, program, args, newenv)
32413252
except ValueError:
32423253
pass
32433254
else:
@@ -3247,7 +3258,7 @@ def _test_invalid_env(self, spawn):
32473258
newenv = os.environ.copy()
32483259
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
32493260
try:
3250-
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
3261+
exitcode = spawn(os.P_WAIT, program, args, newenv)
32513262
except ValueError:
32523263
pass
32533264
else:
@@ -3257,7 +3268,7 @@ def _test_invalid_env(self, spawn):
32573268
newenv = os.environ.copy()
32583269
newenv["FRUIT=ORANGE"] = "lemon"
32593270
try:
3260-
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
3271+
exitcode = spawn(os.P_WAIT, program, args, newenv)
32613272
except ValueError:
32623273
pass
32633274
else:
@@ -3270,10 +3281,11 @@ def _test_invalid_env(self, spawn):
32703281
fp.write('import sys, os\n'
32713282
'if os.getenv("FRUIT") != "orange=lemon":\n'
32723283
' raise AssertionError')
3273-
args = [sys.executable, filename]
3284+
3285+
args = self.quote_args([program, filename])
32743286
newenv = os.environ.copy()
32753287
newenv["FRUIT"] = "orange=lemon"
3276-
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
3288+
exitcode = spawn(os.P_WAIT, program, args, newenv)
32773289
self.assertEqual(exitcode, 0)
32783290

32793291
@requires_os_func('spawnve')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix the :func:`os.spawn* <os.spawnl>` tests failing on Windows
2+
when the working directory or interpreter path contains spaces.

0 commit comments

Comments
 (0)