1 """
2 Test harness for the venv module.
3
4 Copyright (C) 2011-2012 Vinay Sajip.
5 Licensed to the PSF under a contributor agreement.
6 """
7
8 import contextlib
9 import ensurepip
10 import os
11 import os.path
12 import pathlib
13 import re
14 import shutil
15 import struct
16 import subprocess
17 import sys
18 import sysconfig
19 import tempfile
20 from test.support import (captured_stdout, captured_stderr, requires_zlib,
21 skip_if_broken_multiprocessing_synchronize, verbose,
22 requires_subprocess, is_emscripten, is_wasi,
23 requires_venv_with_pip, TEST_HOME_DIR,
24 requires_resource, copy_python_src_ignore)
25 from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree)
26 import unittest
27 import venv
28 from unittest.mock import patch, Mock
29
30 try:
31 import ctypes
32 except ImportError:
33 ctypes = None
34
35 # Platforms that set sys._base_executable can create venvs from within
36 # another venv, so no need to skip tests that require venv.create().
37 requireVenvCreate = unittest.skipUnless(
38 sys.prefix == sys.base_prefix
39 or sys._base_executable != sys.executable,
40 'cannot run venv.create from within a venv on this platform')
41
42 if is_emscripten or is_wasi:
43 raise unittest.SkipTest("venv is not available on Emscripten/WASI.")
44
45 @requires_subprocess()
46 def check_output(cmd, encoding=None):
47 p = subprocess.Popen(cmd,
48 stdout=subprocess.PIPE,
49 stderr=subprocess.PIPE,
50 encoding=encoding)
51 out, err = p.communicate()
52 if p.returncode:
53 if verbose and err:
54 print(err.decode('utf-8', 'backslashreplace'))
55 raise subprocess.CalledProcessError(
56 p.returncode, cmd, out, err)
57 return out, err
58
59 class ESC[4;38;5;81mBaseTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
60 """Base class for venv tests."""
61 maxDiff = 80 * 50
62
63 def setUp(self):
64 self.env_dir = os.path.realpath(tempfile.mkdtemp())
65 if os.name == 'nt':
66 self.bindir = 'Scripts'
67 self.lib = ('Lib',)
68 self.include = 'Include'
69 else:
70 self.bindir = 'bin'
71 self.lib = ('lib', 'python%d.%d' % sys.version_info[:2])
72 self.include = 'include'
73 executable = sys._base_executable
74 self.exe = os.path.split(executable)[-1]
75 if (sys.platform == 'win32'
76 and os.path.lexists(executable)
77 and not os.path.exists(executable)):
78 self.cannot_link_exe = True
79 else:
80 self.cannot_link_exe = False
81
82 def tearDown(self):
83 rmtree(self.env_dir)
84
85 def run_with_capture(self, func, *args, **kwargs):
86 with captured_stdout() as output:
87 with captured_stderr() as error:
88 func(*args, **kwargs)
89 return output.getvalue(), error.getvalue()
90
91 def get_env_file(self, *args):
92 return os.path.join(self.env_dir, *args)
93
94 def get_text_file_contents(self, *args, encoding='utf-8'):
95 with open(self.get_env_file(*args), 'r', encoding=encoding) as f:
96 result = f.read()
97 return result
98
99 class ESC[4;38;5;81mBasicTest(ESC[4;38;5;149mBaseTest):
100 """Test venv module functionality."""
101
102 def isdir(self, *args):
103 fn = self.get_env_file(*args)
104 self.assertTrue(os.path.isdir(fn))
105
106 def test_defaults_with_str_path(self):
107 """
108 Test the create function with default arguments and a str path.
109 """
110 rmtree(self.env_dir)
111 self.run_with_capture(venv.create, self.env_dir)
112 self._check_output_of_default_create()
113
114 def test_defaults_with_pathlib_path(self):
115 """
116 Test the create function with default arguments and a pathlib.Path path.
117 """
118 rmtree(self.env_dir)
119 self.run_with_capture(venv.create, pathlib.Path(self.env_dir))
120 self._check_output_of_default_create()
121
122 def _check_output_of_default_create(self):
123 self.isdir(self.bindir)
124 self.isdir(self.include)
125 self.isdir(*self.lib)
126 # Issue 21197
127 p = self.get_env_file('lib64')
128 conditions = ((struct.calcsize('P') == 8) and (os.name == 'posix') and
129 (sys.platform != 'darwin'))
130 if conditions:
131 self.assertTrue(os.path.islink(p))
132 else:
133 self.assertFalse(os.path.exists(p))
134 data = self.get_text_file_contents('pyvenv.cfg')
135 executable = sys._base_executable
136 path = os.path.dirname(executable)
137 self.assertIn('home = %s' % path, data)
138 self.assertIn('executable = %s' %
139 os.path.realpath(sys.executable), data)
140 copies = '' if os.name=='nt' else ' --copies'
141 cmd = f'command = {sys.executable} -m venv{copies} --without-pip {self.env_dir}'
142 self.assertIn(cmd, data)
143 fn = self.get_env_file(self.bindir, self.exe)
144 if not os.path.exists(fn): # diagnostics for Windows buildbot failures
145 bd = self.get_env_file(self.bindir)
146 print('Contents of %r:' % bd)
147 print(' %r' % os.listdir(bd))
148 self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)
149
150 def test_config_file_command_key(self):
151 attrs = [
152 (None, None),
153 ('symlinks', '--copies'),
154 ('with_pip', '--without-pip'),
155 ('system_site_packages', '--system-site-packages'),
156 ('clear', '--clear'),
157 ('upgrade', '--upgrade'),
158 ('upgrade_deps', '--upgrade-deps'),
159 ('prompt', '--prompt'),
160 ]
161 for attr, opt in attrs:
162 rmtree(self.env_dir)
163 if not attr:
164 b = venv.EnvBuilder()
165 else:
166 b = venv.EnvBuilder(
167 **{attr: False if attr in ('with_pip', 'symlinks') else True})
168 b.upgrade_dependencies = Mock() # avoid pip command to upgrade deps
169 b._setup_pip = Mock() # avoid pip setup
170 self.run_with_capture(b.create, self.env_dir)
171 data = self.get_text_file_contents('pyvenv.cfg')
172 if not attr:
173 for opt in ('--system-site-packages', '--clear', '--upgrade',
174 '--upgrade-deps', '--prompt'):
175 self.assertNotRegex(data, rf'command = .* {opt}')
176 elif os.name=='nt' and attr=='symlinks':
177 pass
178 else:
179 self.assertRegex(data, rf'command = .* {opt}')
180
181 def test_prompt(self):
182 env_name = os.path.split(self.env_dir)[1]
183
184 rmtree(self.env_dir)
185 builder = venv.EnvBuilder()
186 self.run_with_capture(builder.create, self.env_dir)
187 context = builder.ensure_directories(self.env_dir)
188 data = self.get_text_file_contents('pyvenv.cfg')
189 self.assertEqual(context.prompt, '(%s) ' % env_name)
190 self.assertNotIn("prompt = ", data)
191
192 rmtree(self.env_dir)
193 builder = venv.EnvBuilder(prompt='My prompt')
194 self.run_with_capture(builder.create, self.env_dir)
195 context = builder.ensure_directories(self.env_dir)
196 data = self.get_text_file_contents('pyvenv.cfg')
197 self.assertEqual(context.prompt, '(My prompt) ')
198 self.assertIn("prompt = 'My prompt'\n", data)
199
200 rmtree(self.env_dir)
201 builder = venv.EnvBuilder(prompt='.')
202 cwd = os.path.basename(os.getcwd())
203 self.run_with_capture(builder.create, self.env_dir)
204 context = builder.ensure_directories(self.env_dir)
205 data = self.get_text_file_contents('pyvenv.cfg')
206 self.assertEqual(context.prompt, '(%s) ' % cwd)
207 self.assertIn("prompt = '%s'\n" % cwd, data)
208
209 def test_upgrade_dependencies(self):
210 builder = venv.EnvBuilder()
211 bin_path = 'Scripts' if sys.platform == 'win32' else 'bin'
212 python_exe = os.path.split(sys.executable)[1]
213 with tempfile.TemporaryDirectory() as fake_env_dir:
214 expect_exe = os.path.normcase(
215 os.path.join(fake_env_dir, bin_path, python_exe)
216 )
217 if sys.platform == 'win32':
218 expect_exe = os.path.normcase(os.path.realpath(expect_exe))
219
220 def pip_cmd_checker(cmd, **kwargs):
221 cmd[0] = os.path.normcase(cmd[0])
222 self.assertEqual(
223 cmd,
224 [
225 expect_exe,
226 '-m',
227 'pip',
228 'install',
229 '--upgrade',
230 'pip',
231 'setuptools'
232 ]
233 )
234
235 fake_context = builder.ensure_directories(fake_env_dir)
236 with patch('venv.subprocess.check_output', pip_cmd_checker):
237 builder.upgrade_dependencies(fake_context)
238
239 @requireVenvCreate
240 def test_prefixes(self):
241 """
242 Test that the prefix values are as expected.
243 """
244 # check a venv's prefixes
245 rmtree(self.env_dir)
246 self.run_with_capture(venv.create, self.env_dir)
247 envpy = os.path.join(self.env_dir, self.bindir, self.exe)
248 cmd = [envpy, '-c', None]
249 for prefix, expected in (
250 ('prefix', self.env_dir),
251 ('exec_prefix', self.env_dir),
252 ('base_prefix', sys.base_prefix),
253 ('base_exec_prefix', sys.base_exec_prefix)):
254 cmd[2] = 'import sys; print(sys.%s)' % prefix
255 out, err = check_output(cmd)
256 self.assertEqual(out.strip(), expected.encode(), prefix)
257
258 @requireVenvCreate
259 def test_sysconfig(self):
260 """
261 Test that the sysconfig functions work in a virtual environment.
262 """
263 rmtree(self.env_dir)
264 self.run_with_capture(venv.create, self.env_dir, symlinks=False)
265 envpy = os.path.join(self.env_dir, self.bindir, self.exe)
266 cmd = [envpy, '-c', None]
267 for call, expected in (
268 # installation scheme
269 ('get_preferred_scheme("prefix")', 'venv'),
270 ('get_default_scheme()', 'venv'),
271 # build environment
272 ('is_python_build()', str(sysconfig.is_python_build())),
273 ('get_makefile_filename()', sysconfig.get_makefile_filename()),
274 ('get_config_h_filename()', sysconfig.get_config_h_filename())):
275 with self.subTest(call):
276 cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
277 out, err = check_output(cmd)
278 self.assertEqual(out.strip(), expected.encode(), err)
279
280 @requireVenvCreate
281 @unittest.skipUnless(can_symlink(), 'Needs symlinks')
282 def test_sysconfig_symlinks(self):
283 """
284 Test that the sysconfig functions work in a virtual environment.
285 """
286 rmtree(self.env_dir)
287 self.run_with_capture(venv.create, self.env_dir, symlinks=True)
288 envpy = os.path.join(self.env_dir, self.bindir, self.exe)
289 cmd = [envpy, '-c', None]
290 for call, expected in (
291 # installation scheme
292 ('get_preferred_scheme("prefix")', 'venv'),
293 ('get_default_scheme()', 'venv'),
294 # build environment
295 ('is_python_build()', str(sysconfig.is_python_build())),
296 ('get_makefile_filename()', sysconfig.get_makefile_filename()),
297 ('get_config_h_filename()', sysconfig.get_config_h_filename())):
298 with self.subTest(call):
299 cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
300 out, err = check_output(cmd)
301 self.assertEqual(out.strip(), expected.encode(), err)
302
303 if sys.platform == 'win32':
304 ENV_SUBDIRS = (
305 ('Scripts',),
306 ('Include',),
307 ('Lib',),
308 ('Lib', 'site-packages'),
309 )
310 else:
311 ENV_SUBDIRS = (
312 ('bin',),
313 ('include',),
314 ('lib',),
315 ('lib', 'python%d.%d' % sys.version_info[:2]),
316 ('lib', 'python%d.%d' % sys.version_info[:2], 'site-packages'),
317 )
318
319 def create_contents(self, paths, filename):
320 """
321 Create some files in the environment which are unrelated
322 to the virtual environment.
323 """
324 for subdirs in paths:
325 d = os.path.join(self.env_dir, *subdirs)
326 os.mkdir(d)
327 fn = os.path.join(d, filename)
328 with open(fn, 'wb') as f:
329 f.write(b'Still here?')
330
331 def test_overwrite_existing(self):
332 """
333 Test creating environment in an existing directory.
334 """
335 self.create_contents(self.ENV_SUBDIRS, 'foo')
336 venv.create(self.env_dir)
337 for subdirs in self.ENV_SUBDIRS:
338 fn = os.path.join(self.env_dir, *(subdirs + ('foo',)))
339 self.assertTrue(os.path.exists(fn))
340 with open(fn, 'rb') as f:
341 self.assertEqual(f.read(), b'Still here?')
342
343 builder = venv.EnvBuilder(clear=True)
344 builder.create(self.env_dir)
345 for subdirs in self.ENV_SUBDIRS:
346 fn = os.path.join(self.env_dir, *(subdirs + ('foo',)))
347 self.assertFalse(os.path.exists(fn))
348
349 def clear_directory(self, path):
350 for fn in os.listdir(path):
351 fn = os.path.join(path, fn)
352 if os.path.islink(fn) or os.path.isfile(fn):
353 os.remove(fn)
354 elif os.path.isdir(fn):
355 rmtree(fn)
356
357 def test_unoverwritable_fails(self):
358 #create a file clashing with directories in the env dir
359 for paths in self.ENV_SUBDIRS[:3]:
360 fn = os.path.join(self.env_dir, *paths)
361 with open(fn, 'wb') as f:
362 f.write(b'')
363 self.assertRaises((ValueError, OSError), venv.create, self.env_dir)
364 self.clear_directory(self.env_dir)
365
366 def test_upgrade(self):
367 """
368 Test upgrading an existing environment directory.
369 """
370 # See Issue #21643: the loop needs to run twice to ensure
371 # that everything works on the upgrade (the first run just creates
372 # the venv).
373 for upgrade in (False, True):
374 builder = venv.EnvBuilder(upgrade=upgrade)
375 self.run_with_capture(builder.create, self.env_dir)
376 self.isdir(self.bindir)
377 self.isdir(self.include)
378 self.isdir(*self.lib)
379 fn = self.get_env_file(self.bindir, self.exe)
380 if not os.path.exists(fn):
381 # diagnostics for Windows buildbot failures
382 bd = self.get_env_file(self.bindir)
383 print('Contents of %r:' % bd)
384 print(' %r' % os.listdir(bd))
385 self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)
386
387 def test_isolation(self):
388 """
389 Test isolation from system site-packages
390 """
391 for ssp, s in ((True, 'true'), (False, 'false')):
392 builder = venv.EnvBuilder(clear=True, system_site_packages=ssp)
393 builder.create(self.env_dir)
394 data = self.get_text_file_contents('pyvenv.cfg')
395 self.assertIn('include-system-site-packages = %s\n' % s, data)
396
397 @unittest.skipUnless(can_symlink(), 'Needs symlinks')
398 def test_symlinking(self):
399 """
400 Test symlinking works as expected
401 """
402 for usl in (False, True):
403 builder = venv.EnvBuilder(clear=True, symlinks=usl)
404 builder.create(self.env_dir)
405 fn = self.get_env_file(self.bindir, self.exe)
406 # Don't test when False, because e.g. 'python' is always
407 # symlinked to 'python3.3' in the env, even when symlinking in
408 # general isn't wanted.
409 if usl:
410 if self.cannot_link_exe:
411 # Symlinking is skipped when our executable is already a
412 # special app symlink
413 self.assertFalse(os.path.islink(fn))
414 else:
415 self.assertTrue(os.path.islink(fn))
416
417 # If a venv is created from a source build and that venv is used to
418 # run the test, the pyvenv.cfg in the venv created in the test will
419 # point to the venv being used to run the test, and we lose the link
420 # to the source build - so Python can't initialise properly.
421 @requireVenvCreate
422 def test_executable(self):
423 """
424 Test that the sys.executable value is as expected.
425 """
426 rmtree(self.env_dir)
427 self.run_with_capture(venv.create, self.env_dir)
428 envpy = os.path.join(os.path.realpath(self.env_dir),
429 self.bindir, self.exe)
430 out, err = check_output([envpy, '-c',
431 'import sys; print(sys.executable)'])
432 self.assertEqual(out.strip(), envpy.encode())
433
434 @unittest.skipUnless(can_symlink(), 'Needs symlinks')
435 def test_executable_symlinks(self):
436 """
437 Test that the sys.executable value is as expected.
438 """
439 rmtree(self.env_dir)
440 builder = venv.EnvBuilder(clear=True, symlinks=True)
441 builder.create(self.env_dir)
442 envpy = os.path.join(os.path.realpath(self.env_dir),
443 self.bindir, self.exe)
444 out, err = check_output([envpy, '-c',
445 'import sys; print(sys.executable)'])
446 self.assertEqual(out.strip(), envpy.encode())
447
448 @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
449 def test_unicode_in_batch_file(self):
450 """
451 Test handling of Unicode paths
452 """
453 rmtree(self.env_dir)
454 env_dir = os.path.join(os.path.realpath(self.env_dir), 'ϼўТλФЙ')
455 builder = venv.EnvBuilder(clear=True)
456 builder.create(env_dir)
457 activate = os.path.join(env_dir, self.bindir, 'activate.bat')
458 envpy = os.path.join(env_dir, self.bindir, self.exe)
459 out, err = check_output(
460 [activate, '&', self.exe, '-c', 'print(0)'],
461 encoding='oem',
462 )
463 self.assertEqual(out.strip(), '0')
464
465 @requireVenvCreate
466 def test_multiprocessing(self):
467 """
468 Test that the multiprocessing is able to spawn.
469 """
470 # bpo-36342: Instantiation of a Pool object imports the
471 # multiprocessing.synchronize module. Skip the test if this module
472 # cannot be imported.
473 skip_if_broken_multiprocessing_synchronize()
474
475 rmtree(self.env_dir)
476 self.run_with_capture(venv.create, self.env_dir)
477 envpy = os.path.join(os.path.realpath(self.env_dir),
478 self.bindir, self.exe)
479 out, err = check_output([envpy, '-c',
480 'from multiprocessing import Pool; '
481 'pool = Pool(1); '
482 'print(pool.apply_async("Python".lower).get(3)); '
483 'pool.terminate()'])
484 self.assertEqual(out.strip(), "python".encode())
485
486 @requireVenvCreate
487 def test_multiprocessing_recursion(self):
488 """
489 Test that the multiprocessing is able to spawn itself
490 """
491 skip_if_broken_multiprocessing_synchronize()
492
493 rmtree(self.env_dir)
494 self.run_with_capture(venv.create, self.env_dir)
495 envpy = os.path.join(os.path.realpath(self.env_dir),
496 self.bindir, self.exe)
497 script = os.path.join(TEST_HOME_DIR, '_test_venv_multiprocessing.py')
498 subprocess.check_call([envpy, script])
499
500 @unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
501 def test_deactivate_with_strict_bash_opts(self):
502 bash = shutil.which("bash")
503 if bash is None:
504 self.skipTest("bash required for this test")
505 rmtree(self.env_dir)
506 builder = venv.EnvBuilder(clear=True)
507 builder.create(self.env_dir)
508 activate = os.path.join(self.env_dir, self.bindir, "activate")
509 test_script = os.path.join(self.env_dir, "test_strict.sh")
510 with open(test_script, "w") as f:
511 f.write("set -euo pipefail\n"
512 f"source {activate}\n"
513 "deactivate\n")
514 out, err = check_output([bash, test_script])
515 self.assertEqual(out, "".encode())
516 self.assertEqual(err, "".encode())
517
518
519 @unittest.skipUnless(sys.platform == 'darwin', 'only relevant on macOS')
520 def test_macos_env(self):
521 rmtree(self.env_dir)
522 builder = venv.EnvBuilder()
523 builder.create(self.env_dir)
524
525 envpy = os.path.join(os.path.realpath(self.env_dir),
526 self.bindir, self.exe)
527 out, err = check_output([envpy, '-c',
528 'import os; print("__PYVENV_LAUNCHER__" in os.environ)'])
529 self.assertEqual(out.strip(), 'False'.encode())
530
531 def test_pathsep_error(self):
532 """
533 Test that venv creation fails when the target directory contains
534 the path separator.
535 """
536 rmtree(self.env_dir)
537 bad_itempath = self.env_dir + os.pathsep
538 self.assertRaises(ValueError, venv.create, bad_itempath)
539 self.assertRaises(ValueError, venv.create, pathlib.Path(bad_itempath))
540
541 @unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
542 @requireVenvCreate
543 def test_zippath_from_non_installed_posix(self):
544 """
545 Test that when create venv from non-installed python, the zip path
546 value is as expected.
547 """
548 rmtree(self.env_dir)
549 # First try to create a non-installed python. It's not a real full
550 # functional non-installed python, but enough for this test.
551 platlibdir = sys.platlibdir
552 non_installed_dir = os.path.realpath(tempfile.mkdtemp())
553 self.addCleanup(rmtree, non_installed_dir)
554 bindir = os.path.join(non_installed_dir, self.bindir)
555 os.mkdir(bindir)
556 shutil.copy2(sys.executable, bindir)
557 libdir = os.path.join(non_installed_dir, platlibdir, self.lib[1])
558 os.makedirs(libdir)
559 landmark = os.path.join(libdir, "os.py")
560 stdlib_zip = "python%d%d.zip" % sys.version_info[:2]
561 zip_landmark = os.path.join(non_installed_dir,
562 platlibdir,
563 stdlib_zip)
564 additional_pythonpath_for_non_installed = []
565
566 # Copy stdlib files to the non-installed python so venv can
567 # correctly calculate the prefix.
568 for eachpath in sys.path:
569 if eachpath.endswith(".zip"):
570 if os.path.isfile(eachpath):
571 shutil.copyfile(
572 eachpath,
573 os.path.join(non_installed_dir, platlibdir))
574 elif os.path.isfile(os.path.join(eachpath, "os.py")):
575 names = os.listdir(eachpath)
576 ignored_names = copy_python_src_ignore(eachpath, names)
577 for name in names:
578 if name in ignored_names:
579 continue
580 if name == "site-packages":
581 continue
582 fn = os.path.join(eachpath, name)
583 if os.path.isfile(fn):
584 shutil.copy(fn, libdir)
585 elif os.path.isdir(fn):
586 shutil.copytree(fn, os.path.join(libdir, name),
587 ignore=copy_python_src_ignore)
588 else:
589 additional_pythonpath_for_non_installed.append(
590 eachpath)
591 cmd = [os.path.join(non_installed_dir, self.bindir, self.exe),
592 "-m",
593 "venv",
594 "--without-pip",
595 self.env_dir]
596 # Our fake non-installed python is not fully functional because
597 # it cannot find the extensions. Set PYTHONPATH so it can run the
598 # venv module correctly.
599 pythonpath = os.pathsep.join(
600 additional_pythonpath_for_non_installed)
601 # For python built with shared enabled. We need to set
602 # LD_LIBRARY_PATH so the non-installed python can find and link
603 # libpython.so
604 ld_library_path = sysconfig.get_config_var("LIBDIR")
605 if not ld_library_path or sysconfig.is_python_build():
606 ld_library_path = os.path.abspath(os.path.dirname(sys.executable))
607 if sys.platform == 'darwin':
608 ld_library_path_env = "DYLD_LIBRARY_PATH"
609 else:
610 ld_library_path_env = "LD_LIBRARY_PATH"
611 subprocess.check_call(cmd,
612 env={"PYTHONPATH": pythonpath,
613 ld_library_path_env: ld_library_path})
614 envpy = os.path.join(self.env_dir, self.bindir, self.exe)
615 # Now check the venv created from the non-installed python has
616 # correct zip path in pythonpath.
617 cmd = [envpy, '-S', '-c', 'import sys; print(sys.path)']
618 out, err = check_output(cmd)
619 self.assertTrue(zip_landmark.encode() in out)
620
621 @requireVenvCreate
622 class ESC[4;38;5;81mEnsurePipTest(ESC[4;38;5;149mBaseTest):
623 """Test venv module installation of pip."""
624 def assert_pip_not_installed(self):
625 envpy = os.path.join(os.path.realpath(self.env_dir),
626 self.bindir, self.exe)
627 out, err = check_output([envpy, '-c',
628 'try:\n import pip\nexcept ImportError:\n print("OK")'])
629 # We force everything to text, so unittest gives the detailed diff
630 # if we get unexpected results
631 err = err.decode("latin-1") # Force to text, prevent decoding errors
632 self.assertEqual(err, "")
633 out = out.decode("latin-1") # Force to text, prevent decoding errors
634 self.assertEqual(out.strip(), "OK")
635
636
637 def test_no_pip_by_default(self):
638 rmtree(self.env_dir)
639 self.run_with_capture(venv.create, self.env_dir)
640 self.assert_pip_not_installed()
641
642 def test_explicit_no_pip(self):
643 rmtree(self.env_dir)
644 self.run_with_capture(venv.create, self.env_dir, with_pip=False)
645 self.assert_pip_not_installed()
646
647 def test_devnull(self):
648 # Fix for issue #20053 uses os.devnull to force a config file to
649 # appear empty. However http://bugs.python.org/issue20541 means
650 # that doesn't currently work properly on Windows. Once that is
651 # fixed, the "win_location" part of test_with_pip should be restored
652 with open(os.devnull, "rb") as f:
653 self.assertEqual(f.read(), b"")
654
655 self.assertTrue(os.path.exists(os.devnull))
656
657 def do_test_with_pip(self, system_site_packages):
658 rmtree(self.env_dir)
659 with EnvironmentVarGuard() as envvars:
660 # pip's cross-version compatibility may trigger deprecation
661 # warnings in current versions of Python. Ensure related
662 # environment settings don't cause venv to fail.
663 envvars["PYTHONWARNINGS"] = "ignore"
664 # ensurepip is different enough from a normal pip invocation
665 # that we want to ensure it ignores the normal pip environment
666 # variable settings. We set PIP_NO_INSTALL here specifically
667 # to check that ensurepip (and hence venv) ignores it.
668 # See http://bugs.python.org/issue19734
669 envvars["PIP_NO_INSTALL"] = "1"
670 # Also check that we ignore the pip configuration file
671 # See http://bugs.python.org/issue20053
672 with tempfile.TemporaryDirectory() as home_dir:
673 envvars["HOME"] = home_dir
674 bad_config = "[global]\nno-install=1"
675 # Write to both config file names on all platforms to reduce
676 # cross-platform variation in test code behaviour
677 win_location = ("pip", "pip.ini")
678 posix_location = (".pip", "pip.conf")
679 # Skips win_location due to http://bugs.python.org/issue20541
680 for dirname, fname in (posix_location,):
681 dirpath = os.path.join(home_dir, dirname)
682 os.mkdir(dirpath)
683 fpath = os.path.join(dirpath, fname)
684 with open(fpath, 'w') as f:
685 f.write(bad_config)
686
687 # Actually run the create command with all that unhelpful
688 # config in place to ensure we ignore it
689 with self.nicer_error():
690 self.run_with_capture(venv.create, self.env_dir,
691 system_site_packages=system_site_packages,
692 with_pip=True)
693 # Ensure pip is available in the virtual environment
694 envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
695 # Ignore DeprecationWarning since pip code is not part of Python
696 out, err = check_output([envpy, '-W', 'ignore::DeprecationWarning',
697 '-W', 'ignore::ImportWarning', '-I',
698 '-m', 'pip', '--version'])
699 # We force everything to text, so unittest gives the detailed diff
700 # if we get unexpected results
701 err = err.decode("latin-1") # Force to text, prevent decoding errors
702 self.assertEqual(err, "")
703 out = out.decode("latin-1") # Force to text, prevent decoding errors
704 expected_version = "pip {}".format(ensurepip.version())
705 self.assertEqual(out[:len(expected_version)], expected_version)
706 env_dir = os.fsencode(self.env_dir).decode("latin-1")
707 self.assertIn(env_dir, out)
708
709 # http://bugs.python.org/issue19728
710 # Check the private uninstall command provided for the Windows
711 # installers works (at least in a virtual environment)
712 with EnvironmentVarGuard() as envvars:
713 with self.nicer_error():
714 # It seems ensurepip._uninstall calls subprocesses which do not
715 # inherit the interpreter settings.
716 envvars["PYTHONWARNINGS"] = "ignore"
717 out, err = check_output([envpy,
718 '-W', 'ignore::DeprecationWarning',
719 '-W', 'ignore::ImportWarning', '-I',
720 '-m', 'ensurepip._uninstall'])
721 # We force everything to text, so unittest gives the detailed diff
722 # if we get unexpected results
723 err = err.decode("latin-1") # Force to text, prevent decoding errors
724 # Ignore the warning:
725 # "The directory '$HOME/.cache/pip/http' or its parent directory
726 # is not owned by the current user and the cache has been disabled.
727 # Please check the permissions and owner of that directory. If
728 # executing pip with sudo, you may want sudo's -H flag."
729 # where $HOME is replaced by the HOME environment variable.
730 err = re.sub("^(WARNING: )?The directory .* or its parent directory "
731 "is not owned or is not writable by the current user.*$", "",
732 err, flags=re.MULTILINE)
733 self.assertEqual(err.rstrip(), "")
734 # Being fairly specific regarding the expected behaviour for the
735 # initial bundling phase in Python 3.4. If the output changes in
736 # future pip versions, this test can likely be relaxed further.
737 out = out.decode("latin-1") # Force to text, prevent decoding errors
738 self.assertIn("Successfully uninstalled pip", out)
739 self.assertIn("Successfully uninstalled setuptools", out)
740 # Check pip is now gone from the virtual environment. This only
741 # applies in the system_site_packages=False case, because in the
742 # other case, pip may still be available in the system site-packages
743 if not system_site_packages:
744 self.assert_pip_not_installed()
745
746 @contextlib.contextmanager
747 def nicer_error(self):
748 """
749 Capture output from a failed subprocess for easier debugging.
750
751 The output this handler produces can be a little hard to read,
752 but at least it has all the details.
753 """
754 try:
755 yield
756 except subprocess.CalledProcessError as exc:
757 out = (exc.output or b'').decode(errors="replace")
758 err = (exc.stderr or b'').decode(errors="replace")
759 self.fail(
760 f"{exc}\n\n"
761 f"**Subprocess Output**\n{out}\n\n"
762 f"**Subprocess Error**\n{err}"
763 )
764
765 @requires_venv_with_pip()
766 @requires_resource('cpu')
767 def test_with_pip(self):
768 self.do_test_with_pip(False)
769 self.do_test_with_pip(True)
770
771
772 if __name__ == "__main__":
773 unittest.main()