import os
import shutil
import tempfile
import pytest
import yaml
from tests.helpers.cli_runner import run_module_main
SERVING_CAST_MODULE = "serving_cast.main"
VALID_INSTANCE_CONFIG = {
"instance_groups": [
{
"num_instances": 2,
"num_devices_per_instance": 4,
"pd_role": "both",
"parallel_config": {
"world_size": 4,
"tp_size": 4,
"dp_size": 1,
"ep_size": 1,
"moe_tp_size": 4,
"moe_dp_size": 1,
},
}
]
}
VALID_COMMON_CONFIG = {
"model_config": {
"name": "Qwen/Qwen3-32B",
},
"load_gen": {
"load_gen_type": "fixed_length",
"num_requests": 10,
"num_input_tokens": 30,
"num_output_tokens": 5,
"request_rate": 2.0,
},
}
INVALID_INSTANCE_CONFIG = {
"instance_groups": [
{
"num_instances": 2,
"pd_role": "both",
"parallel_config": {"tp_size": 4},
}
]
}
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
class TestCLI:
"""Command-line system test class"""
@pytest.fixture(autouse=True)
def reset_config_singleton(self):
"""Reset the process-wide Config singleton so each in-process run starts fresh."""
from serving_cast.config import Config
Config._instance = None
Config._initialized = False
yield
Config._instance = None
Config._initialized = False
@pytest.fixture(autouse=True)
def setup_and_teardown(self):
"""Environment preparation and cleanup before and after tests"""
self.temp_dir = tempfile.mkdtemp()
self.profiling_dir = os.path.join(self.temp_dir, "profiling")
self.valid_instance_path = self._create_config_file(VALID_INSTANCE_CONFIG, "instance_valid.yaml")
self.valid_common_path = self._create_config_file(VALID_COMMON_CONFIG, "common_valid.yaml")
self.invalid_instance_path = self._create_config_file(INVALID_INSTANCE_CONFIG, "instance_invalid.yaml")
yield
shutil.rmtree(self.temp_dir, ignore_errors=True)
def _create_config_file(self, content, filename):
"""Create configuration file for testing"""
file_path = os.path.join(self.temp_dir, filename)
with open(file_path, "w", encoding="utf-8") as f:
yaml.dump(content, f)
return file_path
def _run_command(self, args):
"""Run serving_cast.main's main() in-process so coverage sees the core path."""
return run_module_main(SERVING_CAST_MODULE, args)
def test_basic_functionality(self):
"""Test basic functionality: run command with valid configuration"""
args = [
f"--instance_config_path={self.valid_instance_path}",
f"--common_config_path={self.valid_common_path}",
]
result = self._run_command(args)
assert result.returncode == 0, "Command execution failed"
assert "Simulation" in result.stdout or "Summary" in result.stdout, "Expected output not found"
def test_missing_required_args(self):
"""Test case of missing required arguments"""
args = [f"--instance_config_path={self.valid_instance_path}"]
result = self._run_command(args)
assert result.returncode != 0, "Command should not succeed when required arguments are missing"
assert "error: the following arguments are required" in result.stderr
def test_invalid_file_path(self):
"""Test case of invalid file path"""
args = [
"--instance_config_path=./nonexistent_instance.yaml",
f"--common_config_path={self.valid_common_path}",
]
result = self._run_command(args)
assert "invalid validate_file_path value" in result.stderr
def test_invalid_config_content(self):
"""Test case of invalid configuration file content"""
args = [
f"--instance_config_path={self.invalid_instance_path}",
f"--common_config_path={self.valid_common_path}",
]
result = self._run_command(args)
assert result.returncode != 0