返回 DeepSeek-Reasonix
report_test.go
根目录 / cmd / e2ebench / report_test.go
1 package main
2
3 import (
4 "strings"
5 "testing"
6 )
7
8 func TestResultClassSeparatesGuardStopsFromWrongPatches(t *testing.T) {
9 for _, tc := range []struct {
10 name string
11 r result
12 want string
13 }{
14 {"solved", result{Passed: true, runMetrics: runMetrics{Outcome: "success"}}, "solved"},
15 {"grader failed after a clean run", result{runMetrics: runMetrics{Outcome: "success"}}, "wrong_patch"},
16 {"guard stop", result{runMetrics: runMetrics{Outcome: "max_steps"}}, "max_steps"},
17 {"killed before writing metrics", result{}, "no_metrics"},
18 {"skipped", result{Skipped: true}, "skipped"},
19 } {
20 if got := tc.r.class(); got != tc.want {
21 t.Errorf("%s: class = %q, want %q", tc.name, got, tc.want)
22 }
23 }
24 }
25
26 func TestCostPerSolvedIsNotAnAverageOverAttempts(t *testing.T) {
27 if got := costPerSolved(0.4, 2, "CNY"); got != "CNY 0.2000" {
28 t.Fatalf("cost per solved = %q, want CNY 0.2000", got)
29 }
30 if got := costPerSolved(0.4, 0, "CNY"); got != "n/a" {
31 t.Fatalf("zero solved = %q, want n/a", got)
32 }
33 if got := tokensPerSolved(1000, 0); got != "n/a" {
34 t.Fatalf("zero solved tokens = %q, want n/a", got)
35 }
36 }
37
38 func TestRenderLeadsWithCostPerSolvedAndFailureClasses(t *testing.T) {
39 out := render([]result{
40 {task: task{ID: "solved-one"}, Passed: true, WallMs: 4000,
41 runMetrics: runMetrics{Outcome: "success", Cost: 0.02, Currency: "CNY", PromptTokens: 1000, CacheHitTokens: 900, CacheMissTokens: 100, ToolCalls: 6}},
42 {task: task{ID: "stopped"}, WallMs: 9000,
43 runMetrics: runMetrics{Outcome: "max_steps", Cost: 0.02, Currency: "CNY", PromptTokens: 1000, ToolCalls: 40, ToolFailures: 3}},
44 })
45
46 for _, want := range []string{
47 "**Cost per solved:** CNY 0.0400",
48 "**Failures by class:** max_steps ×1",
49 "| Task | Result | Class |",
50 "| `stopped` | ❌ fail | max_steps |",
51 } {
52 if !strings.Contains(out, want) {
53 t.Errorf("report missing %q:\n%s", want, out)
54 }
55 }
56 }
57
58 // A killed agent writes no metrics. Counting that run as a zero-cost solve
59 // would make every published per-task figure cheaper than the truth.
60 func TestUnaccountedSolvesDoNotDeflateCostPerSolved(t *testing.T) {
61 results := []result{
62 {task: task{ID: "accounted"}, Passed: true, WallMs: 1000,
63 runMetrics: runMetrics{Outcome: "success", Cost: 0.02, Currency: "$", PromptTokens: 1000}},
64 {task: task{ID: "killed"}, Passed: true, WallMs: 1800000, Unaccounted: true,
65 runMetrics: runMetrics{Outcome: "timeout"}},
66 }
67 out := renderBody(results)
68
69 if !strings.Contains(out, "**Solved:** 2/2") {
70 t.Error("both solves must still count toward the solve rate")
71 }
72 if !strings.Contains(out, "**Cost per solved:** $ 0.0200") {
73 t.Errorf("cost per solved must divide by accounted solves only:\n%s", out)
74 }
75 if !strings.Contains(out, "Accounting incomplete for 1 of 2 instances") {
76 t.Errorf("the gap must be disclosed, not hidden:\n%s", out)
77 }
78 }
79
80 func TestRenderShowsCacheResetsByCause(t *testing.T) {
81 out := render([]result{
82 {task: task{ID: "a"}, Passed: true, WallMs: 1000,
83 runMetrics: runMetrics{Outcome: "success", PrefixChangeReasonCounts: map[string]int{"compact_auto": 1, "tools": 2}}},
84 {task: task{ID: "b"}, Passed: true, WallMs: 1000,
85 runMetrics: runMetrics{Outcome: "success", PrefixChangeReasonCounts: map[string]int{"snip": 3}}},
86 })
87 if want := "**Cache resets by cause:** compact_auto ×1 · snip ×3 · tools ×2"; !strings.Contains(out, want) {
88 t.Errorf("report missing %q:\n%s", want, out)
89 }
90 }
91
92 func TestRenderOmitsCacheResetsLineWhenNoReasonsReported(t *testing.T) {
93 out := render([]result{
94 {task: task{ID: "a"}, Passed: true, WallMs: 1000, runMetrics: runMetrics{Outcome: "success"}},
95 })
96 if strings.Contains(out, "Cache resets by cause") {
97 t.Errorf("report should omit the cache-resets line when no run reported any reasons:\n%s", out)
98 }
99 }
100
101 func TestDurFormatsSubMinuteAndMinuteScale(t *testing.T) {
102 if got := dur(4500); got != "4.5s" {
103 t.Errorf("dur(4500) = %q, want 4.5s", got)
104 }
105 if got := dur(125000); got != "2m05s" {
106 t.Errorf("dur(125000) = %q, want 2m05s", got)
107 }
108 if got := dur(0); got != "—" {
109 t.Errorf("dur(0) = %q, want an em dash", got)
110 }
111 }
112
113 func TestMedianPicksTheMiddleWallTime(t *testing.T) {
114 if got := median([]int64{9000, 1000, 4000}); got != 4000 {
115 t.Fatalf("median = %d, want 4000", got)
116 }
117 if got := median(nil); got != 0 {
118 t.Fatalf("empty median = %d, want 0", got)
119 }
120 }
121
121 lines GO