1//go:build linux || darwin
2
3package pty
4
5import (
6 "context"
7 "os"
8 "path/filepath"
9 "slices"
10 "testing"
11 "time"
12
13 "github.com/bnema/vev/internal/domain"
14 "github.com/stretchr/testify/require"
15)
16
17func TestPaneEnvironment(t *testing.T) {
18 for _, tt := range []struct {
19 name string
20 helper string
21 want string
22 symlink bool
23 }{
24 {name: "available", helper: "exit 0", want: "xterm-direct"},
25 {name: "symlinked working directory", helper: "exit 0", want: "xterm-direct", symlink: true},
26 {name: "missing entry", helper: "exit 1", want: "xterm-256color"},
27 {name: "missing helper", want: "xterm-256color"},
28 {name: "hung helper", helper: "exec /bin/sleep 60", want: "xterm-256color"},
29 } {
30 t.Run(tt.name, func(t *testing.T) {
31 bin := t.TempDir()
32 dir := t.TempDir()
33 if tt.symlink {
34 link := filepath.Join(t.TempDir(), "pane-cwd")
35 require.NoError(t, os.Symlink(dir, link))
36 dir = link
37 }
38 t.Setenv("PATH", bin)
39 if tt.helper != "" {
40 // The helper must use the pane's lookup environment and working
41 // directory, but must not be resolved through the pane's PATH.
42 // Compare directory identity: shells can resolve symlinked temp
43 // paths (including macOS's /var) when initializing PWD.
44 script := "#!/bin/sh\n" +
45 "[ \"$1\" = -x ] && [ \"$2\" = xterm-direct ] && [ \"$#\" = 2 ] || exit 2\n" +
46 "[ \"$HOME\" = /pane-home ] && [ \"$TERMINFO\" = relative-db ] && [ \"$TERMINFO_DIRS\" = /pane-db: ] || exit 3\n" +
47 "[ . -ef \"$EXPECTED_DIR\" ] || exit 4\n" + tt.helper + "\n"
48 require.NoError(t, os.WriteFile(filepath.Join(bin, "infocmp"), []byte(script), 0o700))
49 }
50 env := []string{"TERM=outer", "KEEP=a=b", "TERM=duplicate", "COLORTERM=truecolor", "TERM_PROGRAM=vev", "HOME=/pane-home", "TERMINFO=relative-db", "TERMINFO_DIRS=/pane-db:", "PATH=/not-the-daemon-path", "EXPECTED_DIR=" + dir}
51 before := slices.Clone(env)
52 got := paneEnvironment(t.Context(), env, dir)
53 require.Equal(t, before, env)
54 want := append(slices.Clone(env[1:2]), env[3:]...)
55 want = append(want, "TERM="+tt.want)
56 require.Equal(t, want, got)
57 })
58 }
59}
60
61func TestPaneEnvironmentNilInherits(t *testing.T) {
62 t.Setenv("PATH", t.TempDir())
63 t.Setenv("TERM", "outer")
64 t.Setenv("PANE_ENV_TEST", "inherited")
65 env := paneEnvironment(t.Context(), nil, t.TempDir())
66 require.Contains(t, env, "PANE_ENV_TEST=inherited")
67 require.Contains(t, env, "TERM=xterm-256color")
68 require.NotContains(t, env, "TERM=outer")
69}
70
71func TestOpenCancelledDuringTerminfoProbe(t *testing.T) {
72 bin := t.TempDir()
73 t.Setenv("PATH", bin)
74 require.NoError(t, os.WriteFile(filepath.Join(bin, "infocmp"), []byte("#!/bin/sh\nexec /bin/sleep 60\n"), 0o700))
75 ctx, cancel := context.WithTimeout(t.Context(), 50*time.Millisecond)
76 defer cancel()
77 p, err := NewFactory().Open(ctx, "/bin/sh", []string{"-c", "exit 0"}, nil, t.TempDir(), domain.Geometry{})
78 require.ErrorIs(t, err, context.DeadlineExceeded)
79 require.Nil(t, p)
80}