返回 DeepSeek-Reasonix
turn_completion_test.go
根目录 / internal / control / turn_completion_test.go
1 package control
2
3 import (
4 "context"
5 "errors"
6 "sync"
7 "testing"
8
9 "reasonix/internal/event"
10 )
11
12 type completionCountingSink struct {
13 mu sync.Mutex
14 completions int
15 }
16
17 func (*completionCountingSink) Emit(event.Event) {}
18
19 func (s *completionCountingSink) RecordTurnCompletion() {
20 s.mu.Lock()
21 s.completions++
22 s.mu.Unlock()
23 }
24
25 func (s *completionCountingSink) count() int {
26 s.mu.Lock()
27 defer s.mu.Unlock()
28 return s.completions
29 }
30
31 type noOpTurnRunner struct{}
32
33 func (noOpTurnRunner) Run(context.Context, string) error { return nil }
34
35 type gatedTurnRunner struct {
36 started chan struct{}
37 release chan struct{}
38 }
39
40 func (r *gatedTurnRunner) Run(ctx context.Context, _ string) error {
41 close(r.started)
42 select {
43 case <-r.release:
44 return nil
45 case <-ctx.Done():
46 return ctx.Err()
47 }
48 }
49
50 func TestSynchronousControllerRunsRecordCompletion(t *testing.T) {
51 sink := &completionCountingSink{}
52 c := New(Options{Runner: noOpTurnRunner{}, Sink: sink})
53
54 if err := c.Run(context.Background(), "headless"); err != nil {
55 t.Fatal(err)
56 }
57 if err := c.RunTurn(context.Background(), "transport"); err != nil {
58 t.Fatal(err)
59 }
60 if got := sink.count(); got != 2 {
61 t.Fatalf("completion count = %d, want 2", got)
62 }
63 }
64
65 func TestRejectedRunTurnDoesNotRecordCompletion(t *testing.T) {
66 runner := &gatedTurnRunner{started: make(chan struct{}), release: make(chan struct{})}
67 sink := &completionCountingSink{}
68 c := New(Options{Runner: runner, Sink: sink})
69 done := make(chan error, 1)
70 go func() { done <- c.RunTurn(context.Background(), "first") }()
71 <-runner.started
72
73 if err := c.RunTurn(context.Background(), "second"); !errors.Is(err, ErrTurnRunning) {
74 t.Fatalf("second RunTurn error = %v, want ErrTurnRunning", err)
75 }
76 if got := sink.count(); got != 0 {
77 t.Fatalf("rejected turn recorded completion: %d", got)
78 }
79 close(runner.release)
80 if err := <-done; err != nil {
81 t.Fatal(err)
82 }
83 if got := sink.count(); got != 1 {
84 t.Fatalf("completion count = %d, want 1", got)
85 }
86 }
87
87 lines GO