# SPDX-License-Identifier: Apache-2.0 from __future__ import annotations import sys from pathlib import Path import pytest import yaml sys.path.insert(0, str(Path(__file__).resolve().parents[2] / ".buildkite" / "common" / "scripts")) from skip_ci import resolve_ci_decision # noqa: E402 from upload_pipeline import ( # noqa: E402 _expand_mirror_hardwares, _load_bootstrap_steps, _render_bootstrap_pipeline, _render_test_pipeline, ) pytestmark = [pytest.mark.core_model, pytest.mark.cpu] CUDA_BOOTSTRAP_STEPS = Path(".buildkite/cuda/bootstrap-upload-steps.yml") BOOTSTRAP_STEPS_TEMPLATE = """steps: - key: image-build - key: upload-ready-pipeline - key: upload-merge-pipeline - key: upload-nightly-pipeline - key: upload-weekly-pipeline """ def _render(changed_files: list[str]) -> str: decision = resolve_ci_decision(changed_files) return _render_bootstrap_pipeline( BOOTSTRAP_STEPS_TEMPLATE, decision=decision, path=CUDA_BOOTSTRAP_STEPS, ) def test_bootstrap_if_injected_by_step_key() -> None: rendered = _render_bootstrap_pipeline( BOOTSTRAP_STEPS_TEMPLATE, decision=resolve_ci_decision([]), path=Path(".buildkite/npu/bootstrap-upload-steps.yml"), ) doc = yaml.safe_load(rendered) by_key = {step["key"]: step for step in doc["steps"]} assert "image-build" in by_key # Unconditional image has no ``if`` (Buildkite rejects YAML bool if: true). assert "if" not in by_key["image-build"] assert isinstance(by_key["upload-ready-pipeline"]["if"], str) assert isinstance(by_key["upload-nightly-pipeline"]["if"], str) def test_bootstrap_steps_loaded_from_file() -> None: steps = _load_bootstrap_steps(CUDA_BOOTSTRAP_STEPS) assert "key: image-build" in steps assert "key: upload-ready-pipeline" in steps assert "placeholder:" not in steps def test_docs_only_allows_main_scheduled_nightly_weekly_only() -> None: """skip_all: no PR labels; main + NIGHTLY=1 / WEEKLY=1 still gates scheduled CI.""" rendered = _render(["docs/foo.md"]) assert "key: image-build" in rendered assert "key: upload-nightly-pipeline" in rendered assert "key: upload-weekly-pipeline" in rendered # Scheduled nightly also uploads L2/L3 with --e2e assert "key: upload-ready-pipeline" in rendered assert "key: upload-merge-pipeline" in rendered assert 'build.env("NIGHTLY") == "1"' in rendered assert 'build.env("WEEKLY") == "1"' in rendered assert "nightly-test" not in rendered assert "weekly-test" not in rendered assert "merge-test" not in rendered assert 'labels includes "ready"' not in rendered assert "if: false" not in rendered def test_yaml_gated_l45_only_does_not_unconditionally_build_image() -> None: rendered = _render([".buildkite/cuda/test-nightly.yml"]) assert "if: true" not in rendered assert 'build.pull_request.labels includes "nightly-test"' in rendered assert 'build.pull_request.labels includes "weekly-test"' in rendered # L2/L3 upload steps are unconditionally disabled → omitted from pipeline assert "key: upload-ready-pipeline" not in rendered assert "key: upload-merge-pipeline" not in rendered assert "key: upload-weekly-pipeline" in rendered def test_yaml_gated_l2_still_enables_image_via_ready_base() -> None: rendered = _render([".buildkite/cuda/test-ready.yml"]) assert 'build.pull_request.labels includes "ready"' in rendered assert "if: true" not in rendered def test_mirror_hardwares_l4_1_expands_to_agents_and_plugins() -> None: doc = { "steps": [ { "label": "Simple Test", "mirror_hardwares": "l4_1", "commands": ["pytest -sv tests/example"], }, ], } rendered = _render_test_pipeline(doc, changed_files=None) step = rendered["steps"][0] assert "mirror_hardwares" not in step assert step["agents"]["queue"] == "gpu_1_queue" assert step["plugins"][0]["docker#v5.2.0"]["image"].endswith("$BUILDKITE_COMMIT") def test_mirror_hardwares_conflicts_with_explicit_agents() -> None: with pytest.raises(ValueError, match="agents/plugins/image"): _expand_mirror_hardwares( {"label": "bad", "mirror_hardwares": "l4_1", "agents": {"queue": "gpu_1_queue"}}, ) def test_mirror_hardwares_a2b3_npu_4_expands_agents_image_and_plugins() -> None: doc = { "steps": [ { "label": "NPU X2V Test", "mirror_hardwares": "a2b3_npu_4", "commands": ["pytest -sv tests/example"], }, ], } rendered = _render_test_pipeline(doc, changed_files=None) step = rendered["steps"][0] assert "mirror_hardwares" not in step assert step["agents"]["queue"] == "ascend-a2b3" assert step["agents"]["resource_class"] == "npu-4" assert step["image"].endswith("${BUILDKITE_COMMIT}") assert step["plugins"][0]["kubernetes"]["podSpecPatch"]["imagePullSecrets"] == [ {"name": "swr-secret"}, ] def _surviving_labels(doc: dict, changed_files: list[str]) -> set[str]: rendered = _render_test_pipeline(doc, changed_files=changed_files) labels: set[str] = set() def walk(steps: list | None) -> None: for step in steps or []: if not isinstance(step, dict): continue if "label" in step: labels.add(step["label"]) walk(step.get("steps")) walk(rendered.get("steps")) return labels # Synthetic coverage-style job: shared inputs that change what the split measures. _COVERAGE_SHARED_INPUTS_DOC = { "steps": [ { "label": "Coverage Pilot", "source_file_dependencies": [ "tests/e2e/online_serving/test_example.py", ".buildkite/common/scripts/run_cov_split.sh", "pyproject.toml", ], "commands": [".buildkite/common/scripts/run_cov_split.sh --model-id example"], }, { "label": "Unrelated Model Test", "source_file_dependencies": [ "tests/e2e/online_serving/test_other.py", ], "commands": ["pytest -sv tests/e2e/online_serving/test_other.py"], }, ], } @pytest.mark.parametrize( "changed_file", [ ".buildkite/common/scripts/run_cov_split.sh", "pyproject.toml", ], ) def test_coverage_shared_inputs_select_dependent_job(changed_file: str) -> None: """Jobs that list coverage shared inputs must stay selected when those files change.""" labels = _surviving_labels(_COVERAGE_SHARED_INPUTS_DOC, [changed_file]) assert "Coverage Pilot" in labels assert "Unrelated Model Test" not in labels def test_coverage_shared_inputs_ignored_for_unrelated_change() -> None: labels = _surviving_labels( _COVERAGE_SHARED_INPUTS_DOC, ["vllm_omni/entrypoints/openai/serving_chat.py"], ) assert "Coverage Pilot" not in labels assert "Unrelated Model Test" not in labels