Files
llmgw/internal/cli/enginecheck_test.go
Ilker ManapandClaude Opus 5.5 8cc8d7139b SGLang read in its own metric names, started serving them, and asked for its own help
The router read SGLang replicas with vLLM's names, and SGLang was started
without --enable-metrics, so it served no /metrics: every scrape failed as
if unreachable and the replica was routed to blind. It now gets
--enable-metrics and a mapping of its own (sglang:num_running_reqs,
num_queue_reqs, token_usage, the token counters and num_requests_total, as
0.5.21 publishes them). engine-check asks python -m sglang.launch_server
--help instead of Python's own help, and SGLang is given --tp-size rather
than the --tp argparse only takes as an abbreviation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 00:20:25 +02:00

207 lines
7.8 KiB
Go

package cli
import (
"bytes"
"context"
"os"
"path/filepath"
"strings"
"testing"
)
func TestAFlagIsNotSatisfiedByALongerOne(t *testing.T) {
// The failure this guards against is silent: --port would be reported as
// present because --port-range is documented, and a renamed flag would sail
// through the check that exists to catch renamed flags.
help := " --port-range N a range\n --model-name S a name\n"
if mentions(help, "--port") {
t.Error("--port was satisfied by --port-range")
}
if mentions(help, "--model") {
t.Error("--model was satisfied by --model-name")
}
if !mentions(help, "--port-range") {
t.Error("--port-range was not found")
}
}
func TestFlagsAreFoundInTheShapesHelpTextUses(t *testing.T) {
help := `
-m, --model FNAME model path
--host HOST ip address
--parallel, -np N slots
--metrics enable prometheus
--alias=NAME served name
`
for _, flag := range []string{"--model", "--host", "--parallel", "--metrics", "--alias"} {
if !mentions(help, flag) {
t.Errorf("%s was not found in a help text that documents it", flag)
}
}
for _, flag := range []string{"--nonsense", "--mode", "--para"} {
if mentions(help, flag) {
t.Errorf("%s was found in a help text that does not document it", flag)
}
}
}
func TestTheRepresentativeShardIsTheWidestOne(t *testing.T) {
// A flag that only appears in the multi-node case is exactly the one nobody
// notices has been renamed, so the command built for checking has to be the
// widest llmgw would ever produce.
spec := representativeShard("vllm", 2, 8, 4)
if spec.NNodes != 2 || spec.Profile.Nodes != 2 {
t.Errorf("not a multi-node shard: %+v", spec)
}
if len(spec.GPUIndices) != 4 {
t.Errorf("%d GPUs, want 4", len(spec.GPUIndices))
}
if spec.LeaderAddr == "" || spec.LeaderPort == 0 {
t.Error("no rendezvous point, so the flags that carry it will not appear")
}
}
func TestATruncatedHelpIsSuspected(t *testing.T) {
// Found by truncating llama.cpp's 725-line help to 400 lines and being told,
// confidently, that five flags the running cluster uses do not exist.
full := " --help show this\n --model F path\n"
if !mentions(full, "--help") {
t.Fatal("the guard's own signal is missing from a complete help")
}
truncated := " --model F path\n"
if mentions(truncated, "--help") {
t.Error("a truncated help was taken for a complete one")
}
}
// fakeInterpreter stands in for TensorRT-LLM's Python: asked for
// trtllm-serve's help through its module, it prints the help given; asked
// anything else, it fails, as running the launcher here would.
func fakeInterpreter(t *testing.T, help string) string {
t.Helper()
return fakePython(t, "-m tensorrt_llm.commands.serve serve --help", help)
}
// fakePython is an interpreter that prints help when asked exactly ask, and
// fails on anything else — the interpreter's own --help included.
func fakePython(t *testing.T, ask, help string) string {
t.Helper()
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "help.txt"), []byte(help), 0o644); err != nil {
t.Fatal(err)
}
p := filepath.Join(dir, "python")
body := "#!/bin/sh\n" +
`if [ "$*" = "` + ask + `" ]; then cat "` + filepath.Join(dir, "help.txt") + `"; exit 0; fi` + "\n" +
"echo \"unexpected: $*\" >&2; exit 2\n"
if err := os.WriteFile(p, []byte(body), 0o755); err != nil { //nolint:gosec // an executable stand-in
t.Fatal(err)
}
return p
}
// trtllmHelp is the part of trtllm-serve serve --help (1.3) that holds the
// flags llmgw builds.
const trtllmHelp = `Usage: trtllm-serve serve [OPTIONS] MODEL
Options:
--host TEXT Hostname of the server.
--port INTEGER Port of the server.
--tensor_parallel_size, --tp_size INTEGER
Tensor parallelism size.
--pipeline_parallel_size, --pp_size INTEGER
Pipeline parallelism size.
--config, --extra_llm_api_options TEXT
Path to a YAML configuration file.
--served_model_name TEXT The model name used in the API.
--help Show this message and exit.
`
func runEngineCheck(t *testing.T, args ...string) (string, error) {
t.Helper()
c := newEngineCheckCmd()
var out bytes.Buffer
c.SetOut(&out)
c.SetErr(&out)
c.SetArgs(args)
err := c.ExecuteContext(context.Background())
return out.String(), err
}
func TestEngineCheckAsksTrtllmServeThroughItsModule(t *testing.T) {
out, err := runEngineCheck(t, "trtllm", "--bin", fakeInterpreter(t, trtllmHelp))
if err != nil {
t.Fatalf("%v\n%s", err, out)
}
if !strings.Contains(out, "every flag llmgw builds is accepted") {
t.Errorf("unexpected report:\n%s", out)
}
// The interpreter's own help would describe Python, not trtllm-serve; and
// one node is what trtllm runs, so no multi-node flag is in the command.
if strings.Contains(out, "unexpected:") || strings.Contains(out, "skipped") {
t.Errorf("the help was not asked of trtllm-serve:\n%s", out)
}
}
// sglangHelp is the part of python -m sglang.launch_server --help (0.5.21)
// that holds the flags llmgw builds, in argparse's layout.
const sglangHelp = `usage: sglang serve [-h] --model-path MODEL_PATH [--host HOST] [--port PORT]
options:
-h, --help show this help message and exit
--model-path MODEL_PATH, --model MODEL_PATH
Path to model weights.
--host HOST The host of the HTTP server.
--port PORT The port of the HTTP server.
--served-model-name SERVED_MODEL_NAME
Override the model name returned by the v1/models endpoint.
--enable-metrics Enable log prometheus metrics.
--dist-init-addr DIST_INIT_ADDR, --nccl-init-addr DIST_INIT_ADDR
The host address for initializing distributed backend.
--nnodes NNODES The number of nodes.
--node-rank NODE_RANK
The node rank.
--tp-size TP_SIZE, --tensor-parallel-size TP_SIZE
The tensor parallelism size.
`
func TestEngineCheckAsksSGLangThroughItsModule(t *testing.T) {
// The command is the interpreter running llmgw's launcher; asked for
// --help it would describe Python, and every SGLang flag would look
// missing — or, against a real python3, be checked against nothing.
py := fakePython(t, "-m sglang.launch_server --help", sglangHelp)
out, err := runEngineCheck(t, "sglang", "--bin", py)
if err != nil {
t.Fatalf("%v\n%s", err, out)
}
if !strings.Contains(out, "every flag llmgw builds is accepted") {
t.Errorf("unexpected report:\n%s", out)
}
if strings.Contains(out, "unexpected:") || strings.Contains(out, "skipped") {
t.Errorf("the help was not asked of SGLang:\n%s", out)
}
// Two nodes by default, so the rendezvous flags were among those checked.
if !strings.Contains(out, "sglang: 9 flags checked") {
t.Errorf("not every flag was checked:\n%s", out)
}
}
func TestEngineCheckCatchesAnSGLangWithoutTheFlagForItsMetrics(t *testing.T) {
old := strings.ReplaceAll(sglangHelp, " --enable-metrics Enable log prometheus metrics.\n", "")
out, err := runEngineCheck(t, "sglang", "--bin", fakePython(t, "-m sglang.launch_server --help", old))
if err == nil || !strings.Contains(out, "sglang: --enable-metrics") {
t.Errorf("an SGLang without --enable-metrics passed: %v\n%s", err, out)
}
}
func TestEngineCheckCatchesATensorRTLLMTooOldForTheServedName(t *testing.T) {
// TensorRT-LLM 1.2 has no --served_model_name, and a replica of it would
// never be seen serving the name the agent waits for.
old := strings.ReplaceAll(trtllmHelp, " --served_model_name TEXT The model name used in the API.\n", "")
out, err := runEngineCheck(t, "trtllm", "--bin", fakeInterpreter(t, old))
if err == nil || !strings.Contains(out, "trtllm-serve: --served_model_name") {
t.Errorf("an old trtllm-serve passed: %v\n%s", err, out)
}
}