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