gitlab.com/apertussolutions/u-root@v7.0.0+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  }