Skip to content

Commit a34c796

Browse files
gh-75729: Fix os.spawn tests not handling spaces on Windows (#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]> --------- Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent 1377496 commit a34c796

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
@@ -3214,6 +3214,14 @@ def kill_process(pid):
32143214

32153215
@support.requires_subprocess()
32163216
class SpawnTests(unittest.TestCase):
3217+
@staticmethod
3218+
def quote_args(args):
3219+
# On Windows, os.spawn* simply joins arguments with spaces:
3220+
# arguments need to be quoted
3221+
if os.name != 'nt':
3222+
return args
3223+
return [f'"{arg}"' if " " in arg.strip() else arg for arg in args]
3224+
32173225
def create_args(self, *, with_env=False, use_bytes=False):
32183226
self.exitcode = 17
32193227

@@ -3234,115 +3242,118 @@ def create_args(self, *, with_env=False, use_bytes=False):
32343242
with open(filename, "w", encoding="utf-8") as fp:
32353243
fp.write(code)
32363244

3237-
args = [sys.executable, filename]
3245+
program = sys.executable
3246+
args = self.quote_args([program, filename])
32383247
if use_bytes:
3248+
program = os.fsencode(program)
32393249
args = [os.fsencode(a) for a in args]
32403250
self.env = {os.fsencode(k): os.fsencode(v)
32413251
for k, v in self.env.items()}
32423252

3243-
return args
3253+
return program, args
32443254

32453255
@requires_os_func('spawnl')
32463256
def test_spawnl(self):
3247-
args = self.create_args()
3248-
exitcode = os.spawnl(os.P_WAIT, args[0], *args)
3257+
program, args = self.create_args()
3258+
exitcode = os.spawnl(os.P_WAIT, program, *args)
32493259
self.assertEqual(exitcode, self.exitcode)
32503260

32513261
@requires_os_func('spawnle')
32523262
def test_spawnle(self):
3253-
args = self.create_args(with_env=True)
3254-
exitcode = os.spawnle(os.P_WAIT, args[0], *args, self.env)
3263+
program, args = self.create_args(with_env=True)
3264+
exitcode = os.spawnle(os.P_WAIT, program, *args, self.env)
32553265
self.assertEqual(exitcode, self.exitcode)
32563266

32573267
@requires_os_func('spawnlp')
32583268
def test_spawnlp(self):
3259-
args = self.create_args()
3260-
exitcode = os.spawnlp(os.P_WAIT, args[0], *args)
3269+
program, args = self.create_args()
3270+
exitcode = os.spawnlp(os.P_WAIT, program, *args)
32613271
self.assertEqual(exitcode, self.exitcode)
32623272

32633273
@requires_os_func('spawnlpe')
32643274
def test_spawnlpe(self):
3265-
args = self.create_args(with_env=True)
3266-
exitcode = os.spawnlpe(os.P_WAIT, args[0], *args, self.env)
3275+
program, args = self.create_args(with_env=True)
3276+
exitcode = os.spawnlpe(os.P_WAIT, program, *args, self.env)
32673277
self.assertEqual(exitcode, self.exitcode)
32683278

32693279
@requires_os_func('spawnv')
32703280
def test_spawnv(self):
3271-
args = self.create_args()
3272-
exitcode = os.spawnv(os.P_WAIT, args[0], args)
3281+
program, args = self.create_args()
3282+
exitcode = os.spawnv(os.P_WAIT, program, args)
32733283
self.assertEqual(exitcode, self.exitcode)
32743284

32753285
# Test for PyUnicode_FSConverter()
3276-
exitcode = os.spawnv(os.P_WAIT, FakePath(args[0]), args)
3286+
exitcode = os.spawnv(os.P_WAIT, FakePath(program), args)
32773287
self.assertEqual(exitcode, self.exitcode)
32783288

32793289
@requires_os_func('spawnve')
32803290
def test_spawnve(self):
3281-
args = self.create_args(with_env=True)
3282-
exitcode = os.spawnve(os.P_WAIT, args[0], args, self.env)
3291+
program, args = self.create_args(with_env=True)
3292+
exitcode = os.spawnve(os.P_WAIT, program, args, self.env)
32833293
self.assertEqual(exitcode, self.exitcode)
32843294

32853295
@requires_os_func('spawnvp')
32863296
def test_spawnvp(self):
3287-
args = self.create_args()
3288-
exitcode = os.spawnvp(os.P_WAIT, args[0], args)
3297+
program, args = self.create_args()
3298+
exitcode = os.spawnvp(os.P_WAIT, program, args)
32893299
self.assertEqual(exitcode, self.exitcode)
32903300

32913301
@requires_os_func('spawnvpe')
32923302
def test_spawnvpe(self):
3293-
args = self.create_args(with_env=True)
3294-
exitcode = os.spawnvpe(os.P_WAIT, args[0], args, self.env)
3303+
program, args = self.create_args(with_env=True)
3304+
exitcode = os.spawnvpe(os.P_WAIT, program, args, self.env)
32953305
self.assertEqual(exitcode, self.exitcode)
32963306

32973307
@requires_os_func('spawnv')
32983308
def test_nowait(self):
3299-
args = self.create_args()
3300-
pid = os.spawnv(os.P_NOWAIT, args[0], args)
3309+
program, args = self.create_args()
3310+
pid = os.spawnv(os.P_NOWAIT, program, args)
33013311
support.wait_process(pid, exitcode=self.exitcode)
33023312

33033313
@requires_os_func('spawnve')
33043314
def test_spawnve_bytes(self):
33053315
# Test bytes handling in parse_arglist and parse_envlist (#28114)
3306-
args = self.create_args(with_env=True, use_bytes=True)
3307-
exitcode = os.spawnve(os.P_WAIT, args[0], args, self.env)
3316+
program, args = self.create_args(with_env=True, use_bytes=True)
3317+
exitcode = os.spawnve(os.P_WAIT, program, args, self.env)
33083318
self.assertEqual(exitcode, self.exitcode)
33093319

33103320
@requires_os_func('spawnl')
33113321
def test_spawnl_noargs(self):
3312-
args = self.create_args()
3313-
self.assertRaises(ValueError, os.spawnl, os.P_NOWAIT, args[0])
3314-
self.assertRaises(ValueError, os.spawnl, os.P_NOWAIT, args[0], '')
3322+
program, __ = self.create_args()
3323+
self.assertRaises(ValueError, os.spawnl, os.P_NOWAIT, program)
3324+
self.assertRaises(ValueError, os.spawnl, os.P_NOWAIT, program, '')
33153325

33163326
@requires_os_func('spawnle')
33173327
def test_spawnle_noargs(self):
3318-
args = self.create_args()
3319-
self.assertRaises(ValueError, os.spawnle, os.P_NOWAIT, args[0], {})
3320-
self.assertRaises(ValueError, os.spawnle, os.P_NOWAIT, args[0], '', {})
3328+
program, __ = self.create_args()
3329+
self.assertRaises(ValueError, os.spawnle, os.P_NOWAIT, program, {})
3330+
self.assertRaises(ValueError, os.spawnle, os.P_NOWAIT, program, '', {})
33213331

33223332
@requires_os_func('spawnv')
33233333
def test_spawnv_noargs(self):
3324-
args = self.create_args()
3325-
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, args[0], ())
3326-
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, args[0], [])
3327-
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, args[0], ('',))
3328-
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, args[0], [''])
3334+
program, __ = self.create_args()
3335+
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, ())
3336+
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, [])
3337+
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, ('',))
3338+
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, [''])
33293339

33303340
@requires_os_func('spawnve')
33313341
def test_spawnve_noargs(self):
3332-
args = self.create_args()
3333-
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, args[0], (), {})
3334-
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, args[0], [], {})
3335-
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, args[0], ('',), {})
3336-
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, args[0], [''], {})
3342+
program, __ = self.create_args()
3343+
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, (), {})
3344+
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, [], {})
3345+
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, ('',), {})
3346+
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, [''], {})
33373347

33383348
def _test_invalid_env(self, spawn):
3339-
args = [sys.executable, '-c', 'pass']
3349+
program = sys.executable
3350+
args = self.quote_args([program, '-c', 'pass'])
33403351

33413352
# null character in the environment variable name
33423353
newenv = os.environ.copy()
33433354
newenv["FRUIT\0VEGETABLE"] = "cabbage"
33443355
try:
3345-
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
3356+
exitcode = spawn(os.P_WAIT, program, args, newenv)
33463357
except ValueError:
33473358
pass
33483359
else:
@@ -3352,7 +3363,7 @@ def _test_invalid_env(self, spawn):
33523363
newenv = os.environ.copy()
33533364
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
33543365
try:
3355-
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
3366+
exitcode = spawn(os.P_WAIT, program, args, newenv)
33563367
except ValueError:
33573368
pass
33583369
else:
@@ -3362,7 +3373,7 @@ def _test_invalid_env(self, spawn):
33623373
newenv = os.environ.copy()
33633374
newenv["FRUIT=ORANGE"] = "lemon"
33643375
try:
3365-
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
3376+
exitcode = spawn(os.P_WAIT, program, args, newenv)
33663377
except ValueError:
33673378
pass
33683379
else:
@@ -3375,10 +3386,11 @@ def _test_invalid_env(self, spawn):
33753386
fp.write('import sys, os\n'
33763387
'if os.getenv("FRUIT") != "orange=lemon":\n'
33773388
' raise AssertionError')
3378-
args = [sys.executable, filename]
3389+
3390+
args = self.quote_args([program, filename])
33793391
newenv = os.environ.copy()
33803392
newenv["FRUIT"] = "orange=lemon"
3381-
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
3393+
exitcode = spawn(os.P_WAIT, program, args, newenv)
33823394
self.assertEqual(exitcode, 0)
33833395

33843396
@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)