mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
822d42b11a
Use monkeypatch.setenv to mock env vars
495 lines
18 KiB
Python
495 lines
18 KiB
Python
import os
|
|
from contextlib import contextmanager
|
|
from tempfile import NamedTemporaryFile
|
|
|
|
import pytest
|
|
|
|
import pip._internal.configuration
|
|
from pip._internal.cli.main import main
|
|
from pip._internal.commands import create_command
|
|
from pip._internal.exceptions import PipError
|
|
from tests.lib.options_helpers import AddFakeCommandMixin
|
|
|
|
|
|
@contextmanager
|
|
def assert_option_error(capsys, expected):
|
|
"""
|
|
Assert that a SystemExit occurred because of a parsing error.
|
|
|
|
Args:
|
|
expected: an expected substring of stderr.
|
|
"""
|
|
with pytest.raises(SystemExit) as excinfo:
|
|
yield
|
|
|
|
assert excinfo.value.code == 2
|
|
stderr = capsys.readouterr().err
|
|
assert expected in stderr
|
|
|
|
|
|
def assert_is_default_cache_dir(value):
|
|
# This path looks different on different platforms, but the path always
|
|
# has the substring "pip".
|
|
assert 'pip' in value
|
|
|
|
|
|
class TestOptionPrecedence(AddFakeCommandMixin):
|
|
"""
|
|
Tests for confirming our option precedence:
|
|
cli -> environment -> subcommand config -> global config -> option
|
|
defaults
|
|
"""
|
|
|
|
def get_config_section(self, section):
|
|
config = {
|
|
'global': [('timeout', '-3')],
|
|
'fake': [('timeout', '-2')],
|
|
}
|
|
return config[section]
|
|
|
|
def get_config_section_global(self, section):
|
|
config = {
|
|
'global': [('timeout', '-3')],
|
|
'fake': [],
|
|
}
|
|
return config[section]
|
|
|
|
def test_env_override_default_int(self, monkeypatch):
|
|
"""
|
|
Test that environment variable overrides an int option default.
|
|
"""
|
|
monkeypatch.setenv('PIP_TIMEOUT', '-1')
|
|
options, args = main(['fake'])
|
|
assert options.timeout == -1
|
|
|
|
@pytest.mark.parametrize('values', (['F1'], ['F1', 'F2']))
|
|
def test_env_override_default_append(self, values, monkeypatch):
|
|
"""
|
|
Test that environment variable overrides an append option default.
|
|
"""
|
|
monkeypatch.setenv('PIP_FIND_LINKS', ' '.join(values))
|
|
options, args = main(['fake'])
|
|
assert options.find_links == values
|
|
|
|
@pytest.mark.parametrize('choises', (['w'], ['s', 'w']))
|
|
def test_env_override_default_choice(self, choises, monkeypatch):
|
|
"""
|
|
Test that environment variable overrides a choice option default.
|
|
"""
|
|
monkeypatch.setenv('PIP_EXISTS_ACTION', ' '.join(choises))
|
|
options, args = main(['fake'])
|
|
assert options.exists_action == choises
|
|
|
|
@pytest.mark.parametrize('name', ('PIP_LOG_FILE', 'PIP_LOCAL_LOG'))
|
|
def test_env_alias_override_default(self, name, monkeypatch):
|
|
"""
|
|
When an option has multiple long forms, test that the technique of
|
|
using the env variable, "PIP_<long form>" works for all cases.
|
|
(e.g. PIP_LOG_FILE and PIP_LOCAL_LOG should all work)
|
|
"""
|
|
monkeypatch.setenv(name, 'override.log')
|
|
options, args = main(['fake'])
|
|
assert options.log == 'override.log'
|
|
|
|
def test_cli_override_environment(self, monkeypatch):
|
|
"""
|
|
Test the cli overrides and environment variable
|
|
"""
|
|
monkeypatch.setenv('PIP_TIMEOUT', '-1')
|
|
options, args = main(['fake', '--timeout', '-2'])
|
|
assert options.timeout == -2
|
|
|
|
@pytest.mark.parametrize('pip_no_cache_dir', [
|
|
# Enabling --no-cache-dir means no cache directory.
|
|
'1',
|
|
'true',
|
|
'on',
|
|
'yes',
|
|
# For historical / backwards compatibility reasons, we also disable
|
|
# the cache directory if provided a value that translates to 0.
|
|
'0',
|
|
'false',
|
|
'off',
|
|
'no',
|
|
])
|
|
def test_cache_dir__PIP_NO_CACHE_DIR(self, pip_no_cache_dir, monkeypatch):
|
|
"""
|
|
Test setting the PIP_NO_CACHE_DIR environment variable without
|
|
passing any command-line flags.
|
|
"""
|
|
monkeypatch.setenv('PIP_NO_CACHE_DIR', pip_no_cache_dir)
|
|
options, args = main(['fake'])
|
|
assert options.cache_dir is False
|
|
|
|
@pytest.mark.parametrize('pip_no_cache_dir', ['yes', 'no'])
|
|
def test_cache_dir__PIP_NO_CACHE_DIR__with_cache_dir(
|
|
self, pip_no_cache_dir, monkeypatch,
|
|
):
|
|
"""
|
|
Test setting PIP_NO_CACHE_DIR while also passing an explicit
|
|
--cache-dir value.
|
|
"""
|
|
monkeypatch.setenv('PIP_NO_CACHE_DIR', pip_no_cache_dir)
|
|
options, args = main(['--cache-dir', '/cache/dir', 'fake'])
|
|
# The command-line flag takes precedence.
|
|
assert options.cache_dir == '/cache/dir'
|
|
|
|
@pytest.mark.parametrize('pip_no_cache_dir', ['yes', 'no'])
|
|
def test_cache_dir__PIP_NO_CACHE_DIR__with_no_cache_dir(
|
|
self, pip_no_cache_dir, monkeypatch,
|
|
):
|
|
"""
|
|
Test setting PIP_NO_CACHE_DIR while also passing --no-cache-dir.
|
|
"""
|
|
monkeypatch.setenv('PIP_NO_CACHE_DIR', pip_no_cache_dir)
|
|
options, args = main(['--no-cache-dir', 'fake'])
|
|
# The command-line flag should take precedence (which has the same
|
|
# value in this case).
|
|
assert options.cache_dir is False
|
|
|
|
def test_cache_dir__PIP_NO_CACHE_DIR_invalid__with_no_cache_dir(
|
|
self, monkeypatch, capsys,
|
|
):
|
|
"""
|
|
Test setting PIP_NO_CACHE_DIR to an invalid value while also passing
|
|
--no-cache-dir.
|
|
"""
|
|
monkeypatch.setenv('PIP_NO_CACHE_DIR', 'maybe')
|
|
expected_err = "--no-cache-dir error: invalid truth value 'maybe'"
|
|
with assert_option_error(capsys, expected=expected_err):
|
|
main(['--no-cache-dir', 'fake'])
|
|
|
|
|
|
class TestUsePEP517Options(object):
|
|
|
|
"""
|
|
Test options related to using --use-pep517.
|
|
"""
|
|
|
|
def parse_args(self, args):
|
|
# We use DownloadCommand since that is one of the few Command
|
|
# classes with the use_pep517 options.
|
|
command = create_command('download')
|
|
options, args = command.parse_args(args)
|
|
|
|
return options
|
|
|
|
def test_no_option(self):
|
|
"""
|
|
Test passing no option.
|
|
"""
|
|
options = self.parse_args([])
|
|
assert options.use_pep517 is None
|
|
|
|
def test_use_pep517(self):
|
|
"""
|
|
Test passing --use-pep517.
|
|
"""
|
|
options = self.parse_args(['--use-pep517'])
|
|
assert options.use_pep517 is True
|
|
|
|
def test_no_use_pep517(self):
|
|
"""
|
|
Test passing --no-use-pep517.
|
|
"""
|
|
options = self.parse_args(['--no-use-pep517'])
|
|
assert options.use_pep517 is False
|
|
|
|
def test_PIP_USE_PEP517_true(self, monkeypatch):
|
|
"""
|
|
Test setting PIP_USE_PEP517 to "true".
|
|
"""
|
|
monkeypatch.setenv('PIP_USE_PEP517', 'true')
|
|
options = self.parse_args([])
|
|
# This is an int rather than a boolean because strtobool() in pip's
|
|
# configuration code returns an int.
|
|
assert options.use_pep517 == 1
|
|
|
|
def test_PIP_USE_PEP517_false(self, monkeypatch):
|
|
"""
|
|
Test setting PIP_USE_PEP517 to "false".
|
|
"""
|
|
monkeypatch.setenv('PIP_USE_PEP517', 'false')
|
|
options = self.parse_args([])
|
|
# This is an int rather than a boolean because strtobool() in pip's
|
|
# configuration code returns an int.
|
|
assert options.use_pep517 == 0
|
|
|
|
def test_use_pep517_and_PIP_USE_PEP517_false(self, monkeypatch):
|
|
"""
|
|
Test passing --use-pep517 and setting PIP_USE_PEP517 to "false".
|
|
"""
|
|
monkeypatch.setenv('PIP_USE_PEP517', 'false')
|
|
options = self.parse_args(['--use-pep517'])
|
|
assert options.use_pep517 is True
|
|
|
|
def test_no_use_pep517_and_PIP_USE_PEP517_true(self, monkeypatch):
|
|
"""
|
|
Test passing --no-use-pep517 and setting PIP_USE_PEP517 to "true".
|
|
"""
|
|
monkeypatch.setenv('PIP_USE_PEP517', 'true')
|
|
options = self.parse_args(['--no-use-pep517'])
|
|
assert options.use_pep517 is False
|
|
|
|
def test_PIP_NO_USE_PEP517(self, monkeypatch, capsys):
|
|
"""
|
|
Test setting PIP_NO_USE_PEP517, which isn't allowed.
|
|
"""
|
|
monkeypatch.setenv('PIP_NO_USE_PEP517', 'true')
|
|
with assert_option_error(capsys, expected='--no-use-pep517 error'):
|
|
self.parse_args([])
|
|
|
|
|
|
class TestOptionsInterspersed(AddFakeCommandMixin):
|
|
|
|
def test_general_option_after_subcommand(self):
|
|
options, args = main(['fake', '--timeout', '-1'])
|
|
assert options.timeout == -1
|
|
|
|
def test_option_after_subcommand_arg(self):
|
|
options, args = main(['fake', 'arg', '--timeout', '-1'])
|
|
assert options.timeout == -1
|
|
|
|
def test_additive_before_after_subcommand(self):
|
|
options, args = main(['-v', 'fake', '-v'])
|
|
assert options.verbose == 2
|
|
|
|
def test_subcommand_option_before_subcommand_fails(self):
|
|
with pytest.raises(SystemExit):
|
|
main(['--find-links', 'F1', 'fake'])
|
|
|
|
|
|
@contextmanager
|
|
def tmpconfig(option, value, section='global'):
|
|
with NamedTemporaryFile(mode='w', delete=False) as f:
|
|
f.write('[{}]\n{}={}\n'.format(section, option, value))
|
|
name = f.name
|
|
try:
|
|
yield name
|
|
finally:
|
|
os.unlink(name)
|
|
|
|
|
|
class TestCountOptions(AddFakeCommandMixin):
|
|
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', range(4))
|
|
def test_cli_long(self, option, value):
|
|
flags = ['--{}'.format(option)] * value
|
|
opt1, args1 = main(flags+['fake'])
|
|
opt2, args2 = main(['fake']+flags)
|
|
assert getattr(opt1, option) == getattr(opt2, option) == value
|
|
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', range(1, 4))
|
|
def test_cli_short(self, option, value):
|
|
flag = '-' + option[0]*value
|
|
opt1, args1 = main([flag, 'fake'])
|
|
opt2, args2 = main(['fake', flag])
|
|
assert getattr(opt1, option) == getattr(opt2, option) == value
|
|
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', range(4))
|
|
def test_env_var(self, option, value, monkeypatch):
|
|
monkeypatch.setenv('PIP_'+option.upper(), str(value))
|
|
assert getattr(main(['fake'])[0], option) == value
|
|
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', range(3))
|
|
def test_env_var_integrate_cli(self, option, value, monkeypatch):
|
|
monkeypatch.setenv('PIP_'+option.upper(), str(value))
|
|
assert getattr(main(['fake', '--'+option])[0], option) == value + 1
|
|
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', (-1, 'foobar'))
|
|
def test_env_var_invalid(self, option, value, monkeypatch, capsys):
|
|
monkeypatch.setenv('PIP_'+option.upper(), str(value))
|
|
with assert_option_error(capsys, expected='a non-negative integer'):
|
|
main(['fake'])
|
|
|
|
# Undocumented, support for backward compatibility
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', ('no', 'false'))
|
|
def test_env_var_false(self, option, value, monkeypatch):
|
|
monkeypatch.setenv('PIP_'+option.upper(), str(value))
|
|
assert getattr(main(['fake'])[0], option) == 0
|
|
|
|
# Undocumented, support for backward compatibility
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', ('yes', 'true'))
|
|
def test_env_var_true(self, option, value, monkeypatch):
|
|
monkeypatch.setenv('PIP_'+option.upper(), str(value))
|
|
assert getattr(main(['fake'])[0], option) == 1
|
|
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', range(4))
|
|
def test_config_file(self, option, value, monkeypatch):
|
|
with tmpconfig(option, value) as name:
|
|
monkeypatch.setenv('PIP_CONFIG_FILE', name)
|
|
assert getattr(main(['fake'])[0], option) == value
|
|
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', range(3))
|
|
def test_config_file_integrate_cli(self, option, value, monkeypatch):
|
|
with tmpconfig(option, value) as name:
|
|
monkeypatch.setenv('PIP_CONFIG_FILE', name)
|
|
assert getattr(main(['fake', '--'+option])[0], option) == value + 1
|
|
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', (-1, 'foobar'))
|
|
def test_config_file_invalid(self, option, value, monkeypatch, capsys):
|
|
with tmpconfig(option, value) as name:
|
|
monkeypatch.setenv('PIP_CONFIG_FILE', name)
|
|
with assert_option_error(capsys, expected='non-negative integer'):
|
|
main(['fake'])
|
|
|
|
# Undocumented, support for backward compatibility
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', ('no', 'false'))
|
|
def test_config_file_false(self, option, value, monkeypatch):
|
|
with tmpconfig(option, value) as name:
|
|
monkeypatch.setenv('PIP_CONFIG_FILE', name)
|
|
assert getattr(main(['fake'])[0], option) == 0
|
|
|
|
# Undocumented, support for backward compatibility
|
|
@pytest.mark.parametrize('option', ('verbose', 'quiet'))
|
|
@pytest.mark.parametrize('value', ('yes', 'true'))
|
|
def test_config_file_true(self, option, value, monkeypatch):
|
|
with tmpconfig(option, value) as name:
|
|
monkeypatch.setenv('PIP_CONFIG_FILE', name)
|
|
assert getattr(main(['fake'])[0], option) == 1
|
|
|
|
|
|
class TestGeneralOptions(AddFakeCommandMixin):
|
|
|
|
# the reason to specifically test general options is due to the
|
|
# extra processing they receive, and the number of bugs we've had
|
|
|
|
def test_cache_dir__default(self):
|
|
options, args = main(['fake'])
|
|
# With no options the default cache dir should be used.
|
|
assert_is_default_cache_dir(options.cache_dir)
|
|
|
|
def test_cache_dir__provided(self):
|
|
options, args = main(['--cache-dir', '/cache/dir', 'fake'])
|
|
assert options.cache_dir == '/cache/dir'
|
|
|
|
def test_no_cache_dir__provided(self):
|
|
options, args = main(['--no-cache-dir', 'fake'])
|
|
assert options.cache_dir is False
|
|
|
|
def test_require_virtualenv(self):
|
|
options1, args1 = main(['--require-virtualenv', 'fake'])
|
|
options2, args2 = main(['fake', '--require-virtualenv'])
|
|
assert options1.require_venv
|
|
assert options2.require_venv
|
|
|
|
def test_log(self):
|
|
options1, args1 = main(['--log', 'path', 'fake'])
|
|
options2, args2 = main(['fake', '--log', 'path'])
|
|
assert options1.log == options2.log == 'path'
|
|
|
|
def test_local_log(self):
|
|
options1, args1 = main(['--local-log', 'path', 'fake'])
|
|
options2, args2 = main(['fake', '--local-log', 'path'])
|
|
assert options1.log == options2.log == 'path'
|
|
|
|
def test_no_input(self):
|
|
options1, args1 = main(['--no-input', 'fake'])
|
|
options2, args2 = main(['fake', '--no-input'])
|
|
assert options1.no_input
|
|
assert options2.no_input
|
|
|
|
def test_proxy(self):
|
|
options1, args1 = main(['--proxy', 'path', 'fake'])
|
|
options2, args2 = main(['fake', '--proxy', 'path'])
|
|
assert options1.proxy == options2.proxy == 'path'
|
|
|
|
def test_retries(self):
|
|
options1, args1 = main(['--retries', '-1', 'fake'])
|
|
options2, args2 = main(['fake', '--retries', '-1'])
|
|
assert options1.retries == options2.retries == -1
|
|
|
|
def test_timeout(self):
|
|
options1, args1 = main(['--timeout', '-1', 'fake'])
|
|
options2, args2 = main(['fake', '--timeout', '-1'])
|
|
assert options1.timeout == options2.timeout == -1
|
|
|
|
def test_exists_action(self):
|
|
options1, args1 = main(['--exists-action', 'w', 'fake'])
|
|
options2, args2 = main(['fake', '--exists-action', 'w'])
|
|
assert options1.exists_action == options2.exists_action == ['w']
|
|
|
|
def test_cert(self):
|
|
options1, args1 = main(['--cert', 'path', 'fake'])
|
|
options2, args2 = main(['fake', '--cert', 'path'])
|
|
assert options1.cert == options2.cert == 'path'
|
|
|
|
def test_client_cert(self):
|
|
options1, args1 = main(['--client-cert', 'path', 'fake'])
|
|
options2, args2 = main(['fake', '--client-cert', 'path'])
|
|
assert options1.client_cert == options2.client_cert == 'path'
|
|
|
|
|
|
class TestOptionsConfigFiles(object):
|
|
|
|
def test_venv_config_file_found(self, monkeypatch):
|
|
# strict limit on the global config files list
|
|
monkeypatch.setattr(
|
|
pip._internal.utils.appdirs, 'site_config_dirs',
|
|
lambda _: ['/a/place']
|
|
)
|
|
|
|
cp = pip._internal.configuration.Configuration(isolated=False)
|
|
|
|
files = []
|
|
for _, val in cp.iter_config_files():
|
|
files.extend(val)
|
|
|
|
assert len(files) == 4
|
|
|
|
@pytest.mark.parametrize(
|
|
"args, expect",
|
|
(
|
|
([], None),
|
|
(["--global"], "global"),
|
|
(["--site"], "site"),
|
|
(["--user"], "user"),
|
|
(["--global", "--user"], PipError),
|
|
(["--global", "--site"], PipError),
|
|
(["--global", "--site", "--user"], PipError),
|
|
)
|
|
)
|
|
def test_config_file_options(self, monkeypatch, args, expect):
|
|
cmd = create_command('config')
|
|
# Replace a handler with a no-op to avoid side effects
|
|
monkeypatch.setattr(cmd, "get_name", lambda *a: None)
|
|
|
|
options, args = cmd.parser.parse_args(args + ["get", "name"])
|
|
if expect is PipError:
|
|
with pytest.raises(PipError):
|
|
cmd._determine_file(options, need_value=False)
|
|
else:
|
|
assert expect == cmd._determine_file(options, need_value=False)
|
|
|
|
|
|
class TestOptionsExpandUser(AddFakeCommandMixin):
|
|
def test_cache_dir(self):
|
|
options, args = main(['--cache-dir', '~/cache/dir', 'fake'])
|
|
assert options.cache_dir == os.path.expanduser('~/cache/dir')
|
|
|
|
def test_log(self):
|
|
options, args = main(['--log', '~/path', 'fake'])
|
|
assert options.log == os.path.expanduser('~/path')
|
|
|
|
def test_local_log(self):
|
|
options, args = main(['--local-log', '~/path', 'fake'])
|
|
assert options.log == os.path.expanduser('~/path')
|
|
|
|
def test_cert(self):
|
|
options, args = main(['--cert', '~/path', 'fake'])
|
|
assert options.cert == os.path.expanduser('~/path')
|
|
|
|
def test_client_cert(self):
|
|
options, args = main(['--client-cert', '~/path', 'fake'])
|
|
assert options.client_cert == os.path.expanduser('~/path')
|