返回 DeepSeek-Reasonix
stdio_codepage_test.go
根目录 / internal / plugin / stdio_codepage_test.go
1 package plugin
2
3 import (
4 "context"
5 "os"
6 "strings"
7 "testing"
8 "time"
9
10 "golang.org/x/text/encoding/simplifiedchinese"
11 )
12
13 const codePageNotFound = "'read-only-mysql-mcp-server' 不是内部或外部命令,也不是可运行的程序"
14
15 // cmd.exe on a Chinese Windows reports a missing command in the console code
16 // page, not UTF-8; the failure the settings page shows must still be readable.
17 func TestStdioFailureDecodesCodePageStderr(t *testing.T) {
18 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
19 defer cancel()
20
21 host, _ := StartAvailable(ctx, []Spec{{
22 Name: "codepage",
23 Command: os.Args[0],
24 Args: []string{"-test.run=TestCodePageStderrHelper", "--"},
25 Env: map[string]string{"GO_WANT_HELPER_STDERR_CODEPAGE": "1"},
26 }})
27 defer host.Close()
28
29 failures := host.Failures()
30 if len(failures) != 1 {
31 t.Fatalf("failures = %+v, want one", failures)
32 }
33 if !strings.Contains(failures[0].Error, codePageNotFound) {
34 t.Fatalf("failure should carry the decoded stderr, got %q", failures[0].Error)
35 }
36 }
37
38 // TestCodePageStderrHelper is not a real test: as a child it writes a GBK
39 // line to stderr and exits, the way cmd.exe reports a missing command.
40 func TestCodePageStderrHelper(t *testing.T) {
41 if os.Getenv("GO_WANT_HELPER_STDERR_CODEPAGE") != "1" {
42 return
43 }
44 b, _ := simplifiedchinese.GB18030.NewEncoder().Bytes([]byte(codePageNotFound))
45 os.Stderr.Write(b)
46 os.Exit(1)
47 }
48
49 func gbk(t *testing.T, s string) []byte {
50 t.Helper()
51 b, err := simplifiedchinese.GB18030.NewEncoder().Bytes([]byte(s))
52 if err != nil {
53 t.Fatal(err)
54 }
55 return b
56 }
57
58 // A GBK lead byte is not a UTF-8 rune start, so a tail that begins with a CJK
59 // character must be decoded whole; only a tail the limit really cut may lose
60 // part of one.
61 func TestStderrTailDecodesTheServersCodePage(t *testing.T) {
62 const line = "参数格式不正确,服务器无法启动"
63 whole := gbk(t, line)
64 for _, tc := range []struct {
65 name string
66 limit int
67 write [][]byte
68 want string
69 }{
70 {"uncut GBK starting with CJK", 16 * 1024, [][]byte{whole}, line},
71 {"cut GBK starting with CJK", len(whole), [][]byte{gbk(t, "旧输出"), whole}, line},
72 {"UTF-8 cut mid-character", len(line) - 1, [][]byte{[]byte(line)}, line[len("参"):]},
73 } {
74 t.Run(tc.name, func(t *testing.T) {
75 b := &tailBuffer{limit: tc.limit}
76 for _, p := range tc.write {
77 _, _ = b.Write(p)
78 }
79 if got := b.String(); got != tc.want {
80 t.Fatalf("String() = %q, want %q", got, tc.want)
81 }
82 })
83 }
84 }
85
85 lines GO