返回 DeepSeek-Reasonix
compact_commit_cancellation_test.go
根目录 / internal / agent / compact_commit_cancellation_test.go
1 package agent
2
3 import (
4 "context"
5 "errors"
6 "strings"
7 "sync"
8 "testing"
9 "time"
10
11 "reasonix/internal/event"
12 "reasonix/internal/provider"
13 )
14
15 func TestEmptyPositionalRangeIsNoopOnlyBelowHardCeiling(t *testing.T) {
16 for _, overLimit := range []bool{false, true} {
17 sess := NewSession("system")
18 content := "first question"
19 if overLimit {
20 content = strings.Repeat("question ", 40_000)
21 }
22 sess.Add(provider.Message{Role: provider.RoleUser, Content: content})
23 a := New(nil, nil, sess, Options{ContextWindow: 32_000}, event.Discard)
24 if overLimit && a.ContextMaintenanceSnapshot().ProjectedTokens < a.hardInputCeiling() {
25 t.Fatal("fixture must exceed the measured hard ceiling")
26 }
27 before := a.currentProjectionVersion()
28 err := a.SummarizeUpTo(t.Context(), 1)
29 if (err != nil) != overLimit {
30 t.Fatalf("overLimit=%v: empty range error=%v", overLimit, err)
31 }
32 if a.currentProjectionVersion() != before {
33 t.Fatal("empty range installed a projection")
34 }
35 ctx, cancel := context.WithCancel(t.Context())
36 cancel()
37 if err := a.SummarizeUpTo(ctx, 1); !errors.Is(err, context.Canceled) {
38 t.Fatalf("cancelled empty range = %v", err)
39 }
40 }
41 }
42
43 func TestCancellationBeforeTruncationRescueDoesNotInstallProjection(t *testing.T) {
44 for _, trigger := range []string{CompactionTriggerManual, CompactionTriggerPressure, CompactionTriggerOverflow} {
45 t.Run(trigger, func(t *testing.T) {
46 a := agentOverForce(t, &failingSummaryProvider{}, foldableSessionOverForce(14))
47 ctx, cancel := context.WithCancel(context.Background())
48 defer cancel()
49 a.svc.sink = event.FuncSink(func(e event.Event) {
50 if e.Kind == event.ContextMaintenanceEvent && e.Maintenance != nil &&
51 (e.Maintenance.Status == "failed" || e.Maintenance.Status == "blocked") {
52 cancel()
53 }
54 })
55 before := a.currentProjectionVersion()
56 _, err := a.contextManager().Prepare(ctx, ContextPreparePolicy{Trigger: trigger, Force: trigger == CompactionTriggerManual})
57 if !errors.Is(err, context.Canceled) {
58 t.Fatalf("Prepare error = %v, want context.Canceled", err)
59 }
60 if got := a.currentProjectionVersion(); got != before {
61 t.Fatalf("projection version = %d, want unchanged %d", got, before)
62 }
63 })
64 }
65 }
66
67 func TestEmptyManualCompactDoesNotCallSummarizer(t *testing.T) {
68 provider := &failingSummaryProvider{}
69 a := New(provider, nil, NewSession("sys"), Options{ContextWindow: 32_000}, event.Discard)
70 if err := a.CompactNow(context.Background(), ""); err != nil {
71 t.Fatalf("CompactNow(empty) = %v, want nil", err)
72 }
73 if provider.calls != 0 {
74 t.Fatalf("summarizer calls = %d, want 0", provider.calls)
75 }
76 if a.currentProjectionVersion() != 0 || a.sess.compactionState.LastReceipt != nil {
77 t.Fatal("empty compaction changed projection state")
78 }
79 }
80
81 type lateSummaryProvider struct {
82 started chan struct{}
83 release chan struct{}
84 once sync.Once
85 }
86
87 func (p *lateSummaryProvider) Name() string { return "late-summary" }
88 func (p *lateSummaryProvider) Stream(_ context.Context, _ provider.Request) (<-chan provider.Chunk, error) {
89 p.once.Do(func() { close(p.started) })
90 <-p.release // deliberately ignore cancellation like a broken adapter
91 ch := make(chan provider.Chunk, 2)
92 ch <- provider.Chunk{Type: provider.ChunkText, Text: "# Goal\nKeep the task safe."}
93 ch <- provider.Chunk{Type: provider.ChunkDone}
94 close(ch)
95 return ch, nil
96 }
97
98 func TestCancelledLateSummaryCannotCommit(t *testing.T) {
99 prov := &lateSummaryProvider{started: make(chan struct{}), release: make(chan struct{})}
100 sess := NewSession("sys")
101 for range 8 {
102 sess.Add(provider.Message{Role: provider.RoleUser, Content: strings.Repeat("question ", 300)})
103 sess.Add(provider.Message{Role: provider.RoleAssistant, Content: strings.Repeat("answer ", 300)})
104 }
105 a := New(prov, nil, sess, Options{ContextWindow: 32_000}, event.Discard)
106 before := a.ContextMaintenanceSnapshot().ProjectionVersion
107 ctx, cancel := context.WithCancel(context.Background())
108 done := make(chan error, 1)
109 go func() { done <- a.CompactNow(ctx, "") }()
110 select {
111 case <-prov.started:
112 case <-time.After(10 * time.Second):
113 t.Fatal("summary request did not start")
114 }
115 cancel()
116 close(prov.release)
117 select {
118 case err := <-done:
119 if !errors.Is(err, context.Canceled) {
120 t.Fatalf("CompactNow = %v, want context.Canceled", err)
121 }
122 case <-time.After(10 * time.Second):
123 t.Fatal("late summary did not return")
124 }
125 if after := a.ContextMaintenanceSnapshot().ProjectionVersion; after != before {
126 t.Fatalf("cancelled late summary committed projection version %d -> %d", before, after)
127 }
128 }
129
129 lines GO