pty_darwin_test.go RawHistoryFolder

1//go:build darwin
2
3package pty_test
4
5import (
6	"bytes"
7	"context"
8	"io"
9	"os"
10	"strings"
11	"testing"
12	"time"
13
14	"github.com/bnema/vev/internal/adapters/pty"
15	"github.com/bnema/vev/internal/domain"
16	"github.com/bnema/vev/internal/ports"
17	"github.com/stretchr/testify/require"
18)
19
20func readAllDarwin(t *testing.T, p ports.PTY, deadline time.Duration) []byte {
21	t.Helper()
22	type result struct {
23		output []byte
24		err    error
25	}
26	resultCh := make(chan result, 1)
27	go func() {
28		var output bytes.Buffer
29		_, err := io.Copy(&output, p)
30		resultCh <- result{output: output.Bytes(), err: err}
31	}()
32
33	select {
34	case result := <-resultCh:
35		require.NoError(t, result.err)
36		return result.output
37	case <-time.After(deadline):
38		t.Fatal("timed out reading from pty")
39		return nil
40	}
41}
42
43func TestOpen_DarwinEchoAndOutputEOF(t *testing.T) {
44	if testing.Short() {
45		t.Skip("skipping pty integration test in -short mode")
46	}
47
48	factory := pty.NewFactory()
49	outputPTY, err := factory.Open(context.Background(), "sh", []string{"-c", "printf hello"}, os.Environ(), "", domain.Geometry{Size: domain.Size{Cols: 80, Rows: 24}})
50	require.NoError(t, err)
51	t.Cleanup(func() { _ = outputPTY.Close() })
52	require.Equal(t, "hello", string(readAllDarwin(t, outputPTY, 5*time.Second)))
53
54	echoPTY, err := factory.Open(context.Background(), "cat", nil, os.Environ(), "", domain.Geometry{Size: domain.Size{Cols: 80, Rows: 24}})
55	require.NoError(t, err)
56	t.Cleanup(func() { _ = echoPTY.Close() })
57	_, err = io.WriteString(echoPTY, "roundtrip\n")
58	require.NoError(t, err)
59
60	output := readUntilDarwin(t, echoPTY, "roundtrip", 5*time.Second)
61	require.Contains(t, output, "roundtrip")
62}
63
64func readUntilDarwin(t *testing.T, p ports.PTY, marker string, deadline time.Duration) string {
65	t.Helper()
66	type result struct {
67		output string
68		err    error
69	}
70	resultCh := make(chan result, 1)
71	go func() {
72		var output strings.Builder
73		buf := make([]byte, 256)
74		for !strings.Contains(output.String(), marker) {
75			n, err := p.Read(buf)
76			output.Write(buf[:n])
77			if err != nil {
78				resultCh <- result{output: output.String(), err: err}
79				return
80			}
81		}
82		resultCh <- result{output: output.String()}
83	}()
84
85	select {
86	case result := <-resultCh:
87		require.NoError(t, result.err)
88		return result.output
89	case <-time.After(deadline):
90		t.Fatal("timed out waiting for pty readiness")
91		return ""
92	}
93}
94
95func TestOpen_DarwinResizeAndForegroundPgid(t *testing.T) {
96	if testing.Short() {
97		t.Skip("skipping pty integration test in -short mode")
98	}
99
100	const ready = "__vev_ready__"
101	p, err := pty.NewFactory().Open(context.Background(), "sh", []string{"-c", "stty -echo; printf '__vev_ready__\\n'; IFS= read -r release; stty size"}, os.Environ(), "", domain.Geometry{Size: domain.Size{Cols: 80, Rows: 24}})
102	require.NoError(t, err)
103	t.Cleanup(func() { _ = p.Close() })
104
105	require.Contains(t, readUntilDarwin(t, p, ready, 5*time.Second), ready)
106	pgid, err := p.ForegroundPgid()
107	require.NoError(t, err)
108	require.Greater(t, pgid, 0)
109	require.NoError(t, p.Resize(domain.Geometry{Size: domain.Size{Cols: 120, Rows: 40}}))
110	_, err = io.WriteString(p, "release\n")
111	require.NoError(t, err)
112	require.Equal(t, "40 120", strings.TrimSpace(string(readAllDarwin(t, p, 5*time.Second))))
113}
114
115func TestOpen_DarwinContextCancellationEndsStream(t *testing.T) {
116	if testing.Short() {
117		t.Skip("skipping pty integration test in -short mode")
118	}
119
120	ctx, cancel := context.WithCancel(context.Background())
121	defer cancel()
122	p, err := pty.NewFactory().Open(ctx, "sh", []string{"-c", "exec sleep 60"}, os.Environ(), "", domain.Geometry{Size: domain.Size{Cols: 80, Rows: 24}})
123	require.NoError(t, err)
124	t.Cleanup(func() { _ = p.Close() })
125
126	streamEnded := make(chan error, 1)
127	go func() {
128		_, err := io.Copy(io.Discard, p)
129		streamEnded <- err
130	}()
131
132	cancel()
133	select {
134	case err := <-streamEnded:
135		require.NoError(t, err)
136	case <-time.After(5 * time.Second):
137		t.Fatal("pty stream did not end after context cancellation")
138	}
139}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close