1//go:build linux
2
3package rawterm
4
5import (
6 "errors"
7 "os"
8 "syscall"
9 "testing"
10 "unsafe"
11)
12
13// openPTYPair opens a real Linux PTY master/slave pair directly via
14// /dev/ptmx using the package under test, so these tests double as an
15// integration check of PreparePty against the kernel.
16func openPTYPair(t *testing.T) (master, slave *os.File) {
17 t.Helper()
18
19 masterFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
20 if err != nil {
21 t.Skipf("open /dev/ptmx: %v", err)
22 }
23 master = os.NewFile(uintptr(masterFD), "pty-master")
24
25 slave, err = PreparePty(masterFD)
26 if err != nil {
27 _ = master.Close()
28 t.Fatalf("PreparePty: %v", err)
29 }
30
31 return master, slave
32}
33
34func TestIsTerminal(t *testing.T) {
35 master, slave := openPTYPair(t)
36 defer func() { _ = master.Close() }()
37 defer func() { _ = slave.Close() }()
38
39 if !IsTerminal(int(slave.Fd())) {
40 t.Errorf("IsTerminal(pty slave) = false, want true")
41 }
42
43 r, w, err := os.Pipe()
44 if err != nil {
45 t.Fatalf("os.Pipe: %v", err)
46 }
47 defer func() { _ = r.Close() }()
48 defer func() { _ = w.Close() }()
49
50 if IsTerminal(int(r.Fd())) {
51 t.Errorf("IsTerminal(pipe) = true, want false")
52 }
53}
54
55func TestSetWinsizeGetSizeRoundtrip(t *testing.T) {
56 master, slave := openPTYPair(t)
57 defer func() { _ = master.Close() }()
58 defer func() { _ = slave.Close() }()
59
60 tests := []struct {
61 name string
62 cols uint16
63 rows uint16
64 }{
65 {name: "typical", cols: 80, rows: 24},
66 {name: "wide", cols: 220, rows: 60},
67 {name: "minimal", cols: 1, rows: 1},
68 }
69
70 for _, tt := range tests {
71 t.Run(tt.name, func(t *testing.T) {
72 if err := SetWinsize(int(slave.Fd()), tt.cols, tt.rows); err != nil {
73 t.Fatalf("SetWinsize: %v", err)
74 }
75 gotCols, gotRows, err := GetSize(int(slave.Fd()))
76 if err != nil {
77 t.Fatalf("GetSize: %v", err)
78 }
79 if gotCols != int(tt.cols) || gotRows != int(tt.rows) {
80 t.Fatalf("GetSize = (%d, %d), want (%d, %d)", gotCols, gotRows, tt.cols, tt.rows)
81 }
82 })
83 }
84}
85
86func TestMakeRawRestore(t *testing.T) {
87 master, slave := openPTYPair(t)
88 defer func() { _ = master.Close() }()
89 defer func() { _ = slave.Close() }()
90
91 fd := int(slave.Fd())
92
93 var original syscall.Termios
94 if err := ioctl(fd, reqGetTermios, unsafe.Pointer(&original)); err != nil {
95 t.Fatalf("get termios before MakeRaw: %v", err)
96 }
97
98 old, err := MakeRaw(fd)
99 if err != nil {
100 t.Fatalf("MakeRaw: %v", err)
101 }
102 if old == nil {
103 t.Fatalf("MakeRaw returned nil state")
104 }
105
106 var current syscall.Termios
107 if err := ioctl(fd, reqGetTermios, unsafe.Pointer(¤t)); err != nil {
108 t.Fatalf("get termios after MakeRaw: %v", err)
109 }
110 if current.Lflag&syscall.ECHO != 0 {
111 t.Errorf("ECHO still set after MakeRaw")
112 }
113 if current.Lflag&syscall.ICANON != 0 {
114 t.Errorf("ICANON still set after MakeRaw")
115 }
116 if current.Cc[syscall.VMIN] != 1 {
117 t.Errorf("Cc[VMIN] = %d, want 1", current.Cc[syscall.VMIN])
118 }
119
120 if err := Restore(fd, old); err != nil {
121 t.Fatalf("Restore: %v", err)
122 }
123
124 var restored syscall.Termios
125 if err := ioctl(fd, reqGetTermios, unsafe.Pointer(&restored)); err != nil {
126 t.Fatalf("get termios after Restore: %v", err)
127 }
128 if restored != original {
129 t.Errorf("termios after Restore = %#v, want original %#v", restored, original)
130 }
131}
132
133func TestForegroundProcessGroup(t *testing.T) {
134 master, slave := openPTYPair(t)
135 defer func() { _ = master.Close() }()
136 defer func() { _ = slave.Close() }()
137
138 // No session has been established on this pty (no process ever made it
139 // a controlling terminal via Setsid+Setctty), so the kernel legitimately
140 // has no foreground process group to report and returns ENOTTY. That
141 // still exercises the ioctl path; only an unexpected error is a failure.
142 if _, err := ForegroundProcessGroup(int(slave.Fd())); err != nil && !errors.Is(err, syscall.ENOTTY) {
143 t.Fatalf("ForegroundProcessGroup: %v", err)
144 }
145}