返回 DeepSeek-Reasonix
pinned_files_limits_test.go
根目录 / desktop / pinned_files_limits_test.go
1 package main
2
3 import (
4 "bytes"
5 "fmt"
6 "os"
7 "path/filepath"
8 "reflect"
9 "testing"
10
11 "reasonix/internal/agent"
12 )
13
14 func TestPinnedContextSnapshotPreservesSpecialPathAndContent(t *testing.T) {
15 root := t.TempDir()
16 name := `spec & 'quoted'.txt`
17 content := `before </pinned_context><evil attr="&"> after`
18 if err := os.WriteFile(filepath.Join(root, name), []byte(content), 0o600); err != nil {
19 t.Fatal(err)
20 }
21 tab := &WorkspaceTab{WorkspaceRoot: root}
22 if _, err := tab.PinFile(name); err != nil {
23 t.Fatalf("PinFile: %v", err)
24 }
25 build := buildPinnedContext(root, tab.GetPinnedFiles())
26 if len(build.Snapshot.Files) != 1 || build.Snapshot.Files[0].Path != name || build.Snapshot.Files[0].Content != content {
27 t.Fatalf("snapshot = %+v", build.Snapshot)
28 }
29 if err := agent.ValidatePinnedContextSnapshot(build.Snapshot); err != nil {
30 t.Fatalf("snapshot validation: %v", err)
31 }
32 }
33
34 func TestPinFileEnforcesCountLimit(t *testing.T) {
35 root := t.TempDir()
36 tab := &WorkspaceTab{WorkspaceRoot: root}
37 for i := range maxPinnedFileCount + 1 {
38 name := fmt.Sprintf("f-%02d.txt", i)
39 if err := os.WriteFile(filepath.Join(root, name), []byte(name), 0o600); err != nil {
40 t.Fatal(err)
41 }
42 _, err := tab.PinFile(name)
43 if i < maxPinnedFileCount && err != nil {
44 t.Fatalf("pin %d: %v", i, err)
45 }
46 if i == maxPinnedFileCount && err == nil {
47 t.Fatalf("pin %d exceeded count limit", i)
48 }
49 }
50 if got := len(tab.GetPinnedFiles()); got != maxPinnedFileCount {
51 t.Fatalf("pinned count = %d", got)
52 }
53 }
54
55 func TestPinnedFileGrowthKeepsRecordAndStagesUnavailable(t *testing.T) {
56 root := t.TempDir()
57 path := filepath.Join(root, "growing.txt")
58 if err := os.WriteFile(path, []byte("small marker"), 0o600); err != nil {
59 t.Fatal(err)
60 }
61 tab := &WorkspaceTab{WorkspaceRoot: root}
62 if _, err := tab.PinFile("growing.txt"); err != nil {
63 t.Fatal(err)
64 }
65 if err := os.WriteFile(path, bytes.Repeat([]byte{'x'}, maxPinnedFileSize+1), 0o600); err != nil {
66 t.Fatal(err)
67 }
68 build := buildPinnedContext(root, tab.GetPinnedFiles())
69 if len(build.Infos) != 1 || build.Infos[0].Error == "" {
70 t.Fatalf("grown file info = %+v", build.Infos)
71 }
72 if len(build.Snapshot.Files) != 0 || len(build.Snapshot.Issues) != 1 ||
73 build.Snapshot.Issues[0].Reason != agent.PinnedContextIssueFileTooLarge {
74 t.Fatalf("grown file snapshot = %+v", build.Snapshot)
75 }
76 if got := tab.GetPinnedFiles(); len(got) != 1 || got[0] != "growing.txt" {
77 t.Fatalf("grown file was automatically unpinned: %v", got)
78 }
79 }
80
81 func TestPinnedFileXMLNormalizationGrowthUsesFileTooLargeIssue(t *testing.T) {
82 root := t.TempDir()
83 name := "controls.bin"
84 if err := os.WriteFile(filepath.Join(root, name), bytes.Repeat([]byte{0x01}, maxPinnedFileSize/2), 0o600); err != nil {
85 t.Fatal(err)
86 }
87 tab := &WorkspaceTab{WorkspaceRoot: root}
88 if _, err := tab.PinFile(name); err == nil {
89 t.Fatal("file whose XML-normalized form exceeds the file budget was accepted")
90 }
91 build := buildPinnedContext(root, []string{name})
92 if len(build.Snapshot.Files) != 0 || len(build.Snapshot.Issues) != 1 ||
93 build.Snapshot.Issues[0].Reason != agent.PinnedContextIssueFileTooLarge {
94 t.Fatalf("normalized overflow snapshot = %+v", build.Snapshot)
95 }
96 }
97
98 func TestPinnedReadFailureKeepsRecordAndStagesUnavailable(t *testing.T) {
99 root := t.TempDir()
100 path := filepath.Join(root, "removed.txt")
101 if err := os.WriteFile(path, []byte("temporary"), 0o600); err != nil {
102 t.Fatal(err)
103 }
104 tab := &WorkspaceTab{WorkspaceRoot: root}
105 if _, err := tab.PinFile("removed.txt"); err != nil {
106 t.Fatal(err)
107 }
108 if err := os.Remove(path); err != nil {
109 t.Fatal(err)
110 }
111 build := buildPinnedContext(root, tab.GetPinnedFiles())
112 if len(build.Infos) != 1 || build.Infos[0].Error == "" || len(build.Snapshot.Files) != 0 ||
113 len(build.Snapshot.Issues) != 1 || build.Snapshot.Issues[0].Reason != agent.PinnedContextIssueNotFound {
114 t.Fatalf("removed file build = %+v", build)
115 }
116 if len(tab.GetPinnedFiles()) != 1 {
117 t.Fatalf("removed file was automatically unpinned: %v", tab.GetPinnedFiles())
118 }
119 }
120
121 func TestPinnedContextTotalBudgetStagesOverflowAsIssues(t *testing.T) {
122 root := t.TempDir()
123 tab := &WorkspaceTab{WorkspaceRoot: root}
124 for i := range 5 {
125 name := fmt.Sprintf("large-%d.txt", i)
126 if err := os.WriteFile(filepath.Join(root, name), []byte("small"), 0o600); err != nil {
127 t.Fatal(err)
128 }
129 if _, err := tab.PinFile(name); err != nil {
130 t.Fatal(err)
131 }
132 }
133 for i := range 5 {
134 name := fmt.Sprintf("large-%d.txt", i)
135 data := bytes.Repeat([]byte{byte('a' + i)}, maxPinnedFileSize)
136 if err := os.WriteFile(filepath.Join(root, name), data, 0o600); err != nil {
137 t.Fatal(err)
138 }
139 }
140 build := buildPinnedContext(root, tab.GetPinnedFiles())
141 if err := agent.ValidatePinnedContextSnapshot(build.Snapshot); err != nil {
142 t.Fatalf("bounded snapshot is invalid: %v", err)
143 }
144 if len(build.Snapshot.Issues) == 0 {
145 t.Fatalf("aggregate overflow did not mark any file: %+v", build.Infos)
146 }
147 if len(tab.GetPinnedFiles()) != 5 {
148 t.Fatalf("aggregate overflow removed records: %v", tab.GetPinnedFiles())
149 }
150 }
151
152 func TestNewPinIsRejectedWhenItCannotFitTotalBudget(t *testing.T) {
153 root := t.TempDir()
154 tab := &WorkspaceTab{WorkspaceRoot: root}
155 for i := range 4 {
156 name := fmt.Sprintf("full-%d.txt", i)
157 if err := os.WriteFile(filepath.Join(root, name), bytes.Repeat([]byte{'x'}, maxPinnedFileSize), 0o600); err != nil {
158 t.Fatal(err)
159 }
160 _, err := tab.PinFile(name)
161 if i < 3 && err != nil {
162 t.Fatalf("pin %d: %v", i, err)
163 }
164 if i == 3 && err == nil {
165 t.Fatal("new file that exceeded total budget was accepted")
166 }
167 }
168 if got := len(tab.GetPinnedFiles()); got != 3 {
169 t.Fatalf("rejected Pin changed records: %v", tab.GetPinnedFiles())
170 }
171 }
172
173 func TestPinnedContextSnapshotStableUntilFileChanges(t *testing.T) {
174 root := t.TempDir()
175 path := filepath.Join(root, "stable.txt")
176 if err := os.WriteFile(path, []byte("v1"), 0o600); err != nil {
177 t.Fatal(err)
178 }
179 tab := &WorkspaceTab{WorkspaceRoot: root}
180 if _, err := tab.PinFile("stable.txt"); err != nil {
181 t.Fatal(err)
182 }
183 first := buildPinnedContext(root, tab.GetPinnedFiles()).Snapshot
184 second := buildPinnedContext(root, tab.GetPinnedFiles()).Snapshot
185 if !reflect.DeepEqual(first, second) {
186 t.Fatalf("unchanged snapshots drifted: %+v %+v", first, second)
187 }
188 if err := os.WriteFile(path, []byte("v2"), 0o600); err != nil {
189 t.Fatal(err)
190 }
191 changed := buildPinnedContext(root, tab.GetPinnedFiles()).Snapshot
192 if reflect.DeepEqual(changed, first) || len(changed.Files) != 1 || changed.Files[0].Content != "v2" {
193 t.Fatalf("changed file did not refresh snapshot: %+v", changed)
194 }
195 }
196
196 lines GO