Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion posit-bakery/posit_bakery/cli/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ def matrix(
c = BakeryConfig.from_context(context=context, settings=settings)
images = [i for i in c.model.images if image_name is None or re.search(image_name, i.name) is not None]

# A --dev-spec carrying a channel implies the matrix should be filtered to that
# channel. The shared CI workflow folds the dispatched channel into the dev-spec
# and stops passing --dev-channel, so without this the other channels' dev versions
# would still be emitted (only the matching one gets its version pinned).
effective_dev_channel = settings.dev_channel
if effective_dev_channel is None and settings.dev_spec is not None:
effective_dev_channel = settings.dev_spec.channel

data = []
for img in images:
entry = {"image": img.name}
Expand All @@ -153,7 +161,7 @@ def matrix(
# If EXCLUDE: fall through using img.versions (devVersions are appended
# there by load_dev_versions). The dev_versions filter below handles the rest.
for ver in versions:
included, _ = ver.matches_dev_filter(dev_versions, dev_channel)
included, _ = ver.matches_dev_filter(dev_versions, effective_dev_channel)
if not included:
continue
if image_version is not None and not version_matches(ver.name, image_version):
Expand Down
69 changes: 69 additions & 0 deletions posit-bakery/test/cli/test_ci_matrix_dev_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,72 @@ def test_prod_versions_excluded(self, mock_config_with_matrix_dev_image):
versions_in_output = {e["version"] for e in data}
assert prod_ver1.name not in versions_in_output
assert prod_ver2.name not in versions_in_output


@pytest.fixture
def mock_config_with_two_channel_dev_image():
"""Patch BakeryConfig to return one non-matrix image carrying both a
daily and a preview dev version (the workbench/session-init shape)."""
daily = _make_version("2026.99.0+237", is_dev=True, channel=ReleaseChannelEnum.DAILY)
preview = _make_version("2026.99.0+240", is_dev=True, channel=ReleaseChannelEnum.PREVIEW)
img = MagicMock()
img.name = "workbench"
img.matrix = None
img.versions = [daily, preview]

with patch("posit_bakery.cli.ci.BakeryConfig") as mock:
instance = MagicMock()
instance.model.images = [img]
mock.from_context.return_value = instance
yield mock, daily, preview


class TestCiMatrixDevSpecChannelFilter:
"""A --dev-spec carrying a channel filters the matrix to that channel even
when --dev-channel is omitted. The shared workflow folds the channel into
the dev-spec and drops --dev-channel, so the matrix must honor it."""

def test_filters_to_dev_spec_channel(self, mock_config_with_two_channel_dev_image):
_, daily, preview = mock_config_with_two_channel_dev_image
result = runner.invoke(
app,
[
"ci",
"matrix",
"--context",
BASIC_CONTEXT,
"--dev-versions",
"only",
"--dev-spec",
'{"version": "2026.99.0+240", "channel": "preview"}',
],
catch_exceptions=False,
)
assert result.exit_code == 0, result.output
data = json.loads(result.stdout.strip())
versions = {e["version"] for e in data}
assert preview.name in versions
assert daily.name not in versions

def test_branch_only_dev_spec_does_not_filter(self, mock_config_with_two_channel_dev_image):
"""A branch-only dev-spec carries no channel, so all channels stay in the matrix."""
_, daily, preview = mock_config_with_two_channel_dev_image
result = runner.invoke(
app,
[
"ci",
"matrix",
"--context",
BASIC_CONTEXT,
"--dev-versions",
"only",
"--dev-spec",
'{"release_branch": "2026.06"}',
],
catch_exceptions=False,
)
assert result.exit_code == 0, result.output
data = json.loads(result.stdout.strip())
versions = {e["version"] for e in data}
assert preview.name in versions
assert daily.name in versions
Loading