26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
138 lines
5.4 KiB
Go
138 lines
5.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/kunchenguid/no-mistakes/internal/db"
|
|
"github.com/kunchenguid/no-mistakes/internal/paths"
|
|
)
|
|
|
|
// TestStatsAgentsReportsLocalPerformanceTelemetry proves the read-only
|
|
// report surface exposes the locally persisted invocation evidence: per-
|
|
// purpose aggregates via --agents and per-run detail (including accumulated
|
|
// parked time) via --run.
|
|
func TestStatsAgentsReportsLocalPerformanceTelemetry(t *testing.T) {
|
|
nmHome := t.TempDir()
|
|
t.Setenv("NM_HOME", nmHome)
|
|
p := paths.WithRoot(nmHome)
|
|
|
|
d, err := db.Open(p.DB())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repo, err := d.InsertRepoWithID("repo-1", "/tmp/repo", "https://github.com/test/repo", "main")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
run, err := d.InsertRun(repo.ID, "feature/x", "abc", "def")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
seed := []db.AgentInvocation{
|
|
{RunID: run.ID, StepName: "review", Round: 1, Purpose: "review", Agent: "codex", Model: "gpt-5.2", SessionMode: db.InvocationModeStarted, SessionKey: "deadbeef00000000", StartedAt: 1, CompletedAt: 2, DurationMS: 60_000, ExitStatus: "ok", InputTokens: 100, OutputTokens: 10, CacheReadTokens: 40, CacheCreationTokens: statsIntPtr(20)},
|
|
{RunID: run.ID, StepName: "review", Round: 2, Purpose: "review", Agent: "codex", Model: "gpt-5.2", SessionMode: db.InvocationModeResumed, SessionKey: "deadbeef00000000", StartedAt: 3, CompletedAt: 4, DurationMS: 30_000, ExitStatus: "ok", InputTokens: 50, OutputTokens: 5, CacheReadTokens: 45, CacheCreationTokens: statsIntPtr(25)},
|
|
{RunID: run.ID, StepName: "review", Round: 2, Purpose: "review-fix", Agent: "codex", Model: "gpt-5.2", SessionMode: db.InvocationModeStarted, SessionKey: "feedface00000000", StartedAt: 5, CompletedAt: 6, DurationMS: 45_000, ExitStatus: "ok"},
|
|
}
|
|
for _, inv := range seed {
|
|
if _, err := d.InsertAgentInvocation(inv); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := d.AddRunParkedDuration(run.ID, 90_000); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d.Close()
|
|
|
|
out, err := executeCmd("stats", "--agents")
|
|
if err != nil {
|
|
t.Fatalf("stats --agents: %v\n%s", err, out)
|
|
}
|
|
for _, want := range []string{"PURPOSE", "review", "review-fix", "RESUMED", "CACHE WRITE TOK", "45"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Fatalf("stats --agents missing %q in:\n%s", want, out)
|
|
}
|
|
}
|
|
|
|
out, err = executeCmd("stats", "--run", run.ID)
|
|
if err != nil {
|
|
t.Fatalf("stats --run: %v\n%s", err, out)
|
|
}
|
|
for _, want := range []string{run.ID, "parked at gates 1m30s total", "resumed", "deadbeef00000000", "gpt-5.2", "CACHE WR", "20"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Fatalf("stats --run missing %q in:\n%s", want, out)
|
|
}
|
|
}
|
|
// The seeded rows carry no activity metrics, so those fields render as the
|
|
// unknown marker, distinct from a recorded zero.
|
|
if !strings.Contains(out, "-") {
|
|
t.Fatalf("stats --run should render unknown metric fields as \"-\":\n%s", out)
|
|
}
|
|
}
|
|
|
|
func statsIntPtr(v int) *int { return &v }
|
|
func statsInt64Ptr(v int64) *int64 { return &v }
|
|
|
|
// TestStatsRendersPopulatedFidelityMetrics proves the report surfaces the new
|
|
// activity histogram, subprocess/model time split, and per-round token deltas
|
|
// when they are recorded.
|
|
func TestStatsRendersPopulatedFidelityMetrics(t *testing.T) {
|
|
nmHome := t.TempDir()
|
|
t.Setenv("NM_HOME", nmHome)
|
|
p := paths.WithRoot(nmHome)
|
|
|
|
d, err := db.Open(p.DB())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repo, err := d.InsertRepoWithID("repo-1", "/tmp/repo", "https://github.com/test/repo", "main")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
run, err := d.InsertRun(repo.ID, "feature/x", "abc", "def")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inv := db.AgentInvocation{
|
|
RunID: run.ID, StepName: "review", Round: 2, Purpose: "review-fix", Agent: "codex",
|
|
Model: "gpt-5.6-sol", ModelProvider: strPtrCLI("openai"),
|
|
SessionMode: db.InvocationModeResumed, SessionKey: "deadbeef00000000",
|
|
StartedAt: 1, CompletedAt: 2, DurationMS: 10_000, SubprocessWaitMS: statsInt64Ptr(2_000),
|
|
ExitStatus: "ok", InputTokens: 2500, OutputTokens: 250, CacheReadTokens: 1800,
|
|
FreshInputTokens: statsIntPtr(700), ReasoningTokens: statsIntPtr(9),
|
|
DeltaInputTokens: statsIntPtr(1500), DeltaOutputTokens: statsIntPtr(150), DeltaCacheReadTokens: statsIntPtr(1200),
|
|
ModelRoundtrips: statsIntPtr(24), ToolCalls: statsIntPtr(7),
|
|
ToolWaitCalls: statsIntPtr(0), ToolTestLintCalls: statsIntPtr(2), ToolEditCalls: statsIntPtr(3),
|
|
ToolReadCalls: statsIntPtr(1), ToolGitCalls: statsIntPtr(1), ToolOtherCalls: statsIntPtr(0),
|
|
WorkloadFiles: statsIntPtr(12), WorkloadLines: statsIntPtr(1060), FindingCount: statsIntPtr(3),
|
|
}
|
|
if _, err := d.InsertAgentInvocation(inv); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d.Close()
|
|
|
|
out, err := executeCmd("stats", "--agents")
|
|
if err != nil {
|
|
t.Fatalf("stats --agents: %v\n%s", err, out)
|
|
}
|
|
for _, want := range []string{"ROUNDTRIPS", "TEST/LINT", "SUBPROC", "24", "METRICS", "1/1"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Fatalf("stats --agents missing %q in:\n%s", want, out)
|
|
}
|
|
}
|
|
|
|
out, err = executeCmd("stats", "--run", run.ID)
|
|
if err != nil {
|
|
t.Fatalf("stats --run: %v\n%s", err, out)
|
|
}
|
|
// Per-round delta (1500) is shown distinctly from the raw cumulative (2500),
|
|
// the tool histogram and the workload render, and the model-time split appears.
|
|
for _, want := range []string{"Δ IN (round)", "1500", "2500", "7 0/2/3/1/1/0", "12/1060", "MODEL"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Fatalf("stats --run missing %q in:\n%s", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func strPtrCLI(s string) *string { return &s }
|