返回 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
70 ctrl, err := Build(context.Background(), Options{Sink: event.Discard})
71 if err != nil {
72 t.Fatalf("Build: %v", err)
73 }
74 defer ctrl.Close()
75 if err := ctrl.Run(context.Background(), "reply ok"); err != nil {
76 t.Fatalf("Run: %v", err)
77 }
78
79 exec := ctrl.Executor()
80 if exec == nil {
81 t.Fatal("no executor on the built controller")
82 }
83 audit := exec.CapabilityAudit()
84 if audit == nil {
85 t.Fatal("real Build left the capability audit unwired")
86 }
87 phases := audit.Snapshot().Phases
88 if phases.ProviderWaitMs <= 0 {
89 t.Fatalf("ProviderWaitMs = %d, want the provider span billed (%+v)", phases.ProviderWaitMs, phases)
90 }
91 }
92
92 lines GO