140 lines
4.4 KiB
Go
140 lines
4.4 KiB
Go
// This file holds the end-to-end and edge-case tests for /status (US-005, #295):
|
|
// fresh-session behavior, model-switch reflection, telemetry reset on session
|
|
// switch, and render timing, exercised through direct RunStatus calls with a
|
|
// fake host. The REPL-intercept and headless-flag tests live in package main.
|
|
package status
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/smallnest/pigo/internal/agentcore"
|
|
"github.com/smallnest/pigo/internal/cli"
|
|
)
|
|
|
|
// TestStatusFreshSessionAllSections verifies /status renders every section on a
|
|
// brand-new session before any model turn, with "no telemetry yet", and does not
|
|
// panic.
|
|
func TestStatusFreshSessionAllSections(t *testing.T) {
|
|
host := newFakeHost()
|
|
host.cwd = "/tmp/e2e-fresh"
|
|
|
|
var buf bytes.Buffer
|
|
RunStatus(&buf, host)
|
|
output := buf.String()
|
|
|
|
for _, want := range []string{
|
|
"runtime config:",
|
|
"context:",
|
|
"project & environment:",
|
|
"credentials & connectivity:",
|
|
"telemetry:",
|
|
"no telemetry yet",
|
|
} {
|
|
if !strings.Contains(output, want) {
|
|
t.Errorf("fresh-session /status: expected output to contain %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestStatusReflectsModelSwitch verifies /status reads the live run config each
|
|
// invocation, so a /model switch (which mutates live.Model/providerName) is
|
|
// reflected on the next /status without a restart.
|
|
func TestStatusReflectsModelSwitch(t *testing.T) {
|
|
host := newFakeHost()
|
|
host.live.Model = "model-a"
|
|
host.live.ProviderName = "prov-a"
|
|
|
|
var buf bytes.Buffer
|
|
RunStatus(&buf, host)
|
|
if out := buf.String(); !strings.Contains(out, "model: model-a") || !strings.Contains(out, "provider: prov-a") {
|
|
t.Errorf("expected model-a/prov-a, got:\n%s", out)
|
|
}
|
|
|
|
// Simulate a /model switch mutating the live config.
|
|
host.live.Model = "model-b"
|
|
host.live.ProviderName = "prov-b"
|
|
buf.Reset()
|
|
RunStatus(&buf, host)
|
|
out := buf.String()
|
|
if !strings.Contains(out, "model: model-b") || !strings.Contains(out, "provider: prov-b") {
|
|
t.Errorf("expected model-b/prov-b after switch, got:\n%s", out)
|
|
}
|
|
if strings.Contains(out, "model: model-a") {
|
|
t.Errorf("stale model-a still present after switch:\n%s", out)
|
|
}
|
|
}
|
|
|
|
// TestStatusTelemetryResetOnFork verifies that after the telemetry holder is
|
|
// reset (as runForkClone/runImport do on /fork, /clone, /import - wired in
|
|
// #291), /status shows "no telemetry yet" again, so cumulative stats do not
|
|
// bleed across conversations.
|
|
func TestStatusTelemetryResetOnFork(t *testing.T) {
|
|
host := newFakeHost()
|
|
|
|
holder := cli.NewTelemetryHolder()
|
|
holder.Fold(agentcore.TelemetryEvent{
|
|
Turns: 3,
|
|
TruncationCount: 1,
|
|
CompactionCount: 0,
|
|
ContextUtilization: 0.42,
|
|
ContextTokens: 53760,
|
|
ContextWindow: 128000,
|
|
ToolDurationsMs: map[string]agentcore.ToolTiming{"bash": {Count: 2, TotalMs: 150}},
|
|
})
|
|
host.telemetry = holder
|
|
|
|
var buf bytes.Buffer
|
|
RunStatus(&buf, host)
|
|
if out := buf.String(); !strings.Contains(out, "turns: 3") {
|
|
t.Errorf("expected 'turns: 3' before reset, got:\n%s", out)
|
|
}
|
|
|
|
// /fork, /clone, /import all call holder.Reset() (runForkClone/runImport).
|
|
holder.Reset()
|
|
buf.Reset()
|
|
RunStatus(&buf, host)
|
|
out := buf.String()
|
|
if n := strings.Count(out, "no telemetry yet"); n != 2 {
|
|
t.Errorf("expected 2 'no telemetry yet' after reset (cumulative + last run), got %d:\n%s", n, out)
|
|
}
|
|
if strings.Contains(out, "turns: 3") {
|
|
t.Errorf("stale telemetry after reset:\n%s", out)
|
|
}
|
|
}
|
|
|
|
// TestStatusTiming verifies /status renders fast (target <50ms; assert <100ms
|
|
// to absorb CI runner variance). It is pure in-memory rendering - no disk or
|
|
// network I/O on the hot path.
|
|
func TestStatusTiming(t *testing.T) {
|
|
host := newFakeHost()
|
|
host.cwd = "/tmp/e2e-timing"
|
|
host.live.ContextWindow = 128000
|
|
host.agentCtx.Messages = append(host.agentCtx.Messages,
|
|
agentcore.UserMessage{RoleField: agentcore.RoleUser, Content: agentcore.ContentList{agentcore.NewTextContent("hello")}},
|
|
)
|
|
holder := cli.NewTelemetryHolder()
|
|
holder.Fold(agentcore.TelemetryEvent{
|
|
Turns: 5,
|
|
ContextUtilization: 0.5,
|
|
ContextTokens: 64000,
|
|
ContextWindow: 128000,
|
|
ToolDurationsMs: map[string]agentcore.ToolTiming{"bash": {Count: 3, TotalMs: 210}, "read": {Count: 6, TotalMs: 90}},
|
|
})
|
|
host.telemetry = holder
|
|
|
|
// Warm once (allocs), then measure.
|
|
var buf bytes.Buffer
|
|
RunStatus(&buf, host)
|
|
buf.Reset()
|
|
|
|
start := time.Now()
|
|
RunStatus(&buf, host)
|
|
elapsed := time.Since(start)
|
|
if elapsed >= 100*time.Millisecond {
|
|
t.Errorf("/status render took %v, want <100ms (target <50ms)", elapsed)
|
|
}
|
|
}
|