github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/cmd/cmdtest/test_cmd.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package cmdtest 18 19 import ( 20 "bufio" 21 "bytes" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "os" 26 "os/exec" 27 "regexp" 28 "strings" 29 "sync" 30 "syscall" 31 "testing" 32 "text/template" 33 34 "github.com/docker/docker/pkg/reexec" 35 ) 36 37 func NewTestCmd(t *testing.T, data interface{}) *TestCmd { 38 return &TestCmd{T: t, Data: data} 39 } 40 41 type TestCmd struct { 42 // For total convenience, all testing methods are available. 43 *testing.T 44 45 Func template.FuncMap 46 Data interface{} 47 Cleanup func() 48 49 cmd *exec.Cmd 50 stdout *bufio.Reader 51 stdin io.WriteCloser 52 stderr *testlogger 53 // Err will contain the process exit error or interrupt signal error 54 Err error 55 } 56 57 // Run exec's the current binary using name as argv[0] which will trigger the 58 // reexec init function for that name (e.g. "u2u-test" in cmd/run_test.go) 59 func (tt *TestCmd) Run(name string, args ...string) { 60 tt.stderr = &testlogger{t: tt.T} 61 tt.cmd = &exec.Cmd{ 62 Path: reexec.Self(), 63 Args: append([]string{name}, args...), 64 Stderr: tt.stderr, 65 } 66 stdout, err := tt.cmd.StdoutPipe() 67 if err != nil { 68 tt.Fatal(err) 69 } 70 tt.stdout = bufio.NewReader(stdout) 71 if tt.stdin, err = tt.cmd.StdinPipe(); err != nil { 72 tt.Fatal(err) 73 } 74 if err := tt.cmd.Start(); err != nil { 75 tt.Fatal(err) 76 } 77 } 78 79 // InputLine writes the given text to the childs stdin. 80 // This method can also be called from an expect template, e.g.: 81 // 82 // cli.expect(`Passphrase: {{.InputLine "password"}}`) 83 func (tt *TestCmd) InputLine(s string) string { 84 io.WriteString(tt.stdin, s+"\n") 85 return "" 86 } 87 88 func (tt *TestCmd) SetTemplateFunc(name string, fn interface{}) { 89 if tt.Func == nil { 90 tt.Func = make(map[string]interface{}) 91 } 92 tt.Func[name] = fn 93 } 94 95 // Expect runs its argument as a template, then expects the 96 // child process to output the result of the template within 5s. 97 // 98 // If the template starts with a newline, the newline is removed 99 // before matching. 100 func (tt *TestCmd) Expect(tplsource string) { 101 // Generate the expected output by running the template. 102 tpl := template.Must(template.New("").Funcs(tt.Func).Parse(tplsource)) 103 wantbuf := new(bytes.Buffer) 104 if err := tpl.Execute(wantbuf, tt.Data); err != nil { 105 panic(err) 106 } 107 // Trim exactly one newline at the beginning. This makes tests look 108 // much nicer because all expect strings are at column 0. 109 want := bytes.TrimPrefix(wantbuf.Bytes(), []byte("\n")) 110 if err := tt.matchExactOutput(want); err != nil { 111 tt.Fatal(err) 112 } 113 tt.Logf("Matched stdout text:\n%s", want) 114 } 115 116 func (tt *TestCmd) matchExactOutput(want []byte) error { 117 buf := make([]byte, len(want)) 118 n := 0 119 n, _ = io.ReadFull(tt.stdout, buf) 120 buf = buf[:n] 121 if n < len(want) || !bytes.Equal(buf, want) { 122 // Grab any additional buffered output in case of mismatch 123 // because it might help with debugging. 124 buf = append(buf, make([]byte, tt.stdout.Buffered())...) 125 tt.stdout.Read(buf[n:]) 126 // Find the mismatch position. 127 for i := 0; i < n; i++ { 128 if want[i] != buf[i] { 129 return fmt.Errorf("Output mismatch at ā:\n---------------- (stdout text)\n%sā%s\n---------------- (expected text)\n%s", 130 buf[:i], buf[i:n], want) 131 } 132 } 133 if n < len(want) { 134 return fmt.Errorf("Not enough output, got until ā:\n---------------- (stdout text)\n%s\n---------------- (expected text)\n%sā%s", 135 buf, want[:n], want[n:]) 136 } 137 } 138 return nil 139 } 140 141 // ExpectRegexp expects the child process to output text matching the 142 // given regular expression within 5s. 143 // 144 // Note that an arbitrary amount of output may be consumed by the 145 // regular expression. This usually means that expect cannot be used 146 // after ExpectRegexp. 147 func (tt *TestCmd) ExpectRegexp(regex string) (*regexp.Regexp, []string) { 148 regex = strings.TrimPrefix(regex, "\n") 149 var ( 150 re = regexp.MustCompile(regex) 151 rtee = &runeTee{in: tt.stdout} 152 matches []int 153 ) 154 matches = re.FindReaderSubmatchIndex(rtee) 155 output := rtee.buf.Bytes() 156 if matches == nil { 157 tt.Fatalf("Output did not match:\n---------------- (stdout text)\n%s\n---------------- (regular expression)\n%s", 158 output, regex) 159 return re, nil 160 } 161 tt.Logf("Matched stdout text:\n%s", output) 162 var submatches []string 163 for i := 0; i < len(matches); i += 2 { 164 submatch := string(output[matches[i]:matches[i+1]]) 165 submatches = append(submatches, submatch) 166 } 167 return re, submatches 168 } 169 170 // ExpectExit expects the child process to exit within 5s without 171 // printing any additional text on stdout. 172 func (tt *TestCmd) ExpectExit() { 173 var output []byte 174 output, _ = ioutil.ReadAll(tt.stdout) 175 tt.WaitExit() 176 if tt.Cleanup != nil { 177 tt.Cleanup() 178 } 179 if len(output) > 0 { 180 tt.Errorf("Unmatched stdout text:\n%s", output) 181 } 182 } 183 184 func (tt *TestCmd) WaitExit() { 185 tt.Err = tt.cmd.Wait() 186 } 187 188 func (tt *TestCmd) Interrupt() { 189 tt.Err = tt.cmd.Process.Signal(os.Interrupt) 190 } 191 192 // ExitStatus exposes the process' OS exit code 193 // It will only return a valid value after the process has finished. 194 func (tt *TestCmd) ExitStatus() int { 195 if tt.Err != nil { 196 exitErr := tt.Err.(*exec.ExitError) 197 if exitErr != nil { 198 if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 199 return status.ExitStatus() 200 } 201 } 202 } 203 return 0 204 } 205 206 // StderrText returns any stderr output written so far. 207 // The returned text holds all log lines after ExpectExit has 208 // returned. 209 func (tt *TestCmd) StderrText() string { 210 tt.stderr.mu.Lock() 211 defer tt.stderr.mu.Unlock() 212 return tt.stderr.buf.String() 213 } 214 215 // GetOutPipeData returns everything, what is on command stdout pipe 216 func (tt *TestCmd) GetOutPipeData() *[]byte { 217 var output []byte 218 output, _ = ioutil.ReadAll(tt.stdout) 219 if len(output) > 0 { 220 return &output 221 } else { 222 tt.Errorf("No data on stdout") 223 return nil 224 } 225 } 226 227 // GetOutPipeData returns string till '>' cursor character on command stdout pipe 228 func (tt *TestCmd) GetOutDataTillCursor() *string { 229 str, _ := tt.stdout.ReadString('>') 230 return &str 231 } 232 233 func (tt *TestCmd) CloseStdin() { 234 tt.stdin.Close() 235 } 236 237 func (tt *TestCmd) Kill() { 238 tt.cmd.Process.Kill() 239 if tt.Cleanup != nil { 240 tt.Cleanup() 241 } 242 } 243 244 // testlogger logs all written lines via t.Log and also 245 // collects them for later inspection. 246 type testlogger struct { 247 t *testing.T 248 mu sync.Mutex 249 buf bytes.Buffer 250 } 251 252 func (tl *testlogger) Write(b []byte) (n int, err error) { 253 lines := bytes.Split(b, []byte("\n")) 254 for _, line := range lines { 255 if len(line) > 0 { 256 tl.t.Logf("(stderr) %s", line) 257 } 258 } 259 tl.mu.Lock() 260 tl.buf.Write(b) 261 tl.mu.Unlock() 262 return len(b), err 263 } 264 265 // runeTee collects text read through it into buf. 266 type runeTee struct { 267 in interface { 268 io.Reader 269 io.ByteReader 270 io.RuneReader 271 } 272 buf bytes.Buffer 273 } 274 275 func (rtee *runeTee) Read(b []byte) (n int, err error) { 276 n, err = rtee.in.Read(b) 277 rtee.buf.Write(b[:n]) 278 return n, err 279 } 280 281 func (rtee *runeTee) ReadRune() (r rune, size int, err error) { 282 r, size, err = rtee.in.ReadRune() 283 if err == nil { 284 rtee.buf.WriteRune(r) 285 } 286 return r, size, err 287 } 288 289 func (rtee *runeTee) ReadByte() (b byte, err error) { 290 b, err = rtee.in.ReadByte() 291 if err == nil { 292 rtee.buf.WriteByte(b) 293 } 294 return b, err 295 }