diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index ec557ad4..4a4bd814 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -1,11 +1,6 @@ import os import sys -from click import ( - Choice, argument, echo, edit, group, option, pass_context, secho, types, - version_option -) - from pipenv.__version__ import __version__ from pipenv._compat import fix_utf8 from pipenv.cli.options import ( @@ -18,6 +13,10 @@ from pipenv.exceptions import PipenvOptionsError from pipenv.patched import crayons from pipenv.utils import subprocess_run from pipenv.vendor import click_completion +from pipenv.vendor.click import ( + Choice, argument, echo, edit, group, option, pass_context, secho, types, + version_option +) # Enable shell completion. @@ -626,9 +625,7 @@ def run_open(state, module, *args, **kwargs): EDITOR=atom pipenv open requests """ - from ..core import ( - ensure_project, inline_activate_virtual_environment - ) + from ..core import ensure_project, inline_activate_virtual_environment # Ensure that virtualenv is available. ensure_project( diff --git a/pipenv/cli/options.py b/pipenv/cli/options.py index 2099343a..bc00a572 100644 --- a/pipenv/cli/options.py +++ b/pipenv/cli/options.py @@ -1,15 +1,13 @@ import os -import click.types - -from click import ( - BadArgumentUsage, BadParameter, Group, Option, argument, echo, - make_pass_decorator, option -) -from click_didyoumean import DYMMixin - from pipenv.project import Project from pipenv.utils import is_valid_url +from pipenv.vendor.click import ( + BadArgumentUsage, BadParameter, Group, Option, argument, echo, + make_pass_decorator, option +) +from pipenv.vendor.click import types as click_types +from pipenv.vendor.click_didyoumean import DYMMixin CONTEXT_SETTINGS = { @@ -119,7 +117,7 @@ def editable_option(f): state.installstate.editables.extend(value) return value return option('-e', '--editable', expose_value=False, multiple=True, - callback=callback, type=click.types.STRING, help=( + callback=callback, type=click_types.STRING, help=( "An editable Python package URL or path, often to a VCS " "repository." ))(f) @@ -132,7 +130,7 @@ def sequential_option(f): return value return option("--sequential", is_flag=True, default=False, expose_value=False, help="Install dependencies one-at-a-time, instead of concurrently.", - callback=callback, type=click.types.BOOL, show_envvar=True)(f) + callback=callback, type=click_types.BOOL, show_envvar=True)(f) def skip_lock_option(f): @@ -142,7 +140,7 @@ def skip_lock_option(f): return value return option("--skip-lock", is_flag=True, default=False, expose_value=False, help="Skip locking mechanisms and use the Pipfile instead during operation.", - envvar="PIPENV_SKIP_LOCK", callback=callback, type=click.types.BOOL, + envvar="PIPENV_SKIP_LOCK", callback=callback, type=click_types.BOOL, show_envvar=True)(f) @@ -153,7 +151,7 @@ def keep_outdated_option(f): return value return option("--keep-outdated", is_flag=True, default=False, expose_value=False, help="Keep out-dated dependencies from being updated in Pipfile.lock.", - callback=callback, type=click.types.BOOL, show_envvar=True)(f) + callback=callback, type=click_types.BOOL, show_envvar=True)(f) def selective_upgrade_option(f): @@ -161,7 +159,7 @@ def selective_upgrade_option(f): state = ctx.ensure_object(State) state.installstate.selective_upgrade = value return value - return option("--selective-upgrade", is_flag=True, default=False, type=click.types.BOOL, + return option("--selective-upgrade", is_flag=True, default=False, type=click_types.BOOL, help="Update specified packages.", callback=callback, expose_value=False)(f) @@ -173,7 +171,7 @@ def ignore_pipfile_option(f): return value return option("--ignore-pipfile", is_flag=True, default=False, expose_value=False, help="Ignore Pipfile when installing, using the Pipfile.lock.", - callback=callback, type=click.types.BOOL, show_envvar=True)(f) + callback=callback, type=click_types.BOOL, show_envvar=True)(f) def _dev_option(f, help_text): @@ -181,7 +179,7 @@ def _dev_option(f, help_text): state = ctx.ensure_object(State) state.installstate.dev = value return value - return option("--dev", "-d", is_flag=True, default=False, type=click.types.BOOL, + return option("--dev", "-d", is_flag=True, default=False, type=click_types.BOOL, help=help_text, callback=callback, expose_value=False, show_envvar=True)(f) @@ -204,7 +202,7 @@ def pre_option(f): state.installstate.pre = value return value return option("--pre", is_flag=True, default=False, help="Allow pre-releases.", - callback=callback, type=click.types.BOOL, expose_value=False)(f) + callback=callback, type=click_types.BOOL, expose_value=False)(f) def package_arg(f): @@ -213,7 +211,7 @@ def package_arg(f): state.installstate.packages.extend(value) return value return argument('packages', nargs=-1, callback=callback, expose_value=False, - type=click.types.STRING)(f) + type=click_types.STRING)(f) def three_option(f): @@ -237,7 +235,7 @@ def python_option(f): return option("--python", default="", nargs=1, callback=callback, help="Specify which version of Python virtualenv should use.", expose_value=False, allow_from_autoenv=False, - type=click.types.STRING)(f) + type=click_types.STRING)(f) def pypi_mirror_option(f): @@ -263,7 +261,7 @@ def verbose_option(f): state.verbose = True setup_verbosity(ctx, param, 1) return option("--verbose", "-v", is_flag=True, expose_value=False, - callback=callback, help="Verbose mode.", type=click.types.BOOL)(f) + callback=callback, help="Verbose mode.", type=click_types.BOOL)(f) def quiet_option(f): @@ -278,7 +276,7 @@ def quiet_option(f): state.quiet = True setup_verbosity(ctx, param, -1) return option("--quiet", "-q", is_flag=True, expose_value=False, - callback=callback, help="Quiet mode.", type=click.types.BOOL)(f) + callback=callback, help="Quiet mode.", type=click_types.BOOL)(f) def site_packages_option(f): @@ -297,7 +295,7 @@ def clear_option(f): state = ctx.ensure_object(State) state.clear = value return value - return option("--clear", is_flag=True, callback=callback, type=click.types.BOOL, + return option("--clear", is_flag=True, callback=callback, type=click_types.BOOL, help="Clears caches (pipenv, pip, and pip-tools).", expose_value=False, show_envvar=True)(f) @@ -309,7 +307,7 @@ def system_option(f): state.system = value return value return option("--system", is_flag=True, default=False, help="System pip management.", - callback=callback, type=click.types.BOOL, expose_value=False, + callback=callback, type=click_types.BOOL, expose_value=False, show_envvar=True)(f) @@ -321,7 +319,7 @@ def requirementstxt_option(f): return value return option("--requirements", "-r", nargs=1, default="", expose_value=False, help="Import a requirements.txt file.", callback=callback, - type=click.types.STRING)(f) + type=click_types.STRING)(f) def emit_requirements_flag(f): @@ -362,7 +360,7 @@ def code_option(f): return value return option("--code", "-c", nargs=1, default="", help="Install packages " "automatically discovered from import statements.", callback=callback, - expose_value=False, type=click.types.STRING)(f) + expose_value=False, type=click_types.STRING)(f) def deploy_option(f): @@ -370,7 +368,7 @@ def deploy_option(f): state = ctx.ensure_object(State) state.installstate.deploy = value return value - return option("--deploy", is_flag=True, default=False, type=click.types.BOOL, + return option("--deploy", is_flag=True, default=False, type=click_types.BOOL, help="Abort if the Pipfile.lock is out-of-date, or Python version is" " wrong.", callback=callback, expose_value=False)(f) @@ -403,7 +401,7 @@ def validate_python_path(ctx, param, value): def validate_bool_or_none(ctx, param, value): if value is not None: - return click.types.BOOL(value) + return click_types.BOOL(value) return False diff --git a/pipenv/core.py b/pipenv/core.py index 00f6c4c0..21b599ce 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1,6 +1,7 @@ import json as simplejson import logging import os +from posixpath import expandvars import sys import time import warnings @@ -83,7 +84,7 @@ def do_clear(project): # Other processes may be writing into this directory simultaneously. vistir.path.rmtree( locations.USER_CACHE_DIR, - ignore_errors=project.s.PIPENV_IS_CI, + ignore_errors=environments.PIPENV_IS_CI, onerror=vistir.path.handle_remove_readonly ) except OSError as e: @@ -2387,6 +2388,7 @@ def _launch_windows_subprocess(script, path): command = system_which(script.command, path=path) options = {"universal_newlines": True} + script.cmd_args[1:] = [expandvars(arg) for arg in script.args] # Command not found, maybe this is a shell built-in? if not command: diff --git a/pipenv/project.py b/pipenv/project.py index d3cb820a..abca0ea3 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -319,7 +319,7 @@ class Project: def get_environment(self, allow_global=False): # type: (bool) -> Environment is_venv = is_in_virtualenv() - if allow_global or is_venv: + if allow_global and not is_venv: prefix = sys.prefix python = sys.executable else: diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index f34cb4a9..3cbd6574 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -149,10 +149,7 @@ def pathlib_tmpdir(request, tmpdir): def _create_tracked_dir(): - tmp_location = os.environ.get("TEMP", os.environ.get("TMP")) temp_args = {"prefix": "pipenv-", "suffix": "-test"} - if tmp_location is not None: - temp_args["dir"] = tmp_location temp_path = create_tracked_tempdir(**temp_args) return temp_path @@ -305,12 +302,13 @@ class _Pipfile: class _PipenvInstance: """An instance of a Pipenv Project...""" def __init__( - self, pypi=None, pipfile=True, chdir=True, path=None, home_dir=None, + self, pypi=None, pipfile=True, chdir=True, path=None, capfd=None, venv_root=None, ignore_virtualenvs=True, venv_in_project=True, name=None ): self.index_url = os.getenv("PIPENV_TEST_INDEX") self.pypi = None self.env = {} + self.capfd = capfd if pypi: self.pypi = pypi.url elif self.index_url is not None: @@ -392,9 +390,15 @@ class _PipenvInstance: with TemporaryDirectory(prefix='pipenv-', suffix='-cache') as tempdir: cmd_args = shlex.split(cmd) env = {**self.env, **{'PIPENV_CACHE_DIR': tempdir.name}} + self.capfd.readouterr() r = cli_runner.invoke(cli, cmd_args, env=env) r.returncode = r.exit_code # Pretty output for failing tests. + out, err = self.capfd.readouterr() + if out: + r.stdout_bytes = r.stdout_bytes + out + if err: + r.stderr_bytes = r.stderr_bytes + err if block: print(f'$ pipenv {cmd}') print(r.stdout) @@ -450,7 +454,7 @@ def pip_src_dir(request, vistir_tmpdir): @pytest.fixture() -def PipenvInstance(pip_src_dir, monkeypatch, pypi, tmp_path): +def PipenvInstance(pip_src_dir, monkeypatch, pypi, capfdbinary): with temp_environ(), monkeypatch.context() as m: m.setattr(shutil, "rmtree", _rmtree_func) original_umask = os.umask(0o007) @@ -464,13 +468,13 @@ def PipenvInstance(pip_src_dir, monkeypatch, pypi, tmp_path): warnings.simplefilter("ignore", category=ResourceWarning) warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*") try: - yield functools.partial(_PipenvInstance, path=tmp_path, pypi=pypi) + yield functools.partial(_PipenvInstance, capfd=capfdbinary) finally: os.umask(original_umask) @pytest.fixture() -def PipenvInstance_NoPyPI(monkeypatch, pip_src_dir, pypi): +def PipenvInstance_NoPyPI(monkeypatch, pip_src_dir, pypi, capfdbinary): with temp_environ(), monkeypatch.context() as m: m.setattr(shutil, "rmtree", _rmtree_func) original_umask = os.umask(0o007) @@ -482,7 +486,7 @@ def PipenvInstance_NoPyPI(monkeypatch, pip_src_dir, pypi): warnings.simplefilter("ignore", category=ResourceWarning) warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*") try: - yield _PipenvInstance + yield functools.partial(_PipenvInstance, capfd=capfdbinary) finally: os.umask(original_umask) @@ -498,7 +502,7 @@ class VirtualEnv: base_dir = Path(_create_tracked_dir()) self.base_dir = base_dir self.name = name - self.path = base_dir / name + self.path = (base_dir / name).resolve() def __enter__(self): self._old_environ = os.environ.copy() @@ -529,17 +533,14 @@ class VirtualEnv: code = compile(f.read(), str(activate_this), "exec") exec(code, dict(__file__=str(activate_this))) os.environ["VIRTUAL_ENV"] = str(self.path) - try: - return self.path.absolute().resolve() - except OSError: - return self.path.absolute() + return self.path else: raise VirtualenvActivationException("Can't find the activate_this.py script.") @pytest.fixture() def virtualenv(vistir_tmpdir): - with temp_environ(), VirtualEnv(base_dir=vistir_tmpdir) as venv: + with VirtualEnv(base_dir=vistir_tmpdir) as venv: yield venv diff --git a/tests/integration/test_cli.py b/tests/integration/test_cli.py index 5c90c70f..d3b3ed12 100644 --- a/tests/integration/test_cli.py +++ b/tests/integration/test_cli.py @@ -8,7 +8,7 @@ import pytest from flaky import flaky -from pipenv.utils import normalize_drive +from pipenv.utils import normalize_drive, subprocess_run @pytest.mark.cli @@ -217,8 +217,8 @@ def test_help(PipenvInstance): @pytest.mark.cli def test_man(PipenvInstance): - with PipenvInstance() as p: - c = p.pipenv('--man') + with PipenvInstance(): + c = subprocess_run(["pipenv", "--man"]) assert c.returncode == 0, c.stderr diff --git a/tests/integration/test_lock.py b/tests/integration/test_lock.py index 45686f67..84a423fb 100644 --- a/tests/integration/test_lock.py +++ b/tests/integration/test_lock.py @@ -646,16 +646,11 @@ requests = "*" @pytest.mark.lock @pytest.mark.install -def test_lock_no_warnings(PipenvInstance): +def test_lock_no_warnings(PipenvInstance, recwarn): with PipenvInstance(chdir=True) as p: - os.environ["PYTHONWARNINGS"] = "once" c = p.pipenv("install six") assert c.returncode == 0 - c = p.pipenv('run python -c "import warnings; warnings.warn(\\"This is a warning\\", DeprecationWarning); print(\\"hello\\")"') - assert c.returncode == 0 - assert "Warning" in c.stderr - assert "Warning" not in c.stdout - assert "hello" in c.stdout + assert len(recwarn) == 0 @pytest.mark.lock diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index 4a4b12eb..1ad60a2a 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -1,11 +1,13 @@ import os import tarfile +from pathlib import Path + import pytest from pipenv.patched import pipfile from pipenv.project import Project -from pipenv.utils import subprocess_run, temp_environ +from pipenv.utils import temp_environ from pipenv.vendor.vistir.path import is_in_path, normalize_path @@ -170,38 +172,26 @@ def test_include_editable_packages(PipenvInstance, testsroot, pathlib_tmpdir): @pytest.mark.virtualenv def test_run_in_virtualenv_with_global_context(PipenvInstance, virtualenv): with PipenvInstance(chdir=True, venv_root=virtualenv.as_posix(), ignore_virtualenvs=False, venv_in_project=False) as p: - c = subprocess_run( - ["pipenv", "run", "pip", "freeze"], cwd=os.path.abspath(p.path), - env=os.environ.copy() - ) + c = p.pipenv("run pip freeze") assert c.returncode == 0, (c.stdout, c.stderr) assert 'Creating a virtualenv' not in c.stderr, c.stderr project = Project() - assert project.virtualenv_location == virtualenv.as_posix(), ( - project.virtualenv_location, virtualenv.as_posix() - ) - c = subprocess_run( - ["pipenv", "run", "pip", "install", "-i", p.index_url, "click"], - cwd=os.path.abspath(p.path), - env=os.environ.copy() + assert Path(project.virtualenv_location).resolve() == Path(virtualenv), ( + project.virtualenv_location, str(virtualenv) ) + + c = p.pipenv(f"run pip install -i {p.index_url} click") assert c.returncode == 0, (c.stdout, c.stderr) assert "Courtesy Notice" in c.stderr, (c.stdout, c.stderr) - c = subprocess_run( - ["pipenv", "install", "-i", p.index_url, "six"], - cwd=os.path.abspath(p.path), env=os.environ.copy() - ) + + c = p.pipenv("install six") assert c.returncode == 0, (c.stdout, c.stderr) - c = subprocess_run( - ['pipenv', 'run', 'python', '-c', 'import click;print(click.__file__)'], - cwd=os.path.abspath(p.path), env=os.environ.copy() - ) + + c = p.pipenv("run python -c 'import click;print(click.__file__)'") assert c.returncode == 0, (c.stdout, c.stderr) assert is_in_path(c.stdout.strip(), str(virtualenv)), (c.stdout.strip(), str(virtualenv)) - c = subprocess_run( - ["pipenv", "clean", "--dry-run"], cwd=os.path.abspath(p.path), - env=os.environ.copy() - ) + + c = p.pipenv("clean --dry-run") assert c.returncode == 0, (c.stdout, c.stderr) assert "click" in c.stdout, c.stdout @@ -227,6 +217,7 @@ def test_run_in_virtualenv(PipenvInstance): assert c.returncode == 0 assert "click" in c.stdout + @pytest.mark.project @pytest.mark.sources def test_no_sources_in_pipfile(PipenvInstance): @@ -235,7 +226,7 @@ def test_no_sources_in_pipfile(PipenvInstance): contents = """ [packages] pytest = "*" - """.format(os.environ['PIPENV_TEST_INDEX']).strip() + """.strip() f.write(contents) c = p.pipenv('install --skip-lock') assert c.returncode == 0 diff --git a/tests/integration/test_run.py b/tests/integration/test_run.py index 22b29788..3059f88c 100644 --- a/tests/integration/test_run.py +++ b/tests/integration/test_run.py @@ -3,17 +3,16 @@ import os import pytest from pipenv.project import Project -from pipenv.utils import temp_environ +from pipenv.utils import subprocess_run, temp_environ @pytest.mark.run @pytest.mark.dotenv def test_env(PipenvInstance): with PipenvInstance(pipfile=False, chdir=True) as p: - with open('.env', 'w') as f: - f.write('HELLO=WORLD') - - c = p.pipenv('run python -c "import os; print(os.environ[\'HELLO\'])"') + with open(os.path.join(p.path, ".env"), "w") as f: + f.write("HELLO=WORLD") + c = subprocess_run(['pipenv', 'run', 'python', '-c', "import os; print(os.environ['HELLO'])"], env=p.env) assert c.returncode == 0 assert 'WORLD' in c.stdout @@ -35,18 +34,16 @@ multicommand = "bash -c \"cd docs && make html\"" f.write('scriptwithenv = "echo $HELLO"\n') c = p.pipenv('install') assert c.returncode == 0 - c = p.pipenv('run printfoo') assert c.returncode == 0 - assert c.stdout == 'foo\n' - assert c.stderr == '' + assert c.stdout.splitlines()[0] == 'foo' + assert not c.stderr.strip() c = p.pipenv('run notfoundscript') - assert c.returncode == 1 + assert c.returncode != 0 assert c.stdout == '' if os.name != 'nt': # TODO: Implement this message for Windows. - assert 'Error' in c.stderr - assert 'randomthingtotally (from notfoundscript)' in c.stderr + assert 'not found' in c.stderr project = Project() diff --git a/tests/integration/test_uninstall.py b/tests/integration/test_uninstall.py index 14883af2..25dd7f5e 100644 --- a/tests/integration/test_uninstall.py +++ b/tests/integration/test_uninstall.py @@ -193,4 +193,4 @@ def test_uninstall_missing_parameters(PipenvInstance): c = p.pipenv("uninstall") assert c.returncode != 0 - assert "No package provided!" in str(c.exception) + assert "No package provided!" in c.stderr diff --git a/tests/unit/test_help.py b/tests/unit/test_help.py index 5179e4d3..812925f7 100644 --- a/tests/unit/test_help.py +++ b/tests/unit/test_help.py @@ -13,18 +13,3 @@ def test_help(): stderr=subprocess.STDOUT, env=os.environ.copy(), ) assert output - - -@pytest.mark.cli -@pytest.mark.help -def test_count_of_description_pre_option(): - test_command = 'pipenv install --help' - test_line = '--pre Allow pre-releases.' - out = subprocess.Popen(test_command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - stdout, _ = out.communicate() - lines = stdout.decode().split('\n') - count = 0 - for line in lines: - if line.strip().split() == test_line.split(): - count += 1 - assert count == 1