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  }