From 665a32c7be1a7b2eaccbb886159e68983354e8e1 Mon Sep 17 00:00:00 2001 From: "Ian H. Pittwood" Date: Mon, 15 Jun 2026 10:24:42 -0600 Subject: [PATCH 1/4] fix: remove re.escape calls for image versions --- .../plugins/builtin/dgoss/__init__.py | 3 +-- .../plugins/builtin/hadolint/__init__.py | 3 +-- .../plugins/builtin/wizcli/__init__.py | 3 +-- .../test/plugins/builtin/dgoss/test_init.py | 22 +++++++++++++++++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/posit-bakery/posit_bakery/plugins/builtin/dgoss/__init__.py b/posit-bakery/posit_bakery/plugins/builtin/dgoss/__init__.py index 35fd424b..44a19c13 100644 --- a/posit-bakery/posit_bakery/plugins/builtin/dgoss/__init__.py +++ b/posit-bakery/posit_bakery/plugins/builtin/dgoss/__init__.py @@ -1,5 +1,4 @@ import logging -import re import warnings from enum import Enum from pathlib import Path @@ -197,7 +196,7 @@ def run( settings = BakerySettings( filter=BakeryConfigFilter( image_name=image_name, - image_version=re.escape(image_version) if image_version else None, + image_version=image_version, image_variant=image_variant, image_os=image_os, image_platform=[platform], diff --git a/posit-bakery/posit_bakery/plugins/builtin/hadolint/__init__.py b/posit-bakery/posit_bakery/plugins/builtin/hadolint/__init__.py index 3704b115..8b0918e5 100644 --- a/posit-bakery/posit_bakery/plugins/builtin/hadolint/__init__.py +++ b/posit-bakery/posit_bakery/plugins/builtin/hadolint/__init__.py @@ -1,5 +1,4 @@ import logging -import re from enum import Enum from pathlib import Path from typing import Annotated, Optional @@ -212,7 +211,7 @@ def run( settings = BakerySettings( filter=BakeryConfigFilter( image_name=image_name, - image_version=re.escape(image_version) if image_version else None, + image_version=image_version, image_variant=image_variant, image_os=image_os, image_platform=[], diff --git a/posit-bakery/posit_bakery/plugins/builtin/wizcli/__init__.py b/posit-bakery/posit_bakery/plugins/builtin/wizcli/__init__.py index bbe06f8f..37c6e5db 100644 --- a/posit-bakery/posit_bakery/plugins/builtin/wizcli/__init__.py +++ b/posit-bakery/posit_bakery/plugins/builtin/wizcli/__init__.py @@ -1,5 +1,4 @@ import logging -import re from enum import Enum from pathlib import Path from typing import Annotated, Optional @@ -224,7 +223,7 @@ def scan( settings = BakerySettings( filter=BakeryConfigFilter( image_name=image_name, - image_version=re.escape(image_version) if image_version else None, + image_version=image_version, image_variant=image_variant, image_os=image_os, image_platform=[platform], diff --git a/posit-bakery/test/plugins/builtin/dgoss/test_init.py b/posit-bakery/test/plugins/builtin/dgoss/test_init.py index 959afa9e..08dcfebd 100644 --- a/posit-bakery/test/plugins/builtin/dgoss/test_init.py +++ b/posit-bakery/test/plugins/builtin/dgoss/test_init.py @@ -90,6 +90,28 @@ def test_latest_default_false(self, mocked_dgoss_run): assert settings.latest is False +class TestDgossRunImageVersionFilter: + """Regression coverage: `--image-version` must reach the filter verbatim. + + ``BakeryConfigFilter.image_version`` is consumed by ``version_matches()``, + which does segment-aware (not regex) matching, so the value must NOT be + regex-escaped. Escaping a calver build string like ``2026.01.2+418.pro1`` + into ``2026\\.01\\.2\\+418\\.pro1`` made the filter match no versions, so + `dgoss run` silently tested nothing and the CI workflow passed.""" + + def test_image_version_passed_verbatim(self, mocked_dgoss_run): + mock_config, _ = mocked_dgoss_run + version = "2026.01.2+418.pro1" + result = runner.invoke( + app, + ["dgoss", "run", "--context", BASIC_CONTEXT, "--image-version", version], + catch_exceptions=False, + ) + assert result.exit_code == 0, result.stdout + settings = mock_config.from_context.call_args[0][1] + assert settings.filter.image_version == version + + class TestDgossRunJobsFlag: """The --jobs flag is forwarded to DGossPlugin.execute().""" From 9fd50ee34f5ee2a225e8cfcdbfd4d6dd57e35c30 Mon Sep 17 00:00:00 2001 From: "Ian H. Pittwood" Date: Mon, 15 Jun 2026 10:39:06 -0600 Subject: [PATCH 2/4] feat: fail build and tool commands when no targets match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a shared exit_if_no_targets() guard that aborts with a non-zero exit code when the active filters resolve to zero image targets. A build or tool run that matches nothing was previously exiting 0, letting broken CI jobs pass while building/testing nothing — the same silent failure that the re.escape image-version bug surfaced. Wire the guard into build, dgoss run (and the deprecated run dgoss alias), hadolint run, and wizcli scan, right after target generation. The error echoes the active filters back to aid debugging. Co-Authored-By: Claude Opus 4.8 (1M context) --- posit-bakery/posit_bakery/cli/build.py | 4 +- posit-bakery/posit_bakery/cli/common.py | 49 ++++++++++++++++++- posit-bakery/posit_bakery/cli/run.py | 4 +- .../plugins/builtin/dgoss/__init__.py | 4 +- .../plugins/builtin/hadolint/__init__.py | 4 +- .../plugins/builtin/wizcli/__init__.py | 4 +- posit-bakery/test/cli/test_build.py | 34 +++++++++++++ posit-bakery/test/cli/test_run_dgoss.py | 25 +++++++++- .../test/plugins/builtin/dgoss/test_init.py | 27 +++++++++- .../plugins/builtin/hadolint/test_init.py | 24 ++++++++- .../test/plugins/builtin/wizcli/test_init.py | 24 ++++++++- 11 files changed, 192 insertions(+), 11 deletions(-) diff --git a/posit-bakery/posit_bakery/cli/build.py b/posit-bakery/posit_bakery/cli/build.py index 9357ddce..3445d77e 100644 --- a/posit-bakery/posit_bakery/cli/build.py +++ b/posit-bakery/posit_bakery/cli/build.py @@ -6,7 +6,7 @@ import python_on_whales import typer -from posit_bakery.cli.common import with_verbosity_flags, with_temporary_storage, parse_dev_spec +from posit_bakery.cli.common import with_verbosity_flags, with_temporary_storage, parse_dev_spec, exit_if_no_targets from posit_bakery.config import BakeryConfig from posit_bakery.config.config import BakeryConfigFilter, BakerySettings from posit_bakery.config.image.posit_product.const import ReleaseChannelEnum @@ -263,6 +263,8 @@ def build( stderr_console.print(f"❌ {e}", style="error") raise typer.Exit(code=1) + exit_if_no_targets(config, settings) + if plan: if strategy == ImageBuildStrategy.BUILD: # TODO: This should turn into dry-run behavior eventually. diff --git a/posit-bakery/posit_bakery/cli/common.py b/posit-bakery/posit_bakery/cli/common.py index eb724ecc..f36c5e1c 100644 --- a/posit-bakery/posit_bakery/cli/common.py +++ b/posit-bakery/posit_bakery/cli/common.py @@ -4,7 +4,7 @@ import logging import tempfile from pathlib import Path -from typing import Annotated, Optional, Any +from typing import Annotated, Optional, Any, TYPE_CHECKING import typer from pydantic import ValidationError @@ -16,12 +16,57 @@ DependencyVersions, ) from posit_bakery.config.image.dev_version.spec import DevBuildSpec -from posit_bakery.log import init_logging +from posit_bakery.log import init_logging, stderr_console from posit_bakery.settings import SETTINGS +# Runtime import would cycle (config.config indirectly imports the CLI package), +# and these are only needed for type hints. +if TYPE_CHECKING: + from posit_bakery.config.config import BakeryConfig, BakerySettings + log = logging.getLogger(__name__) +def exit_if_no_targets(config: "BakeryConfig", settings: "BakerySettings") -> None: + """Abort the command when the active filters resolved to zero image targets. + + A ``build`` or ``dgoss run`` that matches no targets is almost always a + mistake — a typo'd or non-existent ``--image-version``, an over-narrow + combination of filters, or a ``--dev-versions``/``--matrix-versions`` + selection that excludes everything. Exiting 0 in that case let broken CI + jobs pass while building/testing nothing, so fail loudly and echo the + active filters back to aid debugging. + """ + if config.targets: + return + active = _describe_active_filters(settings) + detail = f" matching {active}" if active else "" + stderr_console.print( + f"❌ No image targets{detail}. Check the --image-name, --image-version, " + "--image-variant, --image-os, and --image-platform filters along with the " + "--dev-versions/--matrix-versions selection.", + style="error", + ) + raise typer.Exit(code=1) + + +def _describe_active_filters(settings: "BakerySettings") -> str: + """Render the set filters as a human-readable ``--flag value`` list.""" + f = settings.filter + parts = [ + f"--{name} {value!r}" + for name, value in ( + ("image-name", f.image_name), + ("image-version", f.image_version), + ("image-variant", f.image_variant), + ("image-os", f.image_os), + ("image-platform", f.image_platform), + ) + if value + ] + return ", ".join(parts) + + def parse_dev_spec(ctx: typer.Context, param: typer.CallbackParam, value: str | None) -> DevBuildSpec | None: if value is None: return None diff --git a/posit-bakery/posit_bakery/cli/run.py b/posit-bakery/posit_bakery/cli/run.py index c1f64411..f2cd1cad 100644 --- a/posit-bakery/posit_bakery/cli/run.py +++ b/posit-bakery/posit_bakery/cli/run.py @@ -6,7 +6,7 @@ import typer -from posit_bakery.cli.common import with_verbosity_flags, parse_dev_spec +from posit_bakery.cli.common import with_verbosity_flags, parse_dev_spec, exit_if_no_targets from posit_bakery.config import BakeryConfig from posit_bakery.config.config import BakeryConfigFilter, BakerySettings from posit_bakery.config.image.posit_product.const import ReleaseChannelEnum @@ -197,6 +197,8 @@ def dgoss( ) c = BakeryConfig.from_context(context, settings) + exit_if_no_targets(c, settings) + if metadata_file: c.load_build_metadata_from_file(metadata_file) diff --git a/posit-bakery/posit_bakery/plugins/builtin/dgoss/__init__.py b/posit-bakery/posit_bakery/plugins/builtin/dgoss/__init__.py index 44a19c13..6c80a326 100644 --- a/posit-bakery/posit_bakery/plugins/builtin/dgoss/__init__.py +++ b/posit-bakery/posit_bakery/plugins/builtin/dgoss/__init__.py @@ -6,7 +6,7 @@ import typer -from posit_bakery.cli.common import with_verbosity_flags, parse_dev_spec +from posit_bakery.cli.common import with_verbosity_flags, parse_dev_spec, exit_if_no_targets from posit_bakery.config.image.posit_product.const import ReleaseChannelEnum from posit_bakery.config.config import BakeryConfig, BakeryConfigFilter, BakerySettings from posit_bakery.const import DevVersionInclusionEnum, MatrixVersionInclusionEnum @@ -210,6 +210,8 @@ def run( ) c = BakeryConfig.from_context(context, settings) + exit_if_no_targets(c, settings) + if metadata_file: c.load_build_metadata_from_file(metadata_file) diff --git a/posit-bakery/posit_bakery/plugins/builtin/hadolint/__init__.py b/posit-bakery/posit_bakery/plugins/builtin/hadolint/__init__.py index 8b0918e5..559f497f 100644 --- a/posit-bakery/posit_bakery/plugins/builtin/hadolint/__init__.py +++ b/posit-bakery/posit_bakery/plugins/builtin/hadolint/__init__.py @@ -5,7 +5,7 @@ import typer -from posit_bakery.cli.common import with_verbosity_flags +from posit_bakery.cli.common import with_verbosity_flags, exit_if_no_targets from posit_bakery.config.config import BakeryConfig, BakeryConfigFilter, BakerySettings from posit_bakery.const import DevVersionInclusionEnum, MatrixVersionInclusionEnum from posit_bakery.error import BakeryToolRuntimeErrorGroup @@ -222,6 +222,8 @@ def run( ) c = BakeryConfig.from_context(context, settings) + exit_if_no_targets(c, settings) + # Build options override from CLI flags override_dict = {} if error or warning or info or style: diff --git a/posit-bakery/posit_bakery/plugins/builtin/wizcli/__init__.py b/posit-bakery/posit_bakery/plugins/builtin/wizcli/__init__.py index 37c6e5db..cd1c22b3 100644 --- a/posit-bakery/posit_bakery/plugins/builtin/wizcli/__init__.py +++ b/posit-bakery/posit_bakery/plugins/builtin/wizcli/__init__.py @@ -5,7 +5,7 @@ import typer -from posit_bakery.cli.common import with_verbosity_flags +from posit_bakery.cli.common import with_verbosity_flags, exit_if_no_targets from posit_bakery.config.config import BakeryConfig, BakeryConfigFilter, BakerySettings from posit_bakery.const import DevVersionInclusionEnum, MatrixVersionInclusionEnum from posit_bakery.error import BakeryToolRuntimeErrorGroup @@ -234,6 +234,8 @@ def scan( ) c = BakeryConfig.from_context(context, settings) + exit_if_no_targets(c, settings) + if metadata_file: c.load_build_metadata_from_file(metadata_file) diff --git a/posit-bakery/test/cli/test_build.py b/posit-bakery/test/cli/test_build.py index b51eb952..25be299c 100644 --- a/posit-bakery/test/cli/test_build.py +++ b/posit-bakery/test/cli/test_build.py @@ -59,6 +59,40 @@ def test_version_substitution_error_exits_with_clean_message(self): assert "Traceback" not in result.output +class TestBuildZeroMatchGuard: + """A filter that matches no targets must fail the build, not silently pass.""" + + def test_no_targets_exits_nonzero(self): + with patch("posit_bakery.cli.build.BakeryConfig") as mock: + instance = MagicMock() + instance.targets = [] + mock.from_context.return_value = instance + result = runner.invoke( + app, + ["build", "--context", BASIC_CONTEXT, "--image-version", "9999.99.99"], + catch_exceptions=False, + ) + assert result.exit_code == 1 + assert "No image targets" in result.output + assert "9999.99.99" in result.output + instance.build_targets.assert_not_called() + + def test_no_targets_blocks_plan_output(self): + """--plan must also fail rather than emit an empty bake plan.""" + with patch("posit_bakery.cli.build.BakeryConfig") as mock: + instance = MagicMock() + instance.targets = [] + mock.from_context.return_value = instance + result = runner.invoke( + app, + ["build", "--plan", "--context", BASIC_CONTEXT, "--image-version", "9999.99.99"], + catch_exceptions=False, + ) + assert result.exit_code == 1 + assert "No image targets" in result.output + instance.bake_plan_targets.assert_not_called() + + class TestBuildLatestFlag: def test_latest_passed_to_settings(self, mock_build_config): result = runner.invoke( diff --git a/posit-bakery/test/cli/test_run_dgoss.py b/posit-bakery/test/cli/test_run_dgoss.py index 2baaeb27..33bd75e5 100644 --- a/posit-bakery/test/cli/test_run_dgoss.py +++ b/posit-bakery/test/cli/test_run_dgoss.py @@ -25,7 +25,8 @@ def mocked_bakery_run_dgoss(): with patch("posit_bakery.cli.run.BakeryConfig") as mock_config: instance = MagicMock() instance.base_path = Path(BASIC_CONTEXT) - instance.targets = [] + # Non-empty so the zero-match guard does not abort the happy-path runs. + instance.targets = [MagicMock()] mock_config.from_context.return_value = instance with patch("posit_bakery.cli.run.get_plugin") as mock_get_plugin: mock_plugin = MagicMock() @@ -77,6 +78,28 @@ def test_emits_deprecation_warning(self, mocked_bakery_run_dgoss): assert "removed" in combined.lower() +class TestRunDgossZeroMatchGuard: + """The deprecated path must also fail loudly when no targets match.""" + + def test_no_targets_exits_nonzero(self): + with patch("posit_bakery.cli.run.BakeryConfig") as mock_config: + instance = MagicMock() + instance.base_path = Path(BASIC_CONTEXT) + instance.targets = [] + mock_config.from_context.return_value = instance + with patch("posit_bakery.cli.run.get_plugin") as mock_get_plugin: + mock_plugin = MagicMock() + mock_get_plugin.return_value = mock_plugin + result = runner.invoke( + app, + ["run", "dgoss", "--context", BASIC_CONTEXT, "--image-version", "9999.99.99"], + catch_exceptions=False, + ) + assert result.exit_code == 1 + assert "No image targets" in result.output + mock_plugin.execute.assert_not_called() + + class TestRunDgossLatestFlag: """The --latest flag is passed through to settings and warns with dev inclusion.""" diff --git a/posit-bakery/test/plugins/builtin/dgoss/test_init.py b/posit-bakery/test/plugins/builtin/dgoss/test_init.py index 08dcfebd..302983c6 100644 --- a/posit-bakery/test/plugins/builtin/dgoss/test_init.py +++ b/posit-bakery/test/plugins/builtin/dgoss/test_init.py @@ -28,7 +28,8 @@ def mocked_dgoss_run(): with patch("posit_bakery.plugins.builtin.dgoss.BakeryConfig") as mock_config: instance = MagicMock() instance.base_path = Path(BASIC_CONTEXT) - instance.targets = [] + # Non-empty so the zero-match guard does not abort the happy-path runs. + instance.targets = [MagicMock()] mock_config.from_context.return_value = instance with ( patch("posit_bakery.plugins.builtin.dgoss.DGossPlugin.execute") as mock_execute, @@ -112,6 +113,30 @@ def test_image_version_passed_verbatim(self, mocked_dgoss_run): assert settings.filter.image_version == version +class TestDgossRunZeroMatchGuard: + """A filter that matches no targets must fail loudly, not silently pass. + + Before the guard, `dgoss run` with a non-matching filter exited 0 having + tested nothing, hiding broken CI jobs.""" + + def test_no_targets_exits_nonzero(self): + with patch("posit_bakery.plugins.builtin.dgoss.BakeryConfig") as mock_config: + instance = MagicMock() + instance.base_path = Path(BASIC_CONTEXT) + instance.targets = [] + mock_config.from_context.return_value = instance + with patch("posit_bakery.plugins.builtin.dgoss.DGossPlugin.execute") as mock_execute: + result = runner.invoke( + app, + ["dgoss", "run", "--context", BASIC_CONTEXT, "--image-version", "9999.99.99"], + catch_exceptions=False, + ) + assert result.exit_code == 1 + assert "No image targets" in result.output + assert "9999.99.99" in result.output + mock_execute.assert_not_called() + + class TestDgossRunJobsFlag: """The --jobs flag is forwarded to DGossPlugin.execute().""" diff --git a/posit-bakery/test/plugins/builtin/hadolint/test_init.py b/posit-bakery/test/plugins/builtin/hadolint/test_init.py index 8dff947f..2f723ffb 100644 --- a/posit-bakery/test/plugins/builtin/hadolint/test_init.py +++ b/posit-bakery/test/plugins/builtin/hadolint/test_init.py @@ -30,7 +30,8 @@ def mocked_hadolint_run(): with patch("posit_bakery.plugins.builtin.hadolint.BakeryConfig") as mock_config: instance = MagicMock() instance.base_path = Path(BASIC_CONTEXT) - instance.targets = [] + # Non-empty so the zero-match guard does not abort the happy-path runs. + instance.targets = [MagicMock()] mock_config.from_context.return_value = instance with ( patch("posit_bakery.plugins.builtin.hadolint.HadolintPlugin.execute") as mock_execute, @@ -40,6 +41,27 @@ def mocked_hadolint_run(): yield mock_config, mock_execute +class TestHadolintRunZeroMatchGuard: + """A filter that matches no targets must fail loudly, not silently pass.""" + + def test_no_targets_exits_nonzero(self): + with patch("posit_bakery.plugins.builtin.hadolint.BakeryConfig") as mock_config: + instance = MagicMock() + instance.base_path = Path(BASIC_CONTEXT) + instance.targets = [] + mock_config.from_context.return_value = instance + with patch("posit_bakery.plugins.builtin.hadolint.HadolintPlugin.execute") as mock_execute: + result = runner.invoke( + app, + ["hadolint", "run", "--context", BASIC_CONTEXT, "--image-version", "9999.99.99"], + catch_exceptions=False, + ) + assert result.exit_code == 1 + assert "No image targets" in result.output + assert "9999.99.99" in result.output + mock_execute.assert_not_called() + + class TestHadolintRunLatestFlag: """The --latest flag is passed through to settings and warns with dev inclusion.""" diff --git a/posit-bakery/test/plugins/builtin/wizcli/test_init.py b/posit-bakery/test/plugins/builtin/wizcli/test_init.py index 628a8499..b6c67f80 100644 --- a/posit-bakery/test/plugins/builtin/wizcli/test_init.py +++ b/posit-bakery/test/plugins/builtin/wizcli/test_init.py @@ -30,7 +30,8 @@ def mocked_wizcli_scan(): with patch("posit_bakery.plugins.builtin.wizcli.BakeryConfig") as mock_config: instance = MagicMock() instance.base_path = Path(BASIC_CONTEXT) - instance.targets = [] + # Non-empty so the zero-match guard does not abort the happy-path runs. + instance.targets = [MagicMock()] mock_config.from_context.return_value = instance with ( patch("posit_bakery.plugins.builtin.wizcli.WizCLIPlugin.execute") as mock_execute, @@ -40,6 +41,27 @@ def mocked_wizcli_scan(): yield mock_config, mock_execute +class TestWizcliScanZeroMatchGuard: + """A filter that matches no targets must fail loudly, not silently pass.""" + + def test_no_targets_exits_nonzero(self): + with patch("posit_bakery.plugins.builtin.wizcli.BakeryConfig") as mock_config: + instance = MagicMock() + instance.base_path = Path(BASIC_CONTEXT) + instance.targets = [] + mock_config.from_context.return_value = instance + with patch("posit_bakery.plugins.builtin.wizcli.WizCLIPlugin.execute") as mock_execute: + result = runner.invoke( + app, + ["wizcli", "scan", "--context", BASIC_CONTEXT, "--image-version", "9999.99.99"], + catch_exceptions=False, + ) + assert result.exit_code == 1 + assert "No image targets" in result.output + assert "9999.99.99" in result.output + mock_execute.assert_not_called() + + class TestWizcliScanLatestFlag: """The --latest flag is passed through to settings and warns with dev inclusion.""" From f4b6e4307f8735862e9e3664482175b8792749c7 Mon Sep 17 00:00:00 2001 From: "Ian H. Pittwood" Date: Mon, 15 Jun 2026 10:47:01 -0600 Subject: [PATCH 3/4] fix: `bakery-pr` job must target current ref for CI --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad008720..a1a4c11d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -176,6 +176,7 @@ jobs: packages: write uses: "./.github/workflows/bakery-build-pr.yml" with: + version: ${{ github.head_ref || github.ref_name }} context: "./posit-bakery/test/resources/multiplatform/" dev-versions: include From b13aecf8b44dc84727e24e41fde8d5e226407233 Mon Sep 17 00:00:00 2001 From: "Ian H. Pittwood" Date: Mon, 15 Jun 2026 11:08:11 -0600 Subject: [PATCH 4/4] test: give dgoss dev-spec/dev-stream fixtures non-empty targets These dgoss run and run dgoss happy-path fixtures used empty target lists, which the new zero-match guard now correctly rejects with a non-zero exit. Set non-empty targets so they exercise the success path again. Co-Authored-By: Claude Opus 4.8 (1M context) --- posit-bakery/test/cli/test_dev_spec.py | 6 ++++-- posit-bakery/test/cli/test_dev_stream_deprecated.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/posit-bakery/test/cli/test_dev_spec.py b/posit-bakery/test/cli/test_dev_spec.py index 9216736d..2c4983d9 100644 --- a/posit-bakery/test/cli/test_dev_spec.py +++ b/posit-bakery/test/cli/test_dev_spec.py @@ -196,7 +196,8 @@ def _invoke(self, extra_args: list[str], env: dict | None = None): ): instance = MagicMock() instance.base_path = Path(BASIC_CONTEXT) - instance.targets = [] + # Non-empty so the zero-match guard does not abort the happy-path runs. + instance.targets = [MagicMock()] mock_config.from_context.return_value = instance result = runner.invoke( app, @@ -264,7 +265,8 @@ def _invoke(self, extra_args: list[str], env: dict | None = None): ): instance = MagicMock() instance.base_path = Path(BASIC_CONTEXT) - instance.targets = [] + # Non-empty so the zero-match guard does not abort the happy-path runs. + instance.targets = [MagicMock()] mock_config.from_context.return_value = instance mock_plugin = MagicMock() mock_plugin.execute.return_value = [] diff --git a/posit-bakery/test/cli/test_dev_stream_deprecated.py b/posit-bakery/test/cli/test_dev_stream_deprecated.py index 5c5753c7..8e6d023e 100644 --- a/posit-bakery/test/cli/test_dev_stream_deprecated.py +++ b/posit-bakery/test/cli/test_dev_stream_deprecated.py @@ -195,7 +195,8 @@ def mock_run(self): with patch("posit_bakery.cli.run.BakeryConfig") as mock_config: instance = MagicMock() instance.base_path = Path(BASIC_CONTEXT) - instance.targets = [] + # Non-empty so the zero-match guard does not abort the happy-path runs. + instance.targets = [MagicMock()] mock_config.from_context.return_value = instance with patch("posit_bakery.cli.run.get_plugin") as mock_plugin: mock_dgoss = MagicMock()