diff --git a/.github/actions/fetch_ctk/action.yml b/.github/actions/fetch_ctk/action.yml index 994e35924fc..f18332acc68 100644 --- a/.github/actions/fetch_ctk/action.yml +++ b/.github/actions/fetch_ctk/action.yml @@ -136,6 +136,7 @@ runs: - name: Upload CTK cache if: ${{ !cancelled() && + github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && steps.ctk-get-cache.outputs.cache-hit != 'true' }} uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: diff --git a/ci/tools/tests/test_ctk_cache_save_gate.py b/ci/tools/tests/test_ctk_cache_save_gate.py new file mode 100644 index 00000000000..66e04e0abe3 --- /dev/null +++ b/ci/tools/tests/test_ctk_cache_save_gate.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +"""Regression coverage for the mini-CTK cache save boundary.""" + +import re +from pathlib import Path + +import pytest + +ACTION = Path(__file__).parents[3] / ".github/actions/fetch_ctk/action.yml" + + +def should_save(ref: str, default_branch: str, cancelled: bool, cache_hit: str) -> bool: + """Model the save condition using GitHub's full ref and cache-hit output.""" + return not cancelled and ref == f"refs/heads/{default_branch}" and cache_hit != "true" + + +@pytest.mark.agent_authored(model="gpt-6") +def test_workflow_condition_uses_full_default_branch_ref_and_preserves_guards(): + action = ACTION.read_text() + match = re.search( + r"(?ms)^ - name: Upload CTK cache\n if: \$\{\{(.*?)\}\}", + action, + ) + assert match is not None + expression = " ".join(match.group(1).split()) + assert expression == ( + "!cancelled() && " + "github.ref == format('refs/heads/{0}', " + "github.event.repository.default_branch) && " + "steps.ctk-get-cache.outputs.cache-hit != 'true'" + ) + assert ("- name: Get CUDA components\n if: ${{ steps.ctk-get-cache.outputs.cache-hit != 'true' }}") in action + assert ("- name: Restore CTK cache\n if: ${{ steps.ctk-get-cache.outputs.cache-hit == 'true' }}") in action + + +@pytest.mark.agent_authored(model="gpt-6") +def test_cache_save_ref_and_status_matrix(): + cases = ( + # The default branch saves after a miss, including an empty output. + ("refs/heads/main", "main", False, "false", True), + ("refs/heads/main", "main", False, "", True), + # Other branches and PR merge refs restore but never save. + ("refs/heads/feature", "main", False, "false", False), + ("refs/pull/3071/merge", "main", False, "false", False), + # Full-ref comparison prevents a same-named tag from saving. + ("refs/tags/main", "main", False, "false", False), + # The repository's configured default branch may differ from main. + ("refs/heads/ctk-next", "ctk-next", False, "false", True), + ("refs/heads/main", "main", True, "false", False), + ("refs/heads/main", "main", False, "true", False), + ) + for ref, default_branch, cancelled, cache_hit, expected in cases: + assert should_save(ref, default_branch, cancelled, cache_hit) == expected