From 825cf6602319f8e31426e6d557a122a230229e83 Mon Sep 17 00:00:00 2001 From: Kevin Conley Date: Sat, 7 Nov 2020 15:19:38 -0800 Subject: [PATCH 1/2] Fix running scripts of editable deps on Windows --- poetry/utils/env.py | 1 + tests/console/commands/test_run.py | 34 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index c247bf014f7..f1784b43ddc 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -1100,6 +1100,7 @@ def execute(self, bin, *args, **kwargs): else: return os.execvp(bin, args) else: + kwargs["shell"] = True exe = subprocess.Popen([bin] + list(args), **kwargs) exe.communicate() return exe.returncode diff --git a/tests/console/commands/test_run.py b/tests/console/commands/test_run.py index 351d869d1a9..dd577bd46ef 100644 --- a/tests/console/commands/test_run.py +++ b/tests/console/commands/test_run.py @@ -1,5 +1,7 @@ import pytest +from poetry.utils._compat import WINDOWS + @pytest.fixture def tester(command_tester_factory): @@ -14,3 +16,35 @@ def patches(mocker, env): def test_run_passes_all_args(tester, env): tester.execute("python -V") assert [["python", "-V"]] == env.executed + + +@pytest.mark.skipif( + not WINDOWS, + reason="Poetry only installs CMD script files for console scripts of editable dependencies on Windows", +) +def test_run_console_scripts_of_editable_dependencies_on_windows( + tmp_venv, command_tester_factory, monkeypatch +): + """ + On Windows, Poetry installs console scripts of editable dependencies by creating in the environment's `Scripts/` + directory both: + + A) a Python file named after the console script (no `.py` extension) which imports and calls the console script + using Python code + B) a CMD script file also named after the console script (with `.cmd` extension) which calls `python.exe` to + execute (A) + + This configuration enables calling the console script by name from `cmd.exe` because the `.cmd` file extension + appears by default in the PATHEXT environment variable that `cmd.exe` uses to determine which file should be + executed if a filename without an extension is executed as a command. + + This test validates that you can also run such a CMD script file via `poetry run` just by providing the script's + name without the `.cmd` extension. + """ + tester = command_tester_factory("run", environment=tmp_venv) + + cmd_script_file = tmp_venv._bin_dir / "quix.cmd" + # `/b` ensures we only exit the script instead of any cmd.exe process that called it + cmd_script_file.write_text("exit /b 123") + # We prove that the CMD script executed successfully by verifying the exit code matches what we wrote in the script + assert tester.execute("quix") == 123 From 21521eaf2d8b07042b31d6be773298b5e46e5e7c Mon Sep 17 00:00:00 2001 From: Kevin Conley Date: Sun, 3 Apr 2022 15:29:52 -0700 Subject: [PATCH 2/2] Add Windows-specific logic to fix existing test --- tests/console/commands/test_run.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/console/commands/test_run.py b/tests/console/commands/test_run.py index dd5fcd54767..d86e85523a9 100644 --- a/tests/console/commands/test_run.py +++ b/tests/console/commands/test_run.py @@ -45,14 +45,26 @@ def test_run_keeps_options_passed_before_command( def test_run_has_helpful_error_when_command_not_found( - app_tester: ApplicationTester, env: MockEnv + app_tester: ApplicationTester, env: MockEnv, capfd: pytest.CaptureFixture[str] ): env._execute = True app_tester.execute("run nonexistent-command") assert env.executed == [["nonexistent-command"]] assert app_tester.status_code == 1 - assert app_tester.io.fetch_error() == "Command not found: nonexistent-command\n" + if WINDOWS: + # On Windows we use a shell to run commands which provides its own error + # message when a command is not found that is not captured by the + # ApplicationTester but is captured by pytest, and we can access it via capfd. + # The expected string in this assertion assumes Command Prompt (cmd.exe) is the + # shell used. + assert capfd.readouterr().err.splitlines() == [ + "'nonexistent-command' is not recognized as an internal or external" + " command,", + "operable program or batch file.", + ] + else: + assert app_tester.io.fetch_error() == "Command not found: nonexistent-command\n" @pytest.mark.skipif(