返回 DeepSeek-Reasonix
repair_queue_drain_test.go
根目录 / internal / sessioncatalog / repair_queue_drain_test.go
1 package sessioncatalog
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "path/filepath"
8 "sync"
9 "testing"
10 "time"
11
12 "reasonix/internal/agent"
13 )
14
15 // The wake channel coalesces on one key and is sized by QueueCapacity, so a
16 // capacity far below the pending set must not strand rows: one wake has to
17 // drain every due row. Repair itself is stubbed because the filesystem path
18 // defers a transient failure by repairBackoff's 30s floor, which would decide
19 // this test's outcome for a reason that has nothing to do with queue capacity.
20 func TestRepairDrainEventuallyCompletesBeyondQueue(t *testing.T) {
21 t.Parallel()
22 ctx := context.Background()
23 dir := t.TempDir()
24 path := filepath.Join(t.TempDir(), "catalog.sqlite")
25 seed, err := Open(ctx, Options{Path: path, DisableRepair: true})
26 if err != nil {
27 t.Fatal(err)
28 }
29 const total = 8
30 for i := range total {
31 session := filepath.Join(dir, fmt.Sprintf("%02d.jsonl", i))
32 if err := os.WriteFile(session, []byte(`{"role":"user","content":"turn"}`+"\n"), 0o600); err != nil {
33 t.Fatal(err)
34 }
35 if err := seed.UpsertSession(ctx, SessionRecord{
36 Path: session, Directory: dir, Scope: "global", TopicID: fmt.Sprintf("t%d", i),
37 TurnsState: TurnsUnknown, Health: HealthOK, LastActivityAt: int64(i + 1),
38 }); err != nil {
39 t.Fatal(err)
40 }
41 }
42 if err := seed.Close(context.Background()); err != nil {
43 t.Fatal(err)
44 }
45
46 var mu sync.Mutex
47 repaired := map[string]struct{}{}
48 drained := make(chan struct{})
49 catalog, err := Open(ctx, Options{
50 Path: path, QueueCapacity: 2, Now: time.Now,
51 repairSession: func(_ context.Context, session string) (agent.SessionListingRepairResult, error) {
52 mu.Lock()
53 defer mu.Unlock()
54 if _, seen := repaired[session]; !seen {
55 repaired[session] = struct{}{}
56 if len(repaired) == total {
57 close(drained)
58 }
59 }
60 return agent.SessionListingRepairResult{Status: agent.SessionListingRepairApplied, Preview: "ok", Turns: 1}, nil
61 },
62 })
63 if err != nil {
64 t.Fatal(err)
65 }
66 t.Cleanup(func() { _ = catalog.Close(context.Background()) })
67
68 select {
69 case <-drained:
70 case <-time.After(30 * time.Second):
71 mu.Lock()
72 seen := len(repaired)
73 mu.Unlock()
74 t.Fatalf("repaired %d of %d sessions; a queue of 2 stranded the rest", seen, total)
75 }
76 // Every row reached repair; the batch commit that clears the pending count
77 // lands just after the last call, so wait for the count the drain owes.
78 deadline := time.Now().Add(10 * time.Second)
79 for {
80 if catalog.Status().RepairPending == 0 {
81 return
82 }
83 if time.Now().After(deadline) {
84 t.Fatalf("repair pending stuck at %d after every session was repaired", catalog.Status().RepairPending)
85 }
86 time.Sleep(10 * time.Millisecond)
87 }
88 }
89
89 lines GO