github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/internal/cmdtest/test_cmd.go (about)

     1  
     2  //<developer>
     3  //    <name>linapex 曹一峰</name>
     4  //    <email>linapex@163.com</email>
     5  //    <wx>superexc</wx>
     6  //    <qqgroup>128148617</qqgroup>
     7  //    <url>https://jsq.ink</url>
     8  //    <role>pku engineer</role>
     9  //    <date>2019-03-16 19:16:38</date>
    10  //</624450091830218752>
    11  
    12  
    13  package cmdtest
    14  
    15  import (
    16  	"bufio"
    17  	"bytes"
    18  	"fmt"
    19  	"io"
    20  	"io/ioutil"
    21  	"os"
    22  	"os/exec"
    23  	"regexp"
    24  	"strings"
    25  	"sync"
    26  	"syscall"
    27  	"testing"
    28  	"text/template"
    29  	"time"
    30  
    31  	"github.com/docker/docker/pkg/reexec"
    32  )
    33  
    34  func NewTestCmd(t *testing.T, data interface{}) *TestCmd {
    35  	return &TestCmd{T: t, Data: data}
    36  }
    37  
    38  type TestCmd struct {
    39  //为方便起见,所有测试方法都可用。
    40  	*testing.T
    41  
    42  	Func    template.FuncMap
    43  	Data    interface{}
    44  	Cleanup func()
    45  
    46  	cmd    *exec.Cmd
    47  	stdout *bufio.Reader
    48  	stdin  io.WriteCloser
    49  	stderr *testlogger
    50  //错误将包含进程退出错误或中断信号错误
    51  	Err error
    52  }
    53  
    54  //运行exec的当前二进制文件,使用argv[0]作为名称,它将触发
    55  //该名称的reexec init函数(例如,在cmd/geth/run\test.go中的“geth test”)。
    56  func (tt *TestCmd) Run(name string, args ...string) {
    57  	tt.stderr = &testlogger{t: tt.T}
    58  	tt.cmd = &exec.Cmd{
    59  		Path:   reexec.Self(),
    60  		Args:   append([]string{name}, args...),
    61  		Stderr: tt.stderr,
    62  	}
    63  	stdout, err := tt.cmd.StdoutPipe()
    64  	if err != nil {
    65  		tt.Fatal(err)
    66  	}
    67  	tt.stdout = bufio.NewReader(stdout)
    68  	if tt.stdin, err = tt.cmd.StdinPipe(); err != nil {
    69  		tt.Fatal(err)
    70  	}
    71  	if err := tt.cmd.Start(); err != nil {
    72  		tt.Fatal(err)
    73  	}
    74  }
    75  
    76  //inputline将给定文本写入childs stdin。
    77  //这种方法也可以从期望模板调用,例如:
    78  //
    79  //geth.expect(`passphrase:.inputline“password”`)
    80  func (tt *TestCmd) InputLine(s string) string {
    81  	io.WriteString(tt.stdin, s+"\n")
    82  	return ""
    83  }
    84  
    85  func (tt *TestCmd) SetTemplateFunc(name string, fn interface{}) {
    86  	if tt.Func == nil {
    87  		tt.Func = make(map[string]interface{})
    88  	}
    89  	tt.Func[name] = fn
    90  }
    91  
    92  //Expect将其参数作为模板运行,然后期望
    93  //子进程在5s内输出模板的结果。
    94  //
    95  //如果模板以换行符开头,则将删除换行符
    96  //匹配前。
    97  func (tt *TestCmd) Expect(tplsource string) {
    98  //通过运行模板生成预期的输出。
    99  	tpl := template.Must(template.New("").Funcs(tt.Func).Parse(tplsource))
   100  	wantbuf := new(bytes.Buffer)
   101  	if err := tpl.Execute(wantbuf, tt.Data); err != nil {
   102  		panic(err)
   103  	}
   104  //在开始处只修剪一条新行。这使得测试看起来
   105  //更好,因为所有期望的字符串都在第0列。
   106  	want := bytes.TrimPrefix(wantbuf.Bytes(), []byte("\n"))
   107  	if err := tt.matchExactOutput(want); err != nil {
   108  		tt.Fatal(err)
   109  	}
   110  	tt.Logf("Matched stdout text:\n%s", want)
   111  }
   112  
   113  func (tt *TestCmd) matchExactOutput(want []byte) error {
   114  	buf := make([]byte, len(want))
   115  	n := 0
   116  	tt.withKillTimeout(func() { n, _ = io.ReadFull(tt.stdout, buf) })
   117  	buf = buf[:n]
   118  	if n < len(want) || !bytes.Equal(buf, want) {
   119  //在不匹配的情况下获取任何额外的缓冲输出
   120  //因为它可能有助于调试。
   121  		buf = append(buf, make([]byte, tt.stdout.Buffered())...)
   122  		tt.stdout.Read(buf[n:])
   123  //找到不匹配的位置。
   124  		for i := 0; i < n; i++ {
   125  			if want[i] != buf[i] {
   126  				return fmt.Errorf("Output mismatch at ◊:\n---------------- (stdout text)\n%s◊%s\n---------------- (expected text)\n%s",
   127  					buf[:i], buf[i:n], want)
   128  			}
   129  		}
   130  		if n < len(want) {
   131  			return fmt.Errorf("Not enough output, got until ◊:\n---------------- (stdout text)\n%s\n---------------- (expected text)\n%s◊%s",
   132  				buf, want[:n], want[n:])
   133  		}
   134  	}
   135  	return nil
   136  }
   137  
   138  //expectregexp要求子进程输出与
   139  //在5s内给出正则表达式。
   140  //
   141  //注意,任意数量的输出可能被
   142  //正则表达式。这通常意味着不能使用expect
   143  //在expectregexp之后。
   144  func (tt *TestCmd) ExpectRegexp(regex string) (*regexp.Regexp, []string) {
   145  	regex = strings.TrimPrefix(regex, "\n")
   146  	var (
   147  		re      = regexp.MustCompile(regex)
   148  		rtee    = &runeTee{in: tt.stdout}
   149  		matches []int
   150  	)
   151  	tt.withKillTimeout(func() { matches = re.FindReaderSubmatchIndex(rtee) })
   152  	output := rtee.buf.Bytes()
   153  	if matches == nil {
   154  		tt.Fatalf("Output did not match:\n---------------- (stdout text)\n%s\n---------------- (regular expression)\n%s",
   155  			output, regex)
   156  		return re, nil
   157  	}
   158  	tt.Logf("Matched stdout text:\n%s", output)
   159  	var submatches []string
   160  	for i := 0; i < len(matches); i += 2 {
   161  		submatch := string(output[matches[i]:matches[i+1]])
   162  		submatches = append(submatches, submatch)
   163  	}
   164  	return re, submatches
   165  }
   166  
   167  //expectexit期望子进程在5秒内退出
   168  //在stdout上打印任何附加文本。
   169  func (tt *TestCmd) ExpectExit() {
   170  	var output []byte
   171  	tt.withKillTimeout(func() {
   172  		output, _ = ioutil.ReadAll(tt.stdout)
   173  	})
   174  	tt.WaitExit()
   175  	if tt.Cleanup != nil {
   176  		tt.Cleanup()
   177  	}
   178  	if len(output) > 0 {
   179  		tt.Errorf("Unmatched stdout text:\n%s", output)
   180  	}
   181  }
   182  
   183  func (tt *TestCmd) WaitExit() {
   184  	tt.Err = tt.cmd.Wait()
   185  }
   186  
   187  func (tt *TestCmd) Interrupt() {
   188  	tt.Err = tt.cmd.Process.Signal(os.Interrupt)
   189  }
   190  
   191  //exitstatus公开进程的操作系统退出代码
   192  //它只会在进程完成后返回一个有效值。
   193  func (tt *TestCmd) ExitStatus() int {
   194  	if tt.Err != nil {
   195  		exitErr := tt.Err.(*exec.ExitError)
   196  		if exitErr != nil {
   197  			if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
   198  				return status.ExitStatus()
   199  			}
   200  		}
   201  	}
   202  	return 0
   203  }
   204  
   205  //stderrtext返回到目前为止写入的任何stderr输出。
   206  //返回的文本保留Expectexit之后的所有日志行
   207  //返回。
   208  func (tt *TestCmd) StderrText() string {
   209  	tt.stderr.mu.Lock()
   210  	defer tt.stderr.mu.Unlock()
   211  	return tt.stderr.buf.String()
   212  }
   213  
   214  func (tt *TestCmd) CloseStdin() {
   215  	tt.stdin.Close()
   216  }
   217  
   218  func (tt *TestCmd) Kill() {
   219  	tt.cmd.Process.Kill()
   220  	if tt.Cleanup != nil {
   221  		tt.Cleanup()
   222  	}
   223  }
   224  
   225  func (tt *TestCmd) withKillTimeout(fn func()) {
   226  	timeout := time.AfterFunc(5*time.Second, func() {
   227  		tt.Log("killing the child process (timeout)")
   228  		tt.Kill()
   229  	})
   230  	defer timeout.Stop()
   231  	fn()
   232  }
   233  
   234  //testlogger logs all written lines via t.Log and also
   235  //收集以备日后检查。
   236  type testlogger struct {
   237  	t   *testing.T
   238  	mu  sync.Mutex
   239  	buf bytes.Buffer
   240  }
   241  
   242  func (tl *testlogger) Write(b []byte) (n int, err error) {
   243  	lines := bytes.Split(b, []byte("\n"))
   244  	for _, line := range lines {
   245  		if len(line) > 0 {
   246  			tl.t.Logf("(stderr) %s", line)
   247  		}
   248  	}
   249  	tl.mu.Lock()
   250  	tl.buf.Write(b)
   251  	tl.mu.Unlock()
   252  	return len(b), err
   253  }
   254  
   255  //runetee将通过它读取的文本收集到buf中。
   256  type runeTee struct {
   257  	in interface {
   258  		io.Reader
   259  		io.ByteReader
   260  		io.RuneReader
   261  	}
   262  	buf bytes.Buffer
   263  }
   264  
   265  func (rtee *runeTee) Read(b []byte) (n int, err error) {
   266  	n, err = rtee.in.Read(b)
   267  	rtee.buf.Write(b[:n])
   268  	return n, err
   269  }
   270  
   271  func (rtee *runeTee) ReadRune() (r rune, size int, err error) {
   272  	r, size, err = rtee.in.ReadRune()
   273  	if err == nil {
   274  		rtee.buf.WriteRune(r)
   275  	}
   276  	return r, size, err
   277  }
   278  
   279  func (rtee *runeTee) ReadByte() (b byte, err error) {
   280  	b, err = rtee.in.ReadByte()
   281  	if err == nil {
   282  		rtee.buf.WriteByte(b)
   283  	}
   284  	return b, err
   285  }
   286