12 "github.com/stretchr/testify/require"
13 "golang.org/x/sys/unix"
16 func TestClientEndsWhenTheDaemonEnds(t *testing.T) {
18 listener, err := daemonListenSocket(filepath.Join(dir, socketFileName))
19 require.NoError(t, err)
21 accepted := make(chan net.Conn)
23 conn, err := listener.Accept()
29 conn, err := net.Dial("unix", filepath.Join(dir, socketFileName))
30 require.NoError(t, err)
33 done := make(chan error)
35 done <- terminalSession(t.Context(), conn, in, io.Discard)
38 require.NoError(t, (<-accepted).Close())
39 require.ErrorIs(t, <-done, errTerminalDaemonClosed)
40 require.NoError(t, listener.Close())
43 func TestEventText(t *testing.T) {
44 testCases := []struct {
50 name: "reply as it is",
51 event: Event{Kind: "reply", Text: "Done."},
55 name: "own user message not shown",
56 event: Event{Kind: "user", Text: "typed", Source: terminalSource},
60 name: "user message of another source marked",
61 event: Event{Kind: "user", Text: "from the phone", Source: "telegram"},
62 text: "[telegram] from the phone",
65 name: "start of a subagent",
66 event: Event{Kind: "subagent", Text: "GreenOtter"},
67 text: "[GreenOtter] started",
70 name: "report of a subagent whole",
71 event: Event{Kind: "work", Text: "[GreenOtter]\nThere are 3 files.\n\nFull chat: zoom(\"GreenOtter\")", Source: "GreenOtter", Status: subagentFinished},
72 text: "[GreenOtter]\nThere are 3 files.\n\nFull chat: zoom(\"GreenOtter\")",
75 name: "long result cut",
76 event: Event{Kind: "echo", Text: strings.Repeat("line\n", 12)},
77 text: "[result] " + strings.Repeat("line\n", 9) + "line\n[2 more lines]",
81 for _, tc := range testCases {
82 t.Run(tc.name, func(t *testing.T) {
83 require.Equal(t, tc.text, terminalEventText(tc.event))
88 func TestTerminalEventsKeepMarkdownInNonterminalOutput(t *testing.T) {
89 var out strings.Builder
91 text := "# Heading\n\n**bold** and `code`\nA | B\n--- | ---\nx | y"
92 terminalTestShowEvents(t, &out, Event{
97 Text: "[BlueBird]\n" + text,
99 require.Equal(t, text+"\n[BlueBird]\n"+text+"\n", out.String())
102 func TestTerminalFileOutputKeepsMarkdown(t *testing.T) {
103 out, err := os.CreateTemp(t.TempDir(), "output")
104 require.NoError(t, err)
107 require.NoError(t, out.Close())
110 text := "**plain**\n---\n`code`"
111 terminalTestShowEvents(t, out, Event{
115 require.Zero(t, terminalColumns(out))
117 contents, err := os.ReadFile(out.Name())
118 require.NoError(t, err)
119 require.Equal(t, text+"\n", string(contents))
122 func TestTerminalStylesOnlyRepliesAndWork(t *testing.T) {
123 fd, err := unix.Open("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY, 0)
124 require.NoError(t, err)
127 require.NoError(t, unix.Close(fd))
130 require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 12}))
132 out := TerminalTestOutput{fd: fd}
133 require.Equal(t, 12, terminalColumns(&out))
134 terminalTestShowEvents(t, &out,
137 Text: "**first next last**",
141 Text: "[BlueBird]\n# Report",
145 Text: "**raw tool**",
149 Text: "**raw result**",
153 Text: "**raw error**",
157 Text: "**raw notice**",
161 Text: "**raw input**",
166 Text: "**raw agent**",
175 require.Equal(t, "\x1b[1mfirst next\x1b[0m\n\x1b[1mlast\x1b[0m\n[BlueBird]\n\x1b[1mReport\x1b[0m\n"+
176 "[tool] **raw tool**\n[result] **raw result**\n[error] **raw error**\n**raw notice**\n[telegram] **raw input**\n"+
177 "[connected to **raw agent**]\n[BlueBird] started\n[working]\n[done]\n", out.String())
180 func TestTerminalZeroWidthUsesEightyAndReadsChanges(t *testing.T) {
181 fd, err := unix.Open("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY, 0)
182 require.NoError(t, err)
185 require.NoError(t, unix.Close(fd))
188 out := TerminalTestOutput{fd: fd}
189 require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 0}))
190 require.Equal(t, 80, terminalColumns(&out))
191 terminalTestShowEvents(t, &out, Event{
193 Text: strings.Repeat("a", 81),
195 require.Equal(t, strings.Repeat("a", 80)+"\na\n", out.String())
196 require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 40}))
197 require.Equal(t, 40, terminalColumns(&out))
200 // TerminalTestOutput captures text while it supplies a test terminal
202 type TerminalTestOutput struct {
207 func (o *TerminalTestOutput) Fd() uintptr {
211 func terminalTestShowEvents(t *testing.T, out io.Writer, events ...Event) {
214 server, client := net.Pipe()
215 done := make(chan error, 1)
217 done <- terminalShowEvents(client, out)
220 encoder := json.NewEncoder(server)
221 for _, event := range events {
222 require.NoError(t, encoder.Encode(event))
225 require.NoError(t, server.Close())
226 require.NoError(t, <-done)
227 require.NoError(t, client.Close())