Skip to content

Commit

Permalink
Fix python 2 activator when generated from python 3 is invalid (pypa#…
Browse files Browse the repository at this point in the history
  • Loading branch information
gaborbernat authored May 3, 2020
1 parent 481e395 commit 19fc7bc
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 17 deletions.
1 change: 1 addition & 0 deletions docs/changelog/1776.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix generating a Python 2 environment from Python 3 creates invalid python activator - by :user:`gaborbernat`.
4 changes: 3 additions & 1 deletion src/virtualenv/activation/python/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import absolute_import, unicode_literals

import os
import sys
from collections import OrderedDict

from virtualenv.util.path import Path
Expand Down Expand Up @@ -29,5 +30,6 @@ def replacements(self, creator, dest_folder):
def _repr_unicode(creator, value):
py2 = creator.interpreter.version_info.major == 2
if py2: # on Python 2 we need to encode this into explicit utf-8, py3 supports unicode literals
value = ensure_text(repr(value.encode("utf-8"))[1:-1])
start = 2 if sys.version_info[0] == 3 else 1
value = ensure_text(repr(value.encode("utf-8"))[start:-1])
return value
6 changes: 5 additions & 1 deletion src/virtualenv/activation/via_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ def templates(self):
def generate(self, creator):
dest_folder = creator.bin_dir
replacements = self.replacements(creator, dest_folder)
self._generate(replacements, self.templates(), dest_folder, creator)
generated = self._generate(replacements, self.templates(), dest_folder, creator)
if self.flag_prompt is not None:
creator.pyenv_cfg["prompt"] = self.flag_prompt
return generated

def replacements(self, creator, dest_folder):
return {
Expand All @@ -39,10 +40,13 @@ def replacements(self, creator, dest_folder):
}

def _generate(self, replacements, templates, to_folder, creator):
generated = []
for template in templates:
text = self.instantiate_template(replacements, template, creator)
dest = to_folder / self.as_name(template)
dest.write_text(text, encoding="utf-8")
generated.append(dest)
return generated

def as_name(self, template):
return template.name
Expand Down
13 changes: 13 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pytest
import six

from virtualenv.discovery.builtin import get_interpreter
from virtualenv.discovery.py_info import PythonInfo
from virtualenv.info import IS_PYPY, IS_WIN, fs_supports_symlink
from virtualenv.report import LOGGER
Expand Down Expand Up @@ -318,3 +319,15 @@ def temp_app_data(monkeypatch, tmp_path):
app_data = tmp_path / "app-data"
monkeypatch.setenv(str("VIRTUALENV_OVERRIDE_APP_DATA"), str(app_data))
return app_data


@pytest.fixture(scope="session")
def cross_python(is_inside_ci, session_app_data):
spec = str(2 if sys.version_info[0] == 3 else 3)
interpreter = get_interpreter(spec, session_app_data)
if interpreter is None:
msg = "could not find {}".format(spec)
if is_inside_ci:
raise RuntimeError(msg)
pytest.skip(msg=msg)
yield interpreter
26 changes: 26 additions & 0 deletions tests/unit/activation/test_activate_this.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from virtualenv.activation import PythonActivator
from virtualenv.config.cli.parser import VirtualEnvOptions
from virtualenv.run import session_via_cli


def test_python_activator_cross(session_app_data, cross_python, special_name_dir):
options = VirtualEnvOptions()
cli_args = [
str(special_name_dir),
"-p",
str(cross_python.executable),
"--app-data",
str(session_app_data.path),
"--without-pip",
"--activators",
"",
]
session = session_via_cli(cli_args, options)
activator = PythonActivator(options)
session.creator.bin_dir.mkdir(parents=True)
results = activator.generate(session.creator)
assert len(results) == 1
result = results[0]
content = result.read_text()
# check that the repr strings have been correctly stripped
assert "\"'" not in content
7 changes: 6 additions & 1 deletion tests/unit/activation/test_fish.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@


@pytest.mark.skipif(IS_WIN, reason="we have not setup fish in CI yet")
def test_fish(activation_tester_class, activation_tester):
def test_fish(activation_tester_class, activation_tester, monkeypatch, tmp_path):
monkeypatch.setenv(str("HOME"), str(tmp_path))
fish_conf_dir = tmp_path / ".config" / "fish"
fish_conf_dir.mkdir(parents=True)
(fish_conf_dir / "config.fish").write_text("")

class Fish(activation_tester_class):
def __init__(self, session):
super(Fish, self).__init__(FishActivator, session, "fish", "activate.fish", "fish")
Expand Down
13 changes: 0 additions & 13 deletions tests/unit/create/test_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from virtualenv.create.via_global_ref.builtin.cpython.cpython2 import CPython2PosixBase
from virtualenv.create.via_global_ref.builtin.cpython.cpython3 import CPython3Posix
from virtualenv.create.via_global_ref.builtin.python2.python2 import Python2
from virtualenv.discovery.builtin import get_interpreter
from virtualenv.discovery.py_info import PythonInfo
from virtualenv.info import IS_PYPY, IS_WIN, PY2, PY3, fs_is_case_sensitive
from virtualenv.pyenv_cfg import PyEnvCfg
Expand Down Expand Up @@ -315,18 +314,6 @@ def test_prompt_set(tmp_path, creator, prompt):
assert cfg["prompt"] == actual_prompt


@pytest.fixture(scope="session")
def cross_python(is_inside_ci, session_app_data):
spec = "{}{}".format(CURRENT.implementation, 2 if CURRENT.version_info.major == 3 else 3)
interpreter = get_interpreter(spec, session_app_data)
if interpreter is None:
msg = "could not find {}".format(spec)
if is_inside_ci:
raise RuntimeError(msg)
pytest.skip(msg=msg)
yield interpreter


@pytest.mark.slow
def test_cross_major(cross_python, coverage_env, tmp_path, session_app_data, current_fastest):
cmd = [
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ commands =
coverage erase
coverage run -m pytest \
--junitxml {toxworkdir}/junit.{envname}.xml \
tests {posargs:--int --timeout 600 -n {env:PYTEST_XDIST:0}}
tests {posargs:--int --timeout 600 -n {env:PYTEST_XDIST:auto}}

coverage combine
coverage report
Expand Down

0 comments on commit 19fc7bc

Please sign in to comment.