From 55caf36a6a4aec10cee9e1ed9f0cd922c38d7be5 Mon Sep 17 00:00:00 2001 From: siddharth ravikumar Date: Sat, 24 May 2025 12:11:59 -0400 Subject: [PATCH 1/4] activate.fish: update fish major version check Use fish's [`string sub`][ss] command to get fish's major version from `$FISH_VERSION`. The [`head`][head] program in OpenBSD does not have the `-c` flag. Addresses #2892. [head]: https://man.openbsd.org/head [ss]: https://fishshell.com/docs/current/cmds/string.html#sub-subcommand --- src/virtualenv/activation/fish/activate.fish | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/virtualenv/activation/fish/activate.fish b/src/virtualenv/activation/fish/activate.fish index f3cd1f2ab..518bc4cff 100644 --- a/src/virtualenv/activation/fish/activate.fish +++ b/src/virtualenv/activation/fish/activate.fish @@ -18,7 +18,7 @@ function deactivate -d 'Exit virtualenv mode and return to the normal environmen # 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 | head -c 1) -lt 3 + if test (string sub -s 1 -l 1 $FISH_VERSION) -lt 3 set -gx PATH (_fishify_path "$_OLD_VIRTUAL_PATH") else set -gx PATH $_OLD_VIRTUAL_PATH @@ -61,7 +61,7 @@ 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 | head -c 1) -lt 3 +if test (string sub -s 1 -l 1 $FISH_VERSION) -lt 3 set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH) else set -gx _OLD_VIRTUAL_PATH $PATH From 08b690f41d1fe5c10e7782700a8729588283d1c8 Mon Sep 17 00:00:00 2001 From: siddharth ravikumar Date: Sat, 28 Jun 2025 12:25:06 -0400 Subject: [PATCH 2/4] tests: update test_fish.py Add `test_fish_path` to verify that the PATH is reverted to the original PATH after virtualenv deactivation. This verification confirms that the `_OLD_VIRTUAL_PATH` was correctly set in fish during the activation of the virtualenv. --- tests/unit/activation/test_fish.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/unit/activation/test_fish.py b/tests/unit/activation/test_fish.py index 6a92a2790..1cc763223 100644 --- a/tests/unit/activation/test_fish.py +++ b/tests/unit/activation/test_fish.py @@ -21,3 +21,33 @@ def print_prompt(self): return "fish_prompt" activation_tester(Fish) + + +@pytest.mark.skipif(IS_WIN, reason="we have not setup fish in CI yet") +def test_fish_path(activation_tester_class, activation_tester, monkeypatch, tmp_path): + 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): + return "fish_prompt" + + def _get_test_lines(self, activate_script): + return [ + self.print_os_env_var("PATH"), + self.activate_call(activate_script), + self.print_os_env_var("PATH"), + self.deactivate, + self.print_os_env_var("PATH"), + ] + + def assert_output(self, out, raw, _): + assert out[0] == out[2], raw + assert out[0] != out[1], raw + + activation_tester(Fish) From d0d8343a718d6ff0bc42bff0b06e84ff3915776b Mon Sep 17 00:00:00 2001 From: siddharth ravikumar Date: Mon, 30 Jun 2025 09:49:53 -0400 Subject: [PATCH 3/4] tests: update test_fish.py Move test `test_fish_path` into `test_fish`. Rename the class that verifies the preservation of original `PATH` after virtualenv deactivation to `FishPath`. --- tests/unit/activation/test_fish.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/unit/activation/test_fish.py b/tests/unit/activation/test_fish.py index 1cc763223..d92efc965 100644 --- a/tests/unit/activation/test_fish.py +++ b/tests/unit/activation/test_fish.py @@ -20,17 +20,7 @@ def __init__(self, session) -> None: def print_prompt(self): return "fish_prompt" - activation_tester(Fish) - - -@pytest.mark.skipif(IS_WIN, reason="we have not setup fish in CI yet") -def test_fish_path(activation_tester_class, activation_tester, monkeypatch, tmp_path): - 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): + class FishPath(activation_tester_class): def __init__(self, session) -> None: super().__init__(FishActivator, session, "fish", "activate.fish", "fish") @@ -51,3 +41,4 @@ def assert_output(self, out, raw, _): assert out[0] != out[1], raw activation_tester(Fish) + activation_tester(FishPath) From 7220fc9d738396aea8f0688f7096f70494926e00 Mon Sep 17 00:00:00 2001 From: siddharth ravikumar Date: Mon, 30 Jun 2025 17:19:58 -0400 Subject: [PATCH 4/4] tests: update test_fish.py Merge test in `FishPath` into `Fish`. --- tests/unit/activation/test_fish.py | 52 ++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/tests/unit/activation/test_fish.py b/tests/unit/activation/test_fish.py index d92efc965..0f3bee25f 100644 --- a/tests/unit/activation/test_fish.py +++ b/tests/unit/activation/test_fish.py @@ -1,5 +1,8 @@ from __future__ import annotations +import os +import sys + import pytest from virtualenv.activation import FishActivator @@ -20,25 +23,54 @@ def __init__(self, session) -> None: def print_prompt(self): return "fish_prompt" - class FishPath(activation_tester_class): - def __init__(self, session) -> None: - super().__init__(FishActivator, session, "fish", "activate.fish", "fish") - - def print_prompt(self): - 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, _): - assert out[0] == out[2], raw - assert out[0] != out[1], raw + # pre-activation + assert out[0], raw + assert out[1] == "None", raw + assert out[2] == "None", raw + # post-activation + 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) - activation_tester(FishPath)