Skip to content

Commit 30d8588

Browse files
authored
test: move backend unit tests (#3958)
Signed-off-by: pvijayakrish <[email protected]>
1 parent 9d76583 commit 30d8588

File tree

12 files changed

+430
-312
lines changed

12 files changed

+430
-312
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Conftest for dynamo.sglang unit tests only.
5+
Handles conditional test collection to prevent import errors when the sglang
6+
framework is not installed in the current container.
7+
"""
8+
9+
import importlib.util
10+
import sys
11+
12+
import pytest
13+
14+
15+
def pytest_ignore_collect(collection_path, config):
16+
"""Skip collecting sglang test files if sglang module isn't installed.
17+
Checks test file naming pattern: test_sglang_*.py
18+
"""
19+
filename = collection_path.name
20+
if filename.startswith("test_sglang_"):
21+
if importlib.util.find_spec("sglang") is None:
22+
return True # sglang not available, skip this file
23+
return None
24+
25+
26+
def make_cli_args_fixture(module_name: str):
27+
"""Create a pytest fixture for mocking CLI arguments for sglang backend."""
28+
29+
@pytest.fixture
30+
def mock_cli_args(monkeypatch):
31+
def set_args(*args, **kwargs):
32+
if args:
33+
argv = [module_name, *args]
34+
else:
35+
argv = [module_name]
36+
for param_name, param_value in kwargs.items():
37+
cli_flag = f"--{param_name.replace('_', '-')}"
38+
argv.extend([cli_flag, str(param_value)])
39+
monkeypatch.setattr(sys, "argv", argv)
40+
41+
return set_args
42+
43+
return mock_cli_args
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Unit tests for Prometheus utilities."""
5+
6+
from unittest.mock import Mock
7+
8+
import pytest
9+
10+
from dynamo.common.utils.prometheus import get_prometheus_expfmt
11+
12+
pytestmark = [
13+
pytest.mark.unit,
14+
]
15+
16+
17+
class TestGetPrometheusExpfmt:
18+
"""Test class for get_prometheus_expfmt function."""
19+
20+
@pytest.fixture
21+
def sglang_registry(self):
22+
"""Create a mock registry with SGLang-style metrics."""
23+
registry = Mock()
24+
25+
sample_metrics = """# HELP python_gc_objects_collected_total Objects collected during gc
26+
# TYPE python_gc_objects_collected_total counter
27+
python_gc_objects_collected_total{generation="0"} 123.0
28+
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds
29+
# TYPE process_cpu_seconds_total counter
30+
process_cpu_seconds_total 45.6
31+
# HELP sglang:prompt_tokens_total Number of prefill tokens processed
32+
# TYPE sglang:prompt_tokens_total counter
33+
sglang:prompt_tokens_total{model_name="meta-llama/Llama-3.1-8B-Instruct"} 8128902.0
34+
# HELP sglang:generation_tokens_total Number of generation tokens processed
35+
# TYPE sglang:generation_tokens_total counter
36+
sglang:generation_tokens_total{model_name="meta-llama/Llama-3.1-8B-Instruct"} 7557572.0
37+
# HELP sglang:cache_hit_rate The cache hit rate
38+
# TYPE sglang:cache_hit_rate gauge
39+
sglang:cache_hit_rate{model_name="meta-llama/Llama-3.1-8B-Instruct"} 0.0075
40+
"""
41+
42+
def mock_generate_latest(reg):
43+
return sample_metrics.encode("utf-8")
44+
45+
import dynamo.common.utils.prometheus
46+
47+
original_generate_latest = dynamo.common.utils.prometheus.generate_latest
48+
dynamo.common.utils.prometheus.generate_latest = mock_generate_latest
49+
50+
yield registry
51+
52+
dynamo.common.utils.prometheus.generate_latest = original_generate_latest
53+
54+
def test_sglang_use_case(self, sglang_registry):
55+
"""Test SGLang use case: filter to sglang: metrics and exclude python_/process_."""
56+
result = get_prometheus_expfmt(
57+
sglang_registry,
58+
metric_prefix_filter="sglang:",
59+
exclude_prefixes=["python_", "process_"],
60+
)
61+
62+
# Should only contain sglang: metrics
63+
assert "sglang:prompt_tokens_total" in result
64+
assert "sglang:generation_tokens_total" in result
65+
assert "sglang:cache_hit_rate" in result
66+
assert "# HELP sglang:prompt_tokens_total" in result
67+
68+
# Should not contain excluded metrics
69+
assert "python_gc_objects_collected_total" not in result
70+
assert "process_cpu_seconds_total" not in result
71+
72+
# Check specific content
73+
assert 'model_name="meta-llama/Llama-3.1-8B-Instruct"' in result
74+
assert "8128902.0" in result # prompt tokens value
75+
assert result.endswith("\n")
76+
77+
def test_error_handling(self):
78+
"""Test error handling when registry fails."""
79+
# Create a registry that raises an exception
80+
bad_registry = Mock()
81+
bad_registry.side_effect = Exception("Registry error")
82+
83+
result = get_prometheus_expfmt(bad_registry)
84+
85+
# Should return empty string on error
86+
assert result == ""

tests/unit/test_sglang_unit.py renamed to components/src/dynamo/sglang/tests/test_sglang_unit.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
import pytest
1111

1212
from dynamo.sglang.args import parse_args
13-
from tests.unit.conftest import make_cli_args_fixture
13+
from dynamo.sglang.tests.conftest import make_cli_args_fixture
1414

1515
# Get path relative to this test file
16-
TEST_DIR = Path(__file__).parent.parent
17-
JINJA_TEMPLATE_PATH = str(TEST_DIR / "serve" / "fixtures" / "custom_template.jinja")
18-
19-
16+
REPO_ROOT = Path(__file__).resolve().parents[5]
17+
TEST_DIR = REPO_ROOT / "tests"
18+
# Now construct the full path to the shared test fixture
19+
JINJA_TEMPLATE_PATH = str(
20+
REPO_ROOT / "tests" / "serve" / "fixtures" / "custom_template.jinja"
21+
)
2022
pytestmark = [
2123
pytest.mark.unit,
2224
pytest.mark.sglang,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Conftest for dynamo.trtllm unit tests only.
5+
Handles conditional test collection to prevent import errors when the tensorrt_llm
6+
framework is not installed in the current container.
7+
"""
8+
9+
import importlib.util
10+
import sys
11+
12+
import pytest
13+
14+
15+
def pytest_ignore_collect(collection_path, config):
16+
"""Skip collecting trtllm test files if tensorrt_llm module isn't installed.
17+
Checks test file naming pattern: test_trtllm_*.py
18+
"""
19+
filename = collection_path.name
20+
if filename.startswith("test_trtllm_"):
21+
if importlib.util.find_spec("tensorrt_llm") is None:
22+
return True # tensorrt_llm not available, skip this file
23+
return None
24+
25+
26+
def make_cli_args_fixture(module_name: str):
27+
"""Create a pytest fixture for mocking CLI arguments for trtllm backend."""
28+
29+
@pytest.fixture
30+
def mock_cli_args(monkeypatch):
31+
def set_args(*args, **kwargs):
32+
if args:
33+
argv = [module_name, *args]
34+
else:
35+
argv = [module_name]
36+
for param_name, param_value in kwargs.items():
37+
cli_flag = f"--{param_name.replace('_', '-')}"
38+
argv.extend([cli_flag, str(param_value)])
39+
monkeypatch.setattr(sys, "argv", argv)
40+
41+
return set_args
42+
43+
return mock_cli_args
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Unit tests for Prometheus utilities."""
5+
6+
from unittest.mock import Mock
7+
8+
import pytest
9+
10+
from dynamo.common.utils.prometheus import get_prometheus_expfmt
11+
12+
pytestmark = [
13+
pytest.mark.unit,
14+
]
15+
16+
17+
class TestGetPrometheusExpfmt:
18+
"""Test class for get_prometheus_expfmt function."""
19+
20+
@pytest.fixture
21+
def trtllm_registry(self):
22+
"""Create a mock registry with TensorRT-LLM-style metrics (no existing prefixes)."""
23+
registry = Mock()
24+
25+
sample_metrics = """# HELP python_gc_objects_collected_total Objects collected during gc
26+
# TYPE python_gc_objects_collected_total counter
27+
python_gc_objects_collected_total{generation="0"} 123.0
28+
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds
29+
# TYPE process_cpu_seconds_total counter
30+
process_cpu_seconds_total 45.6
31+
# HELP request_latency_seconds Request latency in seconds
32+
# TYPE request_latency_seconds histogram
33+
request_latency_seconds_bucket{le="0.1"} 10.0
34+
request_latency_seconds_count 25.0
35+
# HELP num_requests_running Number of requests currently running
36+
# TYPE num_requests_running gauge
37+
num_requests_running 3.0
38+
# HELP tokens_per_second Tokens generated per second
39+
# TYPE tokens_per_second gauge
40+
tokens_per_second 245.7
41+
"""
42+
43+
def mock_generate_latest(reg):
44+
return sample_metrics.encode("utf-8")
45+
46+
import dynamo.common.utils.prometheus
47+
48+
original_generate_latest = dynamo.common.utils.prometheus.generate_latest
49+
dynamo.common.utils.prometheus.generate_latest = mock_generate_latest
50+
51+
yield registry
52+
53+
dynamo.common.utils.prometheus.generate_latest = original_generate_latest
54+
55+
def test_trtllm_use_case(self, trtllm_registry):
56+
"""Test TensorRT-LLM use case: exclude python_/process_ and add trtllm: prefix."""
57+
result = get_prometheus_expfmt(
58+
trtllm_registry,
59+
exclude_prefixes=["python_", "process_"],
60+
add_prefix="trtllm:",
61+
)
62+
63+
# Should not contain excluded metrics
64+
assert "python_gc_objects_collected_total" not in result
65+
assert "process_cpu_seconds_total" not in result
66+
67+
# All remaining metrics should have trtllm: prefix
68+
assert "trtllm:request_latency_seconds" in result
69+
assert "trtllm:num_requests_running" in result
70+
assert "trtllm:tokens_per_second" in result
71+
72+
# HELP/TYPE comments should have prefix
73+
assert "# HELP trtllm:request_latency_seconds" in result
74+
assert "# TYPE trtllm:num_requests_running" in result
75+
76+
# Check specific content and structure preservation
77+
assert 'trtllm:request_latency_seconds_bucket{le="0.1"} 10.0' in result
78+
assert "trtllm:tokens_per_second 245.7" in result
79+
assert result.endswith("\n")
80+
81+
def test_no_filtering_all_frameworks(self, trtllm_registry):
82+
"""Test that without any filters, all metrics are returned."""
83+
result = get_prometheus_expfmt(trtllm_registry)
84+
85+
# Should contain all metrics including excluded ones
86+
assert "python_gc_objects_collected_total" in result
87+
assert "process_cpu_seconds_total" in result
88+
assert "request_latency_seconds" in result
89+
assert "num_requests_running" in result
90+
assert result.endswith("\n")
91+
92+
def test_empty_result_handling(self, trtllm_registry):
93+
"""Test handling when all metrics are filtered out."""
94+
result = get_prometheus_expfmt(
95+
trtllm_registry,
96+
exclude_prefixes=["python_", "process_", "request_", "num_", "tokens_"],
97+
)
98+
99+
# Should return empty string with newline or just newline
100+
assert result == "\n" or result == ""
101+
102+
def test_error_handling(self):
103+
"""Test error handling when registry fails."""
104+
# Create a registry that raises an exception
105+
bad_registry = Mock()
106+
bad_registry.side_effect = Exception("Registry error")
107+
108+
result = get_prometheus_expfmt(bad_registry)
109+
110+
# Should return empty string on error
111+
assert result == ""

tests/unit/test_trtllm_unit.py renamed to components/src/dynamo/trtllm/tests/test_trtllm_unit.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88

99
import pytest
1010

11+
from dynamo.trtllm.tests.conftest import make_cli_args_fixture
1112
from dynamo.trtllm.utils.trtllm_utils import cmd_line_args
12-
from tests.unit.conftest import make_cli_args_fixture
1313

1414
# Get path relative to this test file
15-
TEST_DIR = Path(__file__).parent.parent
16-
JINJA_TEMPLATE_PATH = str(TEST_DIR / "serve" / "fixtures" / "custom_template.jinja")
17-
15+
REPO_ROOT = Path(__file__).resolve().parents[5]
16+
TEST_DIR = REPO_ROOT / "tests"
17+
# Now construct the full path to the shared test fixture
18+
JINJA_TEMPLATE_PATH = str(
19+
REPO_ROOT / "tests" / "serve" / "fixtures" / "custom_template.jinja"
20+
)
1821

1922
pytestmark = [
2023
pytest.mark.unit,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Conftest for dynamo.vllm unit tests only.
5+
Handles conditional test collection to prevent import errors when the vllm
6+
framework is not installed in the current container.
7+
"""
8+
9+
import importlib.util
10+
import sys
11+
12+
import pytest
13+
14+
15+
def pytest_ignore_collect(collection_path, config):
16+
"""Skip collecting vllm test files if vllm module isn't installed.
17+
Checks test file naming pattern: test_vllm_*.py
18+
"""
19+
filename = collection_path.name
20+
if filename.startswith("test_vllm_"):
21+
if importlib.util.find_spec("vllm") is None:
22+
return True # vllm not available, skip this file
23+
return None
24+
25+
26+
def make_cli_args_fixture(module_name: str):
27+
"""Create a pytest fixture for mocking CLI arguments for vllm backend."""
28+
29+
@pytest.fixture
30+
def mock_cli_args(monkeypatch):
31+
def set_args(*args, **kwargs):
32+
if args:
33+
argv = [module_name, *args]
34+
else:
35+
argv = [module_name]
36+
for param_name, param_value in kwargs.items():
37+
cli_flag = f"--{param_name.replace('_', '-')}"
38+
argv.extend([cli_flag, str(param_value)])
39+
monkeypatch.setattr(sys, "argv", argv)
40+
41+
return set_args
42+
43+
return mock_cli_args

0 commit comments

Comments
 (0)