Files
vllm/rust/src/cmd/src/cli/tests.rs
T
Bugen ZhaoGitHubmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
c01618fdc8 [Rust][Benchmark] Integrate vllm-bench to vllm-rs & vllm CLI (#48930)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Signed-off-by: Bugen Zhao <i@bugenzhao.com>
2026-07-20 09:31:25 +00:00

1742 lines
49 KiB
Rust

// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright contributors to the vLLM project
use expect_test::expect;
use vllm_engine_core_client::TransportMode;
use vllm_server::{Config, HttpListenerMode, ParserSelection, RendererSelection};
use super::{BenchCommand, Cli, Command};
#[test]
fn bench_serve_args_parse_without_managed_engine_repartition() {
let cli = Cli::try_parse_from([
"vllm-rs",
"bench",
"serve",
"--backend",
"openai-chat",
"--request-rate",
"inf",
])
.unwrap();
let Command::Bench(BenchCommand::Serve(args)) = cli.command else {
panic!("expected bench serve args");
};
assert_eq!(args.backend, vllm_bench::BackendKind::OpenaiChat);
assert!(args.request_rate.is_infinite());
}
#[test]
fn serve_args_forward_python_flags_with_separator() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--python",
"../vllm/.venv/bin/python",
"--max-model-len",
"512",
"--",
"--dtype",
"float16",
])
.unwrap();
expect![[r#"
Cli {
command: Serve(
ServeArgs {
headless: false,
host: "127.0.0.1",
port: 8000,
uds: None,
runtime: SharedRuntimeArgs {
model: "Qwen/Qwen3-0.6B",
engine_ready_timeout_secs: 600,
tool_call_parser: Auto,
reasoning_parser: Auto,
renderer: Auto,
language_model_only: false,
max_model_len: Some(
512,
),
max_logprobs: None,
grpc_port: None,
shutdown_timeout: 0,
http_timeout_keep_alive: None,
chat_template: None,
default_chat_template_kwargs: None,
chat_template_content_format: Auto,
enable_log_requests: false,
enable_prompt_tokens_details: false,
enable_request_id_headers: false,
disable_log_stats: false,
served_model_name: [],
allowed_origins: JsonStringList(
[
"*",
],
),
allowed_methods: JsonStringList(
[
"*",
],
),
allowed_headers: JsonStringList(
[
"*",
],
),
allow_credentials: false,
ssl_keyfile: None,
ssl_certfile: None,
ssl_ca_certs: None,
ssl_cert_reqs: 0,
ssl_ciphers: None,
profiler_config: None,
},
managed_engine: ManagedEngineArgs {
python: "../vllm/.venv/bin/python",
handshake_host: "127.0.0.1",
handshake_port: None,
data_parallel_size: 1,
data_parallel_size_local: None,
python_args: [
"--dtype",
"float16",
],
},
},
),
}
"#]]
.assert_debug_eq(&cli);
}
#[test]
fn serve_args_auto_forward_python_flags_without_separator() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--python",
"python3",
"--quantization",
"awq",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
expect![[r#"
[
"--quantization",
"awq",
]
"#]]
.assert_debug_eq(&args.managed_engine.python_args);
}
#[test]
fn serve_args_auto_forward_enable_lora_to_python() {
let cli =
Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B", "--enable-lora"]).unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
expect![[r#"
[
"--enable-lora",
]
"#]]
.assert_debug_eq(&args.managed_engine.python_args);
}
#[test]
fn serve_args_forward_shutdown_timeout_to_managed_engine() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--shutdown-timeout",
"60",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
assert_eq!(args.runtime.shutdown_timeout, 60);
let config = args.to_managed_engine_config(5555);
expect![[r#"
[
"--reasoning-parser",
"qwen3",
"--shutdown-timeout",
"60",
]
"#]]
.assert_debug_eq(&config.python_args);
}
#[test]
fn serve_args_forward_disable_log_stats_to_managed_engine() {
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B", "--disable-log-stats"])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
assert!(args.runtime.disable_log_stats);
let config = args.to_managed_engine_config(5555);
expect![[r#"
[
"--reasoning-parser",
"qwen3",
"--disable-log-stats",
]
"#]]
.assert_debug_eq(&config.python_args);
}
#[test]
fn serve_args_forward_profiler_config_to_managed_engine() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--profiler-config",
r#"{"profiler":"torch","torch_profiler_dir":"/tmp/profile"}"#,
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
assert_eq!(args.runtime.profiler().as_deref(), Some("torch"));
let config = args.to_managed_engine_config(5555);
let profiler_flag_index = config
.python_args
.iter()
.position(|arg| arg == "--profiler-config")
.expect("profiler config flag");
let profiler_config: serde_json::Value =
serde_json::from_str(&config.python_args[profiler_flag_index + 1])
.expect("profiler config json");
assert_eq!(
profiler_config,
serde_json::json!({
"profiler": "torch",
"torch_profiler_dir": "/tmp/profile",
})
);
}
#[test]
fn serve_args_forward_max_logprobs_to_frontend_and_managed_engine() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--max-logprobs",
"-1",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
assert_eq!(args.runtime.max_logprobs, Some(-1));
let frontend_config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
assert_eq!(frontend_config.max_logprobs, Some(-1));
let engine_config = args.to_managed_engine_config(5555);
expect![[r#"
[
"--max-logprobs",
"-1",
"--reasoning-parser",
"qwen3",
]
"#]]
.assert_debug_eq(&engine_config.python_args);
}
#[test]
fn serve_args_resolve_auto_reasoning_parser_for_managed_engine() {
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B"]).unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
assert_eq!(args.runtime.reasoning_parser, ParserSelection::Auto);
let config = args.to_managed_engine_config(5555);
expect![[r#"
[
"--reasoning-parser",
"qwen3",
]
"#]]
.assert_debug_eq(&config.python_args);
}
#[test]
fn serve_args_forward_explicit_reasoning_parser_to_managed_engine() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Unknown/Model",
"--reasoning-parser",
"deepseek_r1",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_managed_engine_config(5555);
expect![[r#"
[
"--reasoning-parser",
"deepseek_r1",
]
"#]]
.assert_debug_eq(&config.python_args);
}
#[test]
fn serve_args_do_not_forward_disabled_reasoning_parser_to_managed_engine() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--reasoning-parser",
"none",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_managed_engine_config(5555);
assert!(config.python_args.is_empty());
}
#[test]
fn serve_args_forward_reasoning_parser_even_with_passthrough_reasoning_parser() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--",
"--reasoning-parser",
"deepseek_r1",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_managed_engine_config(5555);
expect![[r#"
[
"--reasoning-parser",
"deepseek_r1",
"--reasoning-parser",
"qwen3",
]
"#]]
.assert_debug_eq(&config.python_args);
}
#[test]
fn serve_args_auto_forward_python_multi_char_alias_without_separator() {
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B", "-tp", "2"]).unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
expect![[r#"
[
"--tensor-parallel-size",
"2",
]
"#]]
.assert_debug_eq(&args.managed_engine.python_args);
}
#[test]
fn serve_args_accept_explicit_deepseek_v32_renderer() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--tokenizer-mode",
"deepseek_v32",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
assert_eq!(args.runtime.renderer, RendererSelection::DeepSeekV32);
}
#[test]
fn serve_passes_enable_request_id_headers_into_config() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--enable-request-id-headers",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
assert!(config.api_server_options.enable_request_id_headers);
}
#[test]
fn serve_passes_enable_prompt_tokens_details_into_config() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--enable-prompt-tokens-details",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
assert!(config.api_server_options.enable_prompt_tokens_details);
}
#[test]
fn serve_passes_tls_into_config() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--ssl-certfile",
"/tmp/cert.pem",
"--ssl-keyfile",
"/tmp/key.pem",
"--ssl-ca-certs",
"/tmp/ca.pem",
"--ssl-cert-reqs",
"2",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
let tls = config.tls.expect("tls configured");
assert_eq!(tls.cert_file.as_deref(), Some("/tmp/cert.pem"));
assert_eq!(tls.key_file.as_deref(), Some("/tmp/key.pem"));
assert_eq!(tls.ca_certs.as_deref(), Some("/tmp/ca.pem"));
assert_eq!(tls.cert_reqs, 2);
}
#[test]
fn serve_without_ssl_flags_has_no_tls() {
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B"]).unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
assert!(config.tls.is_none());
}
#[test]
fn serve_ssl_keyfile_without_certfile_fails_validation() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--ssl-keyfile",
"/tmp/key.pem",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
// TLS is requested (a key was given) but there is no certificate, so
// validation fails loud rather than silently serving plaintext.
assert_eq!(config.tls.as_ref().expect("tls requested").cert_file, None);
let err = config.validate().unwrap_err().to_string();
assert!(err.contains("--ssl-certfile is required"), "{err}");
}
#[test]
fn serve_mtls_without_ca_certs_fails_validation() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--ssl-certfile",
"/tmp/cert.pem",
"--ssl-cert-reqs",
"2",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
// Client-cert verification without a CA bundle has nothing to verify
// against, so it fails loud at startup.
let err = config.validate().unwrap_err().to_string();
assert!(err.contains("--ssl-ca-certs is required"), "{err}");
}
#[test]
fn frontend_args_json_passes_tls_into_config() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","ssl_certfile":"/tmp/cert.pem","ssl_keyfile":"/tmp/key.pem"}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
let config = args.into_config();
let tls = config.tls.expect("tls configured");
assert_eq!(tls.cert_file.as_deref(), Some("/tmp/cert.pem"));
assert_eq!(tls.key_file.as_deref(), Some("/tmp/key.pem"));
}
#[test]
fn frontend_args_json_rejects_out_of_range_cert_reqs() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","ssl_certfile":"/tmp/cert.pem","ssl_cert_reqs":5}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
// The JSON path bypasses clap's range check, so validate() is the only guard.
let config = args.into_config();
let err = config.validate().unwrap_err().to_string();
assert!(err.contains("--ssl-cert-reqs"), "{err}");
}
#[test]
fn frontend_args_json_passes_enable_request_id_headers_into_config() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","enable_request_id_headers":true}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
let config = args.into_config();
assert!(config.api_server_options.enable_request_id_headers);
}
#[test]
fn serve_passes_api_keys_into_config() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--api-key",
"secret-a",
"--api-key",
"secret-b",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_frontend_config("tcp://127.0.0.1:62100".to_string());
assert_eq!(config.api_keys, vec!["secret-a", "secret-b"]);
let debug = format!("{config:#?}");
assert!(debug.contains("api_keys: [<redacted>; 2]"));
assert!(!debug.contains("secret-a"));
assert!(!debug.contains("secret-b"));
}
#[test]
fn frontend_args_json_accepts_api_key_string() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","api_key":"secret"}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
let config = args.into_config();
assert_eq!(config.api_keys, vec!["secret"]);
}
#[test]
fn frontend_args_json_accepts_api_key_list() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","api_key":["secret-a","secret-b"]}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
let config = args.into_config();
assert_eq!(config.api_keys, vec!["secret-a", "secret-b"]);
}
#[test]
fn serve_args_reject_unknown_renderer_value() {
let error = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--tokenizer-mode",
"definitely_missing",
])
.unwrap_err();
expect![[r#"
error: invalid value 'definitely_missing' for '--tokenizer-mode <RENDERER>': unknown renderer `definitely_missing` (expected one of: auto, hf, deepseek_v32, deepseek_v4, harmony, inkling)
For more information, try '--help'.
"#]]
.assert_eq(&error.to_string());
}
#[test]
fn serve_args_reject_unsupported_flag_arg() {
let error = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--root-path",
"/prefix",
])
.unwrap_err();
expect![[r#"
error: invalid value '/prefix' for '--root-path <ROOT_PATH>': argument is not implemented in Rust frontend yet
Remove this unsupported argument to continue.
Alternatively, if you intend to pass it only to the Python engine, put it after `--` (e.g., `-- <arg>`).
This may lead to unexpected behavior as the Rust frontend will completely ignore that argument.
For more information, try '--help'.
"#]].assert_eq(&error.to_string());
}
#[test]
fn serve_args_reject_unsupported_no_flag_alias() {
let error = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--no-enable-log-deltas",
])
.unwrap_err();
expect![[r#"
error: invalid value 'true' for '--enable-log-deltas [<ENABLE_LOG_DELTAS>]': argument is not implemented in Rust frontend yet
Remove this unsupported argument to continue.
Alternatively, if you intend to pass it only to the Python engine, put it after `--` (e.g., `-- <arg>`).
This may lead to unexpected behavior as the Rust frontend will completely ignore that argument.
For more information, try '--help'.
"#]]
.assert_eq(&error.to_string());
}
#[test]
fn frontend_args_accept_json() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--coordinator-address",
"tcp://127.0.0.1:7000",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","engine_count":2}"#,
])
.unwrap();
expect![[r#"
Cli {
command: Frontend(
FrontendArgs {
listen_fd: 3,
input_address: "ipc:///tmp/input.sock",
output_address: "ipc:///tmp/output.sock",
coordinator_address: Some(
"tcp://127.0.0.1:7000",
),
engine_start_index: 0,
engine_count: 1,
runtime: SharedRuntimeArgs {
model: "Qwen/Qwen3-0.6B",
engine_ready_timeout_secs: 600,
tool_call_parser: None,
reasoning_parser: None,
renderer: Auto,
language_model_only: false,
max_model_len: None,
max_logprobs: None,
grpc_port: None,
shutdown_timeout: 0,
http_timeout_keep_alive: None,
chat_template: None,
default_chat_template_kwargs: None,
chat_template_content_format: Auto,
enable_log_requests: false,
enable_prompt_tokens_details: false,
enable_request_id_headers: false,
disable_log_stats: false,
served_model_name: [],
allowed_origins: JsonStringList(
[
"*",
],
),
allowed_methods: JsonStringList(
[
"*",
],
),
allowed_headers: JsonStringList(
[
"*",
],
),
allow_credentials: false,
ssl_keyfile: None,
ssl_certfile: None,
ssl_ca_certs: None,
ssl_cert_reqs: 0,
ssl_ciphers: None,
profiler_config: None,
},
},
),
}
"#]]
.assert_debug_eq(&cli);
}
#[test]
fn frontend_args_json_applies_defaults() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B"}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
assert_eq!(args.runtime.model, "Qwen/Qwen3-0.6B");
assert_eq!(args.runtime.engine_ready_timeout_secs, 600);
assert_eq!(args.runtime.tool_call_parser, ParserSelection::None);
assert_eq!(args.runtime.reasoning_parser, ParserSelection::None);
assert_eq!(args.runtime.renderer, RendererSelection::Auto);
assert_eq!(args.runtime.max_model_len, None);
assert_eq!(args.runtime.max_logprobs, None);
assert_eq!(args.runtime.shutdown_timeout, 0);
}
#[test]
fn frontend_args_json_accepts_supported_non_default_fields() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","engine_ready_timeout_secs":42,"tool_call_parser":"hermes","reasoning_parser":"qwen3_thinking","tokenizer_mode":"deepseek_v32","language_model_only":true,"max_model_len":8192,"max_logprobs":-1,"shutdown_timeout":3}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
assert_eq!(args.runtime.engine_ready_timeout_secs, 42);
assert_eq!(
args.runtime.tool_call_parser,
ParserSelection::Explicit("hermes".to_string())
);
assert_eq!(
args.runtime.reasoning_parser,
ParserSelection::Explicit("qwen3_thinking".to_string())
);
assert_eq!(args.runtime.renderer, RendererSelection::DeepSeekV32);
assert!(args.runtime.language_model_only);
assert_eq!(args.runtime.max_model_len, Some(8192));
assert_eq!(args.runtime.max_logprobs, Some(-1));
assert_eq!(args.runtime.shutdown_timeout, 3);
}
#[test]
fn serve_args_accept_none_reasoning_parser() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--reasoning-parser",
"none",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
assert_eq!(args.runtime.reasoning_parser, ParserSelection::None);
assert_eq!(args.runtime.tool_call_parser, ParserSelection::Auto);
}
#[test]
fn frontend_args_json_ignores_unknown_fields() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","uds":"/tmp/vllm.sock","nested_unknown":{"x":1}}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
assert_eq!(args.runtime.model, "Qwen/Qwen3-0.6B");
}
#[test]
fn frontend_args_json_sets_prompt_tokens_details_flag() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","api_server_count":2,"enable_prompt_tokens_details":true}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
assert_eq!(args.runtime.model, "Qwen/Qwen3-0.6B");
assert!(args.runtime.enable_prompt_tokens_details);
}
#[test]
fn serve_args_parse_cors_flags() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--allowed-origins",
r#"["http://a.com","http://b.com"]"#,
"--allowed-methods",
r#"["GET","POST"]"#,
"--allow-credentials",
])
.unwrap();
let Command::Serve(serve) = cli.command else {
panic!("expected serve args");
};
assert_eq!(
serve.runtime.allowed_origins.0,
["http://a.com", "http://b.com"]
);
assert_eq!(serve.runtime.allowed_methods.0, ["GET", "POST"]);
assert!(serve.runtime.allow_credentials);
// Unspecified lists keep the permissive default.
assert_eq!(serve.runtime.allowed_headers.0, ["*"]);
}
#[test]
fn serve_args_cors_defaults_are_permissive() {
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B"]).unwrap();
let Command::Serve(serve) = cli.command else {
panic!("expected serve args");
};
assert_eq!(serve.runtime.allowed_origins.0, ["*"]);
assert_eq!(serve.runtime.allowed_methods.0, ["*"]);
assert_eq!(serve.runtime.allowed_headers.0, ["*"]);
assert!(!serve.runtime.allow_credentials);
}
#[test]
fn frontend_args_json_parses_cors_fields() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","allowed_origins":["http://a.com"],"allow_credentials":true}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
assert_eq!(args.runtime.allowed_origins.0, ["http://a.com"]);
assert!(args.runtime.allow_credentials);
// Unspecified lists fall back to the permissive default via serde.
assert_eq!(args.runtime.allowed_methods.0, ["*"]);
}
#[test]
fn frontend_args_json_rejects_unsupported_fields() {
let error = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","root_path":"/prefix"}"#,
])
.unwrap_err();
expect![[r#"
error: invalid value '{"model_tag":"Qwen/Qwen3-0.6B","root_path":"/prefix"}' for '--args-json <JSON>':
The following arguments are not implemented in Rust frontend yet:
- root_path
Remove these arguments to continue.
For more information, try '--help'.
"#]].assert_eq(&error.to_string());
}
#[test]
fn frontend_args_json_aggregates_multiple_unsupported_fields() {
let error = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","response_role":"assistant","root_path":"/prefix"}"#,
])
.unwrap_err();
let actual = error.to_string().replace(": \n", ":\n");
expect![[r#"
error: invalid value '{"model_tag":"Qwen/Qwen3-0.6B","response_role":"assistant","root_path":"/prefix"}' for '--args-json <JSON>':
The following arguments are not implemented in Rust frontend yet:
- response_role
- root_path
Remove these arguments to continue.
For more information, try '--help'.
"#]].assert_eq(&actual);
}
#[test]
fn frontend_args_json_rejects_malformed_json() {
let error = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B""#,
])
.unwrap_err();
expect![[r#"
error: invalid value '{"model_tag":"Qwen/Qwen3-0.6B"' for '--args-json <JSON>': invalid JSON arguments: EOF while parsing an object at line 1 column 30
For more information, try '--help'.
"#]].assert_eq(&error.to_string());
}
#[test]
fn serve_args_reject_flags_before_model() {
let error = Cli::try_parse_from(["vllm-rs", "serve", "--python", "python3", "Qwen/Qwen3-0.6B"])
.unwrap_err();
expect![[r#"
error: the model must appear immediately after the command
Usage: vllm-rs serve <MODEL> [OPTIONS] [-- <PYTHON_ARGS>...]
For more information, try '--help'.
"#]]
.assert_eq(&error.to_string());
}
#[test]
fn serve_args_accept_headless_mode() {
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B", "--headless"]).unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
assert!(args.headless);
}
#[test]
fn serve_args_keep_python_passthrough_flags_after_separator() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--python",
"python3",
"--",
"--tensor-parallel-size",
"2",
"--dtype",
"float16",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
expect![[r#"
[
"--tensor-parallel-size",
"2",
"--dtype",
"float16",
]
"#]]
.assert_debug_eq(&args.managed_engine.python_args);
}
#[test]
fn serve_args_keep_python_multi_char_alias_after_separator() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--python",
"python3",
"--",
"-tp",
"2",
"--dtype",
"float16",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
expect![[r#"
[
"-tp",
"2",
"--dtype",
"float16",
]
"#]]
.assert_debug_eq(&args.managed_engine.python_args);
}
#[test]
fn serve_args_keep_frontend_arg_after_separator() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--",
"--uds",
"/tmp/vllm.sock",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
expect![[r#"
[
"--uds",
"/tmp/vllm.sock",
]
"#]]
.assert_debug_eq(&args.managed_engine.python_args);
}
#[test]
fn serve_args_keep_python_multi_char_engine_aliases_after_separator() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--",
"-dpr",
"1",
"-dpl",
"2",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
expect![[r#"
[
"-dpr",
"1",
"-dpl",
"2",
]
"#]]
.assert_debug_eq(&args.managed_engine.python_args);
}
#[test]
fn serve_args_auto_forward_unknown_flags_without_separator() {
let cli = Cli::try_parse_from(["vllm-rs", "serve", "Qwen/Qwen3-0.6B", "--foo", "bar"]).unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
expect![[r#"
[
"--foo",
"bar",
]
"#]]
.assert_debug_eq(&args.managed_engine.python_args);
}
#[test]
fn serve_args_auto_forward_negative_value_without_separator() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--num-gpu-blocks-override",
"-1",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
expect![[r#"
[
"--num-gpu-blocks-override",
"-1",
]
"#]]
.assert_debug_eq(&args.managed_engine.python_args);
}
#[test]
fn serve_args_accept_handshake_aliases() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--python",
"python3",
"--handshake-host",
"10.99.48.128",
"--handshake-port",
"13345",
"--data-parallel-size",
"4",
])
.unwrap();
expect![[r#"
Cli {
command: Serve(
ServeArgs {
headless: false,
host: "127.0.0.1",
port: 8000,
uds: None,
runtime: SharedRuntimeArgs {
model: "Qwen/Qwen3-0.6B",
engine_ready_timeout_secs: 600,
tool_call_parser: Auto,
reasoning_parser: Auto,
renderer: Auto,
language_model_only: false,
max_model_len: None,
max_logprobs: None,
grpc_port: None,
shutdown_timeout: 0,
http_timeout_keep_alive: None,
chat_template: None,
default_chat_template_kwargs: None,
chat_template_content_format: Auto,
enable_log_requests: false,
enable_prompt_tokens_details: false,
enable_request_id_headers: false,
disable_log_stats: false,
served_model_name: [],
allowed_origins: JsonStringList(
[
"*",
],
),
allowed_methods: JsonStringList(
[
"*",
],
),
allowed_headers: JsonStringList(
[
"*",
],
),
allow_credentials: false,
ssl_keyfile: None,
ssl_certfile: None,
ssl_ca_certs: None,
ssl_cert_reqs: 0,
ssl_ciphers: None,
profiler_config: None,
},
managed_engine: ManagedEngineArgs {
python: "python3",
handshake_host: "10.99.48.128",
handshake_port: Some(
13345,
),
data_parallel_size: 4,
data_parallel_size_local: None,
python_args: [],
},
},
),
}
"#]]
.assert_debug_eq(&cli);
}
#[test]
fn serve_args_accept_data_parallel_primary_flags() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--data-parallel-address",
"10.99.48.128",
"--data-parallel-rpc-port",
"13345",
"--data-parallel-size",
"4",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
assert!(!args.headless);
assert_eq!(args.managed_engine.handshake_host, "10.99.48.128");
assert_eq!(args.managed_engine.handshake_port, Some(13345));
assert_eq!(args.managed_engine.data_parallel_size, 4);
}
#[test]
fn serve_frontend_config_uses_dp_address_as_advertised_host() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--handshake-host",
"10.99.48.128",
"--data-parallel-size",
"4",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_frontend_config("tcp://10.99.48.128:29550".to_string());
let TransportMode::HandshakeOwner {
handshake_address,
advertised_host,
engine_count,
ready_timeout,
local_input_address,
local_output_address,
} = &config.transport_mode
else {
panic!("expected handshake-owned transport");
};
assert_eq!(handshake_address, "tcp://10.99.48.128:29550");
assert_eq!(advertised_host, "10.99.48.128");
assert_eq!(*engine_count, 4);
assert!(
local_input_address
.as_deref()
.is_some_and(|address| address.starts_with("ipc://"))
);
assert!(
local_output_address
.as_deref()
.is_some_and(|address| address.starts_with("ipc://"))
);
assert_ne!(local_input_address, local_output_address);
expect![[r#"
Config {
transport_mode: HandshakeOwner {
handshake_address: "tcp://10.99.48.128:29550",
advertised_host: "10.99.48.128",
engine_count: 4,
ready_timeout: 600s,
local_input_address: Some(
"<ipc input>",
),
local_output_address: Some(
"<ipc output>",
),
},
coordinator_mode: MaybeInProc,
model: "Qwen/Qwen3-0.6B",
served_model_name: [],
listener_mode: BindTcp {
host: "127.0.0.1",
port: 8000,
},
tool_call_parser: Auto,
reasoning_parser: Auto,
renderer: Auto,
language_model_only: false,
chat_template: None,
default_chat_template_kwargs: None,
chat_template_content_format: Auto,
max_logprobs: None,
api_server_options: ApiServerOptions {
enable_log_requests: false,
enable_prompt_tokens_details: false,
enable_request_id_headers: false,
},
cors: CorsConfig {
allow_origins: [
"*",
],
allow_methods: [
"*",
],
allow_headers: [
"*",
],
allow_credentials: false,
},
tls: None,
api_keys: [],
disable_log_stats: false,
grpc_port: None,
shutdown_timeout: 0ns,
keep_alive_timeout: 5s,
profiler: None,
}
"#]]
.assert_debug_eq(&Config {
transport_mode: TransportMode::HandshakeOwner {
handshake_address: handshake_address.clone(),
advertised_host: advertised_host.clone(),
engine_count: *engine_count,
ready_timeout: *ready_timeout,
local_input_address: Some("<ipc input>".to_string()),
local_output_address: Some("<ipc output>".to_string()),
},
..config.clone()
});
}
#[test]
fn serve_frontend_config_keeps_tcp_transport_for_non_local_only_topology() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--data-parallel-address",
"10.99.48.128",
"--data-parallel-size",
"4",
"--data-parallel-size-local",
"2",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_frontend_config("tcp://10.99.48.128:29550".to_string());
expect![[r#"
Config {
transport_mode: HandshakeOwner {
handshake_address: "tcp://10.99.48.128:29550",
advertised_host: "10.99.48.128",
engine_count: 4,
ready_timeout: 600s,
local_input_address: None,
local_output_address: None,
},
coordinator_mode: MaybeInProc,
model: "Qwen/Qwen3-0.6B",
served_model_name: [],
listener_mode: BindTcp {
host: "127.0.0.1",
port: 8000,
},
tool_call_parser: Auto,
reasoning_parser: Auto,
renderer: Auto,
language_model_only: false,
chat_template: None,
default_chat_template_kwargs: None,
chat_template_content_format: Auto,
max_logprobs: None,
api_server_options: ApiServerOptions {
enable_log_requests: false,
enable_prompt_tokens_details: false,
enable_request_id_headers: false,
},
cors: CorsConfig {
allow_origins: [
"*",
],
allow_methods: [
"*",
],
allow_headers: [
"*",
],
allow_credentials: false,
},
tls: None,
api_keys: [],
disable_log_stats: false,
grpc_port: None,
shutdown_timeout: 0ns,
keep_alive_timeout: 5s,
profiler: None,
}
"#]]
.assert_debug_eq(&config);
}
#[test]
fn frontend_args_reject_legacy_handshake_flags() {
let error = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B"}"#,
"--handshake-address",
"tcp://127.0.0.1:62100",
])
.unwrap_err();
assert!(error.to_string().contains("--handshake-address"));
}
#[test]
fn frontend_config_uses_external_coordinator_when_coordinator_address_is_present() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--coordinator-address",
"tcp://127.0.0.1:7000",
"--engine-start-index",
"3",
"--engine-count",
"1",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B"}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
let config = args.into_config();
expect![[r#"
Config {
transport_mode: Bootstrapped {
input_address: "ipc:///tmp/input.sock",
output_address: "ipc:///tmp/output.sock",
engine_start_index: 3,
engine_count: 1,
ready_timeout: 600s,
},
coordinator_mode: External {
address: "tcp://127.0.0.1:7000",
},
model: "Qwen/Qwen3-0.6B",
served_model_name: [],
listener_mode: InheritedFd {
fd: 3,
},
tool_call_parser: None,
reasoning_parser: None,
renderer: Auto,
language_model_only: false,
chat_template: None,
default_chat_template_kwargs: None,
chat_template_content_format: Auto,
max_logprobs: None,
api_server_options: ApiServerOptions {
enable_log_requests: false,
enable_prompt_tokens_details: false,
enable_request_id_headers: false,
},
cors: CorsConfig {
allow_origins: [
"*",
],
allow_methods: [
"*",
],
allow_headers: [
"*",
],
allow_credentials: false,
},
tls: None,
api_keys: [],
disable_log_stats: false,
grpc_port: None,
shutdown_timeout: 0ns,
keep_alive_timeout: 5s,
profiler: None,
}
"#]]
.assert_debug_eq(&config);
}
#[test]
fn serve_frontend_config_uses_unix_listener_when_uds_is_present() {
let cli = Cli::try_parse_from([
"vllm-rs",
"serve",
"Qwen/Qwen3-0.6B",
"--uds",
"/tmp/vllm.sock",
])
.unwrap();
let Command::Serve(args) = cli.command else {
panic!("expected serve args");
};
let config = args.to_frontend_config("tcp://127.0.0.1:29550".to_string());
assert_eq!(
config.listener_mode,
HttpListenerMode::BindUnix {
path: "/tmp/vllm.sock".to_string(),
}
);
}
#[test]
fn frontend_args_json_enables_profiling_when_profiler_config_set() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","profiler_config":{"profiler":"torch","torch_profiler_dir":"/tmp/profile"}}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
assert_eq!(args.runtime.profiler().as_deref(), Some("torch"));
let config = args.into_config();
assert_eq!(config.profiler.as_deref(), Some("torch"));
}
#[test]
fn frontend_args_json_disables_profiling_when_profiler_config_absent() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B"}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
assert_eq!(args.runtime.profiler(), None);
let config = args.into_config();
assert_eq!(config.profiler, None);
}
#[test]
fn frontend_args_json_disables_profiling_when_profiler_type_is_null() {
let cli = Cli::try_parse_from([
"vllm-rs",
"frontend",
"--listen-fd",
"3",
"--input-address",
"ipc:///tmp/input.sock",
"--output-address",
"ipc:///tmp/output.sock",
"--args-json",
r#"{"model_tag":"Qwen/Qwen3-0.6B","profiler_config":{"profiler":null}}"#,
])
.unwrap();
let Command::Frontend(args) = cli.command else {
panic!("expected frontend args");
};
assert_eq!(args.runtime.profiler(), None);
let config = args.into_config();
assert_eq!(config.profiler, None);
}