返回 DeepSeek-Reasonix
session_temp_test.go
根目录 / internal / control / session_temp_test.go
1 package control
2
3 import (
4 "os"
5 "path/filepath"
6 "testing"
7
8 "reasonix/internal/agent"
9 "reasonix/internal/event"
10 "reasonix/internal/sessiontemp"
11 )
12
13 func TestSessionTempSurvivesHotRebuildStyleRetain(t *testing.T) {
14 m := sessiontemp.New()
15 old := New(Options{SessionTemp: m, Sink: event.Discard})
16 lease, err := old.SessionTemp().Acquire()
17 if err != nil {
18 t.Fatal(err)
19 }
20 dir := lease.Dir()
21 if err := os.WriteFile(filepath.Join(dir, "keep.txt"), []byte("alive"), 0o600); err != nil {
22 t.Fatal(err)
23 }
24 lease.Release()
25
26 // Hot rebuild: replacement retains the same Manager before old closes.
27 replacement := New(Options{SessionTemp: old.SessionTemp(), Sink: event.Discard})
28 old.ReleaseResources()
29
30 again, err := replacement.SessionTemp().Acquire()
31 if err != nil {
32 t.Fatal(err)
33 }
34 defer again.Release()
35 if again.Dir() != dir {
36 t.Fatalf("hot rebuild changed generation: got %q want %q", again.Dir(), dir)
37 }
38 body, err := os.ReadFile(filepath.Join(dir, "keep.txt"))
39 if err != nil || string(body) != "alive" {
40 t.Fatalf("temp file lost across rebuild: %q %v", body, err)
41 }
42 replacement.ReleaseResources()
43 }
44
45 func TestNewSessionRotatesSessionTemp(t *testing.T) {
46 dir := t.TempDir()
47 exec := agent.New(nil, nil, agent.NewSession("sys"), agent.Options{}, event.Discard)
48 c := New(Options{
49 Executor: exec,
50 SessionDir: dir,
51 Label: "test",
52 Sink: event.Discard,
53 })
54 defer c.Close()
55
56 lease, err := c.SessionTemp().Acquire()
57 if err != nil {
58 t.Fatal(err)
59 }
60 oldDir := lease.Dir()
61 if err := os.WriteFile(filepath.Join(oldDir, "old.txt"), []byte("x"), 0o600); err != nil {
62 t.Fatal(err)
63 }
64 lease.Release()
65
66 if err := c.NewSession(); err != nil {
67 t.Fatal(err)
68 }
69 fresh, err := c.SessionTemp().Acquire()
70 if err != nil {
71 t.Fatal(err)
72 }
73 defer fresh.Release()
74 if fresh.Dir() == oldDir {
75 t.Fatal("NewSession should rotate the private temporary directory")
76 }
77 if _, err := os.Stat(filepath.Join(fresh.Dir(), "old.txt")); !os.IsNotExist(err) {
78 t.Fatalf("new session must not see old temp files: %v", err)
79 }
80 }
81
82 func TestSetSessionPathDoesNotRotateSessionTemp(t *testing.T) {
83 dir := t.TempDir()
84 exec := agent.New(nil, nil, agent.NewSession("sys"), agent.Options{}, event.Discard)
85 c := New(Options{
86 Executor: exec,
87 SessionDir: dir,
88 Label: "test",
89 Sink: event.Discard,
90 })
91 defer c.Close()
92
93 lease, err := c.SessionTemp().Acquire()
94 if err != nil {
95 t.Fatal(err)
96 }
97 oldDir := lease.Dir()
98 lease.Release()
99
100 c.SetSessionPath(filepath.Join(dir, "rebind.jsonl"))
101 again, err := c.SessionTemp().Acquire()
102 if err != nil {
103 t.Fatal(err)
104 }
105 defer again.Release()
106 if again.Dir() != oldDir {
107 t.Fatalf("SetSessionPath must not rotate temp: got %q want %q", again.Dir(), oldDir)
108 }
109 }
110
111 func TestResumeOtherSessionRotatesSessionTemp(t *testing.T) {
112 dir := t.TempDir()
113 exec := agent.New(nil, nil, agent.NewSession("sys"), agent.Options{}, event.Discard)
114 pathA := agent.NewSessionPath(dir, "a")
115 pathB := agent.NewSessionPath(dir, "b")
116 c := New(Options{
117 Executor: exec,
118 SessionDir: dir,
119 SessionPath: pathA,
120 Label: "test",
121 Sink: event.Discard,
122 })
123 defer c.Close()
124
125 lease, err := c.SessionTemp().Acquire()
126 if err != nil {
127 t.Fatal(err)
128 }
129 oldDir := lease.Dir()
130 lease.Release()
131
132 // Same-path resume (rebuild style) keeps generation.
133 c.Resume(agent.NewSession("sys"), pathA)
134 same, err := c.SessionTemp().Acquire()
135 if err != nil {
136 t.Fatal(err)
137 }
138 if same.Dir() != oldDir {
139 t.Fatalf("same-path Resume rotated temp: got %q want %q", same.Dir(), oldDir)
140 }
141 same.Release()
142
143 // Different path (user /resume) rotates.
144 c.Resume(agent.NewSession("sys"), pathB)
145 other, err := c.SessionTemp().Acquire()
146 if err != nil {
147 t.Fatal(err)
148 }
149 defer other.Release()
150 if other.Dir() == oldDir {
151 t.Fatal("resume of another session should rotate private temp")
152 }
153 }
154
155 func TestShouldRotateSessionTempOnResume(t *testing.T) {
156 cases := []struct {
157 prev, next string
158 want bool
159 }{
160 {"", "/a", false},
161 {"/a", "", false},
162 {"/a", "/a", false},
163 {"/a", "/b", true},
164 {"/a/../b", "/b", false},
165 }
166 for _, c := range cases {
167 if got := shouldRotateSessionTempOnResume(c.prev, c.next); got != c.want {
168 t.Errorf("shouldRotate(%q, %q) = %v, want %v", c.prev, c.next, got, c.want)
169 }
170 }
171 }
172
173 func TestFailedReplacementDoesNotDropOldSessionTemp(t *testing.T) {
174 m := sessiontemp.New()
175 old := New(Options{SessionTemp: m, Sink: event.Discard})
176 lease, err := old.SessionTemp().Acquire()
177 if err != nil {
178 t.Fatal(err)
179 }
180 dir := lease.Dir()
181 lease.Release()
182
183 // Simulate a failed rebuild: retain then release the replacement only.
184 m.Retain()
185 m.Release()
186 // Old controller still owns the Manager.
187 again, err := old.SessionTemp().Acquire()
188 if err != nil {
189 t.Fatal(err)
190 }
191 defer again.Release()
192 if again.Dir() != dir {
193 t.Fatalf("failed rebuild affected old session temp: got %q want %q", again.Dir(), dir)
194 }
195 old.Close()
196 }
197
197 lines GO