github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/core/elvish/edit/e2e_test.go (about) 1 // +build !windows,!plan9 2 3 // End-to-end tests for the editor. Only enabled on UNIX where pseudo-terminals 4 // are supported. 5 6 package edit 7 8 import ( 9 "io" 10 "os" 11 "syscall" 12 "testing" 13 "time" 14 15 "github.com/kr/pty" 16 "github.com/u-root/u-root/cmds/core/elvish/eval" 17 "github.com/u-root/u-root/cmds/core/elvish/sys" 18 ) 19 20 // readLineTest contains the data for a test case of ReadLine. 21 type readLineTest struct { 22 // The input to write to the control tty side. 23 input []byte 24 // Places where SIGINT should be sent to the editor, as indicies into the 25 // input string. For example, if sigints is {1}, it means that a SIGINT 26 // should be sent right after the first byte is sent. 27 sigints []int 28 // Expected line to be returned from ReadLine. 29 wantLine string 30 } 31 32 func newTest(input string, wantLine string) *readLineTest { 33 return &readLineTest{input: []byte(input), wantLine: wantLine} 34 } 35 36 func (t *readLineTest) sigint(x ...int) *readLineTest { 37 t.sigints = x 38 return t 39 } 40 41 var readLineTests = []*readLineTest{ 42 newTest("\n", ""), 43 newTest("test\n", "test"), 44 // \x7f is DEL and erases the previous character 45 newTest("abc\x7fd\n", "abd"), 46 // \x17 is ^U and erases the line before the cursor 47 newTest("abc\x17d\n", "d"), 48 // SIGINT resets the editor and erases the line. Disabled for now. 49 // newTest("000123\n", "123").sigint(3), 50 } 51 52 var readLineTimeout = 5 * time.Second 53 54 func TestReadLine(t *testing.T) { 55 ev := eval.NewEvaler() 56 defer ev.Close() 57 58 for _, test := range readLineTests { 59 // Editor output is only used in failure messages. 60 var outputs []byte 61 sigs := make(chan os.Signal, 10) 62 defer close(sigs) 63 control, lineChan, errChan := run(ev, sigs, &outputs) 64 defer control.Close() 65 66 write(control, sigs, test.input, test.sigints) 67 68 select { 69 case line := <-lineChan: 70 if line != test.wantLine { 71 t.Errorf("ReadLine() => %q, want %q (input %q)", line, test.wantLine, test.input) 72 } 73 case err := <-errChan: 74 t.Errorf("ReadLine() => error %v (input %q)", err, test.input) 75 case <-time.After(readLineTimeout): 76 t.Errorf("ReadLine() timed out (input %q)", test.input) 77 t.Log("Stack trace: \n" + sys.DumpStack()) 78 t.Logf("Terminal output: %q", outputs) 79 t.FailNow() 80 } 81 } 82 } 83 84 // run sets up a testing environment for an Editor, and calls its ReadLine 85 // method in a goroutine. It returns the control end of the pty the Editor is 86 // connected to, and two channels onto which the result of ReadLine will be 87 // delivered. The caller is responsible for closing the control tty file. 88 func run(ev *eval.Evaler, sigs <-chan os.Signal, ptrOutputs *[]byte) (*os.File, 89 <-chan string, <-chan error) { 90 91 control, tty, err := pty.Open() 92 if err != nil { 93 panic(err) 94 } 95 // Continually consume tty outputs so that the editor is not blocked on 96 // writing. 97 go drain(control, ptrOutputs) 98 99 lineChan := make(chan string) 100 errChan := make(chan error) 101 102 go func() { 103 ed := NewEditor(tty, tty, nil, ev) 104 line, err := ed.ReadLine() 105 if err != nil { 106 errChan <- err 107 } else { 108 lineChan <- line 109 } 110 ed.Close() 111 tty.Close() 112 close(lineChan) 113 close(errChan) 114 }() 115 116 return control, lineChan, errChan 117 } 118 119 // drain drains the given reader. If a non-nil []byte pointer is passed, it also 120 // makes the outputs available. It returns when r.Read returns an error. 121 func drain(r io.Reader, ptrOutputs *[]byte) { 122 var buf [256]byte 123 for { 124 nr, err := r.Read(buf[:]) 125 if err != nil { 126 return 127 } 128 if ptrOutputs != nil { 129 *ptrOutputs = append(*ptrOutputs, buf[:nr]...) 130 } 131 } 132 } 133 134 // write interprets the input and sigints arguments, and write inputs and 135 // signals to the writer and signal channel. 136 func write(w *os.File, sigs chan<- os.Signal, input []byte, sigints []int) { 137 if len(sigints) == 0 { 138 mustWrite(w, input) 139 return 140 } 141 for i, idx := range sigints { 142 lastidx := 0 143 if i > 0 { 144 lastidx = sigints[i-1] 145 } 146 mustWrite(w, input[lastidx:idx]) 147 sigs <- syscall.SIGINT 148 } 149 mustWrite(w, input[sigints[len(sigints)-1]:]) 150 } 151 152 func mustWrite(w io.Writer, p []byte) { 153 _, err := w.Write(p) 154 if err != nil { 155 panic(err) 156 } 157 }