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 20import shlex 21from test.support import (captured_stdout, captured_stderr, 22 skip_if_broken_multiprocessing_synchronize, verbose, 23 requires_subprocess, is_emscripten, is_wasi, 24 requires_venv_with_pip, TEST_HOME_DIR) 25from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) 26import unittest 27import venv 28from unittest.mock import patch, Mock 29 30try: 31 import ctypes 32except 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(). 37requireVenvCreate = 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 42if is_emscripten or is_wasi: 43 raise unittest.SkipTest("venv is not available on Emscripten/WASI.") 44 45@requires_subprocess() 46def 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 59class BaseTest(unittest.TestCase): 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 99class BasicTest(BaseTest): 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 # gh-124651: test quoted strings 449 @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows') 450 def test_special_chars_bash(self): 451 """ 452 Test that the template strings are quoted properly (bash) 453 """ 454 rmtree(self.env_dir) 455 bash = shutil.which('bash') 456 if bash is None: 457 self.skipTest('bash required for this test') 458 env_name = '"\';&&$e|\'"' 459 env_dir = os.path.join(os.path.realpath(self.env_dir), env_name) 460 builder = venv.EnvBuilder(clear=True) 461 builder.create(env_dir) 462 activate = os.path.join(env_dir, self.bindir, 'activate') 463 test_script = os.path.join(self.env_dir, 'test_special_chars.sh') 464 with open(test_script, "w") as f: 465 f.write(f'source {shlex.quote(activate)}\n' 466 'python -c \'import sys; print(sys.executable)\'\n' 467 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n' 468 'deactivate\n') 469 out, err = check_output([bash, test_script]) 470 lines = out.splitlines() 471 self.assertTrue(env_name.encode() in lines[0]) 472 self.assertEndsWith(lines[1], env_name.encode()) 473 474 # gh-124651: test quoted strings 475 @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows') 476 def test_special_chars_csh(self): 477 """ 478 Test that the template strings are quoted properly (csh) 479 """ 480 rmtree(self.env_dir) 481 csh = shutil.which('tcsh') or shutil.which('csh') 482 if csh is None: 483 self.skipTest('csh required for this test') 484 env_name = '"\';&&$e|\'"' 485 env_dir = os.path.join(os.path.realpath(self.env_dir), env_name) 486 builder = venv.EnvBuilder(clear=True) 487 builder.create(env_dir) 488 activate = os.path.join(env_dir, self.bindir, 'activate.csh') 489 test_script = os.path.join(self.env_dir, 'test_special_chars.csh') 490 with open(test_script, "w") as f: 491 f.write(f'source {shlex.quote(activate)}\n' 492 'python -c \'import sys; print(sys.executable)\'\n' 493 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n' 494 'deactivate\n') 495 out, err = check_output([csh, test_script]) 496 lines = out.splitlines() 497 self.assertTrue(env_name.encode() in lines[0]) 498 self.assertEndsWith(lines[1], env_name.encode()) 499 500 # gh-124651: test quoted strings on Windows 501 @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows') 502 def test_special_chars_windows(self): 503 """ 504 Test that the template strings are quoted properly on Windows 505 """ 506 rmtree(self.env_dir) 507 env_name = "'&&^$e" 508 env_dir = os.path.join(os.path.realpath(self.env_dir), env_name) 509 builder = venv.EnvBuilder(clear=True) 510 builder.create(env_dir) 511 activate = os.path.join(env_dir, self.bindir, 'activate.bat') 512 test_batch = os.path.join(self.env_dir, 'test_special_chars.bat') 513 with open(test_batch, "w") as f: 514 f.write('@echo off\n' 515 f'"{activate}" & ' 516 f'{self.exe} -c "import sys; print(sys.executable)" & ' 517 f'{self.exe} -c "import os; print(os.environ[\'VIRTUAL_ENV\'])" & ' 518 'deactivate') 519 out, err = check_output([test_batch]) 520 lines = out.splitlines() 521 self.assertTrue(env_name.encode() in lines[0]) 522 self.assertEndsWith(lines[1], env_name.encode()) 523 524 @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows') 525 def test_unicode_in_batch_file(self): 526 """ 527 Test handling of Unicode paths 528 """ 529 rmtree(self.env_dir) 530 env_dir = os.path.join(os.path.realpath(self.env_dir), 'ϼўТλФЙ') 531 builder = venv.EnvBuilder(clear=True) 532 builder.create(env_dir) 533 activate = os.path.join(env_dir, self.bindir, 'activate.bat') 534 envpy = os.path.join(env_dir, self.bindir, self.exe) 535 out, err = check_output( 536 [activate, '&', self.exe, '-c', 'print(0)'], 537 encoding='oem', 538 ) 539 self.assertEqual(out.strip(), '0') 540 541 @requireVenvCreate 542 def test_multiprocessing(self): 543 """ 544 Test that the multiprocessing is able to spawn. 545 """ 546 # bpo-36342: Instantiation of a Pool object imports the 547 # multiprocessing.synchronize module. Skip the test if this module 548 # cannot be imported. 549 skip_if_broken_multiprocessing_synchronize() 550 551 rmtree(self.env_dir) 552 self.run_with_capture(venv.create, self.env_dir) 553 envpy = os.path.join(os.path.realpath(self.env_dir), 554 self.bindir, self.exe) 555 out, err = check_output([envpy, '-c', 556 'from multiprocessing import Pool; ' 557 'pool = Pool(1); ' 558 'print(pool.apply_async("Python".lower).get(3)); ' 559 'pool.terminate()']) 560 self.assertEqual(out.strip(), "python".encode()) 561 562 @requireVenvCreate 563 def test_multiprocessing_recursion(self): 564 """ 565 Test that the multiprocessing is able to spawn itself 566 """ 567 skip_if_broken_multiprocessing_synchronize() 568 569 rmtree(self.env_dir) 570 self.run_with_capture(venv.create, self.env_dir) 571 envpy = os.path.join(os.path.realpath(self.env_dir), 572 self.bindir, self.exe) 573 script = os.path.join(TEST_HOME_DIR, '_test_venv_multiprocessing.py') 574 subprocess.check_call([envpy, script]) 575 576 @unittest.skipIf(os.name == 'nt', 'not relevant on Windows') 577 def test_deactivate_with_strict_bash_opts(self): 578 bash = shutil.which("bash") 579 if bash is None: 580 self.skipTest("bash required for this test") 581 rmtree(self.env_dir) 582 builder = venv.EnvBuilder(clear=True) 583 builder.create(self.env_dir) 584 activate = os.path.join(self.env_dir, self.bindir, "activate") 585 test_script = os.path.join(self.env_dir, "test_strict.sh") 586 with open(test_script, "w") as f: 587 f.write("set -euo pipefail\n" 588 f"source {activate}\n" 589 "deactivate\n") 590 out, err = check_output([bash, test_script]) 591 self.assertEqual(out, "".encode()) 592 self.assertEqual(err, "".encode()) 593 594 595 @unittest.skipUnless(sys.platform == 'darwin', 'only relevant on macOS') 596 def test_macos_env(self): 597 rmtree(self.env_dir) 598 builder = venv.EnvBuilder() 599 builder.create(self.env_dir) 600 601 envpy = os.path.join(os.path.realpath(self.env_dir), 602 self.bindir, self.exe) 603 out, err = check_output([envpy, '-c', 604 'import os; print("__PYVENV_LAUNCHER__" in os.environ)']) 605 self.assertEqual(out.strip(), 'False'.encode()) 606 607 def test_pathsep_error(self): 608 """ 609 Test that venv creation fails when the target directory contains 610 the path separator. 611 """ 612 rmtree(self.env_dir) 613 bad_itempath = self.env_dir + os.pathsep 614 self.assertRaises(ValueError, venv.create, bad_itempath) 615 self.assertRaises(ValueError, venv.create, pathlib.Path(bad_itempath)) 616 617 @unittest.skipIf(os.name == 'nt', 'not relevant on Windows') 618 @requireVenvCreate 619 def test_zippath_from_non_installed_posix(self): 620 """ 621 Test that when create venv from non-installed python, the zip path 622 value is as expected. 623 """ 624 rmtree(self.env_dir) 625 # First try to create a non-installed python. It's not a real full 626 # functional non-installed python, but enough for this test. 627 platlibdir = sys.platlibdir 628 non_installed_dir = os.path.realpath(tempfile.mkdtemp()) 629 self.addCleanup(rmtree, non_installed_dir) 630 bindir = os.path.join(non_installed_dir, self.bindir) 631 os.mkdir(bindir) 632 shutil.copy2(sys.executable, bindir) 633 libdir = os.path.join(non_installed_dir, platlibdir, self.lib[1]) 634 os.makedirs(libdir) 635 landmark = os.path.join(libdir, "os.py") 636 stdlib_zip = "python%d%d.zip" % sys.version_info[:2] 637 zip_landmark = os.path.join(non_installed_dir, 638 platlibdir, 639 stdlib_zip) 640 additional_pythonpath_for_non_installed = [] 641 # Copy stdlib files to the non-installed python so venv can 642 # correctly calculate the prefix. 643 for eachpath in sys.path: 644 if eachpath.endswith(".zip"): 645 if os.path.isfile(eachpath): 646 shutil.copyfile( 647 eachpath, 648 os.path.join(non_installed_dir, platlibdir)) 649 elif os.path.isfile(os.path.join(eachpath, "os.py")): 650 for name in os.listdir(eachpath): 651 if name == "site-packages": 652 continue 653 fn = os.path.join(eachpath, name) 654 if os.path.isfile(fn): 655 shutil.copy(fn, libdir) 656 elif os.path.isdir(fn): 657 shutil.copytree(fn, os.path.join(libdir, name)) 658 else: 659 additional_pythonpath_for_non_installed.append( 660 eachpath) 661 cmd = [os.path.join(non_installed_dir, self.bindir, self.exe), 662 "-m", 663 "venv", 664 "--without-pip", 665 self.env_dir] 666 # Our fake non-installed python is not fully functional because 667 # it cannot find the extensions. Set PYTHONPATH so it can run the 668 # venv module correctly. 669 pythonpath = os.pathsep.join( 670 additional_pythonpath_for_non_installed) 671 # For python built with shared enabled. We need to set 672 # LD_LIBRARY_PATH so the non-installed python can find and link 673 # libpython.so 674 ld_library_path = sysconfig.get_config_var("LIBDIR") 675 if not ld_library_path or sysconfig.is_python_build(): 676 ld_library_path = os.path.abspath(os.path.dirname(sys.executable)) 677 if sys.platform == 'darwin': 678 ld_library_path_env = "DYLD_LIBRARY_PATH" 679 else: 680 ld_library_path_env = "LD_LIBRARY_PATH" 681 subprocess.check_call(cmd, 682 env={"PYTHONPATH": pythonpath, 683 ld_library_path_env: ld_library_path}) 684 envpy = os.path.join(self.env_dir, self.bindir, self.exe) 685 # Now check the venv created from the non-installed python has 686 # correct zip path in pythonpath. 687 cmd = [envpy, '-S', '-c', 'import sys; print(sys.path)'] 688 out, err = check_output(cmd) 689 self.assertTrue(zip_landmark.encode() in out) 690 691@requireVenvCreate 692class EnsurePipTest(BaseTest): 693 """Test venv module installation of pip.""" 694 def assert_pip_not_installed(self): 695 envpy = os.path.join(os.path.realpath(self.env_dir), 696 self.bindir, self.exe) 697 out, err = check_output([envpy, '-c', 698 'try:\n import pip\nexcept ImportError:\n print("OK")']) 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 self.assertEqual(out.strip(), "OK") 705 706 707 def test_no_pip_by_default(self): 708 rmtree(self.env_dir) 709 self.run_with_capture(venv.create, self.env_dir) 710 self.assert_pip_not_installed() 711 712 def test_explicit_no_pip(self): 713 rmtree(self.env_dir) 714 self.run_with_capture(venv.create, self.env_dir, with_pip=False) 715 self.assert_pip_not_installed() 716 717 def test_devnull(self): 718 # Fix for issue #20053 uses os.devnull to force a config file to 719 # appear empty. However http://bugs.python.org/issue20541 means 720 # that doesn't currently work properly on Windows. Once that is 721 # fixed, the "win_location" part of test_with_pip should be restored 722 with open(os.devnull, "rb") as f: 723 self.assertEqual(f.read(), b"") 724 725 self.assertTrue(os.path.exists(os.devnull)) 726 727 def do_test_with_pip(self, system_site_packages): 728 rmtree(self.env_dir) 729 with EnvironmentVarGuard() as envvars: 730 # pip's cross-version compatibility may trigger deprecation 731 # warnings in current versions of Python. Ensure related 732 # environment settings don't cause venv to fail. 733 envvars["PYTHONWARNINGS"] = "ignore" 734 # ensurepip is different enough from a normal pip invocation 735 # that we want to ensure it ignores the normal pip environment 736 # variable settings. We set PIP_NO_INSTALL here specifically 737 # to check that ensurepip (and hence venv) ignores it. 738 # See http://bugs.python.org/issue19734 739 envvars["PIP_NO_INSTALL"] = "1" 740 # Also check that we ignore the pip configuration file 741 # See http://bugs.python.org/issue20053 742 with tempfile.TemporaryDirectory() as home_dir: 743 envvars["HOME"] = home_dir 744 bad_config = "[global]\nno-install=1" 745 # Write to both config file names on all platforms to reduce 746 # cross-platform variation in test code behaviour 747 win_location = ("pip", "pip.ini") 748 posix_location = (".pip", "pip.conf") 749 # Skips win_location due to http://bugs.python.org/issue20541 750 for dirname, fname in (posix_location,): 751 dirpath = os.path.join(home_dir, dirname) 752 os.mkdir(dirpath) 753 fpath = os.path.join(dirpath, fname) 754 with open(fpath, 'w') as f: 755 f.write(bad_config) 756 757 # Actually run the create command with all that unhelpful 758 # config in place to ensure we ignore it 759 with self.nicer_error(): 760 self.run_with_capture(venv.create, self.env_dir, 761 system_site_packages=system_site_packages, 762 with_pip=True) 763 # Ensure pip is available in the virtual environment 764 envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe) 765 # Ignore DeprecationWarning since pip code is not part of Python 766 out, err = check_output([envpy, '-W', 'ignore::DeprecationWarning', 767 '-W', 'ignore::ImportWarning', '-I', 768 '-m', 'pip', '--version']) 769 # We force everything to text, so unittest gives the detailed diff 770 # if we get unexpected results 771 err = err.decode("latin-1") # Force to text, prevent decoding errors 772 self.assertEqual(err, "") 773 out = out.decode("latin-1") # Force to text, prevent decoding errors 774 expected_version = "pip {}".format(ensurepip.version()) 775 self.assertEqual(out[:len(expected_version)], expected_version) 776 env_dir = os.fsencode(self.env_dir).decode("latin-1") 777 self.assertIn(env_dir, out) 778 779 # http://bugs.python.org/issue19728 780 # Check the private uninstall command provided for the Windows 781 # installers works (at least in a virtual environment) 782 with EnvironmentVarGuard() as envvars: 783 with self.nicer_error(): 784 # It seems ensurepip._uninstall calls subprocesses which do not 785 # inherit the interpreter settings. 786 envvars["PYTHONWARNINGS"] = "ignore" 787 out, err = check_output([envpy, 788 '-W', 'ignore::DeprecationWarning', 789 '-W', 'ignore::ImportWarning', '-I', 790 '-m', 'ensurepip._uninstall']) 791 # We force everything to text, so unittest gives the detailed diff 792 # if we get unexpected results 793 err = err.decode("latin-1") # Force to text, prevent decoding errors 794 # Ignore the warning: 795 # "The directory '$HOME/.cache/pip/http' or its parent directory 796 # is not owned by the current user and the cache has been disabled. 797 # Please check the permissions and owner of that directory. If 798 # executing pip with sudo, you may want sudo's -H flag." 799 # where $HOME is replaced by the HOME environment variable. 800 err = re.sub("^(WARNING: )?The directory .* or its parent directory " 801 "is not owned or is not writable by the current user.*$", "", 802 err, flags=re.MULTILINE) 803 self.assertEqual(err.rstrip(), "") 804 # Being fairly specific regarding the expected behaviour for the 805 # initial bundling phase in Python 3.4. If the output changes in 806 # future pip versions, this test can likely be relaxed further. 807 out = out.decode("latin-1") # Force to text, prevent decoding errors 808 self.assertIn("Successfully uninstalled pip", out) 809 self.assertIn("Successfully uninstalled setuptools", out) 810 # Check pip is now gone from the virtual environment. This only 811 # applies in the system_site_packages=False case, because in the 812 # other case, pip may still be available in the system site-packages 813 if not system_site_packages: 814 self.assert_pip_not_installed() 815 816 @contextlib.contextmanager 817 def nicer_error(self): 818 """ 819 Capture output from a failed subprocess for easier debugging. 820 821 The output this handler produces can be a little hard to read, 822 but at least it has all the details. 823 """ 824 try: 825 yield 826 except subprocess.CalledProcessError as exc: 827 out = (exc.output or b'').decode(errors="replace") 828 err = (exc.stderr or b'').decode(errors="replace") 829 self.fail( 830 f"{exc}\n\n" 831 f"**Subprocess Output**\n{out}\n\n" 832 f"**Subprocess Error**\n{err}" 833 ) 834 835 @requires_venv_with_pip() 836 def test_with_pip(self): 837 self.do_test_with_pip(False) 838 self.do_test_with_pip(True) 839 840 841if __name__ == "__main__": 842 unittest.main() 843