diff options
Diffstat (limited to 'Lib/test/test_venv.py')
| -rw-r--r-- | Lib/test/test_venv.py | 256 |
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() |
