Files
ray-project--ray/ci/ray_ci/automation/test_push_release_test_image.py
T
2026-07-13 13:17:40 +08:00

395 lines
13 KiB
Python

import sys
import pytest
from ci.ray_ci.automation.image_tags_lib import (
format_platform_tag,
format_python_tag,
)
from ci.ray_ci.automation.push_release_test_image import (
ReleaseTestImagePushContext,
_annotate_pushed_image,
)
from ci.ray_ci.configs import DEFAULT_PYTHON_TAG_VERSION
from ci.ray_ci.docker_container import GPU_PLATFORM
def make_ctx(
image_type: str = "ray",
python_version: str = "3.11",
platform: str = "cu12.3.2-cudnn9",
branch: str = "master",
commit: str = "abc123def456",
rayci_build_id: str = "build-123",
pull_request: str = "false",
) -> ReleaseTestImagePushContext:
"""Helper to create a context with defaults."""
return ReleaseTestImagePushContext(
image_type=image_type,
python_version=python_version,
platform=platform,
branch=branch,
commit=commit,
rayci_build_id=rayci_build_id,
pull_request=pull_request,
)
class TestFormatPythonTag:
@pytest.mark.parametrize(
("python_version", "expected"),
[
("3.10", "-py310"),
("3.11", "-py311"),
("3.12", "-py312"),
("3.9", "-py39"),
],
)
def test_format_python_tag(self, python_version, expected):
assert format_python_tag(python_version) == expected
class TestFormatPlatformTag:
@pytest.mark.parametrize(
("platform", "expected"),
[
("cpu", "-cpu"),
("cu11.7.1-cudnn8", "-cu117"),
("cu11.8.0-cudnn8", "-cu118"),
("cu12.1.1-cudnn8", "-cu121"),
("cu12.3.2-cudnn9", "-cu123"),
("cu12.8.1-cudnn", "-cu128"),
],
)
def test_format_platform_tag(self, platform, expected):
assert format_platform_tag(platform) == expected
class TestWandaImageName:
@pytest.mark.parametrize(
("python_version", "platform", "image_type", "expected"),
[
("3.10", "cpu", "ray", "ray-anyscale-py3.10-cpu"),
("3.11", "cu12.1.1-cudnn8", "ray", "ray-anyscale-py3.11-cu12.1.1-cudnn8"),
("3.10", "cpu", "ray-llm", "ray-llm-anyscale-py3.10-cpu"),
(
"3.12",
"cu13.0.0-cudnn",
"ray-llm",
"ray-llm-anyscale-py3.12-cu13.0.0-cudnn",
),
("3.10", "cpu", "ray-ml", "ray-ml-anyscale-py3.10-cpu"),
(
"3.11",
"cu12.3.2-cudnn9",
"ray-ml",
"ray-ml-anyscale-py3.11-cu12.3.2-cudnn9",
),
],
)
def test_wanda_image_name(self, python_version, platform, image_type, expected):
ctx = make_ctx(
python_version=python_version, platform=platform, image_type=image_type
)
assert ctx.wanda_image_name() == expected
class TestDestinationTags:
def test_master_branch_tags(self):
ctx = make_ctx(
python_version="3.11",
platform="cu12.3.2-cudnn9",
image_type="ray",
branch="master",
commit="abc123def456",
rayci_build_id="build-123",
)
tags = ctx.destination_tags()
assert tags == [
"abc123-py311-cu123",
"build-123-py311-cu123",
]
def test_release_branch_tags(self):
ctx = make_ctx(
python_version="3.11",
platform="cu12.3.2-cudnn9",
image_type="ray",
branch="releases/2.44.0",
commit="abc123def456",
rayci_build_id="build-456",
)
tags = ctx.destination_tags()
assert tags == [
"2.44.0.abc123-py311-cu123",
"build-456-py311-cu123",
]
def test_gpu_platform_includes_alias(self):
"""GPU_PLATFORM gets -gpu alias, other platforms do not."""
gpu_ctx = make_ctx(
python_version="3.11", platform=GPU_PLATFORM, image_type="ray"
)
other_cuda_ctx = make_ctx(
python_version="3.11", platform="cu12.3.2-cudnn9", image_type="ray"
)
cpu_ctx = make_ctx(python_version="3.11", platform="cpu", image_type="ray")
gpu_tags = gpu_ctx.destination_tags()
other_cuda_tags = other_cuda_ctx.destination_tags()
cpu_tags = cpu_ctx.destination_tags()
assert any("-gpu" in tag for tag in gpu_tags)
assert not any("-gpu" in tag for tag in other_cuda_tags)
assert not any("-gpu" in tag for tag in cpu_tags)
def test_ray_ml_gpu_platform_includes_empty_platform_suffix(self):
"""ray-ml with GPU_PLATFORM gets -gpu alias AND empty platform suffix."""
ctx = make_ctx(
python_version="3.11", platform=GPU_PLATFORM, image_type="ray-ml"
)
tags = ctx.destination_tags()
# Should have: -cu121, -gpu, and empty suffix (no platform)
assert "abc123-py311-cu121" in tags
assert "abc123-py311-gpu" in tags
assert "abc123-py311" in tags
assert "build-123-py311-cu121" in tags
assert "build-123-py311-gpu" in tags
assert "build-123-py311" in tags
def test_ray_ml_non_gpu_platform_no_empty_suffix(self):
"""ray-ml with non-GPU_PLATFORM does NOT get empty suffix."""
ctx = make_ctx(
python_version="3.11", platform="cu12.3.2-cudnn9", image_type="ray-ml"
)
tags = ctx.destination_tags()
assert tags == [
"abc123-py311-cu123",
"build-123-py311-cu123",
]
def test_pr_branch_tags(self):
ctx = make_ctx(
python_version="3.12",
platform="cu12.3.2-cudnn9",
image_type="ray",
branch="feature-branch",
commit="abc123def456",
rayci_build_id="build-789",
pull_request="123",
)
tags = ctx.destination_tags()
assert tags == [
"pr-123.abc123-py312-cu123",
"build-789-py312-cu123",
]
def test_non_pr_feature_branch_tags(self):
ctx = make_ctx(
python_version="3.11",
platform="cu12.3.2-cudnn9",
image_type="ray",
branch="feature-branch",
commit="abc123def456",
rayci_build_id="build-789",
pull_request="false",
)
tags = ctx.destination_tags()
assert tags == [
"abc123-py311-cu123",
"build-789-py311-cu123",
]
def test_default_python_version_includes_empty_suffix(self):
"""DEFAULT_PYTHON_TAG_VERSION (3.10) gets empty python suffix."""
assert DEFAULT_PYTHON_TAG_VERSION == "3.10"
ctx = make_ctx(
python_version="3.10", platform="cu12.3.2-cudnn9", image_type="ray"
)
tags = ctx.destination_tags()
# Should have both -py310 and empty python suffix
assert "abc123-py310-cu123" in tags
assert "abc123-cu123" in tags
assert "build-123-py310-cu123" in tags
assert "build-123-cu123" in tags
def test_cpu_ray_includes_empty_platform_suffix(self):
"""ray with cpu gets empty platform suffix."""
ctx = make_ctx(python_version="3.11", platform="cpu", image_type="ray")
tags = ctx.destination_tags()
# Should have both -cpu and empty platform suffix
assert "abc123-py311-cpu" in tags
assert "abc123-py311" in tags
assert "build-123-py311-cpu" in tags
assert "build-123-py311" in tags
def test_cpu_ray_ml_no_empty_platform_suffix(self):
"""ray-ml with cpu does NOT get empty platform suffix."""
ctx = make_ctx(python_version="3.11", platform="cpu", image_type="ray-ml")
tags = ctx.destination_tags()
assert tags == [
"abc123-py311-cpu",
"build-123-py311-cpu",
]
def test_extra_image_type_includes_variation_suffix(self):
"""-extra image types get -extra variation suffix."""
ray_extra_ctx = make_ctx(
python_version="3.11", platform="cu12.3.2-cudnn9", image_type="ray-extra"
)
ray_ml_extra_ctx = make_ctx(
python_version="3.11", platform="cu12.3.2-cudnn9", image_type="ray-ml-extra"
)
ray_extra_tags = ray_extra_ctx.destination_tags()
ray_ml_extra_tags = ray_ml_extra_ctx.destination_tags()
assert "abc123-extra-py311-cu123" in ray_extra_tags
assert "abc123-extra-py311-cu123" in ray_ml_extra_tags
def test_ray_extra_cpu_includes_empty_platform_suffix(self):
"""ray-extra with cpu gets empty platform suffix (like ray)."""
ctx = make_ctx(python_version="3.11", platform="cpu", image_type="ray-extra")
tags = ctx.destination_tags()
# Should have both -cpu and empty platform suffix
assert "abc123-extra-py311-cpu" in tags
assert "abc123-extra-py311" in tags
class TestAnnotatePushedImage:
@pytest.fixture
def captured_calls(self, monkeypatch):
calls: list = []
monkeypatch.setattr(
"ci.ray_ci.automation.push_release_test_image.subprocess.run",
lambda args: calls.append(args),
)
return calls
@staticmethod
def _apply_env(monkeypatch, env: dict) -> None:
for key, value in env.items():
if value is None:
monkeypatch.delenv(key, raising=False)
else:
monkeypatch.setenv(key, value)
@pytest.mark.parametrize(
"env",
[
pytest.param({"BUILDKITE": None}, id="not_buildkite"),
pytest.param(
{
"BUILDKITE": "true",
"BUILDKITE_STEP_KEY": "ray",
"RAYCI_SELECT": "ray-ml,ray-data",
},
id="step_key_is_substring_of_selected",
),
pytest.param(
{
"BUILDKITE": "true",
"BUILDKITE_STEP_KEY": "release-ray",
"RAYCI_SELECT": None,
},
id="rayci_select_unset",
),
pytest.param(
{
"BUILDKITE": "true",
"BUILDKITE_STEP_KEY": "release-ray",
"RAYCI_SELECT": "",
},
id="rayci_select_empty",
),
pytest.param(
{
"BUILDKITE": "true",
"BUILDKITE_STEP_KEY": "release-ray",
"RAYCI_SELECT": "release-ray-ml,release-ray-data",
},
id="step_key_not_in_selection",
),
pytest.param(
{
"BUILDKITE": "true",
"BUILDKITE_STEP_KEY": None,
"RAYCI_SELECT": "release-ray,release-ray-ml",
},
id="step_key_unset",
),
pytest.param(
{
"BUILDKITE": "true",
"BUILDKITE_STEP_KEY": "",
"RAYCI_SELECT": "release-ray,release-ray-ml",
},
id="step_key_empty_string",
),
pytest.param(
{
"BUILDKITE": "true",
"BUILDKITE_STEP_KEY": "release-ray",
"RAYCI_SELECT": " , ",
},
id="rayci_select_whitespace_only",
),
],
)
def test_skips_annotation(self, monkeypatch, captured_calls, env):
self._apply_env(monkeypatch, env)
_annotate_pushed_image("example/image:tag", "ray")
assert captured_calls == []
@pytest.mark.parametrize(
"env",
[
pytest.param(
{
"BUILDKITE": "true",
"BUILDKITE_STEP_KEY": "release-ray",
"RAYCI_SELECT": "release-ray,release-ray-ml",
},
id="exact_step_key_selected",
),
pytest.param(
{
"BUILDKITE": "true",
"BUILDKITE_STEP_KEY": "release-ray",
"RAYCI_SELECT": "release-ray",
},
id="single_item_selection_matches",
),
pytest.param(
{
"BUILDKITE": "true",
"BUILDKITE_STEP_KEY": "release-ray",
"RAYCI_SELECT": " release-ray , release-ray-ml ",
},
id="selection_has_whitespace",
),
],
)
def test_runs_annotation(self, monkeypatch, captured_calls, env):
self._apply_env(monkeypatch, env)
_annotate_pushed_image("example/image:tag", "ray")
assert len(captured_calls) == 1
if __name__ == "__main__":
sys.exit(pytest.main(["-vv", __file__]))