diff --git a/.github/workflows/ci-guards.yml b/.github/workflows/ci-guards.yml index 02c755c6eb3a..33871f27d2dd 100644 --- a/.github/workflows/ci-guards.yml +++ b/.github/workflows/ci-guards.yml @@ -419,6 +419,10 @@ jobs: run: | python3 tests/test_ci_change_areas.py python3 tests/test_ci_linux_guard_routing.py + + - name: Validate reusable guard workflow structure + if: ${{ matrix.group == 'ci' }} + run: python3 tests/test_ci_guard_workflow_structure.py python3 tests/test_app_host_test_products.py python3 tests/test_reuse_app_host_products.py python3 tests/test_ci_product_publication.py @@ -536,7 +540,13 @@ jobs: workflow-guard-cli-scripts: if: ${{ inputs.linux_guard_cli == 'true' }} - # Slow guards run beside workflow-guard-tests so they do not extend it. + name: workflow-guard-cli-scripts / ${{ matrix.group }} + strategy: + fail-fast: false + matrix: + group: [tui-resolution, profiling] + # The two slow CLI contracts are independent; run them beside each other + # and let the reusable-workflow job aggregate their result. runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }} timeout-minutes: 15 permissions: @@ -556,9 +566,11 @@ jobs: run: python3 -m pip install --disable-pip-version-check --no-input PyYAML==6.0.3 bashlex==0.18 - name: Validate cmux-tui client commit resolution + if: ${{ matrix.group == 'tui-resolution' }} run: ./tests/test_ci_resolve_cmux_tui_client_commit.sh - name: Validate cmux profiling support scripts + if: ${{ matrix.group == 'profiling' }} run: ./tests/test_start_cmux_profiling.sh workflow-guard-source-lints: diff --git a/tests/test_ci_guard_workflow_structure.py b/tests/test_ci_guard_workflow_structure.py new file mode 100644 index 000000000000..d677a53aa964 --- /dev/null +++ b/tests/test_ci_guard_workflow_structure.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Structural contracts for the reusable Linux guard workflow.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +GUARD_WORKFLOW = ROOT / ".github" / "workflows" / "ci-guards.yml" + + +def workflow_job_block(job_name: str) -> str: + lines = GUARD_WORKFLOW.read_text(encoding="utf-8").splitlines() + marker = f" {job_name}:" + for index, line in enumerate(lines): + if line != marker: + continue + body = [line] + for following in lines[index + 1 :]: + if ( + following.startswith(" ") + and not following.startswith(" ") + and following.strip() + ): + break + body.append(following) + return "\n".join(body) + raise AssertionError(f"{job_name} job not found") + + +def test_cli_guard_matrix_runs_independent_slow_contracts_in_parallel() -> None: + block = workflow_job_block("workflow-guard-cli-scripts") + + assert "name: workflow-guard-cli-scripts / ${{ matrix.group }}" in block + assert "group: [tui-resolution, profiling]" in block + assert ( + "- name: Validate cmux-tui client commit resolution\n" + " if: ${{ matrix.group == 'tui-resolution' }}" + ) in block + assert ( + "- name: Validate cmux profiling support scripts\n" + " if: ${{ matrix.group == 'profiling' }}" + ) in block + + +if __name__ == "__main__": + for name, value in sorted(globals().items()): + if name.startswith("test_") and callable(value): + value() + print("PASS: reusable guard workflow structure")