diff --git a/.github/workflows/ci-trigger-full-suite.yml b/.github/workflows/ci-trigger-full-suite.yml index e96ad9c813..d9ce172576 100644 --- a/.github/workflows/ci-trigger-full-suite.yml +++ b/.github/workflows/ci-trigger-full-suite.yml @@ -44,16 +44,31 @@ jobs: PR_BRANCH: ${{ github.event.pull_request.head.ref }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | + set -euo pipefail # Match both branch and PR number: forks can reuse the same branch name. - builds=$(curl -sS --get -H "Authorization: Bearer $BUILDKITE_API_TOKEN" \ + response=$(curl -sS --fail-with-body --get -H "Authorization: Bearer $BUILDKITE_API_TOKEN" \ --data-urlencode "branch=$PR_BRANCH" \ - --data-urlencode "state=running,scheduled" \ - "https://api.buildkite.com/v2/organizations/${{ vars.BUILDKITE_ORG_SLUG }}/pipelines/${{ vars.BUILDKITE_PIPELINE_SLUG }}/builds" \ - | jq -r --arg pr_number "$PR_NUMBER" \ - '.[] | select((.env.TEST_SCOPE? == "merge") and (.env.PR_NUMBER? == $pr_number)) | .number') + --data-urlencode "state[]=running" \ + --data-urlencode "state[]=scheduled" \ + --data-urlencode "exclude_jobs=true" \ + --data-urlencode "exclude_pipeline=true" \ + "https://api.buildkite.com/v2/organizations/${{ vars.BUILDKITE_ORG_SLUG }}/pipelines/${{ vars.BUILDKITE_PIPELINE_SLUG }}/builds") + if ! jq -e ' + type == "array" + and all(.[]; + type == "object" + and (.number | type == "number") + and ((.env // {}) | type == "object")) + ' >/dev/null <<< "$response"; then + echo "::error::Buildkite list-builds response had an unexpected shape." + exit 1 + fi + builds=$(jq -r --arg pr_number "$PR_NUMBER" \ + '.[] | select((.env.TEST_SCOPE? == "merge") and (.env.PR_NUMBER? == $pr_number)) | .number' \ + <<< "$response") for build_num in $builds; do echo "Cancelling Buildkite build #$build_num" - curl -sS -X PUT -H "Authorization: Bearer $BUILDKITE_API_TOKEN" \ + curl -sS --fail-with-body -o /dev/null -X PUT -H "Authorization: Bearer $BUILDKITE_API_TOKEN" \ "https://api.buildkite.com/v2/organizations/${{ vars.BUILDKITE_ORG_SLUG }}/pipelines/${{ vars.BUILDKITE_PIPELINE_SLUG }}/builds/${build_num}/cancel" done diff --git a/fastvideo/tests/contract/test_ci_test_collection.py b/fastvideo/tests/contract/test_ci_test_collection.py index 4a0e0b1798..6c86a757a3 100644 --- a/fastvideo/tests/contract/test_ci_test_collection.py +++ b/fastvideo/tests/contract/test_ci_test_collection.py @@ -164,6 +164,27 @@ def test_merge_comment_has_one_change_aware_trigger_path(): assert "__FASTVIDEO_CI_PLAN_ALL__" in ready_workflow +def test_merge_gate_buildkite_cancellation_fails_closed_on_api_errors(): + workflow = yaml.safe_load((REPO_ROOT / ".github/workflows/ci-trigger-full-suite.yml").read_text()) + steps = workflow["jobs"]["trigger"]["steps"] + cancel_script = next(step["run"] for step in steps if step.get("name") == "Cancel previous Buildkite builds") + + assert cancel_script.splitlines()[0] == "set -euo pipefail" + assert "response=$(curl -sS --fail-with-body --get" in cancel_script + assert cancel_script.count('--data-urlencode "state[]=running"') == 1 + assert cancel_script.count('--data-urlencode "state[]=scheduled"') == 1 + assert cancel_script.count('--data-urlencode "exclude_jobs=true"') == 1 + assert cancel_script.count('--data-urlencode "exclude_pipeline=true"') == 1 + assert 'state=running,scheduled' not in cancel_script + assert 'type == "array"' in cancel_script + assert 'all(.[];' in cancel_script + assert '(.number | type == "number")' in cancel_script + assert '((.env // {}) | type == "object")' in cancel_script + assert '<<< "$response"' in cancel_script + assert "exit 1" in cancel_script + assert "curl -sS --fail-with-body -o /dev/null -X PUT" in cancel_script + + def test_full_ssim_has_a_weekly_slurm_schedule(): workflow = (REPO_ROOT / ".github/workflows/ci-scheduled-ssim.yml").read_text() ssim_step = next(step for step in _pipeline_steps() if step["key"] == "ssim")