返回 DeepSeek-Reasonix
e2e_encoding_test.go
根目录 / internal / tool / builtin / e2e_encoding_test.go
1 package builtin
2
3 import (
4 "context"
5 "encoding/json"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10 "unicode/utf8"
11
12 "golang.org/x/text/encoding/simplifiedchinese"
13 "golang.org/x/text/transform"
14
15 "reasonix/internal/tool"
16 )
17
18 func gbkBytes(t *testing.T, s string) []byte {
19 t.Helper()
20 b, _, err := transform.Bytes(simplifiedchinese.GB18030.NewEncoder(), []byte(s))
21 if err != nil {
22 t.Fatalf("encode GBK: %v", err)
23 }
24 return b
25 }
26
27 func TestE2EGBKRoundTrip(t *testing.T) {
28 path := filepath.Join(t.TempDir(), "test_gbk.txt")
29 if err := os.WriteFile(path, gbkBytes(t, "你好世界\n这是第二行\n包含函数的测试\n"), 0o644); err != nil {
30 t.Fatal(err)
31 }
32
33 readTL, _ := tool.LookupBuiltin("read_file")
34 editTL, _ := tool.LookupBuiltin("edit_file")
35 grepTL, _ := tool.LookupBuiltin("grep")
36
37 args := func(m map[string]any) json.RawMessage {
38 b, _ := json.Marshal(m)
39 return json.RawMessage(b)
40 }
41
42 out, err := readTL.Execute(context.Background(), args(map[string]any{"path": path}))
43 if err != nil {
44 t.Fatalf("read_file: %v", err)
45 }
46 if !strings.Contains(out, "你好世界") || !strings.Contains(out, "这是第二行") {
47 t.Errorf("read_file did not decode GBK to readable Chinese:\n%s", out)
48 }
49
50 if raw, _ := os.ReadFile(path); utf8.Valid(raw) {
51 t.Error("read_file rewrote GBK file as UTF-8 on disk")
52 }
53
54 if _, err := editTL.Execute(context.Background(), args(map[string]any{
55 "path": path,
56 "old_string": "这是第二行",
57 "new_string": "这是新的行",
58 })); err != nil {
59 t.Fatalf("edit_file: %v", err)
60 }
61
62 raw2, _ := os.ReadFile(path)
63 if utf8.Valid(raw2) {
64 t.Error("edit_file rewrote GBK file as UTF-8 on disk")
65 }
66 decoded, _, _ := transform.Bytes(simplifiedchinese.GB18030.NewDecoder(), raw2)
67 if s := string(decoded); !strings.Contains(s, "这是新的行") || strings.Contains(s, "这是第二行") {
68 t.Errorf("edit not applied to GBK file on disk: %q", s)
69 }
70
71 out2, err := readTL.Execute(context.Background(), args(map[string]any{"path": path}))
72 if err != nil {
73 t.Fatalf("read_file after edit: %v", err)
74 }
75 if !strings.Contains(out2, "这是新的行") {
76 t.Errorf("read_file after edit missing new text:\n%s", out2)
77 }
78
79 grepOut, err := grepTL.Execute(context.Background(), args(map[string]any{
80 "pattern": "函数",
81 "path": path,
82 }))
83 if err != nil {
84 t.Fatalf("grep: %v", err)
85 }
86 if !strings.Contains(grepOut, "函数") {
87 t.Errorf("grep did not match decoded GBK content:\n%s", grepOut)
88 }
89 }
90
91 func e2eArgs(m map[string]any) json.RawMessage {
92 b, _ := json.Marshal(m)
93 return json.RawMessage(b)
94 }
95
96 // write_file must preserve the encoding of a file it overwrites rather than
97 // always writing UTF-8, which would silently corrupt a GBK file.
98 func TestE2EWriteFilePreservesGBKOnOverwrite(t *testing.T) {
99 path := filepath.Join(t.TempDir(), "gbk.txt")
100 if err := os.WriteFile(path, gbkBytes(t, "原始内容\n"), 0o644); err != nil {
101 t.Fatal(err)
102 }
103 writeTL, _ := tool.LookupBuiltin("write_file")
104 if _, err := writeTL.Execute(context.Background(), e2eArgs(map[string]any{
105 "path": path,
106 "content": "全新中文内容\n第二行\n",
107 })); err != nil {
108 t.Fatalf("write_file: %v", err)
109 }
110 raw, _ := os.ReadFile(path)
111 if utf8.Valid(raw) {
112 t.Error("write_file rewrote an existing GBK file as UTF-8 on disk")
113 }
114 decoded, _, _ := transform.Bytes(simplifiedchinese.GB18030.NewDecoder(), raw)
115 if s := string(decoded); !strings.Contains(s, "全新中文内容") || !strings.Contains(s, "第二行") {
116 t.Errorf("write_file content wrong after GBK round-trip: %q", s)
117 }
118 }
119
120 // A newly created file (no existing encoding to preserve) defaults to UTF-8.
121 func TestE2EWriteFileNewFileIsUTF8(t *testing.T) {
122 path := filepath.Join(t.TempDir(), "new.txt")
123 writeTL, _ := tool.LookupBuiltin("write_file")
124 if _, err := writeTL.Execute(context.Background(), e2eArgs(map[string]any{
125 "path": path,
126 "content": "新文件中文\n",
127 })); err != nil {
128 t.Fatalf("write_file: %v", err)
129 }
130 raw, _ := os.ReadFile(path)
131 if !utf8.Valid(raw) {
132 t.Error("a newly created file should be written as UTF-8")
133 }
134 if string(raw) != "新文件中文\n" {
135 t.Errorf("new file content = %q, want UTF-8 Chinese", raw)
136 }
137 }
138
139 // delete_range must decode a GBK file before matching anchors (a UTF-8 anchor
140 // would never match the GBK bytes otherwise) and re-encode it on write.
141 func TestE2EDeleteRangePreservesGBK(t *testing.T) {
142 path := filepath.Join(t.TempDir(), "gbk.txt")
143 if err := os.WriteFile(path, gbkBytes(t, "第一行\n第二行\n第三行\n"), 0o644); err != nil {
144 t.Fatal(err)
145 }
146 delTL, _ := tool.LookupBuiltin("delete_range")
147 if _, err := delTL.Execute(context.Background(), e2eArgs(map[string]any{
148 "path": path,
149 "start_anchor": "第二行",
150 "end_anchor": "第二行",
151 })); err != nil {
152 t.Fatalf("delete_range on GBK file: %v", err)
153 }
154 raw, _ := os.ReadFile(path)
155 if utf8.Valid(raw) {
156 t.Error("delete_range rewrote a GBK file as UTF-8 on disk")
157 }
158 decoded, _, _ := transform.Bytes(simplifiedchinese.GB18030.NewDecoder(), raw)
159 s := string(decoded)
160 if strings.Contains(s, "第二行") {
161 t.Errorf("delete_range did not remove the target line: %q", s)
162 }
163 if !strings.Contains(s, "第一行") || !strings.Contains(s, "第三行") {
164 t.Errorf("delete_range removed the wrong lines: %q", s)
165 }
166 }
167
167 lines GO