diff --git a/src/poetry/utils/env/env_manager.py b/src/poetry/utils/env/env_manager.py index 0c1d52e0433..2087b1b3f6e 100644 --- a/src/poetry/utils/env/env_manager.py +++ b/src/poetry/utils/env/env_manager.py @@ -125,7 +125,7 @@ def activate(self, python: str) -> Env: if self.use_in_project_venv(): create = False venv = self.in_project_venv - if venv.exists(): + if venv.is_dir(): # We need to check if the patch version is correct _venv = VirtualEnv(venv) current_patch = ".".join(str(v) for v in _venv.version_info[:3]) @@ -452,7 +452,7 @@ def create_venv( f"Invalid template string in 'virtualenvs.prompt' setting: {e}" ) from e - if not venv.exists(): + if not venv.is_dir(): if create_venv is False: self._io.write_error_line( "" @@ -463,6 +463,12 @@ def create_venv( return self.get_system_env() + if venv.is_file(): + self._io.write_error_line( + f"{venv} is not a virtual environment but a file. Removing it." + ) + venv.unlink() + self._io.write_error_line( f"Creating virtualenv {name} in" f" {venv_path if not WINDOWS else get_real_windows_path(venv_path)!s}" diff --git a/tests/utils/env/conftest.py b/tests/utils/env/conftest.py index c5250195854..8e24245e1b6 100644 --- a/tests/utils/env/conftest.py +++ b/tests/utils/env/conftest.py @@ -4,6 +4,8 @@ import pytest +from cleo.io.buffered_io import BufferedIO + from poetry.utils.env import EnvManager @@ -19,5 +21,10 @@ def poetry(project_factory: ProjectFactory, fixture_dir: FixtureDirGetter) -> Po @pytest.fixture -def manager(poetry: Poetry) -> EnvManager: - return EnvManager(poetry) +def io() -> BufferedIO: + return BufferedIO() + + +@pytest.fixture +def manager(poetry: Poetry, io: BufferedIO) -> EnvManager: + return EnvManager(poetry, io) diff --git a/tests/utils/env/test_env_manager.py b/tests/utils/env/test_env_manager.py index 71e05e4cb5c..cc6878968c6 100644 --- a/tests/utils/env/test_env_manager.py +++ b/tests/utils/env/test_env_manager.py @@ -32,6 +32,7 @@ from collections.abc import Iterator from unittest.mock import MagicMock + from cleo.io.buffered_io import BufferedIO from pytest import LogCaptureFixture from pytest_mock import MockerFixture @@ -470,6 +471,56 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( assert not envs_file.exists() +def test_activate_with_in_project_setting_if_venv_is_file( + manager: EnvManager, + poetry: Poetry, + io: BufferedIO, + config: Config, + tmp_path: Path, + mocker: MockerFixture, + venv_flags_default: dict[str, bool], + mocked_python_register: MockedPythonRegister, +) -> None: + if "VIRTUAL_ENV" in os.environ: + del os.environ["VIRTUAL_ENV"] + + config.merge( + { + "virtualenvs": { + "path": str(tmp_path / "virtualenvs"), + "in-project": True, + } + } + ) + + mocked_python_register("3.7.1") + m = mocker.patch("poetry.utils.env.EnvManager.build_venv") + + venv_path = poetry.file.path.parent / ".venv" + assert not venv_path.exists() + venv_path.touch() + assert venv_path.is_file() + + manager.activate("python3.7") + + m.assert_called_with( + poetry.file.path.parent / ".venv", + executable=Path("/usr/bin/python3.7"), + flags=venv_flags_default, + prompt="simple-project-py3.7", + ) + + envs_file = TOMLFile(tmp_path / "virtualenvs" / "envs.toml") + assert not envs_file.exists() + + # The .venv file is removed, but no .venv is created because we mocked build_venv. + assert not venv_path.exists() + assert ( + f"{venv_path} is not a virtual environment but a file. Removing it." + in io.fetch_error() + ) + + def test_deactivate_non_activated_but_existing( tmp_path: Path, manager: EnvManager,