| from __future__ import annotations |
| |
| import os |
| import shutil |
| import subprocess |
| import sys |
| from argparse import Namespace |
| |
| import pytest |
| |
| from virtualenv.activation import FishActivator |
| from virtualenv.info import IS_WIN |
| |
| FISH = shutil.which("fish") |
| |
| |
| @pytest.mark.parametrize( |
| ("tcl_lib", "tk_lib", "present"), |
| [ |
| ("/path/to/tcl", "/path/to/tk", True), |
| (None, None, False), |
| ], |
| ) |
| def test_fish_tkinter_generation(tmp_path, tcl_lib, tk_lib, present) -> None: |
| # GIVEN |
| class MockInterpreter: |
| pass |
| |
| interpreter = MockInterpreter() |
| interpreter.tcl_lib = tcl_lib |
| interpreter.tk_lib = tk_lib |
| |
| class MockCreator: |
| def __init__(self, dest) -> None: |
| self.dest = dest |
| self.bin_dir = dest / "bin" |
| self.bin_dir.mkdir() |
| self.interpreter = interpreter |
| self.pyenv_cfg = {} |
| self.env_name = "my-env" |
| |
| creator = MockCreator(tmp_path) |
| options = Namespace(prompt=None) |
| activator = FishActivator(options) |
| |
| # WHEN |
| activator.generate(creator) |
| content = (creator.bin_dir / "activate.fish").read_text(encoding="utf-8") |
| |
| # THEN |
| # PKG_CONFIG_PATH is always set |
| assert 'set -gx _OLD_PKG_CONFIG_PATH "$PKG_CONFIG_PATH"' in content |
| assert 'set -gx PKG_CONFIG_PATH "$VIRTUAL_ENV/lib/pkgconfig:$PKG_CONFIG_PATH"' in content |
| assert "set -e _OLD_PKG_CONFIG_PATH" in content |
| |
| if present: |
| assert "set -gx TCL_LIBRARY '/path/to/tcl'" in content |
| assert "set -gx TK_LIBRARY '/path/to/tk'" in content |
| else: |
| assert "if test -n ''\n if set -q TCL_LIBRARY;" in content |
| assert "if test -n ''\n if set -q TK_LIBRARY;" in content |
| |
| |
| @pytest.mark.skipif(IS_WIN, reason="fish is not available on Windows") |
| @pytest.mark.skipif(FISH is None, reason="fish is not installed") |
| def test_fish_prompt_survives_shadowed_source(activation_python, tmp_path) -> None: |
| # A user function shadowing `.`/`source` must not hijack the prompt's exit-status restore. Source a copy of the |
| # rendered script from an ASCII path so the driver avoids the venv path's special characters. |
| script = tmp_path / "activate.fish" |
| rendered = activation_python.creator.bin_dir / "activate.fish" |
| script.write_text(rendered.read_text(encoding="utf-8"), encoding="utf-8") |
| start, trap = tmp_path / "start", tmp_path / "trap" |
| start.mkdir() |
| trap.mkdir() |
| driver = tmp_path / "driver.fish" |
| driver.write_text( |
| f"cd '{start}'\nfunction . ; cd '{trap}' ; end\nsource '{script}'\nfish_prompt\necho \"PWD=$PWD\"\n", |
| encoding="utf-8", |
| ) |
| out = subprocess.run( |
| [FISH, str(driver)], capture_output=True, text=True, encoding="utf-8", timeout=60, check=True |
| ).stdout |
| assert f"PWD={start}\n" in out, out |
| |
| |
| @pytest.mark.skipif(IS_WIN, reason="we have not setup fish in CI yet") |
| def test_fish(activation_tester_class, activation_tester, monkeypatch, tmp_path) -> None: |
| monkeypatch.setenv("HOME", str(tmp_path)) |
| fish_conf_dir = tmp_path / ".config" / "fish" |
| fish_conf_dir.mkdir(parents=True) |
| (fish_conf_dir / "config.fish").write_text("", encoding="utf-8") |
| |
| class Fish(activation_tester_class): |
| def __init__(self, session) -> None: |
| super().__init__(FishActivator, session, "fish", "activate.fish", "fish") |
| |
| def print_prompt(self) -> str: |
| return "fish_prompt" |
| |
| def _get_test_lines(self, activate_script): |
| return [ |
| self.print_python_exe(), |
| self.print_os_env_var("VIRTUAL_ENV"), |
| self.print_os_env_var("VIRTUAL_ENV_PROMPT"), |
| self.print_os_env_var("PATH"), |
| self.activate_call(activate_script), |
| self.print_python_exe(), |
| self.print_os_env_var("VIRTUAL_ENV"), |
| self.print_os_env_var("VIRTUAL_ENV_PROMPT"), |
| self.print_os_env_var("PATH"), |
| self.print_prompt(), |
| # \\ loads documentation from the virtualenv site packages |
| self.pydoc_call, |
| self.deactivate, |
| self.print_python_exe(), |
| self.print_os_env_var("VIRTUAL_ENV"), |
| self.print_os_env_var("VIRTUAL_ENV_PROMPT"), |
| self.print_os_env_var("PATH"), |
| "", # just finish with an empty new line |
| ] |
| |
| def assert_output(self, out, raw, _) -> None: |
| """Compare _get_test_lines() with the expected values.""" |
| assert out[0], raw |
| assert out[1] == "None", raw |
| assert out[2] == "None", raw |
| # self.activate_call(activate_script) runs at this point |
| expected = self._creator.exe.parent / os.path.basename(sys.executable) |
| assert self.norm_path(out[4]) == self.norm_path(expected), raw |
| assert self.norm_path(out[5]) == self.norm_path(self._creator.dest).replace("\\\\", "\\"), raw |
| assert out[6] == self._creator.env_name |
| # Some attempts to test the prompt output print more than 1 line. |
| # So we need to check if the prompt exists on any of them. |
| prompt_text = f"({self._creator.env_name}) " |
| assert any(prompt_text in line for line in out[7:-5]), raw |
| |
| assert out[-5] == "wrote pydoc_test.html", raw |
| content = tmp_path / "pydoc_test.html" |
| assert content.exists(), raw |
| # post deactivation, same as before |
| assert out[-4] == out[0], raw |
| assert out[-3] == "None", raw |
| assert out[-2] == "None", raw |
| |
| # Check that the PATH is restored |
| assert out[3] == out[13], raw |
| # Check that PATH changed after activation |
| assert out[3] != out[8], raw |
| |
| activation_tester(Fish) |