From ccade9f1e436366fd1916db7088d26425694e2fd Mon Sep 17 00:00:00 2001 From: ddelange <14880945+ddelange@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:36:32 +0200 Subject: [PATCH 1/2] :children_crossing: Show partial solution on build errors --- src/pipgrip/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pipgrip/cli.py b/src/pipgrip/cli.py index b70f192..e3bee09 100755 --- a/src/pipgrip/cli.py +++ b/src/pipgrip/cli.py @@ -479,7 +479,10 @@ def main( exc = None except RuntimeError as e: # RuntimeError coming from pipgrip.pipper - if "Failed to download/build wheel" not in str(e): + if ( + "Failed to download/build wheel for" not in str(e) + and "Failed to get report for" not in str(e) + ): # only continue handling expected RuntimeErrors raise solution = solver.solution From 2f127019681a6f86d5f41b9124891c90113b625c Mon Sep 17 00:00:00 2001 From: ddelange <14880945+ddelange@users.noreply.github.com> Date: Thu, 28 Nov 2024 13:18:08 +0100 Subject: [PATCH 2/2] :white_check_mark: Add test coverage --- src/pipgrip/cli.py | 12 ++++---- src/pipgrip/pipper.py | 12 +++++--- tests/test_cli.py | 66 ++++++++++++++++++++++++++++++++----------- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/pipgrip/cli.py b/src/pipgrip/cli.py index e3bee09..636db95 100755 --- a/src/pipgrip/cli.py +++ b/src/pipgrip/cli.py @@ -52,7 +52,12 @@ from pipgrip.libs.mixology.package import Package from pipgrip.libs.mixology.version_solver import VersionSolver from pipgrip.package_source import PackageSource, render_pin -from pipgrip.pipper import install_packages, read_requirements +from pipgrip.pipper import ( + BUILD_FAILURE_STR, + REPORT_FAILURE_STR, + install_packages, + read_requirements, +) logging.basicConfig(format="%(levelname)s: %(message)s") logger = logging.getLogger() @@ -479,10 +484,7 @@ def main( exc = None except RuntimeError as e: # RuntimeError coming from pipgrip.pipper - if ( - "Failed to download/build wheel for" not in str(e) - and "Failed to get report for" not in str(e) - ): + if REPORT_FAILURE_STR not in str(e) and BUILD_FAILURE_STR not in str(e): # only continue handling expected RuntimeErrors raise solution = solver.solution diff --git a/src/pipgrip/pipper.py b/src/pipgrip/pipper.py index c2e291d..b503066 100644 --- a/src/pipgrip/pipper.py +++ b/src/pipgrip/pipper.py @@ -49,6 +49,10 @@ logger = logging.getLogger(__name__) +REPORT_FAILURE_STR = "Failed to get report for" +BUILD_FAILURE_STR = "Failed to download/build wheel for" +VERSIONS_FAILURE_STR = "Failed to get available versions for" + def read_requirements(path): re_comments = re.compile(r"(?:^|\s+)#") @@ -292,7 +296,7 @@ def _get_available_versions(package, index_url, extra_index_url, pre): ] _available_versions_cache[cache_key] = available_versions return available_versions - raise RuntimeError("Failed to get available versions for {}".format(package)) + raise RuntimeError("{} {}".format(VERSIONS_FAILURE_STR, package)) def _get_package_report( @@ -357,7 +361,7 @@ def _get_package_report( package, output.strip() ) ) - raise RuntimeError("Failed to get report for {}".format(package)) + raise RuntimeError("{} {}".format(REPORT_FAILURE_STR, package)) else: with io.open(report_file, "r", encoding="utf-8") as fp: return json.load(fp) @@ -397,7 +401,7 @@ def _download_wheel( package, output.strip() ) ) - raise RuntimeError("Failed to download/build wheel for {}".format(package)) + raise RuntimeError("{} {}".format(BUILD_FAILURE_STR, package)) out = out.splitlines()[::-1] abs_wheel_dir_lower = abs_wheel_dir.lower() cwd_wheel_dir_lower = cwd_wheel_dir.lower() @@ -474,7 +478,7 @@ def _download_wheel( "\n".join(out[::-1]) ) ) - raise RuntimeError("Failed to download/build wheel for {}".format(package)) + raise RuntimeError("{} {}".format(BUILD_FAILURE_STR, package)) def _extract_metadata(wheel_fname): diff --git a/tests/test_cli.py b/tests/test_cli.py index cb8fa37..294287a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -30,6 +30,9 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # SPDX-License-Identifier: BSD-3-Clause +import logging +import subprocess + import pytest from click.testing import CliRunner @@ -141,7 +144,13 @@ def mock_stream_bash_command(*args, **kwargs): return "I passed" -def invoke_patched(func, arguments, monkeypatch, use_report=False, **kwargs): +def mock_stream_bash_command_failure(*args, **kwargs): + raise subprocess.CalledProcessError(1, args[0]) + + +def invoke_patched( + func, arguments, monkeypatch, use_report=False, mock_failure=False, **kwargs +): def default_environment(): return { "implementation_name": "cpython", @@ -157,16 +166,28 @@ def default_environment(): "sys_platform": "linux", } - monkeypatch.setattr( - pipgrip.pipper, - "_download_wheel", - mock_download_wheel, - ) - monkeypatch.setattr( - pipgrip.pipper, - "_get_package_report", - mock_get_package_report, - ) + if mock_failure: + monkeypatch.setattr( + pipgrip.pipper, + "stream_bash_command", + mock_stream_bash_command_failure, + ) + else: + monkeypatch.setattr( + pipgrip.pipper, + "stream_bash_command", + mock_stream_bash_command, + ) + monkeypatch.setattr( + pipgrip.pipper, + "_download_wheel", + mock_download_wheel, + ) + monkeypatch.setattr( + pipgrip.pipper, + "_get_package_report", + mock_get_package_report, + ) if use_report: monkeypatch.setattr( pipgrip.pipper, @@ -189,11 +210,6 @@ def default_environment(): "default_environment", default_environment, ) - monkeypatch.setattr( - pipgrip.pipper, - "stream_bash_command", - mock_stream_bash_command, - ) runner = CliRunner() return runner.invoke(main, arguments, **kwargs) @@ -426,6 +442,24 @@ def test_solutions(arguments, expected, monkeypatch): ), "Unexpected output:\n{}".format(result.output.strip()) +def test_failue_build(monkeypatch, caplog): + caplog.set_level(logging.DEBUG) + arguments = ["requests[socks]"] + result = invoke_patched(main, arguments, monkeypatch, mock_failure=True) + assert result.exit_code + assert "Best guess" in caplog.text + + +def test_failue_report(monkeypatch, caplog): + caplog.set_level(logging.DEBUG) + arguments = ["requests[socks]"] + result = invoke_patched( + main, arguments, monkeypatch, use_report=True, mock_failure=True + ) + assert result.exit_code + assert "Best guess" in caplog.text + + @pytest.mark.parametrize( "arguments", [