Skip to content

Commit 03e8b49

Browse files
ci: Wire fs_backend Python tests into CI
Signed-off-by: Alberto Perdomo <aperdomo@redhat.com>
1 parent 82d31d1 commit 03e8b49

5 files changed

Lines changed: 107 additions & 7 deletions

File tree

.github/workflows/ci-test.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,16 @@ jobs:
3838
go-version: "${{ env.GO_VERSION }}"
3939
cache-dependency-path: ./go.sum
4040

41+
- name: Set up Python
42+
uses: actions/setup-python@v5
43+
with:
44+
python-version: "3.12"
45+
4146
- name: Install dependencies
42-
run: go mod download
47+
run: |
48+
go mod download
49+
python -m pip install --upgrade pip
50+
python -m pip install -r kv_connectors/llmd_fs_backend/tests/requirements-cpu.txt
4351
4452
- name: Run unit tests
4553
run: make unit-test

Makefile

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ BUILDER := $(shell command -v buildah >/dev/null 2>&1 && echo buildah || echo $(
1717
UDS_TOKENIZER_IMAGE ?= llm-d-uds-tokenizer:e2e-test
1818
FS_BACKEND_NAME ?= llmd-fs-backend
1919
FS_BACKEND_DEV_IMG ?= $(IMAGE_TAG_BASE)/$(FS_BACKEND_NAME):$(DEV_VERSION)
20+
FS_BACKEND_CPU_TESTS ?= kv_connectors/llmd_fs_backend/tests/test_file_mapper.py kv_connectors/llmd_fs_backend/tests/test_storage_events.py
2021

2122
# go source files
2223
SRC = $(shell find . -type f -name '*.go')
@@ -57,13 +58,30 @@ clang:
5758
test: unit-test e2e-test ## Run all tests (unit + e2e)
5859

5960
.PHONY: unit-test
60-
unit-test: unit-test-uds ## Run unit tests
61+
unit-test: unit-test-uds unit-test-fs-backend-cpu ## Run unit tests
6162

6263
.PHONY: unit-test-uds
6364
unit-test-uds: check-go download-zmq ## Run unit tests
6465
@printf "\033[33;1m==== Running unit tests ====\033[0m\n"
6566
@go test -v ./pkg/...
6667

68+
.PHONY: unit-test-fs-backend-cpu
69+
unit-test-fs-backend-cpu: ## Run CPU-safe FS backend Python unit tests
70+
@printf "\033[33;1m==== Running CPU-safe FS backend unit tests ====\033[0m\n"
71+
@tests=(); \
72+
for test_path in $(FS_BACKEND_CPU_TESTS); do \
73+
if [ -e "$$test_path" ]; then \
74+
tests+=("$$test_path"); \
75+
else \
76+
echo "Skipping missing FS backend CPU test path: $$test_path"; \
77+
fi; \
78+
done; \
79+
if [ "$${#tests[@]}" -eq 0 ]; then \
80+
echo "No FS backend CPU test paths found."; \
81+
exit 0; \
82+
fi; \
83+
python3 -m pytest -q "$${tests[@]}"
84+
6785
.PHONY: unit-test-race
6886
unit-test-race: check-go download-zmq ## Run unit tests with Go race detector enabled
6987
@printf "\033[33;1m==== Running unit tests with race detector ====\033[0m\n"

kv_connectors/llmd_fs_backend/tests/conftest.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
sys.path.insert(0, str(Path(__file__).parent))
2323

2424
import pytest
25-
import torch
26-
from vllm.config import VllmConfig, set_current_vllm_config
2725

2826

2927
def pytest_addoption(parser):
@@ -35,20 +33,38 @@ def pytest_addoption(parser):
3533
parser.addoption("--obj-ca_bundle", default=None)
3634

3735

38-
@pytest.fixture(scope="session", autouse=True)
39-
def require_cuda():
36+
def pytest_configure(config):
37+
config.addinivalue_line(
38+
"markers",
39+
"no_cuda_required: mark a test as not requiring CUDA setup/teardown",
40+
)
41+
42+
43+
@pytest.fixture(autouse=True)
44+
def require_cuda(request):
4045
"""Skip all tests in this session if CUDA is not available."""
46+
if request.node.get_closest_marker("no_cuda_required"):
47+
return
48+
49+
import torch
50+
4151
if not torch.cuda.is_available():
4252
pytest.skip("CUDA not available")
4353

4454

4555
@pytest.fixture(autouse=True)
46-
def cuda_teardown():
56+
def cuda_teardown(request):
4757
"""Ensure CUDA and C++ thread-pool resources from one test are fully
4858
released before the next test starts. Without this, async destructors
4959
can cause 'cudaErrorUnknown' or stale file-open errors in subsequent tests.
5060
"""
61+
if request.node.get_closest_marker("no_cuda_required"):
62+
yield
63+
return
64+
5165
yield
66+
import torch
67+
5268
gc.collect() # force Python GC to call C++ destructors immediately
5369
torch.cuda.synchronize() # surface any async CUDA errors in the right test
5470
torch.cuda.empty_cache() # free cached allocations so next test starts clean
@@ -61,6 +77,8 @@ def default_vllm_config():
6177
that use get_current_vllm_config() outside of a full engine context.
6278
This matches vLLM's internal test fixture pattern.
6379
"""
80+
from vllm.config import VllmConfig, set_current_vllm_config
81+
6482
# Use empty VllmConfig() which provides sensible defaults
6583
with set_current_vllm_config(VllmConfig()):
6684
yield
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pytest
2+
msgpack
3+
pyzmq
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2026 The llm-d Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import importlib.util
16+
import sys
17+
from pathlib import Path
18+
19+
import pytest
20+
21+
pytestmark = pytest.mark.no_cuda_required
22+
23+
CONNECTOR_ROOT = Path(__file__).resolve().parents[1]
24+
25+
26+
def load_file_mapper_class():
27+
module_path = CONNECTOR_ROOT / "llmd_fs_backend" / "file_mapper.py"
28+
spec = importlib.util.spec_from_file_location("file_mapper_under_test", module_path)
29+
module = importlib.util.module_from_spec(spec)
30+
sys.modules[spec.name] = module
31+
spec.loader.exec_module(module)
32+
return module.FileMapper
33+
34+
35+
def test_file_mapper_masks_hashes_to_lower_64_bits():
36+
file_mapper = load_file_mapper_class()(
37+
root_dir="/tmp/kv-cache",
38+
model_name="test-model",
39+
gpu_block_size=16,
40+
gpu_blocks_per_file=16,
41+
tp_size=1,
42+
pp_size=1,
43+
pcp_size=1,
44+
rank=0,
45+
dtype="float16",
46+
)
47+
48+
assert file_mapper.get_file_name((1 << 72) + 0x1234).endswith(
49+
"/000/00/0000000000001234.bin"
50+
)
51+
assert file_mapper.get_file_name(b"\x01\x02").endswith(
52+
"/000/00/0000000000000102.bin"
53+
)

0 commit comments

Comments
 (0)