summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_venv.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_venv.py')
-rw-r--r--Lib/test/test_venv.py256
1 files changed, 231 insertions, 25 deletions
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index 0fae88b..b462588 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -5,17 +5,36 @@ Copyright (C) 2011-2012 Vinay Sajip.
Licensed to the PSF under a contributor agreement.
"""
+import ensurepip
import os
import os.path
-import shutil
+import struct
import subprocess
import sys
import tempfile
from test.support import (captured_stdout, captured_stderr, run_unittest,
- can_symlink)
+ can_symlink, EnvironmentVarGuard, rmtree)
+import textwrap
import unittest
import venv
+# pip currently requires ssl support, so we ensure we handle
+# it being missing (http://bugs.python.org/issue19744)
+try:
+ import ssl
+except ImportError:
+ ssl = None
+
+skipInVenv = unittest.skipIf(sys.prefix != sys.base_prefix,
+ 'Test not appropriate in a venv')
+
+# os.path.exists('nul') is False: http://bugs.python.org/issue20541
+if os.devnull.lower() == 'nul':
+ failsOnWindows = unittest.expectedFailure
+else:
+ def failsOnWindows(f):
+ return f
+
class BaseTest(unittest.TestCase):
"""Base class for venv tests."""
@@ -36,7 +55,7 @@ class BaseTest(unittest.TestCase):
self.exe = os.path.split(executable)[-1]
def tearDown(self):
- shutil.rmtree(self.env_dir)
+ rmtree(self.env_dir)
def run_with_capture(self, func, *args, **kwargs):
with captured_stdout() as output:
@@ -63,11 +82,19 @@ class BasicTest(BaseTest):
"""
Test the create function with default arguments.
"""
- shutil.rmtree(self.env_dir)
+ rmtree(self.env_dir)
self.run_with_capture(venv.create, self.env_dir)
self.isdir(self.bindir)
self.isdir(self.include)
self.isdir(*self.lib)
+ # Issue 21197
+ p = self.get_env_file('lib64')
+ conditions = ((struct.calcsize('P') == 8) and (os.name == 'posix') and
+ (sys.platform != 'darwin'))
+ if conditions:
+ self.assertTrue(os.path.islink(p))
+ else:
+ self.assertFalse(os.path.exists(p))
data = self.get_text_file_contents('pyvenv.cfg')
if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__'
in os.environ):
@@ -83,8 +110,7 @@ class BasicTest(BaseTest):
print(' %r' % os.listdir(bd))
self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)
- @unittest.skipIf(sys.prefix != sys.base_prefix, 'Test not appropriate '
- 'in a venv')
+ @skipInVenv
def test_prefixes(self):
"""
Test that the prefix values are as expected.
@@ -94,7 +120,7 @@ class BasicTest(BaseTest):
self.assertEqual(sys.base_exec_prefix, sys.exec_prefix)
# check a venv's prefixes
- shutil.rmtree(self.env_dir)
+ rmtree(self.env_dir)
self.run_with_capture(venv.create, self.env_dir)
envpy = os.path.join(self.env_dir, self.bindir, self.exe)
cmd = [envpy, '-c', None]
@@ -109,29 +135,89 @@ class BasicTest(BaseTest):
out, err = p.communicate()
self.assertEqual(out.strip(), expected.encode())
+ if sys.platform == 'win32':
+ ENV_SUBDIRS = (
+ ('Scripts',),
+ ('Include',),
+ ('Lib',),
+ ('Lib', 'site-packages'),
+ )
+ else:
+ ENV_SUBDIRS = (
+ ('bin',),
+ ('include',),
+ ('lib',),
+ ('lib', 'python%d.%d' % sys.version_info[:2]),
+ ('lib', 'python%d.%d' % sys.version_info[:2], 'site-packages'),
+ )
+
+ def create_contents(self, paths, filename):
+ """
+ Create some files in the environment which are unrelated
+ to the virtual environment.
+ """
+ for subdirs in paths:
+ d = os.path.join(self.env_dir, *subdirs)
+ os.mkdir(d)
+ fn = os.path.join(d, filename)
+ with open(fn, 'wb') as f:
+ f.write(b'Still here?')
+
def test_overwrite_existing(self):
"""
- Test control of overwriting an existing environment directory.
+ Test creating environment in an existing directory.
"""
- self.assertRaises(ValueError, venv.create, self.env_dir)
+ self.create_contents(self.ENV_SUBDIRS, 'foo')
+ venv.create(self.env_dir)
+ for subdirs in self.ENV_SUBDIRS:
+ fn = os.path.join(self.env_dir, *(subdirs + ('foo',)))
+ self.assertTrue(os.path.exists(fn))
+ with open(fn, 'rb') as f:
+ self.assertEqual(f.read(), b'Still here?')
+
builder = venv.EnvBuilder(clear=True)
builder.create(self.env_dir)
+ for subdirs in self.ENV_SUBDIRS:
+ fn = os.path.join(self.env_dir, *(subdirs + ('foo',)))
+ self.assertFalse(os.path.exists(fn))
+
+ def clear_directory(self, path):
+ for fn in os.listdir(path):
+ fn = os.path.join(path, fn)
+ if os.path.islink(fn) or os.path.isfile(fn):
+ os.remove(fn)
+ elif os.path.isdir(fn):
+ rmtree(fn)
+
+ def test_unoverwritable_fails(self):
+ #create a file clashing with directories in the env dir
+ for paths in self.ENV_SUBDIRS[:3]:
+ fn = os.path.join(self.env_dir, *paths)
+ with open(fn, 'wb') as f:
+ f.write(b'')
+ self.assertRaises((ValueError, OSError), venv.create, self.env_dir)
+ self.clear_directory(self.env_dir)
def test_upgrade(self):
"""
Test upgrading an existing environment directory.
"""
- builder = venv.EnvBuilder(upgrade=True)
- self.run_with_capture(builder.create, self.env_dir)
- self.isdir(self.bindir)
- self.isdir(self.include)
- self.isdir(*self.lib)
- fn = self.get_env_file(self.bindir, self.exe)
- if not os.path.exists(fn): # diagnostics for Windows buildbot failures
- bd = self.get_env_file(self.bindir)
- print('Contents of %r:' % bd)
- print(' %r' % os.listdir(bd))
- self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)
+ # See Issue #21643: the loop needs to run twice to ensure
+ # that everything works on the upgrade (the first run just creates
+ # the venv).
+ for upgrade in (False, True):
+ builder = venv.EnvBuilder(upgrade=upgrade)
+ self.run_with_capture(builder.create, self.env_dir)
+ self.isdir(self.bindir)
+ self.isdir(self.include)
+ self.isdir(*self.lib)
+ fn = self.get_env_file(self.bindir, self.exe)
+ if not os.path.exists(fn):
+ # diagnostics for Windows buildbot failures
+ bd = self.get_env_file(self.bindir)
+ print('Contents of %r:' % bd)
+ print(' %r' % os.listdir(bd))
+ self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)
def test_isolation(self):
"""
@@ -162,13 +248,12 @@ class BasicTest(BaseTest):
# run the test, the pyvenv.cfg in the venv created in the test will
# point to the venv being used to run the test, and we lose the link
# to the source build - so Python can't initialise properly.
- @unittest.skipIf(sys.prefix != sys.base_prefix, 'Test not appropriate '
- 'in a venv')
+ @skipInVenv
def test_executable(self):
"""
Test that the sys.executable value is as expected.
"""
- shutil.rmtree(self.env_dir)
+ rmtree(self.env_dir)
self.run_with_capture(venv.create, self.env_dir)
envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
cmd = [envpy, '-c', 'import sys; print(sys.executable)']
@@ -182,7 +267,7 @@ class BasicTest(BaseTest):
"""
Test that the sys.executable value is as expected.
"""
- shutil.rmtree(self.env_dir)
+ rmtree(self.env_dir)
builder = venv.EnvBuilder(clear=True, symlinks=True)
builder.create(self.env_dir)
envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
@@ -192,8 +277,129 @@ class BasicTest(BaseTest):
out, err = p.communicate()
self.assertEqual(out.strip(), envpy.encode())
+
+@skipInVenv
+class EnsurePipTest(BaseTest):
+ """Test venv module installation of pip."""
+ def assert_pip_not_installed(self):
+ envpy = os.path.join(os.path.realpath(self.env_dir),
+ self.bindir, self.exe)
+ try_import = 'try:\n import pip\nexcept ImportError:\n print("OK")'
+ cmd = [envpy, '-c', try_import]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ # We force everything to text, so unittest gives the detailed diff
+ # if we get unexpected results
+ err = err.decode("latin-1") # Force to text, prevent decoding errors
+ self.assertEqual(err, "")
+ out = out.decode("latin-1") # Force to text, prevent decoding errors
+ self.assertEqual(out.strip(), "OK")
+
+
+ def test_no_pip_by_default(self):
+ rmtree(self.env_dir)
+ self.run_with_capture(venv.create, self.env_dir)
+ self.assert_pip_not_installed()
+
+ def test_explicit_no_pip(self):
+ rmtree(self.env_dir)
+ self.run_with_capture(venv.create, self.env_dir, with_pip=False)
+ self.assert_pip_not_installed()
+
+ @failsOnWindows
+ def test_devnull_exists_and_is_empty(self):
+ # Fix for issue #20053 uses os.devnull to force a config file to
+ # appear empty. However http://bugs.python.org/issue20541 means
+ # that doesn't currently work properly on Windows. Once that is
+ # fixed, the "win_location" part of test_with_pip should be restored
+ self.assertTrue(os.path.exists(os.devnull))
+ with open(os.devnull, "rb") as f:
+ self.assertEqual(f.read(), b"")
+
+ # Requesting pip fails without SSL (http://bugs.python.org/issue19744)
+ @unittest.skipIf(ssl is None, ensurepip._MISSING_SSL_MESSAGE)
+ def test_with_pip(self):
+ rmtree(self.env_dir)
+ with EnvironmentVarGuard() as envvars:
+ # pip's cross-version compatibility may trigger deprecation
+ # warnings in current versions of Python. Ensure related
+ # environment settings don't cause venv to fail.
+ envvars["PYTHONWARNINGS"] = "e"
+ # ensurepip is different enough from a normal pip invocation
+ # that we want to ensure it ignores the normal pip environment
+ # variable settings. We set PIP_NO_INSTALL here specifically
+ # to check that ensurepip (and hence venv) ignores it.
+ # See http://bugs.python.org/issue19734
+ envvars["PIP_NO_INSTALL"] = "1"
+ # Also check that we ignore the pip configuration file
+ # See http://bugs.python.org/issue20053
+ with tempfile.TemporaryDirectory() as home_dir:
+ envvars["HOME"] = home_dir
+ bad_config = "[global]\nno-install=1"
+ # Write to both config file names on all platforms to reduce
+ # cross-platform variation in test code behaviour
+ win_location = ("pip", "pip.ini")
+ posix_location = (".pip", "pip.conf")
+ # Skips win_location due to http://bugs.python.org/issue20541
+ for dirname, fname in (posix_location,):
+ dirpath = os.path.join(home_dir, dirname)
+ os.mkdir(dirpath)
+ fpath = os.path.join(dirpath, fname)
+ with open(fpath, 'w') as f:
+ f.write(bad_config)
+
+ # Actually run the create command with all that unhelpful
+ # config in place to ensure we ignore it
+ try:
+ self.run_with_capture(venv.create, self.env_dir,
+ with_pip=True)
+ except subprocess.CalledProcessError as exc:
+ # The output this produces can be a little hard to read,
+ # but at least it has all the details
+ details = exc.output.decode(errors="replace")
+ msg = "{}\n\n**Subprocess Output**\n{}"
+ self.fail(msg.format(exc, details))
+ # Ensure pip is available in the virtual environment
+ envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
+ cmd = [envpy, '-Im', 'pip', '--version']
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ # We force everything to text, so unittest gives the detailed diff
+ # if we get unexpected results
+ err = err.decode("latin-1") # Force to text, prevent decoding errors
+ self.assertEqual(err, "")
+ out = out.decode("latin-1") # Force to text, prevent decoding errors
+ expected_version = "pip {}".format(ensurepip.version())
+ self.assertEqual(out[:len(expected_version)], expected_version)
+ env_dir = os.fsencode(self.env_dir).decode("latin-1")
+ self.assertIn(env_dir, out)
+
+ # http://bugs.python.org/issue19728
+ # Check the private uninstall command provided for the Windows
+ # installers works (at least in a virtual environment)
+ cmd = [envpy, '-Im', 'ensurepip._uninstall']
+ with EnvironmentVarGuard() as envvars:
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ # We force everything to text, so unittest gives the detailed diff
+ # if we get unexpected results
+ err = err.decode("latin-1") # Force to text, prevent decoding errors
+ self.assertEqual(err, "")
+ # Being fairly specific regarding the expected behaviour for the
+ # initial bundling phase in Python 3.4. If the output changes in
+ # future pip versions, this test can likely be relaxed further.
+ out = out.decode("latin-1") # Force to text, prevent decoding errors
+ self.assertIn("Successfully uninstalled pip", out)
+ self.assertIn("Successfully uninstalled setuptools", out)
+ # Check pip is now gone from the virtual environment
+ self.assert_pip_not_installed()
+
+
def test_main():
- run_unittest(BasicTest)
+ run_unittest(BasicTest, EnsurePipTest)
if __name__ == "__main__":
test_main()