github.com/theclapp/sh@v2.6.4+incompatible/cmd/gosh/main_test.go (about)

     1  // Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
     2  // See LICENSE for licensing information
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"testing"
    10  
    11  	"mvdan.cc/sh/interp"
    12  )
    13  
    14  // Each test has an even number of strings, which form input-output pairs for
    15  // the interactive shell. The input string is fed to the interactive shell, and
    16  // bytes are read from its output until the expected output string is matched or
    17  // an error is encountered.
    18  //
    19  // In other words, each first string is what the user types, and each following
    20  // string is what the shell will print back. Note that the first "$ " output is
    21  // implicit.
    22  
    23  var interactiveTests = [][]string{
    24  	{},
    25  	{
    26  		"\n",
    27  		"$ ",
    28  		"\n",
    29  		"$ ",
    30  	},
    31  	{
    32  		"echo foo\n",
    33  		"foo\n",
    34  	},
    35  	{
    36  		"echo foo\n",
    37  		"foo\n$ ",
    38  		"echo bar\n",
    39  		"bar\n",
    40  	},
    41  	{
    42  		"if true\n",
    43  		"> ",
    44  		"then echo bar; fi\n",
    45  		"bar\n",
    46  	},
    47  	{
    48  		"echo 'foo\n",
    49  		"> ",
    50  		"bar'\n",
    51  		"foo\nbar\n",
    52  	},
    53  	{
    54  		"echo foo; echo bar\n",
    55  		"foo\nbar\n",
    56  	},
    57  	{
    58  		"echo foo; echo 'bar\n",
    59  		"> ",
    60  		"baz'\n",
    61  		"foo\nbar\nbaz\n",
    62  	},
    63  	{
    64  		"(\n",
    65  		"> ",
    66  		"echo foo)\n",
    67  		"foo\n",
    68  	},
    69  	{
    70  		"[[\n",
    71  		"> ",
    72  		"true ]]\n",
    73  		"$ ",
    74  	},
    75  	{
    76  		"echo foo ||\n",
    77  		"> ",
    78  		"echo bar\n",
    79  		"foo\n",
    80  	},
    81  	{
    82  		"echo foo |\n",
    83  		"> ",
    84  		"cat\n",
    85  		"foo\n",
    86  	},
    87  	{
    88  		"echo foo",
    89  		"",
    90  		" bar\n",
    91  		"foo bar\n",
    92  	},
    93  	{
    94  		"echo\\\n",
    95  		"> ",
    96  		" foo\n",
    97  		"foo\n",
    98  	},
    99  	{
   100  		"echo foo\\\n",
   101  		"> ",
   102  		"bar\n",
   103  		"foobar\n",
   104  	},
   105  	{
   106  		"echo 你好\n",
   107  		"你好\n$ ",
   108  	},
   109  }
   110  
   111  func TestInteractive(t *testing.T) {
   112  	t.Parallel()
   113  	runner, _ := interp.New()
   114  	for i, tc := range interactiveTests {
   115  		t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
   116  			inReader, inWriter := io.Pipe()
   117  			outReader, outWriter := io.Pipe()
   118  			interp.StdIO(inReader, outWriter, outWriter)(runner)
   119  			runner.Reset()
   120  
   121  			errc := make(chan error)
   122  			go func() {
   123  				errc <- interactive(runner)
   124  			}()
   125  
   126  			if err := readString(outReader, "$ "); err != nil {
   127  				t.Fatal(err)
   128  			}
   129  
   130  			line := 1
   131  			for len(tc) > 0 {
   132  				if _, err := io.WriteString(inWriter, tc[0]); err != nil {
   133  					t.Fatal(err)
   134  				}
   135  				if err := readString(outReader, tc[1]); err != nil {
   136  					t.Fatal(err)
   137  				}
   138  
   139  				line++
   140  				tc = tc[2:]
   141  			}
   142  
   143  			// Close the input pipe, so that the parser can stop.
   144  			inWriter.Close()
   145  
   146  			// Once the input pipe is closed, close the output pipe
   147  			// so that any remaining prompt writes get discarded.
   148  			outReader.Close()
   149  
   150  			if err := <-errc; err != nil {
   151  				t.Fatalf("unexpected error: %v", err)
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  // readString will keep reading from a reader until all bytes from the supplied
   158  // string are read.
   159  func readString(r io.Reader, want string) error {
   160  	p := make([]byte, len(want))
   161  	_, err := io.ReadFull(r, p)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	got := string(p)
   166  	if got != want {
   167  		return fmt.Errorf("ReadString: read %q, wanted %q", got, want)
   168  	}
   169  	return nil
   170  }