94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
233 lines
7.6 KiB
Python
233 lines
7.6 KiB
Python
"""
|
|
Test suite for S3 CloudStorage integration.
|
|
|
|
Tests verify file upload, cleanup, URL generation, and error handling.
|
|
"""
|
|
|
|
import asyncio
|
|
import importlib
|
|
import os
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
import sglang.multimodal_gen.runtime.entrypoints.openai.storage as storage_mod
|
|
from sglang.multimodal_gen.runtime.entrypoints.openai.storage import CloudStorage
|
|
|
|
|
|
def _create_temp_file(tmp_path, name="test.png", content=b"\x89PNG\r\n\x1a\nfake"):
|
|
"""Create a temporary test file."""
|
|
p = tmp_path / name
|
|
p.write_bytes(content)
|
|
return str(p)
|
|
|
|
|
|
# UNIT TESTS
|
|
|
|
|
|
def test_upload_file_success(tmp_path):
|
|
"""Test successful upload with correct URL generation."""
|
|
file_path = _create_temp_file(tmp_path, "image.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "my-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = "https://s3.example.com"
|
|
storage_mod.cloud_storage.region_name = None
|
|
|
|
called = {}
|
|
|
|
def fake_upload(local_path, bucket, key, ExtraArgs=None):
|
|
called["local_path"] = local_path
|
|
called["bucket"] = bucket
|
|
called["key"] = key
|
|
called["extra"] = ExtraArgs
|
|
|
|
storage_mod.cloud_storage.client = SimpleNamespace(upload_file=fake_upload)
|
|
|
|
url = asyncio.run(storage_mod.cloud_storage.upload_file(file_path, "image.png"))
|
|
|
|
assert url == "https://s3.example.com/my-bucket/image.png"
|
|
assert called["local_path"] == file_path
|
|
assert called["bucket"] == "my-bucket"
|
|
assert called["key"] == "image.png"
|
|
assert called["extra"]["ContentType"] == "image/png"
|
|
|
|
|
|
def test_upload_and_cleanup(tmp_path):
|
|
"""Test that local file is deleted after successful upload."""
|
|
file_path = _create_temp_file(tmp_path, "cleanup.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "my-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = "https://s3.example.com"
|
|
storage_mod.cloud_storage.client = SimpleNamespace(
|
|
upload_file=lambda *args, **kwargs: None
|
|
)
|
|
|
|
assert os.path.exists(file_path)
|
|
|
|
url = asyncio.run(storage_mod.cloud_storage.upload_and_cleanup(file_path))
|
|
|
|
assert url == "https://s3.example.com/my-bucket/cleanup.png"
|
|
assert not os.path.exists(file_path)
|
|
|
|
|
|
def test_upload_failure_preserves_file(tmp_path):
|
|
"""Test that file is preserved when upload fails."""
|
|
file_path = _create_temp_file(tmp_path, "preserve.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "my-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = "https://s3.example.com"
|
|
|
|
def fake_upload_raises(*args, **kwargs):
|
|
raise RuntimeError("simulated failure")
|
|
|
|
storage_mod.cloud_storage.client = SimpleNamespace(upload_file=fake_upload_raises)
|
|
|
|
result = asyncio.run(storage_mod.cloud_storage.upload_and_cleanup(file_path))
|
|
|
|
assert result is None
|
|
assert os.path.exists(file_path)
|
|
|
|
|
|
def test_disabled_storage_returns_none(tmp_path):
|
|
"""Test that disabled storage returns None."""
|
|
file_path = _create_temp_file(tmp_path, "test.png")
|
|
|
|
prev_enabled = storage_mod.cloud_storage.enabled
|
|
storage_mod.cloud_storage.enabled = False
|
|
|
|
try:
|
|
result = asyncio.run(
|
|
storage_mod.cloud_storage.upload_file(file_path, "test.png")
|
|
)
|
|
assert result is None
|
|
finally:
|
|
storage_mod.cloud_storage.enabled = prev_enabled
|
|
|
|
|
|
def test_aws_url_with_region(tmp_path):
|
|
"""Test AWS S3 URL generation with specific region."""
|
|
file_path = _create_temp_file(tmp_path, "aws.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "aws-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = None
|
|
storage_mod.cloud_storage.region_name = "us-west-2"
|
|
storage_mod.cloud_storage.client = SimpleNamespace(
|
|
upload_file=lambda *args, **kwargs: None
|
|
)
|
|
|
|
url = asyncio.run(storage_mod.cloud_storage.upload_file(file_path, "aws.png"))
|
|
|
|
assert url == "https://aws-bucket.s3.us-west-2.amazonaws.com/aws.png"
|
|
|
|
|
|
def test_aws_url_default_region(tmp_path):
|
|
"""Test AWS S3 URL defaults to us-east-1 when region not specified."""
|
|
file_path = _create_temp_file(tmp_path, "default.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "default-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = None
|
|
storage_mod.cloud_storage.region_name = None
|
|
storage_mod.cloud_storage.client = SimpleNamespace(
|
|
upload_file=lambda *args, **kwargs: None
|
|
)
|
|
|
|
url = asyncio.run(storage_mod.cloud_storage.upload_file(file_path, "default.png"))
|
|
|
|
assert url == "https://default-bucket.s3.us-east-1.amazonaws.com/default.png"
|
|
|
|
|
|
def test_custom_endpoint_url(tmp_path):
|
|
"""Test URL generation with custom endpoint (MinIO/OSS/COS)."""
|
|
file_path = _create_temp_file(tmp_path, "custom.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "custom-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = "https://minio.example.com/"
|
|
storage_mod.cloud_storage.region_name = None
|
|
storage_mod.cloud_storage.client = SimpleNamespace(
|
|
upload_file=lambda *args, **kwargs: None
|
|
)
|
|
|
|
url = asyncio.run(storage_mod.cloud_storage.upload_file(file_path, "custom.png"))
|
|
|
|
# Verify trailing slash is stripped
|
|
assert url == "https://minio.example.com/custom-bucket/custom.png"
|
|
|
|
|
|
def test_content_type_detection(tmp_path):
|
|
"""Test Content-Type header for different file extensions."""
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "test-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = "https://s3.test"
|
|
|
|
test_cases = [
|
|
("image.png", "image/png"),
|
|
("image.jpg", "image/jpeg"),
|
|
("image.jpeg", "image/jpeg"),
|
|
("image.webp", "image/webp"),
|
|
("video.mp4", "video/mp4"),
|
|
("file.bin", "application/octet-stream"),
|
|
]
|
|
|
|
for filename, expected_type in test_cases:
|
|
called = {}
|
|
|
|
def fake_upload(local_path, bucket, key, ExtraArgs=None):
|
|
called["content_type"] = ExtraArgs.get("ContentType")
|
|
|
|
storage_mod.cloud_storage.client = SimpleNamespace(upload_file=fake_upload)
|
|
|
|
file_path = _create_temp_file(tmp_path, filename)
|
|
asyncio.run(storage_mod.cloud_storage.upload_file(file_path, filename))
|
|
|
|
assert called["content_type"] == expected_type
|
|
|
|
|
|
# requires moto and boto3
|
|
has_moto = (
|
|
importlib.util.find_spec("moto") is not None
|
|
and importlib.util.find_spec("boto3") is not None
|
|
)
|
|
|
|
|
|
@pytest.mark.skipif(not has_moto, reason="moto/boto3 not installed")
|
|
def test_integration_with_moto(tmp_path):
|
|
"""Integration test using moto to mock real S3 service."""
|
|
import boto3
|
|
from moto import mock_aws
|
|
|
|
os.environ["SGLANG_CLOUD_STORAGE_TYPE"] = "s3"
|
|
os.environ["SGLANG_S3_BUCKET_NAME"] = "integration-test"
|
|
os.environ["SGLANG_S3_REGION_NAME"] = "us-east-1"
|
|
|
|
with mock_aws():
|
|
s3 = boto3.client("s3", region_name="us-east-1")
|
|
s3.create_bucket(Bucket="integration-test")
|
|
|
|
storage = CloudStorage()
|
|
assert storage.is_enabled()
|
|
|
|
file_path = _create_temp_file(tmp_path, "integration.png", b"test_data")
|
|
|
|
url = asyncio.run(storage.upload_and_cleanup(file_path))
|
|
|
|
assert url is not None
|
|
assert "integration-test" in url
|
|
assert "integration.png" in url
|
|
assert not os.path.exists(file_path)
|
|
|
|
obj = s3.get_object(Bucket="integration-test", Key="integration.png")
|
|
assert obj["Body"].read() == b"test_data"
|
|
|
|
for key in [
|
|
"SGLANG_CLOUD_STORAGE_TYPE",
|
|
"SGLANG_S3_BUCKET_NAME",
|
|
"SGLANG_S3_REGION_NAME",
|
|
]:
|
|
os.environ.pop(key, None)
|