github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/termios/termios_test.go (about) 1 // Copyright 2015-2017 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build !plan9 6 // +build !plan9 7 8 // Package termios implements basic termios operations including getting 9 // a termio struct, a winsize struct, and setting raw mode. 10 // To set raw mode and then restore, one can do: 11 // t, err := termios.Raw() 12 // do things 13 // t.Set() 14 package termios 15 16 import ( 17 "encoding/json" 18 "errors" 19 "os" 20 "os/exec" 21 "path/filepath" 22 "reflect" 23 "runtime" 24 "strings" 25 "syscall" 26 "testing" 27 28 "github.com/mvdan/u-root-coreutils/pkg/testutil" 29 ) 30 31 var ( 32 // This JSON is from a real device. 33 j = `{ 34 "Ispeed": 0, 35 "Ospeed": 0, 36 "Row": 72, 37 "Col": 238, 38 "CC": { 39 "eof": 4, 40 "eol": 255, 41 "eol2": 255, 42 "erase": 127, 43 "intr": 3, 44 "kill": 21, 45 "lnext": 22, 46 "min": 0, 47 "quit": 28, 48 "start": 17, 49 "stop": 19, 50 "susp": 26, 51 "werase": 23, 52 "time": 3 53 }, 54 "Opts": { 55 "xcase": false, 56 "brkint": true, 57 "clocal": false, 58 "cread": true, 59 "cstopb": false, 60 "echo": true, 61 "echoctl": true, 62 "echoe": true, 63 "echok": true, 64 "echoke": true, 65 "echonl": false, 66 "echoprt": false, 67 "flusho": false, 68 "hupcl": false, 69 "icanon": true, 70 "icrnl": true, 71 "iexten": true, 72 "ignbrk": false, 73 "igncr": false, 74 "ignpar": true, 75 "imaxbel": true, 76 "inlcr": false, 77 "inpck": false, 78 "isig": true, 79 "istrip": false, 80 "iuclc": false, 81 "iutf8": true, 82 "ixany": false, 83 "ixoff": false, 84 "ixon": true, 85 "noflsh": false, 86 "ocrnl": false, 87 "ofdel": false, 88 "ofill": false, 89 "olcuc": false, 90 "onlcr": true, 91 "onlret": false, 92 "onocr": false, 93 "opost": true, 94 "parenb": false, 95 "parmrk": false, 96 "parodd": false, 97 "pendin": true, 98 "tostop": false 99 } 100 }` 101 s = `speed:0 rows:72 cols:238 eof:0x04 eol2:0xff eol:0xff erase:0x7f intr:0x03 kill:0x15 lnext:0x16 min:0x00 quit:0x1c start:0x11 stop:0x13 susp:0x1a time:0x03 werase:0x17 brkint cread echo echoctl echoe echok echoke icanon icrnl iexten ignpar imaxbel isig iutf8 ixon onlcr opost pendin ~clocal ~cstopb ~echonl ~echoprt ~flusho ~hupcl ~ignbrk ~igncr ~inlcr ~inpck ~istrip ~iuclc ~ixany ~ixoff ~noflsh ~ocrnl ~ofdel ~ofill ~olcuc ~onlret ~onocr ~parenb ~parmrk ~parodd ~tostop ~xcase` 102 ) 103 104 func TestNew(t *testing.T) { 105 if _, err := New(); os.IsNotExist(err) || errors.Is(err, syscall.ENXIO) { 106 t.Skipf("No /dev/tty here.") 107 } else if err != nil { 108 t.Errorf("TestNew: want nil, got %v", err) 109 } 110 } 111 112 func TestChangeTermios(t *testing.T) { 113 tty, err := New() 114 if os.IsNotExist(err) || errors.Is(err, syscall.ENXIO) { 115 t.Skipf("No /dev/tty here.") 116 } else if err != nil { 117 t.Fatalf("TestRaw new: want nil, got %v", err) 118 } 119 term, err := tty.Get() 120 if err != nil { 121 t.Fatalf("TestRaw get: want nil, got %v", err) 122 } 123 raw := MakeRaw(term) 124 if reflect.DeepEqual(raw, term) { 125 t.Fatalf("reflect.DeepEqual(%v, %v): true != false", term, raw) 126 } 127 } 128 129 func TestRaw(t *testing.T) { 130 // TestRaw no longer works in CircleCi, Restrict to only VM tests. 131 testutil.SkipIfNotRoot(t) 132 tty, err := New() 133 if os.IsNotExist(err) || errors.Is(err, syscall.ENXIO) { 134 t.Skipf("No /dev/tty here.") 135 } else if err != nil { 136 t.Fatalf("TestRaw new: want nil, got %v", err) 137 } 138 term, err := tty.Get() 139 if err != nil { 140 t.Fatalf("TestRaw get: want nil, got %v", err) 141 } 142 143 n, err := tty.Raw() 144 if err != nil { 145 t.Fatalf("TestRaw raw: want nil, got %v", err) 146 } 147 if !reflect.DeepEqual(term, n) { 148 t.Fatalf("TestRaw: New(%v) and Raw(%v) should be equal, are not", t, n) 149 } 150 if err := tty.Set(n); err != nil { 151 t.Fatalf("TestRaw restore mode: want nil, got %v", err) 152 } 153 n, err = tty.Get() 154 if err != nil { 155 t.Fatalf("TestRaw second call to New(): want nil, got %v", err) 156 } 157 if !reflect.DeepEqual(term, n) { 158 t.Fatalf("TestRaw: After Raw restore: New(%v) and check(%v) should be equal, are not", term, n) 159 } 160 } 161 162 // Test proper unmarshaling and consistent, repeatable output from String() 163 func TestString(t *testing.T) { 164 g := &TTY{} 165 if err := json.Unmarshal([]byte(j), g); err != nil { 166 t.Fatalf("stty load: %v", err) 167 } 168 169 if g.String() != s { 170 t.Errorf("GTTY: want '%v', got '%v'", s, g.String()) 171 as := strings.Split(s, " ") 172 ag := strings.Split(g.String(), " ") 173 if len(as) != len(ag) { 174 t.Fatalf("Wrong # elements in gtty: want %d, got %d", len(as), len(ag)) 175 } 176 for i := range as { 177 t.Errorf("want %s got %s Same %v", as[i], ag[i], as[i] == ag[i]) 178 } 179 180 } 181 } 182 183 func TestSet(t *testing.T) { 184 g := &TTY{} 185 if err := json.Unmarshal([]byte(j), g); err != nil { 186 t.Fatalf("load from JSON: got %v, want nil", err) 187 } 188 sets := [][]string{ 189 {"speed", "0"}, 190 {"rows", "72"}, 191 {"cols", "238"}, 192 {"brkint"}, 193 {"~clocal"}, 194 {"cread"}, 195 {"~cstopb"}, 196 {"echo"}, 197 {"echoctl"}, 198 {"echoe"}, 199 {"echok"}, 200 {"echoke"}, 201 {"~echonl"}, 202 {"~echoprt"}, 203 {"eof", "0x04"}, 204 {"eol2", "0xff"}, 205 {"eol", "0xff"}, 206 {"erase", "0x7f"}, 207 {"~flusho"}, 208 {"~hupcl"}, 209 {"icanon"}, 210 {"icrnl"}, 211 {"iexten"}, 212 {"~ignbrk"}, 213 {"~igncr"}, 214 {"ignpar"}, 215 {"imaxbel"}, 216 {"~inlcr"}, 217 {"~inpck"}, 218 {"intr", "0x03"}, 219 {"isig"}, 220 {"~istrip"}, 221 {"~ixany"}, 222 {"~ixoff"}, 223 {"ixon"}, 224 {"kill", "0x15"}, 225 {"lnext", "0x16"}, 226 {"min", "0x00"}, 227 {"~noflsh"}, 228 {"~ocrnl"}, 229 {"onlcr"}, 230 {"~onlret"}, 231 {"~onocr"}, 232 {"opost"}, 233 {"~parenb"}, 234 {"~parmrk"}, 235 {"~parodd"}, 236 {"pendin"}, 237 {"quit", "0x1c"}, 238 {"start", "0x11"}, 239 {"stop", "0x13"}, 240 {"susp", "0x1a"}, 241 {"time", "0x03"}, 242 {"~tostop"}, 243 {"werase", "0x17"}, 244 } 245 246 if runtime.GOOS == "linux" { 247 sets = append(sets, []string{"~iuclc"}, []string{"~olcuc"}, []string{"~xcase"}) 248 } 249 if runtime.GOOS != "freebsd" { 250 sets = append(sets, []string{"iutf8"}, []string{"~ofdel"}, []string{"~ofill"}) 251 } 252 for _, set := range sets { 253 if err := g.SetOpts(set); err != nil { 254 t.Errorf("Setting %q: got %v, want nil", set, err) 255 } 256 } 257 bad := [][]string{ 258 {"hi", "1"}, 259 {"rows"}, 260 {"rows", "z"}, 261 {"erase"}, 262 {"erase", "z"}, 263 {"hi"}, 264 {"~hi"}, 265 } 266 for _, set := range bad { 267 if err := g.SetOpts(set); err == nil { 268 t.Errorf("Setting %q: got nil, want err", set) 269 } 270 } 271 } 272 273 // This test tries to prevent people from breaking other operating systems. 274 // 275 // Compare: 276 // 277 // GOOS=linux GOARCH=amd64 go doc golang.org/x/sys/unix.Termios 278 // GOOS=darwin GOARCH=amd64 go doc golang.org/x/sys/unix.Termios 279 // GOOS=openbsd GOARCH=amd64 go doc golang.org/x/sys/unix.Termios 280 // 281 // It's all a mess. 282 // 283 // This at least makes sure that the package compiles on all platforms. 284 func TestCrossCompile(t *testing.T) { 285 testutil.SkipIfInVMTest(t) 286 if testing.Short() { 287 t.Skip("skipping in short mode") 288 } 289 platforms := []string{ 290 "linux/386", 291 "linux/amd64", 292 "linux/arm64", 293 "darwin/amd64", 294 "darwin/arm64", 295 "freebsd/amd64", 296 "openbsd/amd64", 297 } 298 // As of Go 1.19+, running "go" selects the right one: 299 // 300 // https://go.dev/doc/go1.19 301 // 302 // "go test and go generate now place GOROOT/bin at the 303 // beginning of the PATH used for the subprocess, so tests and 304 // generators that execute the go command will resolve it to 305 // same GOROOT" 306 // 307 // And this project currently (2023-01-07) uses Go 1.19 as its 308 // CI minimum, so it should be fine to just use "go" here. 309 td := t.TempDir() 310 for _, platform := range platforms { 311 goos, goarch, _ := strings.Cut(platform, "/") 312 t.Run(platform, func(t *testing.T) { 313 t.Parallel() 314 outFile := filepath.Join(td, goos+"-"+goarch+".test") 315 cmd := exec.Command("go", "test", "-c", "-o", outFile, ".") 316 cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch) 317 if out, err := cmd.CombinedOutput(); err != nil { 318 t.Fatalf("Failed: %v, %s", err, out) 319 } 320 }) 321 } 322 }