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 }