Skip to content

Commit

Permalink
capture outputs written in subprocesses
Browse files Browse the repository at this point in the history
  • Loading branch information
frostming committed Aug 3, 2021
1 parent 93251ba commit 904f0a3
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 111 deletions.
13 changes: 5 additions & 8 deletions pipenv/cli/command.py
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -18,6 +13,10 @@
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.
Expand Down Expand Up @@ -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(
Expand Down
48 changes: 23 additions & 25 deletions pipenv/cli/options.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import os

import click.types

from click import (
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 click_didyoumean import DYMMixin

from pipenv.project import Project
from pipenv.utils import is_valid_url
from pipenv.vendor.click import types as click_types
from pipenv.vendor.click_didyoumean import DYMMixin


CONTEXT_SETTINGS = {
Expand Down Expand Up @@ -119,7 +117,7 @@ def callback(ctx, param, value):
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)
Expand All @@ -132,7 +130,7 @@ def callback(ctx, param, value):
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):
Expand All @@ -142,7 +140,7 @@ def callback(ctx, param, value):
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)


Expand All @@ -153,15 +151,15 @@ def callback(ctx, param, value):
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):
def callback(ctx, param, value):
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)

Expand All @@ -173,15 +171,15 @@ def callback(ctx, param, value):
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):
def callback(ctx, param, value):
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)

Expand All @@ -204,7 +202,7 @@ def callback(ctx, param, value):
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):
Expand All @@ -213,7 +211,7 @@ def callback(ctx, param, value):
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):
Expand All @@ -237,7 +235,7 @@ def callback(ctx, param, value):
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):
Expand All @@ -263,7 +261,7 @@ def callback(ctx, param, value):
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):
Expand All @@ -278,7 +276,7 @@ def callback(ctx, param, value):
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):
Expand All @@ -297,7 +295,7 @@ def callback(ctx, param, value):
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)

Expand All @@ -309,7 +307,7 @@ def callback(ctx, param, value):
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)


Expand All @@ -321,7 +319,7 @@ def callback(ctx, param, value):
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):
Expand Down Expand Up @@ -362,15 +360,15 @@ def callback(ctx, param, value):
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):
def callback(ctx, param, value):
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)

Expand Down Expand Up @@ -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


Expand Down
4 changes: 3 additions & 1 deletion pipenv/core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json as simplejson
import logging
import os
from posixpath import expandvars
import sys
import time
import warnings
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pipenv/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ def pipfile_package_names(self):
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:
Expand Down
29 changes: 15 additions & 14 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -305,12 +302,13 @@ def get_url(cls, pkg=None, filename=None):
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:
Expand Down Expand Up @@ -392,9 +390,15 @@ def pipenv(self, cmd, block=True):
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)
Expand Down Expand Up @@ -450,7 +454,7 @@ def finalize():


@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)
Expand All @@ -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.*<ssl.SSLSocket.*>")
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)
Expand All @@ -482,7 +486,7 @@ def PipenvInstance_NoPyPI(monkeypatch, pip_src_dir, pypi):
warnings.simplefilter("ignore", category=ResourceWarning)
warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>")
try:
yield _PipenvInstance
yield functools.partial(_PipenvInstance, capfd=capfdbinary)
finally:
os.umask(original_umask)

Expand All @@ -498,7 +502,7 @@ def __init__(self, name="venv", base_dir=None):
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()
Expand Down Expand Up @@ -529,17 +533,14 @@ def activate(self):
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


Expand Down
6 changes: 3 additions & 3 deletions tests/integration/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from flaky import flaky

from pipenv.utils import normalize_drive
from pipenv.utils import normalize_drive, subprocess_run


@pytest.mark.cli
Expand Down Expand Up @@ -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


Expand Down
9 changes: 2 additions & 7 deletions tests/integration/test_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,16 +646,11 @@ def test_lock_with_incomplete_source(PipenvInstance):

@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
Expand Down
Loading

0 comments on commit 904f0a3

Please sign in to comment.