@@ -146,9 +146,12 @@ def _check_output_of_default_create(self):
146146 self .assertIn ('home = %s' % path , data )
147147 self .assertIn ('executable = %s' %
148148 os .path .realpath (sys .executable ), data )
149- copies = '' if os .name == 'nt' else ' --copies'
150- cmd = (f'command = { sys .executable } -m venv{ copies } --without-pip '
151- f'--without-scm-ignore-files { self .env_dir } ' )
149+ expected_argv = [sys .executable , '-m' , 'venv' ]
150+ if os .name != 'nt' :
151+ expected_argv .append ('--copies' )
152+ expected_argv .extend (['--without-pip' , '--without-scm-ignore-files' ,
153+ self .env_dir ])
154+ cmd = f'command = { shlex .join (expected_argv )} '
152155 self .assertIn (cmd , data )
153156 fn = self .get_env_file (self .bindir , self .exe )
154157 if not os .path .exists (fn ): # diagnostics for Windows buildbot failures
@@ -166,7 +169,7 @@ def test_config_file_command_key(self):
166169 ('--clear' , 'clear' , True ),
167170 ('--upgrade' , 'upgrade' , True ),
168171 ('--upgrade-deps' , 'upgrade_deps' , True ),
169- ('--prompt="foobar" ' , 'prompt' , 'foobar' ),
172+ ('--prompt' , 'prompt' , 'foobar' ),
170173 ('--without-scm-ignore-files' , 'scm_ignore_files' , frozenset ()),
171174 ]
172175 for opt , attr , value in options :
@@ -190,6 +193,32 @@ def test_config_file_command_key(self):
190193 else :
191194 self .assertRegex (data , rf'command = .* { opt } ' )
192195
196+ def test_config_file_command_quotes_paths_with_spaces (self ):
197+ # gh-148315: the `command = ...` line written to pyvenv.cfg must be
198+ # shell-quoted, so a venv created in a directory with whitespace in
199+ # its path (as happens on Windows when the user directory contains a
200+ # space, e.g. "C:\\Users\\Z B") round-trips through shlex.split as
201+ # a single token instead of being truncated at the space.
202+ env_dir_with_space = os .path .join (tempfile .mkdtemp (), 'with space' )
203+ self .addCleanup (rmtree , os .path .dirname (env_dir_with_space ))
204+ b = venv .EnvBuilder ()
205+ b .upgrade_dependencies = Mock ()
206+ b ._setup_pip = Mock ()
207+ self .run_with_capture (b .create , env_dir_with_space )
208+ cfg = pathlib .Path (env_dir_with_space , 'pyvenv.cfg' ).read_text (
209+ encoding = 'utf-8' )
210+ for line in cfg .splitlines ():
211+ key , _ , value = line .partition ('=' )
212+ if key .strip () == 'command' :
213+ parts = shlex .split (value .strip ())
214+ break
215+ else :
216+ self .fail (f'pyvenv.cfg is missing a command key:\n { cfg } ' )
217+ # Last token must be the full env_dir, not a space-split fragment.
218+ self .assertEqual (parts [- 1 ], env_dir_with_space )
219+ # And the whole argv must be parseable by the venv CLI.
220+ self .assertEqual (parts [1 :3 ], ['-m' , 'venv' ])
221+
193222 def test_prompt (self ):
194223 env_name = os .path .split (self .env_dir )[1 ]
195224
0 commit comments