返回 DeepSeek-Reasonix
encoding_fragment_test.go
根目录 / internal / tool / builtin / encoding_fragment_test.go
1 package builtin
2
3 import (
4 "bytes"
5 "context"
6 "errors"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11
12 "golang.org/x/text/encoding/simplifiedchinese"
13
14 fileenc "reasonix/internal/fileutil/encoding"
15 "reasonix/internal/tool"
16 )
17
18 // longGBKLine is a GB18030 file whose first line outruns any detection window
19 // and puts an odd byte before it, so every window ends inside a character with
20 // no newline to cut at.
21 func longGBKLine(t *testing.T, chars int) string {
22 t.Helper()
23 gb, err := simplifiedchinese.GB18030.NewEncoder().String("x" + strings.Repeat("啊", chars) + "\n目标行\n")
24 if err != nil {
25 t.Fatal(err)
26 }
27 path := filepath.Join(t.TempDir(), "long.gbk")
28 if err := os.WriteFile(path, []byte(gb), 0o644); err != nil {
29 t.Fatal(err)
30 }
31 return path
32 }
33
34 func TestGrepGB18030PeekWithoutNewline(t *testing.T) {
35 path := longGBKLine(t, 5000)
36 out := runTool(t, grepTool{}, map[string]any{"pattern": "目标", "path": path})
37 if !strings.Contains(out, "目标行") {
38 t.Fatalf("expected a match past the peek, got:\n%s", out)
39 }
40 }
41
42 func TestReadFileGB18030SampleWithoutNewline(t *testing.T) {
43 path := longGBKLine(t, 140000)
44 readTL, _ := tool.LookupBuiltin("read_file")
45 out, err := readTL.Execute(context.Background(), e2eArgs(map[string]any{"path": path, "offset": 1}))
46 if err != nil {
47 t.Fatalf("read_file: %v", err)
48 }
49 if !strings.Contains(out, "目标行") {
50 t.Fatalf("read_file did not decode GB18030 past the sample:\n%s", out)
51 }
52 }
53
54 // An edit that adds Chinese to a CP936 file holding its 0x80 euro writes CP936:
55 // the file keeps one encoding, and the bytes the edit did not touch.
56 func TestEditKeepsCP936FileWithEuroByte(t *testing.T) {
57 enc := simplifiedchinese.GBK.NewEncoder()
58 before, _ := enc.String("价格 €5\nvalue := 1\n")
59 path := filepath.Join(t.TempDir(), "cp936.txt")
60 if err := os.WriteFile(path, []byte(before), 0o644); err != nil {
61 t.Fatal(err)
62 }
63 editTL, _ := tool.LookupBuiltin("edit_file")
64 if _, err := editTL.Execute(context.Background(), e2eArgs(map[string]any{
65 "path": path,
66 "old_string": "value := 1",
67 "new_string": "value := 2 // 中文",
68 })); err != nil {
69 t.Fatalf("edit_file: %v", err)
70 }
71 want, _ := enc.String("价格 €5\nvalue := 2 // 中文\n")
72 if got, _ := os.ReadFile(path); !bytes.Equal(got, []byte(want)) {
73 t.Fatalf("edit_file wrote % x, want % x", got, want)
74 }
75 }
76
77 // cp936File writes a CP936 file holding its 0x80 euro and returns its path and
78 // bytes, for writes that must leave it untouched.
79 func cp936File(t *testing.T) (string, []byte) {
80 t.Helper()
81 before, _ := simplifiedchinese.GBK.NewEncoder().String("// 中文 €5\nvalue := 1\n")
82 path := filepath.Join(t.TempDir(), "cp936.go")
83 if err := os.WriteFile(path, []byte(before), 0o644); err != nil {
84 t.Fatal(err)
85 }
86 return path, []byte(before)
87 }
88
89 // A write adding a character CP936 cannot hold is refused with that identity,
90 // and the file keeps every byte; nothing rewrites it as UTF-8.
91 func TestWritesRefuseCharacterCP936CannotHold(t *testing.T) {
92 cases := map[string]func(path string) map[string]any{
93 "edit_file": func(path string) map[string]any {
94 return map[string]any{"path": path, "old_string": "value := 1", "new_string": "value := 2 // ✅ 完成"}
95 },
96 "multi_edit": func(path string) map[string]any {
97 return map[string]any{"path": path, "edits": []map[string]any{{"old_string": "value := 1", "new_string": "value := 2 // 🚀"}}}
98 },
99 "write_file": func(path string) map[string]any {
100 return map[string]any{"path": path, "content": "// 中文 €5\nvalue := 2 // ™\n"}
101 },
102 }
103 for name, args := range cases {
104 t.Run(name, func(t *testing.T) {
105 path, before := cp936File(t)
106 tl, _ := tool.LookupBuiltin(name)
107 _, err := tl.Execute(context.Background(), e2eArgs(args(path)))
108 if !errors.Is(err, fileenc.ErrUnencodable) {
109 t.Fatalf("%s err = %v, want ErrUnencodable", name, err)
110 }
111 if got, _ := os.ReadFile(path); !bytes.Equal(got, before) {
112 t.Fatalf("%s changed the file:\n got % x\nwant % x", name, got, before)
113 }
114 })
115 }
116 }
117
117 lines GO