From 3e60d98885258988978f32605b731280d282fde7 Mon Sep 17 00:00:00 2001 From: Siddhant Kumar Date: Wed, 27 Nov 2019 21:33:07 +0000 Subject: [PATCH 01/15] add activation Signed-off-by: Bernat Gabor --- setup.cfg | 7 ++ src/virtualenv/activation/__init__.py | 18 ++++ src/virtualenv/activation/activator.py | 11 +- src/virtualenv/activation/bash/__init__.py | 14 +++ src/virtualenv/activation/bash/activate.sh | 84 +++++++++++++++ src/virtualenv/activation/cshell/__init__.py | 14 +++ src/virtualenv/activation/cshell/activate.csh | 55 ++++++++++ src/virtualenv/activation/dos/__init__.py | 15 +++ src/virtualenv/activation/dos/activate.bat | 35 ++++++ src/virtualenv/activation/dos/deactivate.bat | 19 ++++ src/virtualenv/activation/fish/__init__.py | 14 +++ src/virtualenv/activation/fish/activate.fish | 102 ++++++++++++++++++ .../activation/powershell/__init__.py | 10 ++ .../activation/powershell/activate.ps1 | 60 +++++++++++ src/virtualenv/activation/python/__init__.py | 17 +++ .../activation/python/activate_this.py | 40 +++++++ src/virtualenv/activation/via_template.py | 37 +++++++ src/virtualenv/activation/xonosh/__init__.py | 14 +++ src/virtualenv/activation/xonosh/activate.xsh | 46 ++++++++ src/virtualenv/config/cli/parser.py | 2 +- src/virtualenv/run.py | 24 +++-- src/virtualenv/session.py | 2 +- tests/unit/activation/conftest.py | 22 ++++ tests/unit/activation/test_activate_this.py | 91 ++++++++++++++++ .../activation/test_activation_support.py | 49 +++++++++ .../unit/interpreters/create/test_creator.py | 2 +- 26 files changed, 789 insertions(+), 15 deletions(-) create mode 100644 src/virtualenv/activation/bash/__init__.py create mode 100644 src/virtualenv/activation/bash/activate.sh create mode 100644 src/virtualenv/activation/cshell/__init__.py create mode 100644 src/virtualenv/activation/cshell/activate.csh create mode 100644 src/virtualenv/activation/dos/__init__.py create mode 100644 src/virtualenv/activation/dos/activate.bat create mode 100644 src/virtualenv/activation/dos/deactivate.bat create mode 100644 src/virtualenv/activation/fish/__init__.py create mode 100644 src/virtualenv/activation/fish/activate.fish create mode 100644 src/virtualenv/activation/powershell/__init__.py create mode 100644 src/virtualenv/activation/powershell/activate.ps1 create mode 100644 src/virtualenv/activation/python/__init__.py create mode 100644 src/virtualenv/activation/python/activate_this.py create mode 100644 src/virtualenv/activation/via_template.py create mode 100644 src/virtualenv/activation/xonosh/__init__.py create mode 100644 src/virtualenv/activation/xonosh/activate.xsh create mode 100644 tests/unit/activation/conftest.py create mode 100644 tests/unit/activation/test_activate_this.py create mode 100644 tests/unit/activation/test_activation_support.py diff --git a/setup.cfg b/setup.cfg index 803f9bbb5..822037c48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -78,6 +78,13 @@ virtualenv.seed = pip = virtualenv.seed.embed.pip_invoke:PipInvoke link-app-data = virtualenv.seed.embed.link_app_data:LinkFromAppData virtualenv.activate = + bash = virtualenv.activation.bash:BashActivator + cshell = virtualenv.activation.cshell:CShellActivator + dos = virtualenv.activation.dos:DOSActivator + fish = virtualenv.activation.fish:FishActivator + power-shell = virtualenv.activation.powershell:PowerShellActivator + python = virtualenv.activation.python:PythonActivator + xonosh = virtualenv.activation.xonosh:XonoshActivator [sdist] formats = gztar diff --git a/src/virtualenv/activation/__init__.py b/src/virtualenv/activation/__init__.py index 01e6d4f49..54802306e 100644 --- a/src/virtualenv/activation/__init__.py +++ b/src/virtualenv/activation/__init__.py @@ -1 +1,19 @@ from __future__ import absolute_import, unicode_literals + +from .bash import BashActivator +from .cshell import CShellActivator +from .dos import DOSActivator +from .fish import FishActivator +from .powershell import PowerShellActivator +from .python import PythonActivator +from .xonosh import XonoshActivator + +__all__ = [ + BashActivator, + PowerShellActivator, + XonoshActivator, + CShellActivator, + PythonActivator, + DOSActivator, + FishActivator, +] diff --git a/src/virtualenv/activation/activator.py b/src/virtualenv/activation/activator.py index be8deed7d..d5cd10caa 100644 --- a/src/virtualenv/activation/activator.py +++ b/src/virtualenv/activation/activator.py @@ -12,13 +12,12 @@ def __init__(self, options): @classmethod def add_parser_arguments(cls, parser): - pass + """add activator options""" - def run(self, creator): - self.generate() - if self.flag_prompt is not None: - creator.pyenv_cfg["prompt"] = self.flag_prompt + @classmethod + def supports(cls, interpreter): + return True @abstractmethod - def generate(self): + def generate(self, creator): raise NotImplementedError diff --git a/src/virtualenv/activation/bash/__init__.py b/src/virtualenv/activation/bash/__init__.py new file mode 100644 index 000000000..fd5374149 --- /dev/null +++ b/src/virtualenv/activation/bash/__init__.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import, unicode_literals + +from pathlib2 import Path + +from ..via_template import ViaTemplateActivator + + +class BashActivator(ViaTemplateActivator): + @classmethod + def supports(cls, interpreter): + return interpreter.os != "nt" + + def templates(self): + yield Path("activate.sh") diff --git a/src/virtualenv/activation/bash/activate.sh b/src/virtualenv/activation/bash/activate.sh new file mode 100644 index 000000000..d9b878154 --- /dev/null +++ b/src/virtualenv/activation/bash/activate.sh @@ -0,0 +1,84 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + + +if [ "${BASH_SOURCE-}" = "$0" ]; then + echo "You must source this script: \$ source $0" >&2 + exit 33 +fi + +deactivate () { + unset -f pydoc >/dev/null 2>&1 + + # reset old environment variables + # ! [ -z ${VAR+_} ] returns true if VAR is declared at all + if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then + PATH="$_OLD_VIRTUAL_PATH" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then + PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ] ; then + hash -r 2>/dev/null + fi + + if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then + PS1="$_OLD_VIRTUAL_PS1" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + if [ ! "${1-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="__VIRTUAL_ENV__" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/__BIN_NAME__:$PATH" +export PATH + +# unset PYTHONHOME if set +if ! [ -z "${PYTHONHOME+_}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1-}" + if [ "x__VIRTUAL_PROMPT__" != x ] ; then + PS1="__VIRTUAL_PROMPT__${PS1-}" + else + PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}" + fi + export PS1 +fi + +# Make sure to unalias pydoc if it's already there +alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true + +pydoc () { + python -m pydoc "$@" +} + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ] ; then + hash -r 2>/dev/null +fi diff --git a/src/virtualenv/activation/cshell/__init__.py b/src/virtualenv/activation/cshell/__init__.py new file mode 100644 index 000000000..e818edeeb --- /dev/null +++ b/src/virtualenv/activation/cshell/__init__.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import, unicode_literals + +from pathlib2 import Path + +from ..via_template import ViaTemplateActivator + + +class CShellActivator(ViaTemplateActivator): + @classmethod + def supports(cls, interpreter): + return interpreter.os != "nt" + + def templates(self): + yield Path("activate.csh") diff --git a/src/virtualenv/activation/cshell/activate.csh b/src/virtualenv/activation/cshell/activate.csh new file mode 100644 index 000000000..c4a6d584c --- /dev/null +++ b/src/virtualenv/activation/cshell/activate.csh @@ -0,0 +1,55 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . + +set newline='\ +' + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV "__VIRTUAL_ENV__" + +set _OLD_VIRTUAL_PATH="$PATH:q" +setenv PATH "$VIRTUAL_ENV:q/__BIN_NAME__:$PATH:q" + + + +if ("__VIRTUAL_PROMPT__" != "") then + set env_name = "__VIRTUAL_PROMPT__" +else + set env_name = '('"$VIRTUAL_ENV:t:q"') ' +endif + +if ( $?VIRTUAL_ENV_DISABLE_PROMPT ) then + if ( $VIRTUAL_ENV_DISABLE_PROMPT == "" ) then + set do_prompt = "1" + else + set do_prompt = "0" + endif +else + set do_prompt = "1" +endif + +if ( $do_prompt == "1" ) then + # Could be in a non-interactive environment, + # in which case, $prompt is undefined and we wouldn't + # care about the prompt anyway. + if ( $?prompt ) then + set _OLD_VIRTUAL_PROMPT="$prompt:q" + if ( "$prompt:q" =~ *"$newline:q"* ) then + : + else + set prompt = "$env_name:q$prompt:q" + endif + endif +endif + +unset env_name +unset do_prompt + +alias pydoc python -m pydoc + +rehash diff --git a/src/virtualenv/activation/dos/__init__.py b/src/virtualenv/activation/dos/__init__.py new file mode 100644 index 000000000..3a35940d5 --- /dev/null +++ b/src/virtualenv/activation/dos/__init__.py @@ -0,0 +1,15 @@ +from __future__ import absolute_import, unicode_literals + +from pathlib2 import Path + +from ..via_template import ViaTemplateActivator + + +class DOSActivator(ViaTemplateActivator): + @classmethod + def supports(cls, interpreter): + return interpreter.os == "nt" + + def templates(self): + yield Path("activate.bat") + yield Path("deactivate.bat") diff --git a/src/virtualenv/activation/dos/activate.bat b/src/virtualenv/activation/dos/activate.bat new file mode 100644 index 000000000..96e835b52 --- /dev/null +++ b/src/virtualenv/activation/dos/activate.bat @@ -0,0 +1,35 @@ +@echo off + +set "VIRTUAL_ENV=__VIRTUAL_ENV__" + +if defined _OLD_VIRTUAL_PROMPT ( + set "PROMPT=%_OLD_VIRTUAL_PROMPT%" +) else ( + if not defined PROMPT ( + set "PROMPT=$P$G" + ) + if not defined VIRTUAL_ENV_DISABLE_PROMPT ( + set "_OLD_VIRTUAL_PROMPT=%PROMPT%" + ) +) +if not defined VIRTUAL_ENV_DISABLE_PROMPT ( + set "PROMPT=__VIRTUAL_PROMPT__%PROMPT%" +) + +REM Don't use () to avoid problems with them in %PATH% +if defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME + set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%" +:ENDIFVHOME + +set PYTHONHOME= + +REM if defined _OLD_VIRTUAL_PATH ( +if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH1 + set "PATH=%_OLD_VIRTUAL_PATH%" +:ENDIFVPATH1 +REM ) else ( +if defined _OLD_VIRTUAL_PATH goto ENDIFVPATH2 + set "_OLD_VIRTUAL_PATH=%PATH%" +:ENDIFVPATH2 + +set "PATH=%VIRTUAL_ENV%\__BIN_NAME__;%PATH%" diff --git a/src/virtualenv/activation/dos/deactivate.bat b/src/virtualenv/activation/dos/deactivate.bat new file mode 100644 index 000000000..7bbc56882 --- /dev/null +++ b/src/virtualenv/activation/dos/deactivate.bat @@ -0,0 +1,19 @@ +@echo off + +set VIRTUAL_ENV= + +REM Don't use () to avoid problems with them in %PATH% +if not defined _OLD_VIRTUAL_PROMPT goto ENDIFVPROMPT + set "PROMPT=%_OLD_VIRTUAL_PROMPT%" + set _OLD_VIRTUAL_PROMPT= +:ENDIFVPROMPT + +if not defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME + set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%" + set _OLD_VIRTUAL_PYTHONHOME= +:ENDIFVHOME + +if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH + set "PATH=%_OLD_VIRTUAL_PATH%" + set _OLD_VIRTUAL_PATH= +:ENDIFVPATH diff --git a/src/virtualenv/activation/fish/__init__.py b/src/virtualenv/activation/fish/__init__.py new file mode 100644 index 000000000..0e544068b --- /dev/null +++ b/src/virtualenv/activation/fish/__init__.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import, unicode_literals + +from pathlib2 import Path + +from ..via_template import ViaTemplateActivator + + +class FishActivator(ViaTemplateActivator): + def templates(self): + yield Path("activate.fish") + + @classmethod + def supports(cls, interpreter): + return interpreter.os != "nt" diff --git a/src/virtualenv/activation/fish/activate.fish b/src/virtualenv/activation/fish/activate.fish new file mode 100644 index 000000000..4e2976864 --- /dev/null +++ b/src/virtualenv/activation/fish/activate.fish @@ -0,0 +1,102 @@ +# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*. +# Do not run it directly. + +function _bashify_path -d "Converts a fish path to something bash can recognize" + set fishy_path $argv + set bashy_path $fishy_path[1] + for path_part in $fishy_path[2..-1] + set bashy_path "$bashy_path:$path_part" + end + echo $bashy_path +end + +function _fishify_path -d "Converts a bash path to something fish can recognize" + echo $argv | tr ':' '\n' +end + +function deactivate -d 'Exit virtualenv mode and return to the normal environment.' + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + # https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling + if test (echo $FISH_VERSION | tr "." "\n")[1] -lt 3 + set -gx PATH (_fishify_path $_OLD_VIRTUAL_PATH) + else + set -gx PATH $_OLD_VIRTUAL_PATH + end + set -e _OLD_VIRTUAL_PATH + end + + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + and functions -q _old_fish_prompt + # Set an empty local `$fish_function_path` to allow the removal of `fish_prompt` using `functions -e`. + set -l fish_function_path + + # Erase virtualenv's `fish_prompt` and restore the original. + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + set -e _OLD_FISH_PROMPT_OVERRIDE + end + + set -e VIRTUAL_ENV + + if test "$argv[1]" != 'nondestructive' + # Self-destruct! + functions -e pydoc + functions -e deactivate + functions -e _bashify_path + functions -e _fishify_path + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV "__VIRTUAL_ENV__" + +# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling +if test (echo $FISH_VERSION | tr "." "\n")[1] -lt 3 + set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH) +else + set -gx _OLD_VIRTUAL_PATH $PATH +end +set -gx PATH "$VIRTUAL_ENV/__BIN_NAME__" $PATH + +# Unset `$PYTHONHOME` if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +function pydoc + python -m pydoc $argv +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # Copy the current `fish_prompt` function as `_old_fish_prompt`. + functions -c fish_prompt _old_fish_prompt + + function fish_prompt + # Save the current $status, for fish_prompts that display it. + set -l old_status $status + + # Prompt override provided? + # If not, just prepend the environment name. + if test -n "__VIRTUAL_PROMPT__" + printf '%s%s' "__VIRTUAL_PROMPT__" (set_color normal) + else + printf '%s(%s) ' (set_color normal) (basename "$VIRTUAL_ENV") + end + + # Restore the original $status + echo "exit $old_status" | source + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" +end diff --git a/src/virtualenv/activation/powershell/__init__.py b/src/virtualenv/activation/powershell/__init__.py new file mode 100644 index 000000000..b5b0a7527 --- /dev/null +++ b/src/virtualenv/activation/powershell/__init__.py @@ -0,0 +1,10 @@ +from __future__ import absolute_import, unicode_literals + +from pathlib2 import Path + +from ..via_template import ViaTemplateActivator + + +class PowerShellActivator(ViaTemplateActivator): + def templates(self): + yield Path("activate.ps1") diff --git a/src/virtualenv/activation/powershell/activate.ps1 b/src/virtualenv/activation/powershell/activate.ps1 new file mode 100644 index 000000000..85b210308 --- /dev/null +++ b/src/virtualenv/activation/powershell/activate.ps1 @@ -0,0 +1,60 @@ +$script:THIS_PATH = $myinvocation.mycommand.path +$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent + +function global:deactivate([switch] $NonDestructive) { + if (Test-Path variable:_OLD_VIRTUAL_PATH) { + $env:PATH = $variable:_OLD_VIRTUAL_PATH + Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global + } + + if (Test-Path function:_old_virtual_prompt) { + $function:prompt = $function:_old_virtual_prompt + Remove-Item function:\_old_virtual_prompt + } + + if ($env:VIRTUAL_ENV) { + Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue + } + + if (!$NonDestructive) { + # Self destruct! + Remove-Item function:deactivate + Remove-Item function:pydoc + } +} + +function global:pydoc { + python -m pydoc $args +} + +# unset irrelevant variables +deactivate -nondestructive + +$VIRTUAL_ENV = $BASE_DIR +$env:VIRTUAL_ENV = $VIRTUAL_ENV + +New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH + +$env:PATH = "$env:VIRTUAL_ENV/__BIN_NAME____PATH_SEP__" + $env:PATH +if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) { + function global:_old_virtual_prompt { + "" + } + $function:_old_virtual_prompt = $function:prompt + + if ("__VIRTUAL_PROMPT__" -ne "") { + function global:prompt { + # Add the custom prefix to the existing prompt + $previous_prompt_value = & $function:_old_virtual_prompt + ("__VIRTUAL_PROMPT__" + $previous_prompt_value) + } + } + else { + function global:prompt { + # Add a prefix to the current prompt, but don't discard it. + $previous_prompt_value = & $function:_old_virtual_prompt + $new_prompt_value = "($( Split-Path $env:VIRTUAL_ENV -Leaf )) " + ($new_prompt_value + $previous_prompt_value) + } + } +} diff --git a/src/virtualenv/activation/python/__init__.py b/src/virtualenv/activation/python/__init__.py new file mode 100644 index 000000000..2dae8aca7 --- /dev/null +++ b/src/virtualenv/activation/python/__init__.py @@ -0,0 +1,17 @@ +from __future__ import absolute_import, unicode_literals + +import json + +from pathlib2 import Path + +from ..via_template import ViaTemplateActivator + + +class PythonActivator(ViaTemplateActivator): + def templates(self): + yield Path("activate_this.py") + + def replacements(self, creator): + replacements = super().replacements(creator) + replacements.update({"__SITE_PACKAGES__": json.dumps(list(str(i) for i in creator.site_packages))}) + return replacements diff --git a/src/virtualenv/activation/python/activate_this.py b/src/virtualenv/activation/python/activate_this.py new file mode 100644 index 000000000..88a3ddc42 --- /dev/null +++ b/src/virtualenv/activation/python/activate_this.py @@ -0,0 +1,40 @@ +"""Activate virtualenv for current interpreter: + +Use exec(open(this_file).read(), {'__file__': this_file}). + +This can be used when you must use an existing Python interpreter, not the virtualenv bin/python. +""" +import json +import os +import site +import sys + +try: + __file__ +except NameError: + raise AssertionError("You must use exec(open(this_file).read(), {'__file__': this_file}))") + +# prepend bin to PATH (this file is inside the bin directory) +bin_dir = os.path.dirname(os.path.abspath(__file__)) +os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep)) + +base = os.path.dirname(bin_dir) + +# virtual env is right above bin directory +os.environ["VIRTUAL_ENV"] = base + +# add the virtual environments site-packages to the host python import mechanism +prev = set(sys.path) + +# fmt: off +# turn formatter off as json dumps will contain " characters - so we really need here ' black +for site_package in json.loads('__SITE_PACKAGES__'): + site.addsitedir(site_package) +# fmt: on + +sys.real_prefix = sys.prefix +sys.prefix = base + +# Move the added items to the front of the path, in place +new = list(sys.path) +sys.path[:] = [i for i in new if i not in prev] + [i for i in new if i in prev] diff --git a/src/virtualenv/activation/via_template.py b/src/virtualenv/activation/via_template.py new file mode 100644 index 000000000..8c3a1e739 --- /dev/null +++ b/src/virtualenv/activation/via_template.py @@ -0,0 +1,37 @@ +from __future__ import absolute_import, unicode_literals + +import os +import pkgutil +from abc import ABCMeta, abstractmethod + +import six + +from .activator import Activator + + +@six.add_metaclass(ABCMeta) +class ViaTemplateActivator(Activator): + @abstractmethod + def templates(self): + raise NotImplementedError + + def generate(self, creator): + self._generate(self.replacements(creator), self.templates(), creator.bin_dir) + if self.flag_prompt is not None: + creator.pyenv_cfg["prompt"] = self.flag_prompt + + def replacements(self, creator): + return { + "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt, + "__VIRTUAL_ENV__": str(creator.env_dir), + "__VIRTUAL_NAME__": str(creator.env_name), + "__BIN_NAME__": str(creator.bin_name), + "__PATH_SEP__": os.pathsep, + } + + def _generate(self, replacements, templates, to_folder): + for template in templates: + text = pkgutil.get_data(self.__module__, str(template)).decode("utf-8") + for start, end in replacements.items(): + text = text.replace(start, end) + (to_folder / template).write_text(text) diff --git a/src/virtualenv/activation/xonosh/__init__.py b/src/virtualenv/activation/xonosh/__init__.py new file mode 100644 index 000000000..184851106 --- /dev/null +++ b/src/virtualenv/activation/xonosh/__init__.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import, unicode_literals + +from pathlib2 import Path + +from ..via_template import ViaTemplateActivator + + +class XonoshActivator(ViaTemplateActivator): + def templates(self): + yield Path("activate.xsh") + + @classmethod + def supports(cls, interpreter): + return True if interpreter.version_info >= (3, 4) else False diff --git a/src/virtualenv/activation/xonosh/activate.xsh b/src/virtualenv/activation/xonosh/activate.xsh new file mode 100644 index 000000000..c77ea6278 --- /dev/null +++ b/src/virtualenv/activation/xonosh/activate.xsh @@ -0,0 +1,46 @@ +"""Xonsh activate script for virtualenv""" +from xonsh.tools import get_sep as _get_sep + +def _deactivate(args): + if "pydoc" in aliases: + del aliases["pydoc"] + + if ${...}.get("_OLD_VIRTUAL_PATH", ""): + $PATH = $_OLD_VIRTUAL_PATH + del $_OLD_VIRTUAL_PATH + + if ${...}.get("_OLD_VIRTUAL_PYTHONHOME", ""): + $PYTHONHOME = $_OLD_VIRTUAL_PYTHONHOME + del $_OLD_VIRTUAL_PYTHONHOME + + if "VIRTUAL_ENV" in ${...}: + del $VIRTUAL_ENV + + if "VIRTUAL_ENV_PROMPT" in ${...}: + del $VIRTUAL_ENV_PROMPT + + if "nondestructive" not in args: + # Self destruct! + del aliases["deactivate"] + + +# unset irrelevant variables +_deactivate(["nondestructive"]) +aliases["deactivate"] = _deactivate + +$VIRTUAL_ENV = r"__VIRTUAL_ENV__" + +$_OLD_VIRTUAL_PATH = $PATH +$PATH = $PATH[:] +$PATH.add($VIRTUAL_ENV + _get_sep() + "__BIN_NAME__", front=True, replace=True) + +if ${...}.get("PYTHONHOME", ""): + # unset PYTHONHOME if set + $_OLD_VIRTUAL_PYTHONHOME = $PYTHONHOME + del $PYTHONHOME + +$VIRTUAL_ENV_PROMPT = "__VIRTUAL_PROMPT__" +if not $VIRTUAL_ENV_PROMPT: + del $VIRTUAL_ENV_PROMPT + +aliases["pydoc"] = ["python", "-m", "pydoc"] diff --git a/src/virtualenv/config/cli/parser.py b/src/virtualenv/config/cli/parser.py index 083bda823..e817a423e 100644 --- a/src/virtualenv/config/cli/parser.py +++ b/src/virtualenv/config/cli/parser.py @@ -52,7 +52,7 @@ def parse_args(self, args=None, namespace=None): class HelpFormatter(ArgumentDefaultsHelpFormatter): def __init__(self, prog): - super(HelpFormatter, self).__init__(prog, max_help_position=35, width=240) + super(HelpFormatter, self).__init__(prog, max_help_position=37, width=240) def _get_help_string(self, action): # noinspection PyProtectedMember diff --git a/src/virtualenv/run.py b/src/virtualenv/run.py index e6f0a227a..a9037d7a8 100644 --- a/src/virtualenv/run.py +++ b/src/virtualenv/run.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, unicode_literals import logging +from argparse import ArgumentTypeError from entrypoints import get_group_named @@ -134,17 +135,28 @@ def _collect_seeders(): def _get_activation(interpreter, parser, options): activator_parser = parser.add_argument_group("activation script generator") - activators = _collect_activators(interpreter) + compatible = _collect_activators(interpreter) + default = ",".join(compatible.keys()) + + def _extract_activators(entered_str): + elements = [e.strip() for e in entered_str.split(",") if e.strip()] + missing = [e for e in elements if e not in compatible] + if missing: + raise ArgumentTypeError("the following activators are not available {}".format(",".join(missing))) + return elements + activator_parser.add_argument( "--activators", - choices=list(activators.keys()), - default=list(activators.keys()), + default=default, + metavar="comma_separated_list", required=False, - nargs="*", - help="activators to generate", + help="activators to generate together with virtual environment - default is all available and compatible", + type=_extract_activators, ) yield - active_activators = {k: v for k, v in activators.items() if k in options.activators} + + selected_activators = _extract_activators(default) if options.activators is default else options.activators + active_activators = {k: v for k, v in compatible.items() if k in selected_activators} activator_parser.add_argument( "--prompt", dest="prompt", diff --git a/src/virtualenv/session.py b/src/virtualenv/session.py index 74a50f2af..d69fc8fd6 100644 --- a/src/virtualenv/session.py +++ b/src/virtualenv/session.py @@ -29,7 +29,7 @@ def _seed(self): def _activate(self): for activator in self.activators: - activator.run(self.creator) + activator.generate(self.creator) _DEBUG_MARKER = "=" * 30 + " target debug " + "=" * 30 diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py new file mode 100644 index 000000000..a0aa075e9 --- /dev/null +++ b/tests/unit/activation/conftest.py @@ -0,0 +1,22 @@ +from __future__ import absolute_import, unicode_literals + +import os + +import pytest +from pathlib2 import Path + +from virtualenv.run import run_via_cli + + +@pytest.fixture(scope="session") +def activation_python(tmp_path_factory): + path = tmp_path_factory.mktemp("activation-test-env") + prev_cwd = os.getcwd() + try: + os.chdir(str(path)) + session = run_via_cli([str(path), "--seeder", "none"]) + pydoc_test = Path(session.creator.site_packages[0]) / "pydoc_test.py" + pydoc_test.write_text('"""This is pydoc_test.py"""') + yield session, pydoc_test + finally: + os.chdir(str(prev_cwd)) diff --git a/tests/unit/activation/test_activate_this.py b/tests/unit/activation/test_activate_this.py new file mode 100644 index 000000000..450b4f947 --- /dev/null +++ b/tests/unit/activation/test_activate_this.py @@ -0,0 +1,91 @@ +from __future__ import absolute_import, unicode_literals + +import os +import re +import subprocess +import sys +import textwrap + + +def test_activate_this(activation_python, tmp_path, monkeypatch): + # to test this, we'll try to use the activation env from this Python + session, pydoc_test_path = activation_python + monkeypatch.delenv(str("VIRTUAL_ENV"), raising=False) + monkeypatch.delenv(str("PYTHONPATH"), raising=False) + paths = [str(tmp_path), str(tmp_path / "other")] + start_path = os.pathsep.join(paths) + monkeypatch.setenv(str("PATH"), start_path) + activator = tmp_path.__class__(session.creator.bin_dir) / "activate_this.py" + assert activator.exists() + + activator_at = str(activator) + script = textwrap.dedent( + """ + import os + import sys + print(os.environ.get("VIRTUAL_ENV")) + print(os.environ.get("PATH")) + try: + import pydoc_test + raise RuntimeError("this should not happen") + except ImportError: + pass + print(os.pathsep.join(sys.path)) + file_at = {!r} + exec(open(file_at).read(), {{'__file__': file_at}}) + print(os.environ.get("VIRTUAL_ENV")) + print(os.environ.get("PATH")) + print(os.pathsep.join(sys.path)) + import pydoc_test + print(pydoc_test.__file__) + """.format( + str(activator_at) + ) + ) + script_path = tmp_path / "test.py" + script_path.write_text(script) + try: + raw = subprocess.check_output( + [sys.executable, str(script_path)], stderr=subprocess.STDOUT, universal_newlines=True + ) + + out = re.sub(r"pydev debugger: process \d+ is connecting\n\n", "", raw, re.M).strip().split("\n") + + assert out[0] == "None" + assert out[1] == start_path + prev_sys_path = out[2].split(os.path.pathsep) + + assert out[3] == str(session.creator.env_dir) # virtualenv set as the activated env + + # PATH updated with activated + assert out[4].endswith(str(start_path)) + assert out[4][: -len(start_path)].split(os.pathsep) == [str(session.creator.bin_dir), ""] + + # sys path contains the site package at its start + new_sys_path = out[5].split(os.path.pathsep) + assert new_sys_path[-len(prev_sys_path) :] == prev_sys_path + extra_start = new_sys_path[0 : -len(prev_sys_path)] + assert len(extra_start) == 1 + assert extra_start[0].startswith(str(session.creator.env_dir)) + assert tmp_path.__class__(extra_start[0]).exists() + + # manage to import from activate site package + assert os.path.realpath(out[6]) == os.path.realpath(str(pydoc_test_path)) + except subprocess.CalledProcessError as exception: + assert not exception.returncode, exception.output + + +def test_activate_this_no_file(activation_python, tmp_path): + session, _ = activation_python + activator = tmp_path.__class__(session.creator.bin_dir) / "activate_this.py" + assert activator.exists() + try: + subprocess.check_output( + [sys.executable, "-c", "exec(open({!r}).read())".format(str(activator))], + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + raise RuntimeError("this should not happen") + except subprocess.CalledProcessError as exception: + out = re.sub(r"pydev debugger: process \d+ is connecting\n\n", "", exception.output, re.M).strip() + assert "You must use exec(open(this_file).read(), {'__file__': this_file}))" in out, out diff --git a/tests/unit/activation/test_activation_support.py b/tests/unit/activation/test_activation_support.py new file mode 100644 index 000000000..2be20db75 --- /dev/null +++ b/tests/unit/activation/test_activation_support.py @@ -0,0 +1,49 @@ +from types import SimpleNamespace + +import pytest + +from virtualenv.activation import ( + BashActivator, + CShellActivator, + DOSActivator, + FishActivator, + PowerShellActivator, + PythonActivator, +) +from virtualenv.interpreters.discovery.py_info import PythonInfo + + +@pytest.mark.parametrize("activator_class", [DOSActivator, PowerShellActivator, PythonActivator]) +def test_activator_support_windows(mocker, activator_class): + activator = activator_class(SimpleNamespace(prompt=None)) + + interpreter = mocker.Mock(spec=PythonInfo) + interpreter.os = "nt" + assert activator.supports(interpreter) + + +@pytest.mark.parametrize("activator_class", [BashActivator, CShellActivator, FishActivator]) +def test_activator_no_support_windows(mocker, activator_class): + activator = activator_class(SimpleNamespace(prompt=None)) + + interpreter = mocker.Mock(spec=PythonInfo) + interpreter.os = "nt" + assert not activator.supports(interpreter) + + +@pytest.mark.parametrize( + "activator_class", [BashActivator, CShellActivator, FishActivator, PowerShellActivator, PythonActivator] +) +def test_activator_support_posix(mocker, activator_class): + activator = activator_class(SimpleNamespace(prompt=None)) + interpreter = mocker.Mock(spec=PythonInfo) + interpreter.os = "posix" + assert activator.supports(interpreter) + + +@pytest.mark.parametrize("activator_class", [DOSActivator]) +def test_activator_no_support_posix(mocker, activator_class): + activator = activator_class(SimpleNamespace(prompt=None)) + interpreter = mocker.Mock(spec=PythonInfo) + interpreter.os = "posix" + assert not activator.supports(interpreter) diff --git a/tests/unit/interpreters/create/test_creator.py b/tests/unit/interpreters/create/test_creator.py index 4735ab1fe..a85d158f5 100644 --- a/tests/unit/interpreters/create/test_creator.py +++ b/tests/unit/interpreters/create/test_creator.py @@ -80,7 +80,7 @@ def cleanup_sys_path(path): "use_venv", [False, True] if six.PY3 else [False], ids=["no_venv", "venv"] if six.PY3 else ["no_venv"] ) def test_create_no_seed(python, use_venv, global_access, tmp_path, coverage_env): - cmd = ["-v", "-v", "-p", str(python), str(tmp_path), "--without-pip"] + cmd = ["-v", "-v", "-p", str(python), str(tmp_path), "--without-pip", "--activators", ""] if global_access: cmd.append("--system-site-packages") if use_venv: From a1a635ed2b64bacf4a0d906ed60fa9f4dcdb3172 Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Fri, 29 Nov 2019 14:05:46 +0000 Subject: [PATCH 02/15] fix activation script packaging Signed-off-by: Bernat Gabor --- setup.cfg | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.cfg b/setup.cfg index 822037c48..c85777e7b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,6 +61,12 @@ docs = [options.package_data] virtualenv.seed.embed.wheels = *.whl +virtualenv.activation.bash = *.sh +virtualenv.activation.cshell = *.csh +virtualenv.activation.dos = *.bat +virtualenv.activation.fish = *.fish +virtualenv.activation.powershell = *.ps1 +virtualenv.activation.xonosh = *.xsh [options.entry_points] console_scripts = From 2bc06697d06cd26e22f26d643f52b172506155f6 Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Fri, 29 Nov 2019 14:26:12 +0000 Subject: [PATCH 03/15] python 2 compatibility Signed-off-by: Bernat Gabor --- src/virtualenv/activation/python/__init__.py | 2 +- tests/unit/activation/test_activation_support.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/virtualenv/activation/python/__init__.py b/src/virtualenv/activation/python/__init__.py index 2dae8aca7..68e76f88e 100644 --- a/src/virtualenv/activation/python/__init__.py +++ b/src/virtualenv/activation/python/__init__.py @@ -12,6 +12,6 @@ def templates(self): yield Path("activate_this.py") def replacements(self, creator): - replacements = super().replacements(creator) + replacements = super(PythonActivator, self).replacements(creator) replacements.update({"__SITE_PACKAGES__": json.dumps(list(str(i) for i in creator.site_packages))}) return replacements diff --git a/tests/unit/activation/test_activation_support.py b/tests/unit/activation/test_activation_support.py index 2be20db75..74588c613 100644 --- a/tests/unit/activation/test_activation_support.py +++ b/tests/unit/activation/test_activation_support.py @@ -1,4 +1,4 @@ -from types import SimpleNamespace +from argparse import Namespace import pytest @@ -15,7 +15,7 @@ @pytest.mark.parametrize("activator_class", [DOSActivator, PowerShellActivator, PythonActivator]) def test_activator_support_windows(mocker, activator_class): - activator = activator_class(SimpleNamespace(prompt=None)) + activator = activator_class(Namespace(prompt=None)) interpreter = mocker.Mock(spec=PythonInfo) interpreter.os = "nt" @@ -24,7 +24,7 @@ def test_activator_support_windows(mocker, activator_class): @pytest.mark.parametrize("activator_class", [BashActivator, CShellActivator, FishActivator]) def test_activator_no_support_windows(mocker, activator_class): - activator = activator_class(SimpleNamespace(prompt=None)) + activator = activator_class(Namespace(prompt=None)) interpreter = mocker.Mock(spec=PythonInfo) interpreter.os = "nt" @@ -35,7 +35,7 @@ def test_activator_no_support_windows(mocker, activator_class): "activator_class", [BashActivator, CShellActivator, FishActivator, PowerShellActivator, PythonActivator] ) def test_activator_support_posix(mocker, activator_class): - activator = activator_class(SimpleNamespace(prompt=None)) + activator = activator_class(Namespace(prompt=None)) interpreter = mocker.Mock(spec=PythonInfo) interpreter.os = "posix" assert activator.supports(interpreter) @@ -43,7 +43,7 @@ def test_activator_support_posix(mocker, activator_class): @pytest.mark.parametrize("activator_class", [DOSActivator]) def test_activator_no_support_posix(mocker, activator_class): - activator = activator_class(SimpleNamespace(prompt=None)) + activator = activator_class(Namespace(prompt=None)) interpreter = mocker.Mock(spec=PythonInfo) interpreter.os = "posix" assert not activator.supports(interpreter) From 272056b63524d784abd782e90fb3d663bfb46876 Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Fri, 29 Nov 2019 14:28:15 +0000 Subject: [PATCH 04/15] activate this windows compatible Signed-off-by: Bernat Gabor --- src/virtualenv/activation/python/__init__.py | 2 +- src/virtualenv/activation/python/activate_this.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/virtualenv/activation/python/__init__.py b/src/virtualenv/activation/python/__init__.py index 68e76f88e..4c763730c 100644 --- a/src/virtualenv/activation/python/__init__.py +++ b/src/virtualenv/activation/python/__init__.py @@ -13,5 +13,5 @@ def templates(self): def replacements(self, creator): replacements = super(PythonActivator, self).replacements(creator) - replacements.update({"__SITE_PACKAGES__": json.dumps(list(str(i) for i in creator.site_packages))}) + replacements.update({"__SITE_PACKAGES__": json.dumps(list(str(i) for i in creator.site_packages), indent=2)}) return replacements diff --git a/src/virtualenv/activation/python/activate_this.py b/src/virtualenv/activation/python/activate_this.py index 88a3ddc42..f3cc2c908 100644 --- a/src/virtualenv/activation/python/activate_this.py +++ b/src/virtualenv/activation/python/activate_this.py @@ -28,7 +28,10 @@ # fmt: off # turn formatter off as json dumps will contain " characters - so we really need here ' black -for site_package in json.loads('__SITE_PACKAGES__'): +site_packages = r''' +__SITE_PACKAGES__ +''' +for site_package in json.loads(site_packages): site.addsitedir(site_package) # fmt: on From eb64b9fba73365eee8c286c8a97aeec969c76a94 Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Fri, 29 Nov 2019 15:28:27 +0000 Subject: [PATCH 05/15] allow python activator to be relocate-able Signed-off-by: Bernat Gabor --- src/virtualenv/activation/python/__init__.py | 8 +- .../activation/python/activate_this.py | 4 +- src/virtualenv/activation/via_template.py | 7 +- .../interpreters/create/cpython/common.py | 4 +- src/virtualenv/interpreters/create/creator.py | 12 +- src/virtualenv/run.py | 4 +- src/virtualenv/seed/embed/link_app_data.py | 2 +- tests/unit/activation/conftest.py | 22 -- tests/unit/activation/test_activate_this.py | 91 ------ tests/unit/activation/test_activation.py | 272 ++++++++++++++++++ .../test_boostrap_link_via_app_data.py | 2 +- 11 files changed, 294 insertions(+), 134 deletions(-) delete mode 100644 tests/unit/activation/test_activate_this.py create mode 100644 tests/unit/activation/test_activation.py diff --git a/src/virtualenv/activation/python/__init__.py b/src/virtualenv/activation/python/__init__.py index 4c763730c..1d73e9969 100644 --- a/src/virtualenv/activation/python/__init__.py +++ b/src/virtualenv/activation/python/__init__.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, unicode_literals import json +import os from pathlib2 import Path @@ -11,7 +12,8 @@ class PythonActivator(ViaTemplateActivator): def templates(self): yield Path("activate_this.py") - def replacements(self, creator): - replacements = super(PythonActivator, self).replacements(creator) - replacements.update({"__SITE_PACKAGES__": json.dumps(list(str(i) for i in creator.site_packages), indent=2)}) + def replacements(self, creator, dest_folder): + replacements = super(PythonActivator, self).replacements(creator, dest_folder) + site_dump = json.dumps([os.path.relpath(str(i), str(dest_folder)) for i in creator.site_packages], indent=2) + replacements.update({"__SITE_PACKAGES__": site_dump}) return replacements diff --git a/src/virtualenv/activation/python/activate_this.py b/src/virtualenv/activation/python/activate_this.py index f3cc2c908..fc8d449e7 100644 --- a/src/virtualenv/activation/python/activate_this.py +++ b/src/virtualenv/activation/python/activate_this.py @@ -31,8 +31,10 @@ site_packages = r''' __SITE_PACKAGES__ ''' + for site_package in json.loads(site_packages): - site.addsitedir(site_package) + path = os.path.realpath(os.path.join(os.path.dirname(__file__), site_package)) + site.addsitedir(path) # fmt: on sys.real_prefix = sys.prefix diff --git a/src/virtualenv/activation/via_template.py b/src/virtualenv/activation/via_template.py index 8c3a1e739..864454f95 100644 --- a/src/virtualenv/activation/via_template.py +++ b/src/virtualenv/activation/via_template.py @@ -16,14 +16,15 @@ def templates(self): raise NotImplementedError def generate(self, creator): - self._generate(self.replacements(creator), self.templates(), creator.bin_dir) + dest_folder = creator.bin_dir + self._generate(self.replacements(creator, dest_folder), self.templates(), dest_folder) if self.flag_prompt is not None: creator.pyenv_cfg["prompt"] = self.flag_prompt - def replacements(self, creator): + def replacements(self, creator, dest_folder): return { "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt, - "__VIRTUAL_ENV__": str(creator.env_dir), + "__VIRTUAL_ENV__": str(creator.dest_dir), "__VIRTUAL_NAME__": str(creator.env_name), "__BIN_NAME__": str(creator.bin_name), "__PATH_SEP__": os.pathsep, diff --git a/src/virtualenv/interpreters/create/cpython/common.py b/src/virtualenv/interpreters/create/cpython/common.py index b322be192..d05d47bae 100644 --- a/src/virtualenv/interpreters/create/cpython/common.py +++ b/src/virtualenv/interpreters/create/cpython/common.py @@ -34,7 +34,7 @@ def create(self): self.system_site_package = true_system_site def ensure_directories(self): - dirs = [self.env_dir, self.bin_dir] + dirs = [self.dest_dir, self.bin_dir] dirs.extend(self.site_packages) return dirs @@ -53,7 +53,7 @@ def lib_base(self): @property def lib_dir(self): - return self.env_dir / self.lib_base + return self.dest_dir / self.lib_base @property def system_stdlib(self): diff --git a/src/virtualenv/interpreters/create/creator.py b/src/virtualenv/interpreters/create/creator.py index 079aad933..872d8d10b 100644 --- a/src/virtualenv/interpreters/create/creator.py +++ b/src/virtualenv/interpreters/create/creator.py @@ -102,13 +102,9 @@ def set_pyenv_cfg(self): "virtualenv": __version__, } - @property - def env_dir(self): - return Path(self.dest_dir) - @property def env_name(self): - return self.env_dir.parts[-1] + return self.dest_dir.parts[-1] @property def bin_name(self): @@ -116,7 +112,7 @@ def bin_name(self): @property def bin_dir(self): - return self.env_dir / self.bin_name + return self.dest_dir / self.bin_name @property def lib_dir(self): @@ -127,13 +123,13 @@ def site_packages(self): return [self.lib_dir / "site-packages"] @property - def env_exe(self): + def exe(self): return self.bin_dir / "python{}".format(".exe" if IS_WIN else "") @property def debug(self): if self._debug is None: - self._debug = get_env_debug_info(self.env_exe, self.debug_script()) + self._debug = get_env_debug_info(self.exe, self.debug_script()) return self._debug # noinspection PyMethodMayBeStatic diff --git a/src/virtualenv/run.py b/src/virtualenv/run.py index a9037d7a8..96ad7d708 100644 --- a/src/virtualenv/run.py +++ b/src/virtualenv/run.py @@ -135,7 +135,7 @@ def _collect_seeders(): def _get_activation(interpreter, parser, options): activator_parser = parser.add_argument_group("activation script generator") - compatible = _collect_activators(interpreter) + compatible = collect_activators(interpreter) default = ",".join(compatible.keys()) def _extract_activators(entered_str): @@ -172,7 +172,7 @@ def _extract_activators(entered_str): yield activator_instances -def _collect_activators(interpreter): +def collect_activators(interpreter): all_activators = {e.name: e.load() for e in get_group_named("virtualenv.activate").values()} activators = {k: v for k, v in all_activators.items() if v.supports(interpreter)} return activators diff --git a/src/virtualenv/seed/embed/link_app_data.py b/src/virtualenv/seed/embed/link_app_data.py index f961a4c3c..c400da718 100644 --- a/src/virtualenv/seed/embed/link_app_data.py +++ b/src/virtualenv/seed/embed/link_app_data.py @@ -35,7 +35,7 @@ def run(self, creator): def pip_install(wheels, creator, cache): - site_package, bin_dir, env_exe = creator.site_packages[0], creator.bin_dir, creator.env_exe + site_package, bin_dir, env_exe = creator.site_packages[0], creator.bin_dir, creator.exe folder_link_method, folder_linked = link_folder() for name, wheel in wheels.items(): logging.debug("install %s from wheel %s", name, wheel) diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py index a0aa075e9..e69de29bb 100644 --- a/tests/unit/activation/conftest.py +++ b/tests/unit/activation/conftest.py @@ -1,22 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import os - -import pytest -from pathlib2 import Path - -from virtualenv.run import run_via_cli - - -@pytest.fixture(scope="session") -def activation_python(tmp_path_factory): - path = tmp_path_factory.mktemp("activation-test-env") - prev_cwd = os.getcwd() - try: - os.chdir(str(path)) - session = run_via_cli([str(path), "--seeder", "none"]) - pydoc_test = Path(session.creator.site_packages[0]) / "pydoc_test.py" - pydoc_test.write_text('"""This is pydoc_test.py"""') - yield session, pydoc_test - finally: - os.chdir(str(prev_cwd)) diff --git a/tests/unit/activation/test_activate_this.py b/tests/unit/activation/test_activate_this.py deleted file mode 100644 index 450b4f947..000000000 --- a/tests/unit/activation/test_activate_this.py +++ /dev/null @@ -1,91 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import os -import re -import subprocess -import sys -import textwrap - - -def test_activate_this(activation_python, tmp_path, monkeypatch): - # to test this, we'll try to use the activation env from this Python - session, pydoc_test_path = activation_python - monkeypatch.delenv(str("VIRTUAL_ENV"), raising=False) - monkeypatch.delenv(str("PYTHONPATH"), raising=False) - paths = [str(tmp_path), str(tmp_path / "other")] - start_path = os.pathsep.join(paths) - monkeypatch.setenv(str("PATH"), start_path) - activator = tmp_path.__class__(session.creator.bin_dir) / "activate_this.py" - assert activator.exists() - - activator_at = str(activator) - script = textwrap.dedent( - """ - import os - import sys - print(os.environ.get("VIRTUAL_ENV")) - print(os.environ.get("PATH")) - try: - import pydoc_test - raise RuntimeError("this should not happen") - except ImportError: - pass - print(os.pathsep.join(sys.path)) - file_at = {!r} - exec(open(file_at).read(), {{'__file__': file_at}}) - print(os.environ.get("VIRTUAL_ENV")) - print(os.environ.get("PATH")) - print(os.pathsep.join(sys.path)) - import pydoc_test - print(pydoc_test.__file__) - """.format( - str(activator_at) - ) - ) - script_path = tmp_path / "test.py" - script_path.write_text(script) - try: - raw = subprocess.check_output( - [sys.executable, str(script_path)], stderr=subprocess.STDOUT, universal_newlines=True - ) - - out = re.sub(r"pydev debugger: process \d+ is connecting\n\n", "", raw, re.M).strip().split("\n") - - assert out[0] == "None" - assert out[1] == start_path - prev_sys_path = out[2].split(os.path.pathsep) - - assert out[3] == str(session.creator.env_dir) # virtualenv set as the activated env - - # PATH updated with activated - assert out[4].endswith(str(start_path)) - assert out[4][: -len(start_path)].split(os.pathsep) == [str(session.creator.bin_dir), ""] - - # sys path contains the site package at its start - new_sys_path = out[5].split(os.path.pathsep) - assert new_sys_path[-len(prev_sys_path) :] == prev_sys_path - extra_start = new_sys_path[0 : -len(prev_sys_path)] - assert len(extra_start) == 1 - assert extra_start[0].startswith(str(session.creator.env_dir)) - assert tmp_path.__class__(extra_start[0]).exists() - - # manage to import from activate site package - assert os.path.realpath(out[6]) == os.path.realpath(str(pydoc_test_path)) - except subprocess.CalledProcessError as exception: - assert not exception.returncode, exception.output - - -def test_activate_this_no_file(activation_python, tmp_path): - session, _ = activation_python - activator = tmp_path.__class__(session.creator.bin_dir) / "activate_this.py" - assert activator.exists() - try: - subprocess.check_output( - [sys.executable, "-c", "exec(open({!r}).read())".format(str(activator))], - stderr=subprocess.STDOUT, - universal_newlines=True, - ) - raise RuntimeError("this should not happen") - except subprocess.CalledProcessError as exception: - out = re.sub(r"pydev debugger: process \d+ is connecting\n\n", "", exception.output, re.M).strip() - assert "You must use exec(open(this_file).read(), {'__file__': this_file}))" in out, out diff --git a/tests/unit/activation/test_activation.py b/tests/unit/activation/test_activation.py new file mode 100644 index 000000000..aed11fb0c --- /dev/null +++ b/tests/unit/activation/test_activation.py @@ -0,0 +1,272 @@ +from __future__ import absolute_import, unicode_literals + +import inspect +import os +import pipes +import re +import subprocess +import sys +from os.path import dirname, normcase, realpath + +import pytest +import six + +from virtualenv.activation import ( + BashActivator, + CShellActivator, + FishActivator, + PowerShellActivator, + PythonActivator, + XonoshActivator, +) +from virtualenv.interpreters.discovery.py_info import CURRENT +from virtualenv.run import collect_activators, run_via_cli + + +def norm_path(path): + # python may return Windows short paths, normalize + path = realpath(str(path)) + if sys.platform == "win32": + from ctypes import create_unicode_buffer, windll + + buffer_cont = create_unicode_buffer(256) + get_long_path_name = windll.kernel32.GetLongPathNameW + get_long_path_name(six.text_type(path), buffer_cont, 256) # noqa: F821 + result = buffer_cont.value + else: + result = path + return normcase(result) + + +class ActivationTester(object): + def __init__(self, session, cmd, activate_script, extension): + self._creator = session.creator + self._env = None + self.cmd = cmd + self._version_cmd = [cmd, "--version"] + self._invoke_script = [cmd] + self.activate_script = activate_script + self.activate_cmd = "source" + self.extension = extension + self.non_source_raise = False + + def get_version(self, raise_on_fail): + # locally we disable, so that contributors don't need to have everything setup + try: + return subprocess.check_output(self._version_cmd, universal_newlines=True) + except Exception as exception: + if raise_on_fail: + raise + return RuntimeError("{} is not available due {}".format(self, exception)) + + def __call__(self, monkeypatch, tmp_path): + activate_script = self._creator.bin_dir / self.activate_script + test_script = self._generate_test_script(activate_script, tmp_path) + monkeypatch.chdir(tmp_path) + + monkeypatch.delenv(str("VIRTUAL_ENV"), raising=False) + invoke = self._invoke_script + [str(test_script)] + + try: + raw = subprocess.check_output(invoke, universal_newlines=True, stderr=subprocess.STDOUT, env=self._env) + except subprocess.CalledProcessError as exception: + assert not exception.returncode, exception.output + return + out = re.sub(r"pydev debugger: process \d+ is connecting\n\n", "", raw, re.M).strip().split("\n") + self.assert_output(out, raw, tmp_path) + + if self.non_source_raise: + with pytest.raises(subprocess.CalledProcessError) as context: + invoke = self._invoke_script + [str(activate_script)] + subprocess.check_output(invoke, stderr=subprocess.STDOUT, env=self._env) + assert context.value.returncode, context + + def assert_output(self, out, raw, tmp_path): + # pre-activation + assert out[0], raw + assert out[1] == "None", raw + # post-activation + assert norm_path(out[2]) == norm_path(self._creator.exe), raw + assert norm_path(out[3]) == norm_path(self._creator.dest_dir).replace("\\\\", "\\"), raw + assert out[4] == "wrote pydoc_test.html" + content = tmp_path / "pydoc_test.html" + assert content.exists(), raw + # post deactivation, same as before + assert out[-2] == out[0], raw + assert out[-1] == "None", raw + + def _generate_test_script(self, activate_script, tmp_path): + commands = self._get_test_lines(activate_script) + script = os.linesep.join(commands) + test_script = tmp_path / "script.{}".format(self.extension) + test_script.write_text(script) + return test_script + + def _get_test_lines(self, activate_script): + commands = [ + self.print_python_exe(), + self.print_os_env_var("VIRTUAL_ENV"), + self.activate_call(activate_script), + self.print_python_exe(), + self.print_os_env_var("VIRTUAL_ENV"), + # pydoc loads documentation from the virtualenv site packages + "pydoc -w pydoc_test", + "deactivate", + self.print_python_exe(), + self.print_os_env_var("VIRTUAL_ENV"), + "", # just finish with an empty new line + ] + return commands + + def quote(self, s): + return pipes.quote(s) + + def python_cmd(self, cmd): + return "{} -c {}".format(self.quote(str(self._creator.exe)), self.quote(cmd)) + + def print_python_exe(self): + return self.python_cmd("import sys; print(sys.executable)") + + def print_os_env_var(self, var): + val = '"{}"'.format(var) + return self.python_cmd("import os; print(os.environ.get({}, None))".format(val)) + + def activate_call(self, script): + return "{} {}".format(pipes.quote(str(self.activate_cmd)), pipes.quote(str(script))).strip() + + +class Bash(ActivationTester): + def __init__(self, session): + super(Bash, self).__init__(session, "bash", "activate.sh", "sh") + self.non_source_raise = True + + +class Csh(ActivationTester): + def __init__(self, session): + super(Csh, self).__init__(session, "csh", "activate.csh", "csh") + + +class Fish(ActivationTester): + def __init__(self, session): + super(Fish, self).__init__(session, "fish", "activate.fish", "fish") + + +def win_exe(cmd): + return "{}{}".format(cmd, ".exe" if sys.platform == "win32" else "") + + +class PowerShell(ActivationTester): + def __init__(self, session): + cmd = "powershell.exe" if sys.platform == "win32" else "pwsh" + super(PowerShell, self).__init__(session, cmd, "activate.ps1", "ps1") + self._version_cmd = [self.cmd, "-c", "$PSVersionTable"] + self.activate_cmd = "." + + def quote(self, s): + """powershell double double quote needed for quotes within single quotes""" + return pipes.quote(s).replace('"', '""') + + def invoke_script(self): + return [self.cmd, "-File"] + + +class Xonosh(ActivationTester): + def __init__(self, session): + super(Xonosh, self).__init__(session, "xonsh", "activate.xsh", "xsh") + self._invoke_script = [sys.executable, "-m", "xonsh"] + self.__version_cmd = [sys.executable, "-m", "xonsh", "--version"] + env = os.environ.copy() + env[str("PATH")] = os.pathsep.join([dirname(sys.executable)] + env.get(str("PATH"), str("")).split(os.pathsep)) + env.update({"XONSH_DEBUG": "1", "XONSH_SHOW_TRACEBACK": "True"}) + self._env = env + + def activate_call(self, script): + return "{} {}".format(self.activate_cmd, repr(script)).strip() + + +class Python(ActivationTester): + def __init__(self, session): + cmd = sys.executable + super(Python, self).__init__(session, cmd, "activate_this.py", "py") + + def _get_test_lines(self, activate_script): + raw = inspect.getsource(self.activate_this_test) + raw = raw.replace("__FILENAME__", str(activate_script)) + return [i.lstrip() for i in raw.splitlines()[2:]] + + # noinspection PyUnresolvedReferences + @staticmethod + def activate_this_test(): + import os + import sys + + print(os.environ.get("VIRTUAL_ENV")) + print(os.environ.get("PATH")) + print(os.pathsep.join(sys.path)) + file_at = r"__FILENAME__" + exec(open(file_at).read(), {"__file__": file_at}) + print(os.environ.get("VIRTUAL_ENV")) + print(os.environ.get("PATH")) + print(os.pathsep.join(sys.path)) + import inspect + import pydoc_test + + print(inspect.getsourcefile(pydoc_test)) + + def assert_output(self, out, raw, tmp_path): + assert out[0] == "None" # start with VIRTUAL_ENV None + + prev_path = out[1].split(os.path.pathsep) + prev_sys_path = out[2].split(os.path.pathsep) + + assert out[3] == str(self._creator.dest_dir) # VIRTUAL_ENV now points to the virtual env folder + + new_path = out[4].split(os.pathsep) # PATH now starts with bin path of current + assert ([str(self._creator.bin_dir)] + prev_path) == new_path + + # sys path contains the site package at its start + new_sys_path = out[5].split(os.path.pathsep) + assert ([str(i) for i in self._creator.site_packages] + prev_sys_path) == new_sys_path + + # manage to import from activate site package + assert norm_path(out[6]) == norm_path(self._creator.site_packages[0] / "pydoc_test.py") + + def __call__(self, monkeypatch, tmp_path): + monkeypatch.delenv(str("VIRTUAL_ENV"), raising=False) + monkeypatch.delenv(str("PYTHONPATH"), raising=False) + monkeypatch.setenv(str("PATH"), os.pathsep.join([str(tmp_path), str(tmp_path / "other")])) + super(Python, self).__call__(monkeypatch, tmp_path) + + +ACTIVATION_TEST = { + BashActivator: Bash, + PowerShellActivator: PowerShell, + CShellActivator: Csh, + XonoshActivator: Xonosh, + FishActivator: Fish, + PythonActivator: Python, +} +IS_INSIDE_CI = "CI_RUN" in os.environ + + +@pytest.fixture(scope="session") +def activation_python(tmp_path_factory): + dest = tmp_path_factory.mktemp("a") + session = run_via_cli(["--seed", "none", str(dest)]) + pydoc_test = session.creator.site_packages[0] / "pydoc_test.py" + pydoc_test.write_text('"""This is pydoc_test.py"""') + return session + + +@pytest.fixture(params=list(collect_activators(CURRENT).values()), scope="session") +def activator(request, tmp_path_factory, activation_python): + tester_class = ACTIVATION_TEST[request.param] + tester = tester_class(activation_python) + version = tester.get_version(raise_on_fail=IS_INSIDE_CI) + if not isinstance(version, six.string_types): + pytest.skip(msg=six.text_type(version)) + return tester + + +def test_activation(activation_python, activator, monkeypatch, tmp_path): + activator(monkeypatch, tmp_path) diff --git a/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py b/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py index f92b1feaf..6c5bb20b8 100644 --- a/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py +++ b/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py @@ -32,7 +32,7 @@ def test_base_bootstrap_link_via_app_data(tmp_path, coverage_env): assert pip in files_post_first_create assert setuptools in files_post_first_create - env_exe = result.creator.env_exe + env_exe = result.creator.exe for pip_exe in [ env_exe.with_name("pip{}{}".format(suffix, env_exe.suffix)) for suffix in ( From 47be4249463350199ce7d4aede0fc29a21f27997 Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Fri, 29 Nov 2019 18:39:09 +0000 Subject: [PATCH 06/15] Add tests Signed-off-by: Bernat Gabor --- setup.cfg | 3 +- src/virtualenv/activation/xonosh/__init__.py | 2 +- tests/unit/activation/test_activation.py | 105 ++++++++++++------- 3 files changed, 73 insertions(+), 37 deletions(-) diff --git a/setup.cfg b/setup.cfg index c85777e7b..4e1eb45c9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,7 +53,8 @@ where = src testing = pytest >= 4.0.0, <6 coverage >= 4.5.0, < 5 - pytest-mock + pytest-mock >= 1.12.1, <2 + xonsh >= 0.9.13, <1; python_version > '3.4' docs = sphinx >= 2.0.0, < 3 towncrier >= 18.5.0 diff --git a/src/virtualenv/activation/xonosh/__init__.py b/src/virtualenv/activation/xonosh/__init__.py index 184851106..ceb534057 100644 --- a/src/virtualenv/activation/xonosh/__init__.py +++ b/src/virtualenv/activation/xonosh/__init__.py @@ -11,4 +11,4 @@ def templates(self): @classmethod def supports(cls, interpreter): - return True if interpreter.version_info >= (3, 4) else False + return True if interpreter.version_info >= (3, 5) else False diff --git a/tests/unit/activation/test_activation.py b/tests/unit/activation/test_activation.py index aed11fb0c..932708f20 100644 --- a/tests/unit/activation/test_activation.py +++ b/tests/unit/activation/test_activation.py @@ -14,6 +14,7 @@ from virtualenv.activation import ( BashActivator, CShellActivator, + DOSActivator, FishActivator, PowerShellActivator, PythonActivator, @@ -41,14 +42,12 @@ def norm_path(path): class ActivationTester(object): def __init__(self, session, cmd, activate_script, extension): self._creator = session.creator - self._env = None self.cmd = cmd self._version_cmd = [cmd, "--version"] self._invoke_script = [cmd] self.activate_script = activate_script self.activate_cmd = "source" self.extension = extension - self.non_source_raise = False def get_version(self, raise_on_fail): # locally we disable, so that contributors don't need to have everything setup @@ -65,35 +64,22 @@ def __call__(self, monkeypatch, tmp_path): monkeypatch.chdir(tmp_path) monkeypatch.delenv(str("VIRTUAL_ENV"), raising=False) - invoke = self._invoke_script + [str(test_script)] + invoke, env = self._invoke_script + [str(test_script)], self.env(tmp_path) try: - raw = subprocess.check_output(invoke, universal_newlines=True, stderr=subprocess.STDOUT, env=self._env) + raw = subprocess.check_output(invoke, universal_newlines=True, stderr=subprocess.STDOUT, env=env) except subprocess.CalledProcessError as exception: assert not exception.returncode, exception.output return out = re.sub(r"pydev debugger: process \d+ is connecting\n\n", "", raw, re.M).strip().split("\n") self.assert_output(out, raw, tmp_path) + return env, activate_script - if self.non_source_raise: - with pytest.raises(subprocess.CalledProcessError) as context: - invoke = self._invoke_script + [str(activate_script)] - subprocess.check_output(invoke, stderr=subprocess.STDOUT, env=self._env) - assert context.value.returncode, context + def non_source_activate(self, activate_script): + return self._invoke_script + [str(activate_script)] - def assert_output(self, out, raw, tmp_path): - # pre-activation - assert out[0], raw - assert out[1] == "None", raw - # post-activation - assert norm_path(out[2]) == norm_path(self._creator.exe), raw - assert norm_path(out[3]) == norm_path(self._creator.dest_dir).replace("\\\\", "\\"), raw - assert out[4] == "wrote pydoc_test.html" - content = tmp_path / "pydoc_test.html" - assert content.exists(), raw - # post deactivation, same as before - assert out[-2] == out[0], raw - assert out[-1] == "None", raw + def env(self, tmp_path): + return None def _generate_test_script(self, activate_script, tmp_path): commands = self._get_test_lines(activate_script) @@ -118,6 +104,20 @@ def _get_test_lines(self, activate_script): ] return commands + def assert_output(self, out, raw, tmp_path): + # pre-activation + assert out[0], raw + assert out[1] == "None", raw + # post-activation + assert norm_path(out[2]) == norm_path(self._creator.exe), raw + assert norm_path(out[3]) == norm_path(self._creator.dest_dir).replace("\\\\", "\\"), raw + assert out[4] == "wrote pydoc_test.html" + content = tmp_path / "pydoc_test.html" + assert content.exists(), raw + # post deactivation, same as before + assert out[-2] == out[0], raw + assert out[-1] == "None", raw + def quote(self, s): return pipes.quote(s) @@ -135,10 +135,28 @@ def activate_call(self, script): return "{} {}".format(pipes.quote(str(self.activate_cmd)), pipes.quote(str(script))).strip() -class Bash(ActivationTester): +class RaiseOnNonSourceCall(ActivationTester): + def __init__(self, session, cmd, activate_script, extension, non_source_fail_message): + super(RaiseOnNonSourceCall, self).__init__(session, cmd, activate_script, extension) + self.non_source_fail_message = non_source_fail_message + + def __call__(self, monkeypatch, tmp_path): + env, activate_script = super(RaiseOnNonSourceCall, self).__call__(monkeypatch, tmp_path) + process = subprocess.Popen( + self.non_source_activate(activate_script), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env, + universal_newlines=True, + ) + out, err = process.communicate() + assert process.returncode + assert self.non_source_fail_message in err + + +class Bash(RaiseOnNonSourceCall): def __init__(self, session): - super(Bash, self).__init__(session, "bash", "activate.sh", "sh") - self.non_source_raise = True + super(Bash, self).__init__(session, "bash", "activate.sh", "sh", "You must source this script: $ source ") class Csh(ActivationTester): @@ -146,6 +164,11 @@ def __init__(self, session): super(Csh, self).__init__(session, "csh", "activate.csh", "csh") +class DOS(ActivationTester): + def __init__(self, session): + super(DOS, self).__init__(session, "bat", "activate.bat", "cmd") + + class Fish(ActivationTester): def __init__(self, session): super(Fish, self).__init__(session, "fish", "activate.fish", "fish") @@ -175,19 +198,33 @@ def __init__(self, session): super(Xonosh, self).__init__(session, "xonsh", "activate.xsh", "xsh") self._invoke_script = [sys.executable, "-m", "xonsh"] self.__version_cmd = [sys.executable, "-m", "xonsh", "--version"] + + def env(self, tmp_path): env = os.environ.copy() env[str("PATH")] = os.pathsep.join([dirname(sys.executable)] + env.get(str("PATH"), str("")).split(os.pathsep)) env.update({"XONSH_DEBUG": "1", "XONSH_SHOW_TRACEBACK": "True"}) - self._env = env + return env def activate_call(self, script): - return "{} {}".format(self.activate_cmd, repr(script)).strip() + return "{} {}".format(self.activate_cmd, repr(str(script))).strip() -class Python(ActivationTester): +class Python(RaiseOnNonSourceCall): def __init__(self, session): - cmd = sys.executable - super(Python, self).__init__(session, cmd, "activate_this.py", "py") + super(Python, self).__init__( + session, + sys.executable, + activate_script="activate_this.py", + extension="py", + non_source_fail_message="You must use exec(open(this_file).read(), {'__file__': this_file}))", + ) + + def env(self, tmp_path): + env = os.environ.copy() + for key in {"VIRTUAL_ENV", "PYTHONPATH"}: + env.pop(str(key), None) + env[str("PATH")] = os.pathsep.join([str(tmp_path), str(tmp_path / "other")]) + return env def _get_test_lines(self, activate_script): raw = inspect.getsource(self.activate_this_test) @@ -231,11 +268,8 @@ def assert_output(self, out, raw, tmp_path): # manage to import from activate site package assert norm_path(out[6]) == norm_path(self._creator.site_packages[0] / "pydoc_test.py") - def __call__(self, monkeypatch, tmp_path): - monkeypatch.delenv(str("VIRTUAL_ENV"), raising=False) - monkeypatch.delenv(str("PYTHONPATH"), raising=False) - monkeypatch.setenv(str("PATH"), os.pathsep.join([str(tmp_path), str(tmp_path / "other")])) - super(Python, self).__call__(monkeypatch, tmp_path) + def non_source_activate(self, activate_script): + return self._invoke_script + ["-c", 'exec(open(r"{}").read())'.format(activate_script)] ACTIVATION_TEST = { @@ -245,6 +279,7 @@ def __call__(self, monkeypatch, tmp_path): XonoshActivator: Xonosh, FishActivator: Fish, PythonActivator: Python, + DOSActivator: DOS, } IS_INSIDE_CI = "CI_RUN" in os.environ From 3ad79b4aaf7e56f9207af93a1b0a43a4ba02368c Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Sat, 30 Nov 2019 00:46:26 +0000 Subject: [PATCH 07/15] start to fix batch tests Signed-off-by: Bernat Gabor --- tests/unit/activation/conftest.py | 178 ++++++++++ tests/unit/activation/test_activation.py | 307 ------------------ tests/unit/activation/test_bash.py | 9 + tests/unit/activation/test_csh.py | 9 + tests/unit/activation/test_dos.py | 27 ++ tests/unit/activation/test_fish.py | 9 + tests/unit/activation/test_powershell.py | 22 ++ .../unit/activation/test_python_activator.py | 71 ++++ tests/unit/activation/test_xonosh.py | 26 ++ 9 files changed, 351 insertions(+), 307 deletions(-) delete mode 100644 tests/unit/activation/test_activation.py create mode 100644 tests/unit/activation/test_bash.py create mode 100644 tests/unit/activation/test_csh.py create mode 100644 tests/unit/activation/test_dos.py create mode 100644 tests/unit/activation/test_fish.py create mode 100644 tests/unit/activation/test_powershell.py create mode 100644 tests/unit/activation/test_python_activator.py create mode 100644 tests/unit/activation/test_xonosh.py diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py index e69de29bb..06c1c87e2 100644 --- a/tests/unit/activation/conftest.py +++ b/tests/unit/activation/conftest.py @@ -0,0 +1,178 @@ +from __future__ import absolute_import, unicode_literals + +import os +import pipes +import re +import subprocess +import sys +from os.path import normcase, realpath + +import pytest +import six + +from virtualenv.run import run_via_cli + + +class ActivationTester(object): + def __init__(self, session, cmd, activate_script, extension): + self._creator = session.creator + self.cmd = cmd + self._version_cmd = [cmd, "--version"] + self._invoke_script = [cmd] + self.activate_script = activate_script + self.extension = extension + self.activate_cmd = "source" + self.deactivate = "deactivate" + + def get_version(self, raise_on_fail): + # locally we disable, so that contributors don't need to have everything setup + try: + return subprocess.check_output(self._version_cmd, universal_newlines=True) + except Exception as exception: + if raise_on_fail: + raise + return RuntimeError("{} is not available due {}".format(self, exception)) + + def __call__(self, monkeypatch, tmp_path): + activate_script = self._creator.bin_dir / self.activate_script + test_script = self._generate_test_script(activate_script, tmp_path) + monkeypatch.chdir(tmp_path) + + monkeypatch.delenv(str("VIRTUAL_ENV"), raising=False) + invoke, env = self._invoke_script + [str(test_script)], self.env(tmp_path) + + try: + raw = subprocess.check_output(invoke, universal_newlines=True, stderr=subprocess.STDOUT, env=env) + except subprocess.CalledProcessError as exception: + assert not exception.returncode, exception.output + return + out = re.sub(r"pydev debugger: process \d+ is connecting\n\n", "", raw, re.M).strip().split("\n") + self.assert_output(out, raw, tmp_path) + return env, activate_script + + def non_source_activate(self, activate_script): + return self._invoke_script + [str(activate_script)] + + def env(self, tmp_path): + return None + + def _generate_test_script(self, activate_script, tmp_path): + commands = self._get_test_lines(activate_script) + script = os.linesep.join(commands) + test_script = tmp_path / "script.{}".format(self.extension) + test_script.write_text(script) + return test_script + + def _get_test_lines(self, activate_script): + commands = [ + self.print_python_exe(), + self.print_os_env_var("VIRTUAL_ENV"), + self.activate_call(activate_script), + self.print_python_exe(), + self.print_os_env_var("VIRTUAL_ENV"), + # pydoc loads documentation from the virtualenv site packages + "pydoc -w pydoc_test", + self.deactivate, + self.print_python_exe(), + self.print_os_env_var("VIRTUAL_ENV"), + "", # just finish with an empty new line + ] + return commands + + def assert_output(self, out, raw, tmp_path): + # pre-activation + assert out[0], raw + assert out[1] == "None", raw + # post-activation + assert self.norm_path(out[2]) == self.norm_path(self._creator.exe), raw + assert self.norm_path(out[3]) == self.norm_path(self._creator.dest_dir).replace("\\\\", "\\"), raw + assert out[4] == "wrote pydoc_test.html" + content = tmp_path / "pydoc_test.html" + assert content.exists(), raw + # post deactivation, same as before + assert out[-2] == out[0], raw + assert out[-1] == "None", raw + + def quote(self, s): + return pipes.quote(s) + + def python_cmd(self, cmd): + return "{} -c {}".format(self.quote(str(self._creator.exe)), self.quote(cmd)) + + def print_python_exe(self): + return self.python_cmd("import sys; print(sys.executable)") + + def print_os_env_var(self, var): + val = '"{}"'.format(var) + return self.python_cmd("import os; print(os.environ.get({}, None))".format(val)) + + def activate_call(self, script): + return "{} {}".format(pipes.quote(str(self.activate_cmd)), pipes.quote(str(script))).strip() + + @staticmethod + def norm_path(path): + # python may return Windows short paths, normalize + path = realpath(str(path)) + if sys.platform == "win32": + from ctypes import create_unicode_buffer, windll + + buffer_cont = create_unicode_buffer(256) + get_long_path_name = windll.kernel32.GetLongPathNameW + get_long_path_name(six.text_type(path), buffer_cont, 256) # noqa: F821 + result = buffer_cont.value + else: + result = path + return normcase(result) + + +class RaiseOnNonSourceCall(ActivationTester): + def __init__(self, session, cmd, activate_script, extension, non_source_fail_message): + super(RaiseOnNonSourceCall, self).__init__(session, cmd, activate_script, extension) + self.non_source_fail_message = non_source_fail_message + + def __call__(self, monkeypatch, tmp_path): + env, activate_script = super(RaiseOnNonSourceCall, self).__call__(monkeypatch, tmp_path) + process = subprocess.Popen( + self.non_source_activate(activate_script), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env, + universal_newlines=True, + ) + out, err = process.communicate() + assert process.returncode + assert self.non_source_fail_message in err + + +@pytest.fixture(scope="session") +def activation_tester_class(): + return ActivationTester + + +@pytest.fixture(scope="session") +def raise_on_non_source_class(): + return RaiseOnNonSourceCall + + +@pytest.fixture(scope="session") +def activation_python(tmp_path_factory): + dest = tmp_path_factory.mktemp("a") + session = run_via_cli(["--seed", "none", str(dest)]) + pydoc_test = session.creator.site_packages[0] / "pydoc_test.py" + pydoc_test.write_text('"""This is pydoc_test.py"""') + return session + + +IS_INSIDE_CI = "CI_RUN" in os.environ + + +@pytest.fixture() +def activation_tester(activation_python, monkeypatch, tmp_path): + def _tester(tester_class): + tester = tester_class(activation_python) + version = tester.get_version(raise_on_fail=IS_INSIDE_CI) + if not isinstance(version, six.string_types): + pytest.skip(msg=six.text_type(version)) + return tester(monkeypatch, tmp_path) + + return _tester diff --git a/tests/unit/activation/test_activation.py b/tests/unit/activation/test_activation.py deleted file mode 100644 index 932708f20..000000000 --- a/tests/unit/activation/test_activation.py +++ /dev/null @@ -1,307 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import inspect -import os -import pipes -import re -import subprocess -import sys -from os.path import dirname, normcase, realpath - -import pytest -import six - -from virtualenv.activation import ( - BashActivator, - CShellActivator, - DOSActivator, - FishActivator, - PowerShellActivator, - PythonActivator, - XonoshActivator, -) -from virtualenv.interpreters.discovery.py_info import CURRENT -from virtualenv.run import collect_activators, run_via_cli - - -def norm_path(path): - # python may return Windows short paths, normalize - path = realpath(str(path)) - if sys.platform == "win32": - from ctypes import create_unicode_buffer, windll - - buffer_cont = create_unicode_buffer(256) - get_long_path_name = windll.kernel32.GetLongPathNameW - get_long_path_name(six.text_type(path), buffer_cont, 256) # noqa: F821 - result = buffer_cont.value - else: - result = path - return normcase(result) - - -class ActivationTester(object): - def __init__(self, session, cmd, activate_script, extension): - self._creator = session.creator - self.cmd = cmd - self._version_cmd = [cmd, "--version"] - self._invoke_script = [cmd] - self.activate_script = activate_script - self.activate_cmd = "source" - self.extension = extension - - def get_version(self, raise_on_fail): - # locally we disable, so that contributors don't need to have everything setup - try: - return subprocess.check_output(self._version_cmd, universal_newlines=True) - except Exception as exception: - if raise_on_fail: - raise - return RuntimeError("{} is not available due {}".format(self, exception)) - - def __call__(self, monkeypatch, tmp_path): - activate_script = self._creator.bin_dir / self.activate_script - test_script = self._generate_test_script(activate_script, tmp_path) - monkeypatch.chdir(tmp_path) - - monkeypatch.delenv(str("VIRTUAL_ENV"), raising=False) - invoke, env = self._invoke_script + [str(test_script)], self.env(tmp_path) - - try: - raw = subprocess.check_output(invoke, universal_newlines=True, stderr=subprocess.STDOUT, env=env) - except subprocess.CalledProcessError as exception: - assert not exception.returncode, exception.output - return - out = re.sub(r"pydev debugger: process \d+ is connecting\n\n", "", raw, re.M).strip().split("\n") - self.assert_output(out, raw, tmp_path) - return env, activate_script - - def non_source_activate(self, activate_script): - return self._invoke_script + [str(activate_script)] - - def env(self, tmp_path): - return None - - def _generate_test_script(self, activate_script, tmp_path): - commands = self._get_test_lines(activate_script) - script = os.linesep.join(commands) - test_script = tmp_path / "script.{}".format(self.extension) - test_script.write_text(script) - return test_script - - def _get_test_lines(self, activate_script): - commands = [ - self.print_python_exe(), - self.print_os_env_var("VIRTUAL_ENV"), - self.activate_call(activate_script), - self.print_python_exe(), - self.print_os_env_var("VIRTUAL_ENV"), - # pydoc loads documentation from the virtualenv site packages - "pydoc -w pydoc_test", - "deactivate", - self.print_python_exe(), - self.print_os_env_var("VIRTUAL_ENV"), - "", # just finish with an empty new line - ] - return commands - - def assert_output(self, out, raw, tmp_path): - # pre-activation - assert out[0], raw - assert out[1] == "None", raw - # post-activation - assert norm_path(out[2]) == norm_path(self._creator.exe), raw - assert norm_path(out[3]) == norm_path(self._creator.dest_dir).replace("\\\\", "\\"), raw - assert out[4] == "wrote pydoc_test.html" - content = tmp_path / "pydoc_test.html" - assert content.exists(), raw - # post deactivation, same as before - assert out[-2] == out[0], raw - assert out[-1] == "None", raw - - def quote(self, s): - return pipes.quote(s) - - def python_cmd(self, cmd): - return "{} -c {}".format(self.quote(str(self._creator.exe)), self.quote(cmd)) - - def print_python_exe(self): - return self.python_cmd("import sys; print(sys.executable)") - - def print_os_env_var(self, var): - val = '"{}"'.format(var) - return self.python_cmd("import os; print(os.environ.get({}, None))".format(val)) - - def activate_call(self, script): - return "{} {}".format(pipes.quote(str(self.activate_cmd)), pipes.quote(str(script))).strip() - - -class RaiseOnNonSourceCall(ActivationTester): - def __init__(self, session, cmd, activate_script, extension, non_source_fail_message): - super(RaiseOnNonSourceCall, self).__init__(session, cmd, activate_script, extension) - self.non_source_fail_message = non_source_fail_message - - def __call__(self, monkeypatch, tmp_path): - env, activate_script = super(RaiseOnNonSourceCall, self).__call__(monkeypatch, tmp_path) - process = subprocess.Popen( - self.non_source_activate(activate_script), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=env, - universal_newlines=True, - ) - out, err = process.communicate() - assert process.returncode - assert self.non_source_fail_message in err - - -class Bash(RaiseOnNonSourceCall): - def __init__(self, session): - super(Bash, self).__init__(session, "bash", "activate.sh", "sh", "You must source this script: $ source ") - - -class Csh(ActivationTester): - def __init__(self, session): - super(Csh, self).__init__(session, "csh", "activate.csh", "csh") - - -class DOS(ActivationTester): - def __init__(self, session): - super(DOS, self).__init__(session, "bat", "activate.bat", "cmd") - - -class Fish(ActivationTester): - def __init__(self, session): - super(Fish, self).__init__(session, "fish", "activate.fish", "fish") - - -def win_exe(cmd): - return "{}{}".format(cmd, ".exe" if sys.platform == "win32" else "") - - -class PowerShell(ActivationTester): - def __init__(self, session): - cmd = "powershell.exe" if sys.platform == "win32" else "pwsh" - super(PowerShell, self).__init__(session, cmd, "activate.ps1", "ps1") - self._version_cmd = [self.cmd, "-c", "$PSVersionTable"] - self.activate_cmd = "." - - def quote(self, s): - """powershell double double quote needed for quotes within single quotes""" - return pipes.quote(s).replace('"', '""') - - def invoke_script(self): - return [self.cmd, "-File"] - - -class Xonosh(ActivationTester): - def __init__(self, session): - super(Xonosh, self).__init__(session, "xonsh", "activate.xsh", "xsh") - self._invoke_script = [sys.executable, "-m", "xonsh"] - self.__version_cmd = [sys.executable, "-m", "xonsh", "--version"] - - def env(self, tmp_path): - env = os.environ.copy() - env[str("PATH")] = os.pathsep.join([dirname(sys.executable)] + env.get(str("PATH"), str("")).split(os.pathsep)) - env.update({"XONSH_DEBUG": "1", "XONSH_SHOW_TRACEBACK": "True"}) - return env - - def activate_call(self, script): - return "{} {}".format(self.activate_cmd, repr(str(script))).strip() - - -class Python(RaiseOnNonSourceCall): - def __init__(self, session): - super(Python, self).__init__( - session, - sys.executable, - activate_script="activate_this.py", - extension="py", - non_source_fail_message="You must use exec(open(this_file).read(), {'__file__': this_file}))", - ) - - def env(self, tmp_path): - env = os.environ.copy() - for key in {"VIRTUAL_ENV", "PYTHONPATH"}: - env.pop(str(key), None) - env[str("PATH")] = os.pathsep.join([str(tmp_path), str(tmp_path / "other")]) - return env - - def _get_test_lines(self, activate_script): - raw = inspect.getsource(self.activate_this_test) - raw = raw.replace("__FILENAME__", str(activate_script)) - return [i.lstrip() for i in raw.splitlines()[2:]] - - # noinspection PyUnresolvedReferences - @staticmethod - def activate_this_test(): - import os - import sys - - print(os.environ.get("VIRTUAL_ENV")) - print(os.environ.get("PATH")) - print(os.pathsep.join(sys.path)) - file_at = r"__FILENAME__" - exec(open(file_at).read(), {"__file__": file_at}) - print(os.environ.get("VIRTUAL_ENV")) - print(os.environ.get("PATH")) - print(os.pathsep.join(sys.path)) - import inspect - import pydoc_test - - print(inspect.getsourcefile(pydoc_test)) - - def assert_output(self, out, raw, tmp_path): - assert out[0] == "None" # start with VIRTUAL_ENV None - - prev_path = out[1].split(os.path.pathsep) - prev_sys_path = out[2].split(os.path.pathsep) - - assert out[3] == str(self._creator.dest_dir) # VIRTUAL_ENV now points to the virtual env folder - - new_path = out[4].split(os.pathsep) # PATH now starts with bin path of current - assert ([str(self._creator.bin_dir)] + prev_path) == new_path - - # sys path contains the site package at its start - new_sys_path = out[5].split(os.path.pathsep) - assert ([str(i) for i in self._creator.site_packages] + prev_sys_path) == new_sys_path - - # manage to import from activate site package - assert norm_path(out[6]) == norm_path(self._creator.site_packages[0] / "pydoc_test.py") - - def non_source_activate(self, activate_script): - return self._invoke_script + ["-c", 'exec(open(r"{}").read())'.format(activate_script)] - - -ACTIVATION_TEST = { - BashActivator: Bash, - PowerShellActivator: PowerShell, - CShellActivator: Csh, - XonoshActivator: Xonosh, - FishActivator: Fish, - PythonActivator: Python, - DOSActivator: DOS, -} -IS_INSIDE_CI = "CI_RUN" in os.environ - - -@pytest.fixture(scope="session") -def activation_python(tmp_path_factory): - dest = tmp_path_factory.mktemp("a") - session = run_via_cli(["--seed", "none", str(dest)]) - pydoc_test = session.creator.site_packages[0] / "pydoc_test.py" - pydoc_test.write_text('"""This is pydoc_test.py"""') - return session - - -@pytest.fixture(params=list(collect_activators(CURRENT).values()), scope="session") -def activator(request, tmp_path_factory, activation_python): - tester_class = ACTIVATION_TEST[request.param] - tester = tester_class(activation_python) - version = tester.get_version(raise_on_fail=IS_INSIDE_CI) - if not isinstance(version, six.string_types): - pytest.skip(msg=six.text_type(version)) - return tester - - -def test_activation(activation_python, activator, monkeypatch, tmp_path): - activator(monkeypatch, tmp_path) diff --git a/tests/unit/activation/test_bash.py b/tests/unit/activation/test_bash.py new file mode 100644 index 000000000..8cd51ea11 --- /dev/null +++ b/tests/unit/activation/test_bash.py @@ -0,0 +1,9 @@ +from __future__ import absolute_import, unicode_literals + + +def test_bash(raise_on_non_source_class, activation_tester): + class Bash(raise_on_non_source_class): + def __init__(self, session): + super(Bash, self).__init__(session, "bash", "activate.sh", "sh", "You must source this script: $ source ") + + activation_tester(Bash) diff --git a/tests/unit/activation/test_csh.py b/tests/unit/activation/test_csh.py new file mode 100644 index 000000000..d22bcdbe3 --- /dev/null +++ b/tests/unit/activation/test_csh.py @@ -0,0 +1,9 @@ +from __future__ import absolute_import, unicode_literals + + +def test_csh(activation_tester_class, activation_tester): + class Csh(activation_tester_class): + def __init__(self, session): + super(Csh, self).__init__(session, "csh", "activate.csh", "csh") + + activation_tester(Csh) diff --git a/tests/unit/activation/test_dos.py b/tests/unit/activation/test_dos.py new file mode 100644 index 000000000..aa59060c9 --- /dev/null +++ b/tests/unit/activation/test_dos.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import, unicode_literals + +import pipes + + +def test_dos(activation_tester_class, activation_tester, tmp_path, activation_python): + version_script = tmp_path / "version.bat" + version_script.write_text("ver") + + class DOS(activation_tester_class): + def __init__(self, session): + super(DOS, self).__init__(session, None, "activate.bat", "bat") + self._version_cmd = [str(version_script)] + self._invoke_script = [] + self.deactivate = str(activation_python.creator.bin_dir / "deactivate.bat") + + def _get_test_lines(self, activate_script): + return ["@echo off", ""] + super(DOS, self)._get_test_lines(activate_script) + + def activate_call(self, script): + return str(script) + + def quote(self, s): + """double quotes needs to be single, and single need to be double""" + return "".join(("'" if c == '"' else ('"' if c == "'" else c)) for c in pipes.quote(s)) + + activation_tester(DOS) diff --git a/tests/unit/activation/test_fish.py b/tests/unit/activation/test_fish.py new file mode 100644 index 000000000..665a2a4ed --- /dev/null +++ b/tests/unit/activation/test_fish.py @@ -0,0 +1,9 @@ +from __future__ import absolute_import, unicode_literals + + +def test_csh(activation_tester_class, activation_tester): + class Fish(activation_tester_class): + def __init__(self, session): + super(Fish, self).__init__(session, "fish", "activate.fish", "fish") + + activation_tester(Fish) diff --git a/tests/unit/activation/test_powershell.py b/tests/unit/activation/test_powershell.py new file mode 100644 index 000000000..4d17f389c --- /dev/null +++ b/tests/unit/activation/test_powershell.py @@ -0,0 +1,22 @@ +from __future__ import absolute_import, unicode_literals + +import pipes +import sys + + +def test_powershell(activation_tester_class, activation_tester): + class PowerShell(activation_tester_class): + def __init__(self, session): + cmd = "powershell.exe" if sys.platform == "win32" else "pwsh" + super(PowerShell, self).__init__(session, cmd, "activate.ps1", "ps1") + self._version_cmd = [self.cmd, "-c", "$PSVersionTable"] + self.activate_cmd = "." + + def quote(self, s): + """powershell double double quote needed for quotes within single quotes""" + return pipes.quote(s).replace('"', '""') + + def invoke_script(self): + return [self.cmd, "-File"] + + activation_tester(PowerShell) diff --git a/tests/unit/activation/test_python_activator.py b/tests/unit/activation/test_python_activator.py new file mode 100644 index 000000000..a4cf1d8ca --- /dev/null +++ b/tests/unit/activation/test_python_activator.py @@ -0,0 +1,71 @@ +from __future__ import absolute_import, unicode_literals + +import inspect +import os +import sys + + +def test_python(raise_on_non_source_class, activation_tester): + class Python(raise_on_non_source_class): + def __init__(self, session): + super(Python, self).__init__( + session, + sys.executable, + activate_script="activate_this.py", + extension="py", + non_source_fail_message="You must use exec(open(this_file).read(), {'__file__': this_file}))", + ) + + def env(self, tmp_path): + env = os.environ.copy() + for key in {"VIRTUAL_ENV", "PYTHONPATH"}: + env.pop(str(key), None) + env[str("PATH")] = os.pathsep.join([str(tmp_path), str(tmp_path / "other")]) + return env + + def _get_test_lines(self, activate_script): + raw = inspect.getsource(self.activate_this_test) + raw = raw.replace("__FILENAME__", str(activate_script)) + return [i.lstrip() for i in raw.splitlines()[2:]] + + # noinspection PyUnresolvedReferences + @staticmethod + def activate_this_test(): + import os + import sys + + print(os.environ.get("VIRTUAL_ENV")) + print(os.environ.get("PATH")) + print(os.pathsep.join(sys.path)) + file_at = r"__FILENAME__" + exec(open(file_at).read(), {"__file__": file_at}) + print(os.environ.get("VIRTUAL_ENV")) + print(os.environ.get("PATH")) + print(os.pathsep.join(sys.path)) + import inspect + import pydoc_test + + print(inspect.getsourcefile(pydoc_test)) + + def assert_output(self, out, raw, tmp_path): + assert out[0] == "None" # start with VIRTUAL_ENV None + + prev_path = out[1].split(os.path.pathsep) + prev_sys_path = out[2].split(os.path.pathsep) + + assert out[3] == str(self._creator.dest_dir) # VIRTUAL_ENV now points to the virtual env folder + + new_path = out[4].split(os.pathsep) # PATH now starts with bin path of current + assert ([str(self._creator.bin_dir)] + prev_path) == new_path + + # sys path contains the site package at its start + new_sys_path = out[5].split(os.path.pathsep) + assert ([str(i) for i in self._creator.site_packages] + prev_sys_path) == new_sys_path + + # manage to import from activate site package + assert self.norm_path(out[6]) == self.norm_path(self._creator.site_packages[0] / "pydoc_test.py") + + def non_source_activate(self, activate_script): + return self._invoke_script + ["-c", 'exec(open(r"{}").read())'.format(activate_script)] + + activation_tester(Python) diff --git a/tests/unit/activation/test_xonosh.py b/tests/unit/activation/test_xonosh.py new file mode 100644 index 000000000..680e9c504 --- /dev/null +++ b/tests/unit/activation/test_xonosh.py @@ -0,0 +1,26 @@ +from __future__ import absolute_import, unicode_literals + +import os +import sys +from os.path import dirname + + +def test_xonosh(activation_tester_class, activation_tester): + class Xonosh(activation_tester_class): + def __init__(self, session): + super(Xonosh, self).__init__(session, "xonsh", "activate.xsh", "xsh") + self._invoke_script = [sys.executable, "-m", "xonsh"] + self.__version_cmd = [sys.executable, "-m", "xonsh", "--version"] + + def env(self, tmp_path): + env = os.environ.copy() + env[str("PATH")] = os.pathsep.join( + [dirname(sys.executable)] + env.get(str("PATH"), str("")).split(os.pathsep) + ) + env.update({"XONSH_DEBUG": "1", "XONSH_SHOW_TRACEBACK": "True"}) + return env + + def activate_call(self, script): + return "{} {}".format(self.activate_cmd, repr(str(script))).strip() + + activation_tester(Xonosh) From f19885795699493f6f70395685491ee9c4f9482f Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Sat, 30 Nov 2019 09:26:04 +0000 Subject: [PATCH 08/15] fixes --- src/virtualenv/activation/dos/activate.bat | 2 ++ src/virtualenv/activation/dos/deactivate.bat | 2 ++ tests/unit/activation/conftest.py | 12 +++++++----- tests/unit/activation/test_dos.py | 6 ++---- tests/unit/activation/test_xonosh.py | 7 +------ 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/virtualenv/activation/dos/activate.bat b/src/virtualenv/activation/dos/activate.bat index 96e835b52..8186917fe 100644 --- a/src/virtualenv/activation/dos/activate.bat +++ b/src/virtualenv/activation/dos/activate.bat @@ -33,3 +33,5 @@ if defined _OLD_VIRTUAL_PATH goto ENDIFVPATH2 :ENDIFVPATH2 set "PATH=%VIRTUAL_ENV%\__BIN_NAME__;%PATH%" + +doskey pydoc=python -m pydoc diff --git a/src/virtualenv/activation/dos/deactivate.bat b/src/virtualenv/activation/dos/deactivate.bat index 7bbc56882..f574a3a10 100644 --- a/src/virtualenv/activation/dos/deactivate.bat +++ b/src/virtualenv/activation/dos/deactivate.bat @@ -17,3 +17,5 @@ if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH set "PATH=%_OLD_VIRTUAL_PATH%" set _OLD_VIRTUAL_PATH= :ENDIFVPATH + +doskey a= diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py index 06c1c87e2..340e230ad 100644 --- a/tests/unit/activation/conftest.py +++ b/tests/unit/activation/conftest.py @@ -5,7 +5,7 @@ import re import subprocess import sys -from os.path import normcase, realpath +from os.path import dirname, normcase, realpath import pytest import six @@ -54,7 +54,9 @@ def non_source_activate(self, activate_script): return self._invoke_script + [str(activate_script)] def env(self, tmp_path): - return None + env = os.environ.copy() + env[str("PATH")] = os.pathsep.join([dirname(sys.executable)] + env.get(str("PATH"), str("")).split(os.pathsep)) + return env def _generate_test_script(self, activate_script, tmp_path): commands = self._get_test_lines(activate_script) @@ -70,7 +72,7 @@ def _get_test_lines(self, activate_script): self.activate_call(activate_script), self.print_python_exe(), self.print_os_env_var("VIRTUAL_ENV"), - # pydoc loads documentation from the virtualenv site packages + # \\ loads documentation from the virtualenv site packages "pydoc -w pydoc_test", self.deactivate, self.print_python_exe(), @@ -97,7 +99,7 @@ def quote(self, s): return pipes.quote(s) def python_cmd(self, cmd): - return "{} -c {}".format(self.quote(str(self._creator.exe)), self.quote(cmd)) + return "{} -c {}".format(os.path.basename(sys.executable), self.quote(cmd)) def print_python_exe(self): return self.python_cmd("import sys; print(sys.executable)") @@ -107,7 +109,7 @@ def print_os_env_var(self, var): return self.python_cmd("import os; print(os.environ.get({}, None))".format(val)) def activate_call(self, script): - return "{} {}".format(pipes.quote(str(self.activate_cmd)), pipes.quote(str(script))).strip() + return "{} {}".format(self.quote(str(self.activate_cmd)), self.quote(str(script))).strip() @staticmethod def norm_path(path): diff --git a/tests/unit/activation/test_dos.py b/tests/unit/activation/test_dos.py index aa59060c9..7598c9d48 100644 --- a/tests/unit/activation/test_dos.py +++ b/tests/unit/activation/test_dos.py @@ -12,14 +12,12 @@ def __init__(self, session): super(DOS, self).__init__(session, None, "activate.bat", "bat") self._version_cmd = [str(version_script)] self._invoke_script = [] - self.deactivate = str(activation_python.creator.bin_dir / "deactivate.bat") + self.deactivate = "call {}".format(self.quote(str(activation_python.creator.bin_dir / "deactivate.bat"))) + self.activate_cmd = "call" def _get_test_lines(self, activate_script): return ["@echo off", ""] + super(DOS, self)._get_test_lines(activate_script) - def activate_call(self, script): - return str(script) - def quote(self, s): """double quotes needs to be single, and single need to be double""" return "".join(("'" if c == '"' else ('"' if c == "'" else c)) for c in pipes.quote(s)) diff --git a/tests/unit/activation/test_xonosh.py b/tests/unit/activation/test_xonosh.py index 680e9c504..c8e205ac1 100644 --- a/tests/unit/activation/test_xonosh.py +++ b/tests/unit/activation/test_xonosh.py @@ -1,8 +1,6 @@ from __future__ import absolute_import, unicode_literals -import os import sys -from os.path import dirname def test_xonosh(activation_tester_class, activation_tester): @@ -13,10 +11,7 @@ def __init__(self, session): self.__version_cmd = [sys.executable, "-m", "xonsh", "--version"] def env(self, tmp_path): - env = os.environ.copy() - env[str("PATH")] = os.pathsep.join( - [dirname(sys.executable)] + env.get(str("PATH"), str("")).split(os.pathsep) - ) + env = super(Xonosh, self).env(tmp_path) env.update({"XONSH_DEBUG": "1", "XONSH_SHOW_TRACEBACK": "True"}) return env From 2a8f6ec040e600c4fc756627b3c5f79cd09a3f94 Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Sat, 30 Nov 2019 09:29:08 +0000 Subject: [PATCH 09/15] it's batch not dos --- setup.cfg | 2 +- src/virtualenv/activation/__init__.py | 4 ++-- src/virtualenv/activation/{dos => batch}/__init__.py | 2 +- src/virtualenv/activation/{dos => batch}/activate.bat | 0 .../activation/{dos => batch}/deactivate.bat | 0 tests/unit/activation/test_activation_support.py | 6 +++--- tests/unit/activation/test_dos.py | 10 +++++----- 7 files changed, 12 insertions(+), 12 deletions(-) rename src/virtualenv/activation/{dos => batch}/__init__.py (88%) rename src/virtualenv/activation/{dos => batch}/activate.bat (100%) rename src/virtualenv/activation/{dos => batch}/deactivate.bat (100%) diff --git a/setup.cfg b/setup.cfg index 4e1eb45c9..413d60d59 100644 --- a/setup.cfg +++ b/setup.cfg @@ -87,7 +87,7 @@ virtualenv.seed = virtualenv.activate = bash = virtualenv.activation.bash:BashActivator cshell = virtualenv.activation.cshell:CShellActivator - dos = virtualenv.activation.dos:DOSActivator + batch = virtualenv.activation.batch:BatchActivator fish = virtualenv.activation.fish:FishActivator power-shell = virtualenv.activation.powershell:PowerShellActivator python = virtualenv.activation.python:PythonActivator diff --git a/src/virtualenv/activation/__init__.py b/src/virtualenv/activation/__init__.py index 54802306e..dbe54fc1b 100644 --- a/src/virtualenv/activation/__init__.py +++ b/src/virtualenv/activation/__init__.py @@ -1,8 +1,8 @@ from __future__ import absolute_import, unicode_literals from .bash import BashActivator +from .batch import BatchActivator from .cshell import CShellActivator -from .dos import DOSActivator from .fish import FishActivator from .powershell import PowerShellActivator from .python import PythonActivator @@ -14,6 +14,6 @@ XonoshActivator, CShellActivator, PythonActivator, - DOSActivator, + BatchActivator, FishActivator, ] diff --git a/src/virtualenv/activation/dos/__init__.py b/src/virtualenv/activation/batch/__init__.py similarity index 88% rename from src/virtualenv/activation/dos/__init__.py rename to src/virtualenv/activation/batch/__init__.py index 3a35940d5..1944db2d3 100644 --- a/src/virtualenv/activation/dos/__init__.py +++ b/src/virtualenv/activation/batch/__init__.py @@ -5,7 +5,7 @@ from ..via_template import ViaTemplateActivator -class DOSActivator(ViaTemplateActivator): +class BatchActivator(ViaTemplateActivator): @classmethod def supports(cls, interpreter): return interpreter.os == "nt" diff --git a/src/virtualenv/activation/dos/activate.bat b/src/virtualenv/activation/batch/activate.bat similarity index 100% rename from src/virtualenv/activation/dos/activate.bat rename to src/virtualenv/activation/batch/activate.bat diff --git a/src/virtualenv/activation/dos/deactivate.bat b/src/virtualenv/activation/batch/deactivate.bat similarity index 100% rename from src/virtualenv/activation/dos/deactivate.bat rename to src/virtualenv/activation/batch/deactivate.bat diff --git a/tests/unit/activation/test_activation_support.py b/tests/unit/activation/test_activation_support.py index 74588c613..cff940e7c 100644 --- a/tests/unit/activation/test_activation_support.py +++ b/tests/unit/activation/test_activation_support.py @@ -4,8 +4,8 @@ from virtualenv.activation import ( BashActivator, + BatchActivator, CShellActivator, - DOSActivator, FishActivator, PowerShellActivator, PythonActivator, @@ -13,7 +13,7 @@ from virtualenv.interpreters.discovery.py_info import PythonInfo -@pytest.mark.parametrize("activator_class", [DOSActivator, PowerShellActivator, PythonActivator]) +@pytest.mark.parametrize("activator_class", [BatchActivator, PowerShellActivator, PythonActivator]) def test_activator_support_windows(mocker, activator_class): activator = activator_class(Namespace(prompt=None)) @@ -41,7 +41,7 @@ def test_activator_support_posix(mocker, activator_class): assert activator.supports(interpreter) -@pytest.mark.parametrize("activator_class", [DOSActivator]) +@pytest.mark.parametrize("activator_class", [BatchActivator]) def test_activator_no_support_posix(mocker, activator_class): activator = activator_class(Namespace(prompt=None)) interpreter = mocker.Mock(spec=PythonInfo) diff --git a/tests/unit/activation/test_dos.py b/tests/unit/activation/test_dos.py index 7598c9d48..2a65b8cc6 100644 --- a/tests/unit/activation/test_dos.py +++ b/tests/unit/activation/test_dos.py @@ -3,23 +3,23 @@ import pipes -def test_dos(activation_tester_class, activation_tester, tmp_path, activation_python): +def test_batch(activation_tester_class, activation_tester, tmp_path, activation_python): version_script = tmp_path / "version.bat" version_script.write_text("ver") - class DOS(activation_tester_class): + class Batch(activation_tester_class): def __init__(self, session): - super(DOS, self).__init__(session, None, "activate.bat", "bat") + super(Batch, self).__init__(session, None, "activate.bat", "bat") self._version_cmd = [str(version_script)] self._invoke_script = [] self.deactivate = "call {}".format(self.quote(str(activation_python.creator.bin_dir / "deactivate.bat"))) self.activate_cmd = "call" def _get_test_lines(self, activate_script): - return ["@echo off", ""] + super(DOS, self)._get_test_lines(activate_script) + return ["@echo off", ""] + super(Batch, self)._get_test_lines(activate_script) def quote(self, s): """double quotes needs to be single, and single need to be double""" return "".join(("'" if c == '"' else ('"' if c == "'" else c)) for c in pipes.quote(s)) - activation_tester(DOS) + activation_tester(Batch) From 34ea66841f1ad53177bc105201e786586e8ce72c Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Sat, 30 Nov 2019 09:31:14 +0000 Subject: [PATCH 10/15] fix batch Signed-off-by: Bernat Gabor --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 413d60d59..731beea2e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -64,7 +64,7 @@ docs = virtualenv.seed.embed.wheels = *.whl virtualenv.activation.bash = *.sh virtualenv.activation.cshell = *.csh -virtualenv.activation.dos = *.bat +virtualenv.activation.batch = *.bat virtualenv.activation.fish = *.fish virtualenv.activation.powershell = *.ps1 virtualenv.activation.xonosh = *.xsh From 82dc174e0066b027cfe5da5b1bd0413246185621 Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Sat, 30 Nov 2019 19:10:49 +0000 Subject: [PATCH 11/15] Skip when environment does not support activation test Signed-off-by: Bernat Gabor --- tests/unit/activation/conftest.py | 9 ++++++--- tests/unit/activation/test_bash.py | 6 +++++- tests/unit/activation/test_csh.py | 4 +++- tests/unit/activation/test_dos.py | 4 +++- tests/unit/activation/test_fish.py | 4 +++- tests/unit/activation/test_powershell.py | 4 +++- tests/unit/activation/test_python_activator.py | 3 +++ tests/unit/activation/test_xonosh.py | 6 +++++- 8 files changed, 31 insertions(+), 9 deletions(-) diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py index 340e230ad..af52c305d 100644 --- a/tests/unit/activation/conftest.py +++ b/tests/unit/activation/conftest.py @@ -14,7 +14,8 @@ class ActivationTester(object): - def __init__(self, session, cmd, activate_script, extension): + def __init__(self, of_class, session, cmd, activate_script, extension): + self.of_class = of_class self._creator = session.creator self.cmd = cmd self._version_cmd = [cmd, "--version"] @@ -128,8 +129,8 @@ def norm_path(path): class RaiseOnNonSourceCall(ActivationTester): - def __init__(self, session, cmd, activate_script, extension, non_source_fail_message): - super(RaiseOnNonSourceCall, self).__init__(session, cmd, activate_script, extension) + def __init__(self, of_class, session, cmd, activate_script, extension, non_source_fail_message): + super(RaiseOnNonSourceCall, self).__init__(of_class, session, cmd, activate_script, extension) self.non_source_fail_message = non_source_fail_message def __call__(self, monkeypatch, tmp_path): @@ -172,6 +173,8 @@ def activation_python(tmp_path_factory): def activation_tester(activation_python, monkeypatch, tmp_path): def _tester(tester_class): tester = tester_class(activation_python) + if not tester.of_class.supports(activation_python.creator.interpreter): + pytest.skip("{} not supported on current environment".format(tester.of_class.__name__)) version = tester.get_version(raise_on_fail=IS_INSIDE_CI) if not isinstance(version, six.string_types): pytest.skip(msg=six.text_type(version)) diff --git a/tests/unit/activation/test_bash.py b/tests/unit/activation/test_bash.py index 8cd51ea11..d5d8ad9b0 100644 --- a/tests/unit/activation/test_bash.py +++ b/tests/unit/activation/test_bash.py @@ -1,9 +1,13 @@ from __future__ import absolute_import, unicode_literals +from virtualenv.activation import BashActivator + def test_bash(raise_on_non_source_class, activation_tester): class Bash(raise_on_non_source_class): def __init__(self, session): - super(Bash, self).__init__(session, "bash", "activate.sh", "sh", "You must source this script: $ source ") + super(Bash, self).__init__( + BashActivator, session, "bash", "activate.sh", "sh", "You must source this script: $ source " + ) activation_tester(Bash) diff --git a/tests/unit/activation/test_csh.py b/tests/unit/activation/test_csh.py index d22bcdbe3..69b23b68f 100644 --- a/tests/unit/activation/test_csh.py +++ b/tests/unit/activation/test_csh.py @@ -1,9 +1,11 @@ from __future__ import absolute_import, unicode_literals +from virtualenv.activation import CShellActivator + def test_csh(activation_tester_class, activation_tester): class Csh(activation_tester_class): def __init__(self, session): - super(Csh, self).__init__(session, "csh", "activate.csh", "csh") + super(Csh, self).__init__(CShellActivator, session, "csh", "activate.csh", "csh") activation_tester(Csh) diff --git a/tests/unit/activation/test_dos.py b/tests/unit/activation/test_dos.py index 2a65b8cc6..296be881d 100644 --- a/tests/unit/activation/test_dos.py +++ b/tests/unit/activation/test_dos.py @@ -2,6 +2,8 @@ import pipes +from virtualenv.activation import BatchActivator + def test_batch(activation_tester_class, activation_tester, tmp_path, activation_python): version_script = tmp_path / "version.bat" @@ -9,7 +11,7 @@ def test_batch(activation_tester_class, activation_tester, tmp_path, activation_ class Batch(activation_tester_class): def __init__(self, session): - super(Batch, self).__init__(session, None, "activate.bat", "bat") + super(Batch, self).__init__(BatchActivator, session, None, "activate.bat", "bat") self._version_cmd = [str(version_script)] self._invoke_script = [] self.deactivate = "call {}".format(self.quote(str(activation_python.creator.bin_dir / "deactivate.bat"))) diff --git a/tests/unit/activation/test_fish.py b/tests/unit/activation/test_fish.py index 665a2a4ed..bf154649c 100644 --- a/tests/unit/activation/test_fish.py +++ b/tests/unit/activation/test_fish.py @@ -1,9 +1,11 @@ from __future__ import absolute_import, unicode_literals +from virtualenv.activation import FishActivator + def test_csh(activation_tester_class, activation_tester): class Fish(activation_tester_class): def __init__(self, session): - super(Fish, self).__init__(session, "fish", "activate.fish", "fish") + super(Fish, self).__init__(FishActivator, session, "fish", "activate.fish", "fish") activation_tester(Fish) diff --git a/tests/unit/activation/test_powershell.py b/tests/unit/activation/test_powershell.py index 4d17f389c..397938721 100644 --- a/tests/unit/activation/test_powershell.py +++ b/tests/unit/activation/test_powershell.py @@ -3,12 +3,14 @@ import pipes import sys +from virtualenv.activation import PowerShellActivator + def test_powershell(activation_tester_class, activation_tester): class PowerShell(activation_tester_class): def __init__(self, session): cmd = "powershell.exe" if sys.platform == "win32" else "pwsh" - super(PowerShell, self).__init__(session, cmd, "activate.ps1", "ps1") + super(PowerShell, self).__init__(PowerShellActivator, session, cmd, "activate.ps1", "ps1") self._version_cmd = [self.cmd, "-c", "$PSVersionTable"] self.activate_cmd = "." diff --git a/tests/unit/activation/test_python_activator.py b/tests/unit/activation/test_python_activator.py index a4cf1d8ca..6320e82cb 100644 --- a/tests/unit/activation/test_python_activator.py +++ b/tests/unit/activation/test_python_activator.py @@ -4,11 +4,14 @@ import os import sys +from virtualenv.activation import PythonActivator + def test_python(raise_on_non_source_class, activation_tester): class Python(raise_on_non_source_class): def __init__(self, session): super(Python, self).__init__( + PythonActivator, session, sys.executable, activate_script="activate_this.py", diff --git a/tests/unit/activation/test_xonosh.py b/tests/unit/activation/test_xonosh.py index c8e205ac1..9112631b9 100644 --- a/tests/unit/activation/test_xonosh.py +++ b/tests/unit/activation/test_xonosh.py @@ -2,11 +2,15 @@ import sys +from virtualenv.activation import XonoshActivator + def test_xonosh(activation_tester_class, activation_tester): class Xonosh(activation_tester_class): def __init__(self, session): - super(Xonosh, self).__init__(session, "xonsh", "activate.xsh", "xsh") + super(Xonosh, self).__init__( + XonoshActivator, session, "xonsh.exe" if sys.platform == "win32" else "xonsh", "activate.xsh", "xsh" + ) self._invoke_script = [sys.executable, "-m", "xonsh"] self.__version_cmd = [sys.executable, "-m", "xonsh", "--version"] From dc436a846833d121b514189f0cc868026945c498 Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Sat, 30 Nov 2019 21:16:35 +0000 Subject: [PATCH 12/15] fix powershell --- tests/unit/activation/conftest.py | 1 - tests/unit/activation/{test_dos.py => test_batch.py} | 0 tests/unit/activation/test_powershell.py | 3 ++- tests/unit/activation/test_xonosh.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename tests/unit/activation/{test_dos.py => test_batch.py} (100%) diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py index af52c305d..34a49c18c 100644 --- a/tests/unit/activation/conftest.py +++ b/tests/unit/activation/conftest.py @@ -17,7 +17,6 @@ class ActivationTester(object): def __init__(self, of_class, session, cmd, activate_script, extension): self.of_class = of_class self._creator = session.creator - self.cmd = cmd self._version_cmd = [cmd, "--version"] self._invoke_script = [cmd] self.activate_script = activate_script diff --git a/tests/unit/activation/test_dos.py b/tests/unit/activation/test_batch.py similarity index 100% rename from tests/unit/activation/test_dos.py rename to tests/unit/activation/test_batch.py diff --git a/tests/unit/activation/test_powershell.py b/tests/unit/activation/test_powershell.py index 397938721..e2edac4d3 100644 --- a/tests/unit/activation/test_powershell.py +++ b/tests/unit/activation/test_powershell.py @@ -11,7 +11,8 @@ class PowerShell(activation_tester_class): def __init__(self, session): cmd = "powershell.exe" if sys.platform == "win32" else "pwsh" super(PowerShell, self).__init__(PowerShellActivator, session, cmd, "activate.ps1", "ps1") - self._version_cmd = [self.cmd, "-c", "$PSVersionTable"] + self._version_cmd = [cmd, "-c", "$PSVersionTable"] + self._invoke_script = [cmd, "-ExecutionPolicy", "ByPass", "-File"] self.activate_cmd = "." def quote(self, s): diff --git a/tests/unit/activation/test_xonosh.py b/tests/unit/activation/test_xonosh.py index 9112631b9..0091dd3a9 100644 --- a/tests/unit/activation/test_xonosh.py +++ b/tests/unit/activation/test_xonosh.py @@ -12,7 +12,7 @@ def __init__(self, session): XonoshActivator, session, "xonsh.exe" if sys.platform == "win32" else "xonsh", "activate.xsh", "xsh" ) self._invoke_script = [sys.executable, "-m", "xonsh"] - self.__version_cmd = [sys.executable, "-m", "xonsh", "--version"] + self._version_cmd = [sys.executable, "-m", "xonsh", "--version"] def env(self, tmp_path): env = super(Xonosh, self).env(tmp_path) From 2395bbf7d6fb7cb3d6b021c2cfed5c44e579573d Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Sun, 1 Dec 2019 14:20:48 +0000 Subject: [PATCH 13/15] try to fix batch --- src/virtualenv/activation/batch/__init__.py | 1 + src/virtualenv/activation/batch/activate.bat | 2 -- src/virtualenv/activation/batch/deactivate.bat | 2 -- src/virtualenv/activation/batch/pydoc.bat | 1 + 4 files changed, 2 insertions(+), 4 deletions(-) create mode 100644 src/virtualenv/activation/batch/pydoc.bat diff --git a/src/virtualenv/activation/batch/__init__.py b/src/virtualenv/activation/batch/__init__.py index 1944db2d3..4e4b83965 100644 --- a/src/virtualenv/activation/batch/__init__.py +++ b/src/virtualenv/activation/batch/__init__.py @@ -13,3 +13,4 @@ def supports(cls, interpreter): def templates(self): yield Path("activate.bat") yield Path("deactivate.bat") + yield Path("pydoc.bat") diff --git a/src/virtualenv/activation/batch/activate.bat b/src/virtualenv/activation/batch/activate.bat index 8186917fe..96e835b52 100644 --- a/src/virtualenv/activation/batch/activate.bat +++ b/src/virtualenv/activation/batch/activate.bat @@ -33,5 +33,3 @@ if defined _OLD_VIRTUAL_PATH goto ENDIFVPATH2 :ENDIFVPATH2 set "PATH=%VIRTUAL_ENV%\__BIN_NAME__;%PATH%" - -doskey pydoc=python -m pydoc diff --git a/src/virtualenv/activation/batch/deactivate.bat b/src/virtualenv/activation/batch/deactivate.bat index f574a3a10..7bbc56882 100644 --- a/src/virtualenv/activation/batch/deactivate.bat +++ b/src/virtualenv/activation/batch/deactivate.bat @@ -17,5 +17,3 @@ if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH set "PATH=%_OLD_VIRTUAL_PATH%" set _OLD_VIRTUAL_PATH= :ENDIFVPATH - -doskey a= diff --git a/src/virtualenv/activation/batch/pydoc.bat b/src/virtualenv/activation/batch/pydoc.bat new file mode 100644 index 000000000..3d46a231a --- /dev/null +++ b/src/virtualenv/activation/batch/pydoc.bat @@ -0,0 +1 @@ +python.exe -m pydoc %* From f5a4c55e6421b0c424391d259eba7e3c87d1a95c Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Sun, 15 Dec 2019 18:27:51 +0000 Subject: [PATCH 14/15] try to fix Signed-off-by: Bernat Gabor --- tests/unit/activation/conftest.py | 9 ++++++++- tests/unit/activation/test_batch.py | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py index 34a49c18c..42312741f 100644 --- a/tests/unit/activation/conftest.py +++ b/tests/unit/activation/conftest.py @@ -23,6 +23,7 @@ def __init__(self, of_class, session, cmd, activate_script, extension): self.extension = extension self.activate_cmd = "source" self.deactivate = "deactivate" + self.pydoc_call = "pydoc -w pydoc_test" def get_version(self, raise_on_fail): # locally we disable, so that contributors don't need to have everything setup @@ -53,9 +54,15 @@ def __call__(self, monkeypatch, tmp_path): def non_source_activate(self, activate_script): return self._invoke_script + [str(activate_script)] + # noinspection PyMethodMayBeStatic def env(self, tmp_path): env = os.environ.copy() + # add the current python executable folder to the path so we already have another python on the path + # also keep the path so the shells (fish, bash, etc can be discovered) env[str("PATH")] = os.pathsep.join([dirname(sys.executable)] + env.get(str("PATH"), str("")).split(os.pathsep)) + # clear up some environment variables so they don't affect the tests + for key in [k for k in env.keys() if k.startswith("_OLD") or k.startswith("VIRTUALENV_")]: + del env[key] return env def _generate_test_script(self, activate_script, tmp_path): @@ -73,7 +80,7 @@ def _get_test_lines(self, activate_script): self.print_python_exe(), self.print_os_env_var("VIRTUAL_ENV"), # \\ loads documentation from the virtualenv site packages - "pydoc -w pydoc_test", + self.pydoc_call, self.deactivate, self.print_python_exe(), self.print_os_env_var("VIRTUAL_ENV"), diff --git a/tests/unit/activation/test_batch.py b/tests/unit/activation/test_batch.py index 296be881d..143943b1e 100644 --- a/tests/unit/activation/test_batch.py +++ b/tests/unit/activation/test_batch.py @@ -14,8 +14,9 @@ def __init__(self, session): super(Batch, self).__init__(BatchActivator, session, None, "activate.bat", "bat") self._version_cmd = [str(version_script)] self._invoke_script = [] - self.deactivate = "call {}".format(self.quote(str(activation_python.creator.bin_dir / "deactivate.bat"))) + self.deactivate = "call deactivate" self.activate_cmd = "call" + self.pydoc_call = "call {}".format(self.pydoc_call) def _get_test_lines(self, activate_script): return ["@echo off", ""] + super(Batch, self)._get_test_lines(activate_script) From 745e324ef1540c338734d258cb7c695cc89d64e5 Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Sun, 15 Dec 2019 18:55:54 +0000 Subject: [PATCH 15/15] python 3.4 no longer supported on CI Signed-off-by: Bernat Gabor --- azure-pipelines.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ba7ca3d63..058e686d5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -45,8 +45,6 @@ jobs: image: [linux, windows, macOs] py35: image: [linux, windows, macOs] - py34: - image: [linux, windows] py27: image: [linux, windows, macOs] fix_lint: @@ -67,7 +65,7 @@ jobs: displayName: install fish and csh via brew coverage: with_toxenv: 'coverage' # generate .tox/.coverage, .tox/coverage.xml after tests run - for_envs: [py37, py36, py35, py34, py27] + for_envs: [py38, py37, py36, py35, py27] - ${{ if startsWith(variables['Build.SourceBranch'], 'refs/tags/') }}: - template: publish-pypi.yml@tox