diff --git a/docs/changelog/1776.bugfix.rst b/docs/changelog/1776.bugfix.rst new file mode 100644 index 000000000..493085579 --- /dev/null +++ b/docs/changelog/1776.bugfix.rst @@ -0,0 +1 @@ +Fix generating a Python 2 environment from Python 3 creates invalid python activator - by :user:`gaborbernat`. diff --git a/src/virtualenv/activation/python/__init__.py b/src/virtualenv/activation/python/__init__.py index d37432e8e..c78366181 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 os +import sys from collections import OrderedDict from virtualenv.util.path import Path @@ -29,5 +30,5 @@ 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]) + value = ensure_text(repr(value.encode("utf-8"))[2 if sys.version_info[0] == 3 else 1 : -1]) return value diff --git a/src/virtualenv/activation/via_template.py b/src/virtualenv/activation/via_template.py index 651ed8fe9..4fc03bd33 100644 --- a/src/virtualenv/activation/via_template.py +++ b/src/virtualenv/activation/via_template.py @@ -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) + at_path = self._generate(replacements, self.templates(), dest_folder, creator) if self.flag_prompt is not None: creator.pyenv_cfg["prompt"] = self.flag_prompt + return at_path def replacements(self, creator, dest_folder): return { @@ -43,6 +44,7 @@ def _generate(self, replacements, templates, to_folder, creator): text = self.instantiate_template(replacements, template, creator) dest = to_folder / self.as_name(template) dest.write_text(text, encoding="utf-8") + return dest def as_name(self, template): return template.name diff --git a/tests/conftest.py b/tests/conftest.py index 6fb71f4e5..d7f51ddaa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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 @@ -318,3 +319,16 @@ 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): + current = PythonInfo.current(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 diff --git a/tests/unit/activation/test_activate_this.py b/tests/unit/activation/test_activate_this.py new file mode 100644 index 000000000..ad43c43eb --- /dev/null +++ b/tests/unit/activation/test_activate_this.py @@ -0,0 +1,23 @@ +from virtualenv.activation import PythonActivator +from virtualenv.config.cli.parser import VirtualEnvOptions +from virtualenv.run import session_via_cli + + +def test_from_py3_to_py2(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) + result = activator.generate(session.creator) + content = result.read_text() + assert "\"'" not in content diff --git a/tests/unit/create/test_creator.py b/tests/unit/create/test_creator.py index 969e57335..755f6d9ba 100644 --- a/tests/unit/create/test_creator.py +++ b/tests/unit/create/test_creator.py @@ -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 @@ -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 = [