From 997128ced47936adabb2f267d2606f8eac253ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Mon, 2 Jan 2023 11:58:01 -0800 Subject: [PATCH] Better message when command parsing on empty input (#2807) --- docs/changelog/2695.bugfix.rst | 1 + src/tox/config/loader/str_convert.py | 2 ++ src/tox/tox_env/python/pip/pip_install.py | 5 ++++- tests/config/loader/test_str_convert.py | 1 + tests/session/cmd/test_show_config.py | 8 ++++++++ tests/tox_env/python/pip/test_pip_install.py | 11 +++++++++++ 6 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/2695.bugfix.rst diff --git a/docs/changelog/2695.bugfix.rst b/docs/changelog/2695.bugfix.rst new file mode 100644 index 000000000..30668e9c5 --- /dev/null +++ b/docs/changelog/2695.bugfix.rst @@ -0,0 +1 @@ +Fail more gracefully when pip :ref:`install_command` is empty - by :user:`jayaddison`. diff --git a/src/tox/config/loader/str_convert.py b/src/tox/config/loader/str_convert.py index e31c034f0..f07545f51 100644 --- a/src/tox/config/loader/str_convert.py +++ b/src/tox/config/loader/str_convert.py @@ -63,6 +63,8 @@ def to_command(value: str) -> Command: pos = splitter.instream.tell() except ValueError: args.append(value[pos:]) + if len(args) == 0: + raise ValueError(f"attempting to parse {value!r} into a command failed") if args[0] != "-" and args[0].startswith("-"): args[0] = args[0][1:] args = ["-"] + args diff --git a/src/tox/tox_env/python/pip/pip_install.py b/src/tox/tox_env/python/pip/pip_install.py index a148ce329..2136e8627 100644 --- a/src/tox/tox_env/python/pip/pip_install.py +++ b/src/tox/tox_env/python/pip/pip_install.py @@ -160,7 +160,10 @@ def _execute_installer(self, deps: Sequence[Any], of_type: str) -> None: outcome.assert_success() def build_install_cmd(self, args: Sequence[str]) -> list[str]: - cmd: Command = self._env.conf["install_command"] + try: + cmd: Command = self._env.conf["install_command"] + except ValueError as exc: + raise Fail(f"unable to determine pip install command: {str(exc)}") from exc install_command = cmd.args try: opts_at = install_command.index("{packages}") diff --git a/tests/config/loader/test_str_convert.py b/tests/config/loader/test_str_convert.py index 49948c4fd..4fe0aaff3 100644 --- a/tests/config/loader/test_str_convert.py +++ b/tests/config/loader/test_str_convert.py @@ -62,6 +62,7 @@ def test_str_convert_ok(raw: str, value: Any, of_type: type[Any]) -> None: ("a", TypeVar, TypeError, r"a cannot cast to .*typing.TypeVar.*"), ("3", Literal["1", "2"], ValueError, r"3 must be one of \('1', '2'\)"), ("3", Union[str, int], TypeError, r"3 cannot cast to typing.Union\[str, int\]"), + ("", Command, ValueError, r"attempting to parse '' into a command failed"), ], ) def test_str_convert_nok(raw: str, of_type: type[Any], msg: str, exc_type: type[Exception]) -> None: diff --git a/tests/session/cmd/test_show_config.py b/tests/session/cmd/test_show_config.py index 33c4fc487..646dc2bf7 100644 --- a/tests/session/cmd/test_show_config.py +++ b/tests/session/cmd/test_show_config.py @@ -99,6 +99,14 @@ def test_show_config_exception(tox_project: ToxProjectCreator) -> None: assert txt in outcome.out +def test_show_config_empty_install_command_exception(tox_project: ToxProjectCreator) -> None: + project = tox_project({"tox.ini": "[testenv:a]\ninstall_command="}) + outcome = project.run("c", "-e", "a", "-k", "install_command") + outcome.assert_success() + txt = "\ninstall_command = # Exception: " "ValueError(\"attempting to parse '' into a command failed\")" + assert txt in outcome.out + + @pytest.mark.parametrize("stdout_is_atty", [True, False]) def test_pass_env_config_default(tox_project: ToxProjectCreator, stdout_is_atty: bool, mocker: MockerFixture) -> None: mocker.patch("sys.stdout.isatty", return_value=stdout_is_atty) diff --git a/tests/tox_env/python/pip/test_pip_install.py b/tests/tox_env/python/pip/test_pip_install.py index bc4705738..8fb70a5fc 100644 --- a/tests/tox_env/python/pip/test_pip_install.py +++ b/tests/tox_env/python/pip/test_pip_install.py @@ -6,8 +6,10 @@ from unittest.mock import Mock import pytest +from packaging.requirements import Requirement from tox.pytest import CaptureFixture, ToxProjectCreator +from tox.tox_env.errors import Fail @pytest.mark.parametrize("arg", [object, [object]]) @@ -35,6 +37,15 @@ def test_pip_install_empty_list(tox_project: ToxProjectCreator) -> None: assert execute_calls.call_count == 0 +def test_pip_install_empty_command_error(tox_project: ToxProjectCreator) -> None: + proj = tox_project({"tox.ini": "[testenv]\ninstall_command="}) + result = proj.run("l") + pip = result.state.envs["py"].installer + + with pytest.raises(Fail, match="unable to determine pip install command"): + pip.install([Requirement("name")], "section", "type") + + def test_pip_install_flags_only_error(tox_project: ToxProjectCreator) -> None: proj = tox_project({"tox.ini": "[testenv:py]\ndeps=-i a"}) result = proj.run("r")