返回 DeepSeek-Reasonix
write_claims_test.go
根目录 / internal / agent / write_claims_test.go
1 package agent
2
3 import (
4 "os"
5 "path/filepath"
6 "runtime"
7 "testing"
8 )
9
10 func TestNormalizeConcurrencyLimitsDefaults(t *testing.T) {
11 total, writers := NormalizeConcurrencyLimits(0, 0)
12 if total != DefaultMaxSubagentConcurrency || writers != DefaultMaxParallelWriters {
13 t.Fatalf("got %d/%d, want %d/%d", total, writers, DefaultMaxSubagentConcurrency, DefaultMaxParallelWriters)
14 }
15 total, writers = NormalizeConcurrencyLimits(10, 10)
16 if total != 10 || writers != 10 {
17 t.Fatalf("got %d/%d, want 10/10", total, writers)
18 }
19 total, writers = NormalizeConcurrencyLimits(4, 10)
20 if total != 4 || writers != 4 {
21 t.Fatalf("writers must not exceed total: got %d/%d", total, writers)
22 }
23 total, writers = NormalizeConcurrencyLimits(100, 50)
24 if total != MaxSubagentConcurrencyLimit || writers != MaxSubagentConcurrencyLimit {
25 t.Fatalf("clamp: got %d/%d", total, writers)
26 }
27 }
28
29 func TestNormalizeWritePathsRejectsGlobsAndEscape(t *testing.T) {
30 root := t.TempDir()
31 if _, err := NormalizeWritePaths(root, []string{"docs/*.md"}); err == nil {
32 t.Fatal("expected glob rejection")
33 }
34 outside := filepath.Join(filepath.Dir(root), "outside.txt")
35 if _, err := NormalizeWritePaths(root, []string{outside}); err == nil {
36 t.Fatal("expected workspace escape rejection")
37 }
38 // Relative file is fine even if it does not exist yet.
39 got, err := NormalizeWritePaths(root, []string{"docs/01.md"})
40 if err != nil {
41 t.Fatal(err)
42 }
43 if len(got.Paths) != 1 {
44 t.Fatalf("paths = %v", got.Paths)
45 }
46 if !filepath.IsAbs(got.Paths[0]) {
47 t.Fatalf("expected absolute path, got %q", got.Paths[0])
48 }
49 }
50
51 func TestWritePathOverlapParentChildAndCase(t *testing.T) {
52 root := t.TempDir()
53 a, err := NormalizeWritePaths(root, []string{"docs"})
54 if err != nil {
55 t.Fatal(err)
56 }
57 b, err := NormalizeWritePaths(root, []string{"docs/01.md"})
58 if err != nil {
59 t.Fatal(err)
60 }
61 if !a.Overlaps(b) {
62 t.Fatal("parent/child must overlap")
63 }
64 c, err := NormalizeWritePaths(root, []string{"other.md"})
65 if err != nil {
66 t.Fatal(err)
67 }
68 if a.Overlaps(c) {
69 t.Fatal("disjoint paths must not overlap")
70 }
71 if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
72 // Case-variant of the same relative path should collide.
73 upper := filepath.Join(root, "Docs")
74 _ = os.MkdirAll(upper, 0o755)
75 d, err := NormalizeWritePaths(root, []string{"Docs"})
76 if err != nil {
77 t.Fatal(err)
78 }
79 e, err := NormalizeWritePaths(root, []string{"docs"})
80 if err != nil {
81 t.Fatal(err)
82 }
83 if !d.Overlaps(e) {
84 t.Fatal("case-equivalent paths must overlap on this platform")
85 }
86 }
87 whole, err := WholeWorkspaceWriteClaim(root)
88 if err != nil {
89 t.Fatal(err)
90 }
91 if !whole.Overlaps(c) {
92 t.Fatal("whole workspace must overlap any path claim")
93 }
94 }
95
96 func TestScheduleOverlapsAllowsSiblingDirectoryClaims(t *testing.T) {
97 root := t.TempDir()
98 if err := os.MkdirAll(filepath.Join(root, "src"), 0o755); err != nil {
99 t.Fatal(err)
100 }
101 a, err := NormalizeWritePaths(root, []string{"src/"})
102 if err != nil {
103 t.Fatal(err)
104 }
105 b, err := NormalizeWritePaths(root, []string{"src/"})
106 if err != nil {
107 t.Fatal(err)
108 }
109 if !a.Overlaps(b) {
110 t.Fatal("capability overlap must still treat identical dirs as overlapping")
111 }
112 if ScheduleOverlaps(a, b) {
113 t.Fatal("schedule must allow two directory claims over the same tree")
114 }
115 }
116
117 func TestScheduleOverlapsDirVsFileInside(t *testing.T) {
118 root := t.TempDir()
119 if err := os.MkdirAll(filepath.Join(root, "src"), 0o755); err != nil {
120 t.Fatal(err)
121 }
122 dir, err := NormalizeWritePaths(root, []string{"src/"})
123 if err != nil {
124 t.Fatal(err)
125 }
126 file, err := NormalizeWritePaths(root, []string{"src/a.go"})
127 if err != nil {
128 t.Fatal(err)
129 }
130 if !ScheduleOverlaps(dir, file) {
131 t.Fatal("directory claim must still block a concrete file inside it at start")
132 }
133 }
134
135 func TestScheduleOverlapsWholeWorkspaceUnchanged(t *testing.T) {
136 root := t.TempDir()
137 whole, err := WholeWorkspaceWriteClaim(root)
138 if err != nil {
139 t.Fatal(err)
140 }
141 file, err := NormalizeWritePaths(root, []string{"a.go"})
142 if err != nil {
143 t.Fatal(err)
144 }
145 if !ScheduleOverlaps(whole, file) {
146 t.Fatal("omitted write_paths must still serialize at start")
147 }
148 }
149
150 func TestValidateNonOverlappingWriteClaims(t *testing.T) {
151 root := t.TempDir()
152 a, _ := NormalizeWritePaths(root, []string{"a.md"})
153 b, _ := NormalizeWritePaths(root, []string{"b.md"})
154 if err := ValidateNonOverlappingWriteClaims([]WritePathSet{a, b}); err != nil {
155 t.Fatal(err)
156 }
157 c, _ := NormalizeWritePaths(root, []string{"a.md"})
158 if err := ValidateNonOverlappingWriteClaims([]WritePathSet{a, c}); err == nil {
159 t.Fatal("expected conflict")
160 }
161 }
162
163 func TestWritePathSetAllowsPath(t *testing.T) {
164 root := t.TempDir()
165 claim, err := NormalizeWritePaths(root, []string{"docs"})
166 if err != nil {
167 t.Fatal(err)
168 }
169 inside := filepath.Join(root, "docs", "x.md")
170 if !claim.AllowsPath(inside) {
171 t.Fatalf("should allow %s", inside)
172 }
173 outside := filepath.Join(root, "other.md")
174 if claim.AllowsPath(outside) {
175 t.Fatalf("should reject %s", outside)
176 }
177 }
178
178 lines GO