返回 DeepSeek-Reasonix
terminal_utf8_unix_test.go
根目录 / desktop / terminal_utf8_unix_test.go
1 //go:build !windows
2
3 package main
4
5 import (
6 "bytes"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "strings"
11 "testing"
12 "time"
13 )
14
15 func TestTerminalBashUnicodeInput(t *testing.T) {
16 bash, err := exec.LookPath("bash")
17 if err != nil {
18 t.Fatal(err)
19 }
20 for _, locale := range []string{"", "C", "en_US.UTF-8"} {
21 t.Run("locale="+locale, func(t *testing.T) {
22 dir := t.TempDir()
23 source := filepath.Join(dir, "中文 custom inputrc")
24 if err := os.WriteFile(source, []byte("set editing-mode vi\nset convert-meta on\n"), 0o600); err != nil {
25 t.Fatal(err)
26 }
27 env := []string{"PATH=" + os.Getenv("PATH"), "HOME=" + dir, "INPUTRC=" + source}
28 if locale != "" {
29 env = append(env, "LC_ALL="+locale)
30 }
31 p, err := startTerminalProcess(terminalStartSpec{command: commandForShellPath(bash, "bash"), dir: dir, env: terminalEnvironment(env), cols: 80, rows: 24})
32 if err != nil {
33 t.Fatal(err)
34 }
35 t.Cleanup(func() { _ = p.Close(); _, _ = p.Wait() })
36 chunks := make(chan []byte, 128)
37 go func() {
38 defer close(chunks)
39 buf := make([]byte, 4096)
40 for {
41 n, err := p.Read(buf)
42 if n > 0 {
43 chunks <- bytes.Clone(buf[:n])
44 }
45 if err != nil {
46 return
47 }
48 }
49 }()
50 readUntil := func(marker string) string {
51 t.Helper()
52 var output strings.Builder
53 timer := time.NewTimer(5 * time.Second)
54 defer timer.Stop()
55 for {
56 select {
57 case data, ok := <-chunks:
58 if !ok {
59 t.Fatalf("terminal closed: %q", output.String())
60 }
61 output.Write(data)
62 if strings.Contains(output.String(), marker) {
63 return output.String()
64 }
65 case <-timer.C:
66 t.Fatalf("terminal timeout: %q", output.String())
67 }
68 }
69 }
70 write := func(command string) {
71 t.Helper()
72 if _, err := p.Write([]byte(command + "\n")); err != nil {
73 t.Fatal(err)
74 }
75 }
76 write("stty -echo; PS1=; PS2=; printf '\\nREADY\\n'")
77 readUntil("\r\nREADY\r\n")
78 write("printf '%s\\n' '中文😀'; bind -v | grep 'editing-mode'; printf 'LOCALE=%s\\nDONE\\n' \"$LC_ALL\"")
79 out := readUntil("\r\nDONE\r\n")
80 if !strings.Contains(out, "中文😀\r\n") || !strings.Contains(out, "editing-mode vi") || !strings.Contains(out, "LOCALE="+locale+"\r\n") {
81 t.Fatalf("input/configuration changed: %q", out)
82 }
83 })
84 }
85 }
86
87 func TestTerminalInputrcLifecycle(t *testing.T) {
88 spec := terminalStartSpec{command: terminalCommand{path: "/bin/bash"}, dir: t.TempDir(), env: []string{"HOME=" + t.TempDir()}}
89 env, cleanup, err := terminalInputEnvironment(spec)
90 if err != nil {
91 t.Fatal(err)
92 }
93 t.Cleanup(cleanup)
94 var path string
95 for _, item := range env {
96 if value, ok := strings.CutPrefix(item, "INPUTRC="); ok {
97 path = value
98 }
99 }
100 info, err := os.Stat(path)
101 if err != nil {
102 t.Fatal(err)
103 }
104 if info.Mode().Perm() != 0o600 {
105 t.Fatalf("unsafe mode %v", info.Mode())
106 }
107 cleanup()
108 cleanup()
109 if _, err := os.Stat(path); !os.IsNotExist(err) {
110 t.Fatalf("inputrc not removed: %v", err)
111 }
112 }
113
113 lines GO