返回 DeepSeek-Reasonix
effect_turn_phase_test.go
根目录 / internal / boot / effect_turn_phase_test.go
1 package boot
2
3 // Effect test for turn-phase accounting: the phase buckets are only meaningful
4 // if a real Build assembly bills them, so this asserts through the audit the
5 // CLI actually exports, not through a hand-built Audit.
6
7 import (
8 "context"
9 "sync"
10 "testing"
11 "time"
12
13 "reasonix/internal/event"
14 "reasonix/internal/provider"
15 )
16
17 // slowPhaseProvider stalls before answering so the provider span clears
18 // RecordPhaseMs's sub-millisecond floor by a wide margin.
19 type slowPhaseProvider struct {
20 mu sync.Mutex
21 calls int
22 }
23
24 func (p *slowPhaseProvider) Name() string { return "boot-phase-effect-test" }
25
26 func (p *slowPhaseProvider) Stream(_ context.Context, _ provider.Request) (<-chan provider.Chunk, error) {
27 p.mu.Lock()
28 p.calls++
29 p.mu.Unlock()
30 time.Sleep(10 * time.Millisecond)
31 chunks := []provider.Chunk{
32 {Type: provider.ChunkText, Text: "ok"},
33 {Type: provider.ChunkDone},
34 }
35 ch := make(chan provider.Chunk, len(chunks))
36 for _, chunk := range chunks {
37 ch <- chunk
38 }
39 close(ch)
40 return ch, nil
41 }
42
43 // TestEffectTurnPhaseBillsProviderWaitThroughRealBuild pins the boundary the
44 // run --metrics JSON reads. Before the phase clock closed at turn end this
45 // reported zero for every bucket no matter how long the provider took.
46 func TestEffectTurnPhaseBillsProviderWaitThroughRealBuild(t *testing.T) {
47 isolateConfigHome(t)
48 dir := robustTempDir(t)
49 t.Chdir(dir)
50
51 prov := &slowPhaseProvider{}
52 provider.Register("phase-effect", func(provider.Config) (provider.Provider, error) {
53 return prov, nil
54 })
55 writeFile(t, dir, "reasonix.toml", `
56 default_model = "test-model"
57
58 [agent]
59 system_prompt = "BASE"
60
61 [environment]
62 enabled = false
63
64 [[providers]]
65 name = "test-model"
66 kind = "phase-effect"
67 model = "x"
68 `)
69 approveWorkspace(t, dir)
70
71 ctrl, err := Build(context.Background(), Options{Sink: event.Discard})
72 if err != nil {
73 t.Fatalf("Build: %v", err)
74 }
75 defer ctrl.Close()
76 if err := ctrl.Run(context.Background(), "reply ok"); err != nil {
77 t.Fatalf("Run: %v", err)
78 }
79
80 exec := ctrl.Executor()
81 if exec == nil {
82 t.Fatal("no executor on the built controller")
83 }
84 audit := exec.CapabilityAudit()
85 if audit == nil {
86 t.Fatal("real Build left the capability audit unwired")
87 }
88 phases := audit.Snapshot().Phases
89 if phases.ProviderWaitMs <= 0 {
90 t.Fatalf("ProviderWaitMs = %d, want the provider span billed (%+v)", phases.ProviderWaitMs, phases)
91 }
92 }
93
93 lines GO