python (3.12.0)
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,
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)
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 ]
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 child_env = {
605 "PYTHONPATH": pythonpath,
606 ld_library_path_env: ld_library_path,
607 }
608 if asan_options := os.environ.get("ASAN_OPTIONS"):
609 # prevent https://github.com/python/cpython/issues/104839
610 child_env["ASAN_OPTIONS"] = asan_options
611 subprocess.check_call(cmd, env=child_env)
612 envpy = os.path.join(self.env_dir, self.bindir, self.exe)
613 # Now check the venv created from the non-installed python has
614 # correct zip path in pythonpath.
615 cmd = [envpy, '-S', '-c', 'import sys; print(sys.path)']
616 out, err = check_output(cmd)
617 self.assertTrue(zip_landmark.encode() in out)
618
619 def test_activate_shell_script_has_no_dos_newlines(self):
620 """
621 Test that the `activate` shell script contains no CR LF.
622 This is relevant for Cygwin, as the Windows build might have
623 converted line endings accidentally.
624 """
625 venv_dir = pathlib.Path(self.env_dir)
626 rmtree(venv_dir)
627 [[scripts_dir], *_] = self.ENV_SUBDIRS
628 script_path = venv_dir / scripts_dir / "activate"
629 venv.create(venv_dir)
630 with open(script_path, 'rb') as script:
631 for i, line in enumerate(script, 1):
632 error_message = f"CR LF found in line {i}"
633 self.assertFalse(line.endswith(b'\r\n'), error_message)
634
635 @requireVenvCreate
636 class ESC[4;38;5;81mEnsurePipTest(ESC[4;38;5;149mBaseTest):
637 """Test venv module installation of pip."""
638 def assert_pip_not_installed(self):
639 envpy = os.path.join(os.path.realpath(self.env_dir),
640 self.bindir, self.exe)
641 out, err = check_output([envpy, '-c',
642 'try:\n import pip\nexcept ImportError:\n print("OK")'])
643 # We force everything to text, so unittest gives the detailed diff
644 # if we get unexpected results
645 err = err.decode("latin-1") # Force to text, prevent decoding errors
646 self.assertEqual(err, "")
647 out = out.decode("latin-1") # Force to text, prevent decoding errors
648 self.assertEqual(out.strip(), "OK")
649
650
651 def test_no_pip_by_default(self):
652 rmtree(self.env_dir)
653 self.run_with_capture(venv.create, self.env_dir)
654 self.assert_pip_not_installed()
655
656 def test_explicit_no_pip(self):
657 rmtree(self.env_dir)
658 self.run_with_capture(venv.create, self.env_dir, with_pip=False)
659 self.assert_pip_not_installed()
660
661 def test_devnull(self):
662 # Fix for issue #20053 uses os.devnull to force a config file to
663 # appear empty. However http://bugs.python.org/issue20541 means
664 # that doesn't currently work properly on Windows. Once that is
665 # fixed, the "win_location" part of test_with_pip should be restored
666 with open(os.devnull, "rb") as f:
667 self.assertEqual(f.read(), b"")
668
669 self.assertTrue(os.path.exists(os.devnull))
670
671 def do_test_with_pip(self, system_site_packages):
672 rmtree(self.env_dir)
673 with EnvironmentVarGuard() as envvars:
674 # pip's cross-version compatibility may trigger deprecation
675 # warnings in current versions of Python. Ensure related
676 # environment settings don't cause venv to fail.
677 envvars["PYTHONWARNINGS"] = "ignore"
678 # ensurepip is different enough from a normal pip invocation
679 # that we want to ensure it ignores the normal pip environment
680 # variable settings. We set PIP_NO_INSTALL here specifically
681 # to check that ensurepip (and hence venv) ignores it.
682 # See http://bugs.python.org/issue19734
683 envvars["PIP_NO_INSTALL"] = "1"
684 # Also check that we ignore the pip configuration file
685 # See http://bugs.python.org/issue20053
686 with tempfile.TemporaryDirectory() as home_dir:
687 envvars["HOME"] = home_dir
688 bad_config = "[global]\nno-install=1"
689 # Write to both config file names on all platforms to reduce
690 # cross-platform variation in test code behaviour
691 win_location = ("pip", "pip.ini")
692 posix_location = (".pip", "pip.conf")
693 # Skips win_location due to http://bugs.python.org/issue20541
694 for dirname, fname in (posix_location,):
695 dirpath = os.path.join(home_dir, dirname)
696 os.mkdir(dirpath)
697 fpath = os.path.join(dirpath, fname)
698 with open(fpath, 'w') as f:
699 f.write(bad_config)
700
701 # Actually run the create command with all that unhelpful
702 # config in place to ensure we ignore it
703 with self.nicer_error():
704 self.run_with_capture(venv.create, self.env_dir,
705 system_site_packages=system_site_packages,
706 with_pip=True)
707 # Ensure pip is available in the virtual environment
708 envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
709 # Ignore DeprecationWarning since pip code is not part of Python
710 out, err = check_output([envpy, '-W', 'ignore::DeprecationWarning',
711 '-W', 'ignore::ImportWarning', '-I',
712 '-m', 'pip', '--version'])
713 # We force everything to text, so unittest gives the detailed diff
714 # if we get unexpected results
715 err = err.decode("latin-1") # Force to text, prevent decoding errors
716 self.assertEqual(err, "")
717 out = out.decode("latin-1") # Force to text, prevent decoding errors
718 expected_version = "pip {}".format(ensurepip.version())
719 self.assertEqual(out[:len(expected_version)], expected_version)
720 env_dir = os.fsencode(self.env_dir).decode("latin-1")
721 self.assertIn(env_dir, out)
722
723 # http://bugs.python.org/issue19728
724 # Check the private uninstall command provided for the Windows
725 # installers works (at least in a virtual environment)
726 with EnvironmentVarGuard() as envvars:
727 with self.nicer_error():
728 # It seems ensurepip._uninstall calls subprocesses which do not
729 # inherit the interpreter settings.
730 envvars["PYTHONWARNINGS"] = "ignore"
731 out, err = check_output([envpy,
732 '-W', 'ignore::DeprecationWarning',
733 '-W', 'ignore::ImportWarning', '-I',
734 '-m', 'ensurepip._uninstall'])
735 # We force everything to text, so unittest gives the detailed diff
736 # if we get unexpected results
737 err = err.decode("latin-1") # Force to text, prevent decoding errors
738 # Ignore the warning:
739 # "The directory '$HOME/.cache/pip/http' or its parent directory
740 # is not owned by the current user and the cache has been disabled.
741 # Please check the permissions and owner of that directory. If
742 # executing pip with sudo, you may want sudo's -H flag."
743 # where $HOME is replaced by the HOME environment variable.
744 err = re.sub("^(WARNING: )?The directory .* or its parent directory "
745 "is not owned or is not writable by the current user.*$", "",
746 err, flags=re.MULTILINE)
747 self.assertEqual(err.rstrip(), "")
748 # Being fairly specific regarding the expected behaviour for the
749 # initial bundling phase in Python 3.4. If the output changes in
750 # future pip versions, this test can likely be relaxed further.
751 out = out.decode("latin-1") # Force to text, prevent decoding errors
752 self.assertIn("Successfully uninstalled pip", out)
753 # Check pip is now gone from the virtual environment. This only
754 # applies in the system_site_packages=False case, because in the
755 # other case, pip may still be available in the system site-packages
756 if not system_site_packages:
757 self.assert_pip_not_installed()
758
759 @contextlib.contextmanager
760 def nicer_error(self):
761 """
762 Capture output from a failed subprocess for easier debugging.
763
764 The output this handler produces can be a little hard to read,
765 but at least it has all the details.
766 """
767 try:
768 yield
769 except subprocess.CalledProcessError as exc:
770 out = (exc.output or b'').decode(errors="replace")
771 err = (exc.stderr or b'').decode(errors="replace")
772 self.fail(
773 f"{exc}\n\n"
774 f"**Subprocess Output**\n{out}\n\n"
775 f"**Subprocess Error**\n{err}"
776 )
777
778 @requires_venv_with_pip()
779 @requires_resource('cpu')
780 def test_with_pip(self):
781 self.do_test_with_pip(False)
782 self.do_test_with_pip(True)
783
784
785 if __name__ == "__main__":
786 unittest.main()