diff --git a/commodore/dependency_mgmt/__init__.py b/commodore/dependency_mgmt/__init__.py index 03da1b4c..bd849d8e 100644 --- a/commodore/dependency_mgmt/__init__.py +++ b/commodore/dependency_mgmt/__init__.py @@ -110,7 +110,11 @@ def fetch_parallel(fetch_fun, cfg, to_fetch): Fetch dependencies in parallel threads with ThreadPoolExecutor. """ with ThreadPoolExecutor() as exe: - exe.map(fetch_fun, itertools.repeat(cfg), to_fetch) + # We need to collect the results from the iterator produced by exe.map to ensure + # that any exceptions raised in `fetch_fun` are propagated, cf. + # https://docs.python.org/3/library/concurrent.futures.html#executor-objects. We + # do so by simply materializing the iterator into a list. + list(exe.map(fetch_fun, itertools.repeat(cfg), to_fetch)) def register_components(cfg: Config): diff --git a/tests/test_dependency_mgmt.py b/tests/test_dependency_mgmt.py index a03a66c7..9a42ba7d 100644 --- a/tests/test_dependency_mgmt.py +++ b/tests/test_dependency_mgmt.py @@ -197,6 +197,23 @@ def test_fetch_components_raises( ) +@patch("commodore.dependency_mgmt._read_components") +@patch("commodore.dependency_mgmt._discover_components") +def test_fetch_components_raises_giterror( + patch_discover, patch_read, config: Config, tmp_path: Path +): + components = ["foo"] + patch_discover.return_value = (components, {}) + read_retval = setup_components_upstream(tmp_path, components) + read_retval["foo"].version = "nonexistent" + patch_read.return_value = read_retval + + with pytest.raises(Exception) as excinfo: + dependency_mgmt.fetch_components(config) + + assert "Failed to checkout revision 'nonexistent'" in str(excinfo.value) + + @patch("commodore.dependency_mgmt._read_components") @patch("commodore.dependency_mgmt._discover_components") def test_fetch_components_is_minimal(