diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 10506bc0..b2326555 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,15 +1,11 @@ Be sure to check the existing issues (both open and closed!). -Describe the issue briefly here. - -Please run `$ python -m pipenv.help`, and paste the results here. - -If you're on MacOS, just run the following: - - $ python -m pipenv.help | pbcopy - ------------ +##### Issue description + +Describe the issue briefly here. + ##### Expected result Describe what you expected. @@ -21,3 +17,15 @@ When possible, provide the verbose output (`--verbose`), especially for locking ##### Steps to replicate Provide the steps to replicate (which usually at least includes the commands and the Pipfile). + +------------- + +Please run `$ pipenv --support`, and paste the results here. Don't put backticks (`` ` ``) around it! The output already contains Markdown formatting. + +If you're on macOS, run the following: + + $ pipenv --support | pbcopy + +If you're on Windows, run the following: + + > pipenv --support | clip diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 03aa41a1..c6b47876 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -1,29 +1,37 @@ ---- -name: Bug report -about: Create a report to help us improve - ---- - -Be sure to check the existing issues (both open and closed!). - -Describe the issue briefly here. - -Please run `$ python -m pipenv.help`, and paste the results here. - -If you're on MacOS, just run the following: - - $ python -m pipenv.help | pbcopy - ------------- - -##### Expected result - -Describe what you expected. - -##### Actual result - -When possible, provide the verbose output (`--verbose`), especially for locking and dependencies resolving issues. - -##### Steps to replicate - -Provide the steps to replicate (which usually at least includes the commands and the Pipfile). +--- +name: Bug report +about: Create a report to help us improve + +--- + +Be sure to check the existing issues (both open and closed!). + +------------ + +##### Issue description + +Describe the issue briefly here. + +##### Expected result + +Describe what you expected. + +##### Actual result + +When possible, provide the verbose output (`--verbose`), especially for locking and dependencies resolving issues. + +##### Steps to replicate + +Provide the steps to replicate (which usually at least includes the commands and the Pipfile). + +------------- + +Please run `$ pipenv --support`, and paste the results here. Don't put backticks (`` ` ``) around it! The output already contains Markdown formatting. + +If you're on macOS, run the following: + + $ pipenv --support | pbcopy + +If you're on Windows, run the following: + + > pipenv --support | clip diff --git a/.github/ISSUE_TEMPLATE/Custom.md b/.github/ISSUE_TEMPLATE/Custom.md index d31ed75e..5166a09e 100644 --- a/.github/ISSUE_TEMPLATE/Custom.md +++ b/.github/ISSUE_TEMPLATE/Custom.md @@ -1,7 +1,22 @@ ---- -name: Usage / Requests for Help -about: Requests for assistance or general usage guidance. - ---- - -Please refer to our [StackOverflow tag](https://stackoverflow.com/questions/tagged/pipenv) for more information. +--- +name: Usage / Requests for Help +about: Requests for assistance or general usage guidance. + +--- + +Please refer to our [StackOverflow tag](https://stackoverflow.com/questions/tagged/pipenv) for more information. + +If Pipenv is not functioning as you would like it to, consider filing either a bug report, or a feature request instead. + + +------------- + +Please run `$ pipenv --support`, and paste the results here. Don't put backticks (`` ` ``) around it! The output already contains Markdown formatting. + +If you're on macOS, run the following: + + $ pipenv --support | pbcopy + +If you're on Windows, run the following: + + > pipenv --support | clip diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index 53842951..4876c2a8 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -1,17 +1,23 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. +--- +name: Feature request +about: Suggest an idea for this project + +--- + +Be sure to check the existing issues (both open and closed!). + +##### Is your feature request related to a problem? Please describe. + +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +##### Describe the solution you'd like + +A clear and concise description of what you want to happen. + +##### Describe alternatives you've considered + +A clear and concise description of any alternative solutions or features you've considered. + +##### Additional context + +Add any other context or screenshots about the feature request here. It may be a good idea to mention that platform and Python version you are on. diff --git a/appveyor.yml b/appveyor.yml index 281be915..c67a8cfb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,24 +1,40 @@ build: off version: 1.0.{build} - skip_branch_with_pr: true init: +- ps: >- + + git config --global core.sharedRepository true + + git config --global core.longpaths true + + git config --global core.autocrlf input + + if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` + https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` + Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` + Write-Host "There are newer queued builds for this pull request, skipping build." + Exit-AppveyorBuild + } + + If (($env:SKIP_NOTAG -eq "true") -and ($env:APPVEYOR_REPO_TAG -ne "true")) { + Write-Host "Skipping build, not at a tag." + Exit-AppveyorBuild + } - - git config --global core.sharedRepository true - - git config --global core.longpaths true - - git config --global core.autocrlf input environment: PYPI_VENDOR_DIR: '.\tests\pypi\' GIT_ASK_YESNO: 'false' + APPVEYOR_SAVE_CACHE_ON_ERROR: 'true' + APPVEYOR_SKIP_FINALIZE_ON_EXIT: 'true' SHELL: 'windows' PYTHON_ARCH: '64' PYTHONIOENCODING: 'utf-8' matrix: - - PYTHON: 'C:\Python27-x64' PYTHON_VERSION: '2.7.x' TEST_SUITE: 'not install' @@ -39,19 +55,60 @@ environment: PYTEST_ADDOPTS: '--cache-clear' RUN_INTEGRATION_TESTS: 'True' + - PYTHON: 'C:\Python37-x64' + PYTHON_VERSION: '3.7.x' + TEST_SUITE: 'not install' + + - PYTHON: 'C:\Python37-x64' + PYTHON_VERSION: '3.7.x' + TEST_SUITE: 'install' + PYTEST_ADDOPTS: '--cache-clear' + RUN_INTEGRATION_TESTS: 'True' + install: - - 'set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%' - - '%PYTHON%\python.exe -m pip install --upgrade pip' - - '%PYTHON%\python.exe -m pip install -e .' - - '%PYTHON%\python.exe -m pipenv run pip install -e .' - - '%PYTHON%\python.exe -m pipenv install --dev' - - '%PYTHON%\python.exe -m pipenv --venv' - - '%PYTHON%\python.exe -m pipenv --py' - - '%PYTHON%\python.exe -m pipenv run python --version' +- ps: >- + + $script_path = Join-Path -path $env:PYTHON -childpath Scripts + + $py_exe = Join-Path -path $env:PYTHON -childpath python.exe + + $pipenv_exe = Join-Path -path $script_path -childpath pipenv.exe + + $env:PATH = "$py_path;$script_path;$env:PATH" + + Invoke-Expression "$py_exe -m pip install --upgrade pip invoke" + + Invoke-Expression "$py_exe -m pip install -e ." + + Invoke-Expression "$pipenv_exe install --dev" + + Invoke-Expression "$pipenv_exe --venv" + + Invoke-Expression "$pipenv_exe --py" cache: - - '%LocalAppData%\pip\cache' +- '%LocalAppData%\pip\cache' +- '%LocalAppData%\pipenv\cache' test_script: - - 'if "%RUN_INTEGRATION_TESTS%" == "True" (rmdir /s /q %LocalAppData%\pip\cache)' - - '%PYTHON%\python.exe -m pipenv run pytest -v -n 4 -m "%TEST_SUITE%" tests' + +- ps: >- + $script_path = Join-Path -path $env:PYTHON -childpath Scripts + + $py_exe = Join-Path -path $env:PYTHON -childpath python.exe + + $pipenv_exe = Join-Path -path $script_path -childpath pipenv.exe + + $env:PATH = "$py_path;$script_path;$env:PATH" + + $invoke_path = Join-Path -path $script_path -childpath invoke.exe + + Invoke-Expression "$pipenv_exe run pytest -v -n 4 --ignore=pipenv\patched --ignore=pipenv\vendor -m `"$env:TEST_SUITE`" tests" + + If ($env:RUN_INTEGRATION_TESTS -and ($env:PYTHON_VERSION -eq "3.6.x")) { + + Invoke-Expression "$py_exe -m pip install invoke parver" + + Invoke-Expression "$invoke_path vendoring.update" + + } diff --git a/news/2371.feature b/news/2371.feature new file mode 100644 index 00000000..3d71fe0d --- /dev/null +++ b/news/2371.feature @@ -0,0 +1 @@ +All calls to ``pipenv shell`` are now implemented from the ground up using `shellingham `_, a custom library which was purpose built to handle edge cases and shell detection. diff --git a/news/2371.vendor b/news/2371.vendor new file mode 100644 index 00000000..3d71fe0d --- /dev/null +++ b/news/2371.vendor @@ -0,0 +1 @@ +All calls to ``pipenv shell`` are now implemented from the ground up using `shellingham `_, a custom library which was purpose built to handle edge cases and shell detection. diff --git a/news/2477.feature b/news/2477.feature new file mode 100644 index 00000000..9276e1cb --- /dev/null +++ b/news/2477.feature @@ -0,0 +1 @@ +Added new flag ``pipenv --support`` to replace the diagnostic command ``python -m pipenv.help``. \ No newline at end of file diff --git a/pipenv/_compat.py b/pipenv/_compat.py index 07e75072..fa9e0a1d 100644 --- a/pipenv/_compat.py +++ b/pipenv/_compat.py @@ -37,6 +37,11 @@ if sys.version_info[:2] >= (3, 5): else: from .vendor.pathlib2 import Path +# Backport required for earlier versions of Python. +if sys.version_info < (3, 3): + from .vendor.backports.shutil_get_terminal_size import get_terminal_size +else: + from shutil import get_terminal_size try: from weakref import finalize diff --git a/pipenv/cli.py b/pipenv/cli.py index bb41c103..646cd0ac 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -152,6 +152,11 @@ def validate_pypi_mirror(ctx, param, value): callback=validate_pypi_mirror, help="Specify a PyPI mirror.", ) +@option( + '--support', + is_flag=True, + help="Output diagnostic information for use in Github issues." +) @version_option( prog_name=crayons.normal('pipenv', bold=True), version=__version__ ) @@ -171,6 +176,7 @@ def cli( man=False, completion=False, pypi_mirror=None, + support=None ): if completion: # Handle this ASAP to make shell startup fast. from . import shells @@ -229,6 +235,11 @@ def cli( elif py: do_py() sys.exit() + # --support was passed... + elif support: + from .help import get_pipenv_diagnostics + get_pipenv_diagnostics() + sys.exit(0) # --venv was passed... elif venv: # There is no virtualenv yet. diff --git a/pipenv/core.py b/pipenv/core.py index 494a1efc..ea8a0698 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -79,11 +79,6 @@ from .environments import ( PIPENV_CACHE_DIR, ) -# Backport required for earlier versions of Python. -if sys.version_info < (3, 3): - from .vendor.backports.shutil_get_terminal_size import get_terminal_size -else: - from shutil import get_terminal_size # Packages that should be ignored later. BAD_PACKAGES = ('setuptools', 'pip', 'wheel', 'packaging', 'distribute') # Are we using the default Python? @@ -480,7 +475,8 @@ def ensure_python(three=None, python=None): '3.3': '3.3.7', '3.4': '3.4.8', '3.5': '3.5.5', - '3.6': '3.6.5', + '3.6': '3.6.6', + '3.7': '3.7.0', } try: if len(python.split('.')) == 2: @@ -1169,29 +1165,6 @@ def do_lock( return lockfile -def activate_virtualenv(source=True): - """Returns the string to activate a virtualenv.""" - # Suffix and source command for other shells. - suffix = '' - command = ' .' if source else '' - # Support for fish shell. - if PIPENV_SHELL and 'fish' in PIPENV_SHELL: - suffix = '.fish' - command = 'source' - # Support for csh shell. - if PIPENV_SHELL and 'csh' in PIPENV_SHELL: - suffix = '.csh' - command = 'source' - # Escape any spaces located within the virtualenv path to allow - # for proper activation. - venv_location = project.virtualenv_location.replace(' ', r'\ ') - if source: - return '{2} {0}/bin/activate{1}'.format(venv_location, suffix, command) - - else: - return '{0}/bin/activate'.format(venv_location) - - def do_purge(bare=False, downloads=False, allow_global=False, verbose=False): """Executes the purge functionality.""" if downloads: @@ -2169,94 +2142,39 @@ def do_uninstall( do_lock(system=system, keep_outdated=keep_outdated, pypi_mirror=pypi_mirror) -def do_shell(three=None, python=False, fancy=False, shell_args=None, pypi_mirror=None): - from .patched.pew import pew +def do_shell(three=None, python=False, fancy=False, shell_args=None, pypi_mirror=None): # Ensure that virtualenv is available. ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror) # Set an environment variable, so we know we're in the environment. os.environ['PIPENV_ACTIVE'] = '1' - compat = (not fancy) # Support shell compatibility mode. if PIPENV_SHELL_FANCY: - compat = False - # Compatibility mode: - if compat: - if PIPENV_SHELL: - shell = os.path.abspath(PIPENV_SHELL) - else: - click.echo( - crayons.red( - 'Please ensure that the {0} environment variable ' - 'is set before activating shell.'.format( - crayons.normal('SHELL', bold=True) - ) - ), - err=True, - ) - sys.exit(1) + fancy = True + + from .shells import choose_shell + shell = choose_shell() + click.echo("Launching subshell in virtual environment…", err=True) + + fork_args = ( + project.virtualenv_location, + project.project_directory, + shell_args, + ) + + if fancy: + shell.fork(*fork_args) + return + + try: + shell.fork_compat(*fork_args) + except (AttributeError, ImportError): click.echo( - crayons.normal( - 'Spawning environment shell ({0}). Use {1} to leave.'.format( - crayons.red(shell), crayons.normal("'exit'", bold=True) - ), - bold=True, - ), + u'Compatibility mode not supported. ' + u'Trying to continue as well-configured shell…', err=True, ) - cmd = "{0} -i'".format(shell) - args = [] - # Standard (properly configured shell) mode: - else: - if project.is_venv_in_project(): - # use .venv as the target virtualenv name - workon_name = '.venv' - else: - workon_name = project.virtualenv_name - cmd = sys.executable - args = ['-m', 'pipenv.pew', 'workon', workon_name] - # Grab current terminal dimensions to replace the hardcoded default - # dimensions of pexpect - terminal_dimensions = get_terminal_size() - try: - with temp_environ(): - if project.is_venv_in_project(): - os.environ['WORKON_HOME'] = project.project_directory - c = pexpect.spawn( - cmd, - args, - dimensions=( - terminal_dimensions.lines, terminal_dimensions.columns - ), - ) - # Windows! - except AttributeError: - # import subprocess - # Tell pew to use the project directory as its workon_home - with temp_environ(): - if project.is_venv_in_project(): - os.environ['WORKON_HOME'] = project.project_directory - pew.workon_cmd([workon_name]) - sys.exit(0) - # Activate the virtualenv if in compatibility mode. - if compat: - c.sendline(activate_virtualenv()) - # Send additional arguments to the subshell. - if shell_args: - c.sendline(' '.join(shell_args)) - - # Handler for terminal resizing events - # Must be defined here to have the shell process in its context, since we - # can't pass it as an argument - def sigwinch_passthrough(sig, data): - terminal_dimensions = get_terminal_size() - c.setwinsize(terminal_dimensions.lines, terminal_dimensions.columns) - - signal.signal(signal.SIGWINCH, sigwinch_passthrough) - # Interact with the new shell. - c.interact(escape_character=None) - c.close() - sys.exit(c.exitstatus) + shell.fork(*fork_args) def inline_activate_virtualenv(): diff --git a/pipenv/environments.py b/pipenv/environments.py index 36fada8c..3ab5eefc 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -76,6 +76,7 @@ PYENV_INSTALLED = ( SESSION_IS_INTERACTIVE = bool(os.isatty(sys.stdout.fileno())) PIPENV_SHELL_EXPLICIT = os.environ.get('PIPENV_SHELL') PIPENV_SHELL = os.environ.get('SHELL') or os.environ.get('PYENV_SHELL') +PIPENV_EMULATOR = os.environ.get('PIPENV_EMULATOR') PIPENV_CACHE_DIR = os.environ.get('PIPENV_CACHE_DIR', user_cache_dir('pipenv')) # Tells pipenv to override PyPI index urls with a mirror. PIPENV_PYPI_MIRROR = os.environ.get('PIPENV_PYPI_MIRROR') diff --git a/pipenv/help.py b/pipenv/help.py index 0d06680d..4f853266 100644 --- a/pipenv/help.py +++ b/pipenv/help.py @@ -16,8 +16,8 @@ def print_utf(line): print(line.encode('utf-8')) -def main(): - print('
$ python -m pipenv.help output') +def get_pipenv_diagnostics(): + print('
$ pipenv --support') print('') print('Pipenv version: `{0!r}`'.format(__version__)) print('') @@ -93,5 +93,5 @@ def main(): if __name__ == '__main__': - main() + get_pipenv_diagnostics() diff --git a/pipenv/shells.py b/pipenv/shells.py index 921c72ce..87692b25 100644 --- a/pipenv/shells.py +++ b/pipenv/shells.py @@ -1,11 +1,17 @@ +import collections +import contextlib import os +import signal +import subprocess +import sys -from .environments import PIPENV_SHELL_EXPLICIT, PIPENV_SHELL +from ._compat import get_terminal_size, Path +from .environments import PIPENV_SHELL_EXPLICIT, PIPENV_SHELL, PIPENV_EMULATOR +from .utils import temp_environ from .vendor import shellingham -class ShellDetectionFailure(shellingham.ShellDetectionFailure): - pass +ShellDetectionFailure = shellingham.ShellDetectionFailure def _build_info(value): @@ -21,3 +27,192 @@ def detect_info(): if PIPENV_SHELL: return _build_info(PIPENV_SHELL) raise ShellDetectionFailure + + +def _get_activate_script(venv): + """Returns the string to activate a virtualenv. + + This is POSIX-only at the moment since the compat (pexpect-based) shell + does not work elsewhere anyway. + """ + # Suffix and source command for other shells. + # Support for fish shell. + if PIPENV_SHELL and 'fish' in PIPENV_SHELL: + suffix = '.fish' + command = 'source' + # Support for csh shell. + elif PIPENV_SHELL and 'csh' in PIPENV_SHELL: + suffix = '.csh' + command = 'source' + else: + suffix = '' + command = '.' + # Escape any spaces located within the virtualenv path to allow + # for proper activation. + venv_location = str(venv).replace(' ', r'\ ') + # The leading space can make history cleaner in some shells. + return ' {2} {0}/bin/activate{1}'.format(venv_location, suffix, command) + + +def _handover(cmd, args): + args = [cmd] + args + if os.name != 'nt': + os.execvp(cmd, args) + else: + proc = subprocess.run(args, shell=True, universal_newlines=True) + sys.exit(proc.returncode) + + +class Shell(object): + + def __init__(self, cmd): + self.cmd = cmd + self.args = [] + + @contextlib.contextmanager + def inject_path(self, venv): + with temp_environ(): + os.environ['PATH'] = '{0}{1}{2}'.format( + os.pathsep.join(str(p.parent) for p in _iter_python(venv)), + os.pathsep, + os.environ['PATH'], + ) + yield + + def fork(self, venv, cwd, args): + # FIXME: This isn't necessarily the correct prompt. We should read the + # actual prompt by peeking into the activation script. + name = os.path.basename(venv) + os.environ['VIRTUAL_ENV'] = str(venv) + if 'PROMPT' in os.environ: + os.environ['PROMPT'] = '({0}) {1}'.format( + name, os.environ['PROMPT'], + ) + if 'PS1' in os.environ: + os.environ['PS1'] = '({0}) {1}'.format( + name, os.environ['PS1'], + ) + with self.inject_path(venv): + os.chdir(cwd) + _handover(self.cmd, self.args + list(args)) + + def fork_compat(self, venv, cwd, args): + from .vendor import pexpect + + # Grab current terminal dimensions to replace the hardcoded default + # dimensions of pexpect. + dims = get_terminal_size() + with temp_environ(): + c = pexpect.spawn( + self.cmd, ['-i'], dimensions=(dims.lines, dims.columns), + ) + c.sendline(_get_activate_script(venv)) + if args: + c.sendline(' '.join(args)) + + # Handler for terminal resizing events + # Must be defined here to have the shell process in its context, since + # we can't pass it as an argument + def sigwinch_passthrough(sig, data): + dims = get_terminal_size() + c.setwinsize(dims.lines, dims.columns) + + signal.signal(signal.SIGWINCH, sigwinch_passthrough) + + # Interact with the new shell. + c.interact(escape_character=None) + c.close() + sys.exit(c.exitstatus) + + +POSSIBLE_ENV_PYTHON = [ + Path('bin', 'python'), + Path('Scripts', 'python.exe'), +] + + +def _iter_python(venv): + for path in POSSIBLE_ENV_PYTHON: + full_path = Path(venv, path) + if full_path.is_file(): + yield full_path + + +class Bash(Shell): + # The usual PATH injection technique does not work with Bash. + # https://github.com/berdario/pew/issues/58#issuecomment-102182346 + @contextlib.contextmanager + def inject_path(self, venv): + from ._compat import NamedTemporaryFile + bashrc_path = Path.home().joinpath('.bashrc') + with NamedTemporaryFile('w+') as rcfile: + if bashrc_path.is_file(): + base_rc_src = 'source "{0}"\n'.format(bashrc_path.as_posix()) + rcfile.write(base_rc_src) + + export_path = 'export PATH="{0}:$PATH"\n'.format(':'.join( + python.parent.as_posix() + for python in _iter_python(venv) + )) + rcfile.write(export_path) + rcfile.flush() + self.args.extend(['--rcfile', rcfile.name]) + yield + + +class CmderEmulatedShell(Shell): + def fork(self, venv, cwd, args): + if cwd: + os.environ['CMDER_START'] = cwd + super(CmderEmulatedShell, self).fork(venv, cwd, args) + + +class CmderCommandPrompt(CmderEmulatedShell): + def fork(self, venv, cwd, args): + rc = os.path.expandvars('%CMDER_ROOT%\\vendor\\init.bat') + if os.path.exists(rc): + self.args.extend(['/k', rc]) + super(CmderCommandPrompt, self).fork(venv, cwd, args) + + +class CmderPowershell(Shell): + def fork(self, venv, cwd, args): + rc = os.path.expandvars('%CMDER_ROOT%\\vendor\\profile.ps1') + if os.path.exists(rc): + self.args.extend([ + '-ExecutionPolicy', 'Bypass', '-NoLogo', '-NoProfile', + '-NoExit', '-Command', + "Invoke-Expression '. ''{0}'''".format(rc), + ]) + super(CmderPowershell, self).fork(venv, cwd, args) + + +# Two dimensional dict. First is the shell type, second is the emulator type. +# Example: SHELL_LOOKUP['powershell']['cmder'] => CmderPowershell. +SHELL_LOOKUP = collections.defaultdict( + lambda: collections.defaultdict(lambda: Shell), + { + 'bash': collections.defaultdict(lambda: Bash), + 'cmd': collections.defaultdict(lambda: Shell, { + 'cmder': CmderCommandPrompt, + }), + 'powershell': collections.defaultdict(lambda: Shell, { + 'cmder': CmderPowershell, + }), + 'pwsh': collections.defaultdict(lambda: Shell, { + 'cmder': CmderPowershell, + }), + }, +) + + +def _detect_emulator(): + if os.environ.get('CMDER_ROOT'): + return 'cmder' + return '' + + +def choose_shell(): + emulator = PIPENV_EMULATOR or _detect_emulator() + type_, command = detect_info() + return SHELL_LOOKUP[type_][emulator](command) diff --git a/run-tests.sh b/run-tests.sh index a7059322..ce62ea5a 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -19,6 +19,7 @@ if [[ ! -z "$TEST_SUITE" ]]; then fi export PATH="~/.local/bin:$PATH" +pip uninstall -y pipenv echo "Installing Pipenv…" pip install -e "$(pwd)" --upgrade pipenv install --deploy --dev @@ -50,5 +51,9 @@ echo "$ pipenv run time pytest -v -n auto tests -m \"$TEST_SUITE\"" # Better to run them sequentially. PIPENV_PYTHON=2.7 pipenv run time pytest -v -n auto tests -m "$TEST_SUITE" PIPENV_PYTHON=3.6 pipenv run time pytest -v -n auto tests -m "$TEST_SUITE" + +# test revendoring +pip3 install --upgrade invoke requests parver +python3 -m invoke vendoring.update # Cleanup junk. rm -fr .venv diff --git a/tests/integration/test_cli.py b/tests/integration/test_cli.py index 56ae2210..a51095ce 100644 --- a/tests/integration/test_cli.py +++ b/tests/integration/test_cli.py @@ -31,6 +31,12 @@ def test_pipenv_py(PipenvInstance): assert os.path.basename(python).startswith('python') +@pytest.mark.cli +def test_pipenv_support(PipenvInstance): + with PipenvInstance() as p: + assert p.pipenv('--support').out + + @pytest.mark.cli def test_pipenv_rm(PipenvInstance): with PipenvInstance() as p: diff --git a/tests/integration/test_pipenv.py b/tests/integration/test_pipenv.py index 89ee793e..4734cbd7 100644 --- a/tests/integration/test_pipenv.py +++ b/tests/integration/test_pipenv.py @@ -2,13 +2,14 @@ XXX: Try our best to reduce tests in this file. """ + import os -from tempfile import gettempdir, mkdtemp +from tempfile import mkdtemp import mock import pytest -from pipenv.core import activate_virtualenv +from pipenv.utils import temp_environ from pipenv.project import Project from pipenv.vendor import delegator from pipenv._compat import Path @@ -25,15 +26,6 @@ def test_code_import_manual(PipenvInstance): assert 'requests' in p.pipfile['packages'] -@pytest.mark.code -@pytest.mark.virtualenv -@pytest.mark.project -def test_activate_virtualenv_no_source(): - command = activate_virtualenv(source=False) - venv = Project().virtualenv_location - assert command == '{0}/bin/activate'.format(venv) - - @pytest.mark.lock @pytest.mark.deploy @pytest.mark.cli @@ -103,14 +95,11 @@ def test_proper_names_unamanged_virtualenv(PipenvInstance, pypi): def test_directory_with_leading_dash(PipenvInstance): def mocked_mkdtemp(suffix, prefix, dir): if suffix == '-project': - temp_dir = Path(gettempdir()) / '-dir-with-leading-dash' - temp_dir.mkdir() - return str(temp_dir) - else: - return mkdtemp(suffix, prefix, dir) + prefix = '-dir-with-leading-dash' + return mkdtemp(suffix, prefix, dir) with mock.patch('pipenv._compat.mkdtemp', side_effect=mocked_mkdtemp): - with PipenvInstance(chdir=True) as p: + with temp_environ(), PipenvInstance(chdir=True) as p: # This environment variable is set in the context manager and will # cause pipenv to use virtualenv, not pew. del os.environ['PIPENV_VENV_IN_PROJECT']