github.com/qiniu/x@v1.11.9/ts/expect.go (about)

     1  package ts
     2  
     3  import (
     4  	"bytes"
     5  	"os"
     6  	"reflect"
     7  	"runtime"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/qiniu/x/errors"
    14  )
    15  
    16  // ----------------------------------------------------------------------------
    17  
    18  type captureData struct {
    19  	target **os.File
    20  	old    *os.File
    21  	exp    chan interface{}
    22  	done   chan bool
    23  }
    24  
    25  func (p *captureData) expect(t *testing.T, v interface{}) {
    26  	p.exp <- v
    27  	if equal := <-p.done; !equal {
    28  		t.FailNow()
    29  	}
    30  }
    31  
    32  // Expecting represents a expecting object.
    33  type Expecting struct {
    34  	t     *testing.T
    35  	cdata [2]*captureData
    36  	msg   []byte
    37  	rcov  interface{}
    38  	cstk  *stack
    39  }
    40  
    41  const (
    42  	// CapStdout - capture stdout
    43  	CapStdout = 1
    44  	// CapStderr - capture stderr
    45  	CapStderr = 2
    46  	// CapOutput - capture stdout and stderr
    47  	CapOutput = CapStdout | CapStderr
    48  )
    49  
    50  // StartExpecting starts expecting with a capture mode.
    51  func StartExpecting(t *testing.T, mode int) *Expecting {
    52  	p := &Expecting{t: t}
    53  	if mode == 0 {
    54  		mode = CapOutput
    55  	}
    56  	if (mode & CapStdout) != 0 {
    57  		p.capture(0, &os.Stdout)
    58  	}
    59  	if (mode & CapStderr) != 0 {
    60  		p.capture(1, &os.Stderr)
    61  	}
    62  	return p
    63  }
    64  
    65  // Call creates a test case, and then calls a function.
    66  func (p *Expecting) Call(fn interface{}, args ...interface{}) (e *Expecting) {
    67  	e = p
    68  	e.msg = errors.CallDetail(nil, fn, args...)
    69  	defer func() {
    70  		if e.rcov = recover(); e.rcov != nil {
    71  			e.cstk = callers(3)
    72  		}
    73  	}()
    74  	e.rcov = nil
    75  	reflect.ValueOf(fn).Call(makeArgs(args))
    76  	return
    77  }
    78  
    79  // Expect expects stdout ouput is equal to text.
    80  func (p *Expecting) Expect(text interface{}) *Expecting {
    81  	p.cdata[0].expect(p.t, text)
    82  	return p
    83  }
    84  
    85  // ExpectErr expects stderr ouput is equal to text.
    86  func (p *Expecting) ExpectErr(text interface{}) *Expecting {
    87  	p.cdata[1].expect(p.t, text)
    88  	return p
    89  }
    90  
    91  // NoPanic indicates that no panic occurs.
    92  func (p *Expecting) NoPanic() {
    93  	if p.rcov != nil {
    94  		p.t.Fatalf("panic: %v\n%+v\n", p.rcov, p.cstk)
    95  	}
    96  }
    97  
    98  // Panic checks if function call panics or not. Panic(v) means
    99  // function call panics with `v`. If v == nil, it means we don't
   100  // care any detail information about panic. Panic() indicates
   101  // that no panic occurs.
   102  func (p *Expecting) Panic(panicMsg ...interface{}) *Expecting {
   103  	if panicMsg == nil {
   104  		p.NoPanic()
   105  	} else {
   106  		assertPanic(p.t, p.msg, p.rcov, panicMsg[0])
   107  	}
   108  	return p
   109  }
   110  
   111  // Close stops expecting.
   112  func (p *Expecting) Close() error {
   113  	for i, cdata := range p.cdata {
   114  		if cdata != nil {
   115  			p.cdata[i] = nil
   116  			w := *cdata.target
   117  			*cdata.target = cdata.old
   118  			w.Close()
   119  			cdata.exp <- nil // close pipe
   120  		}
   121  	}
   122  	return nil
   123  }
   124  
   125  func (p *Expecting) capture(idx int, target **os.File) {
   126  	old := *target
   127  	cdata := &captureData{
   128  		target: target,
   129  		old:    old,
   130  		exp:    make(chan interface{}),
   131  		done:   make(chan bool, 1),
   132  	}
   133  	p.cdata[idx] = cdata
   134  	r, w, err := os.Pipe()
   135  	if err != nil {
   136  		panic(err)
   137  	}
   138  	var mutex sync.Mutex
   139  	var buf = bytes.NewBuffer(nil)
   140  	go func() {
   141  		b := make([]byte, 1024)
   142  		for {
   143  			n, err := r.Read(b)
   144  			if err != nil {
   145  				return
   146  			}
   147  			if n > 0 {
   148  				mutex.Lock()
   149  				buf.Write(b[:n])
   150  				old.Write(b[:n])
   151  				mutex.Unlock()
   152  			}
   153  		}
   154  	}()
   155  	go func() {
   156  		for exp := range cdata.exp {
   157  			if exp == nil { // close pipe
   158  				r.Close()
   159  				return
   160  			}
   161  			deadline := time.Now().Add(time.Second / 10) // 0.1s
   162  			off, b := 0, toString(exp)
   163  		retry:
   164  			mutex.Lock()
   165  			a := string(buf.Bytes())
   166  			buf.Reset()
   167  			mutex.Unlock()
   168  			equal := (a == b[off:])
   169  			if !equal {
   170  				if strings.HasPrefix(b[off:], a) && time.Now().Before(deadline) {
   171  					off += len(a)
   172  					runtime.Gosched()
   173  					goto retry
   174  				}
   175  				*target = old
   176  				p.t.Logf("%s:\n%s\nExpect:\n%s\n", string(p.msg), b[:off]+a, b)
   177  				*target = w
   178  			}
   179  			cdata.done <- equal
   180  		}
   181  	}()
   182  	*target = w
   183  }
   184  
   185  func toString(exp interface{}) string {
   186  	switch v := exp.(type) {
   187  	case []byte:
   188  		return string(v)
   189  	case string:
   190  		return v
   191  	default:
   192  		panic("expect: unsupport type - " + reflect.TypeOf(exp).String())
   193  	}
   194  }
   195  
   196  // ----------------------------------------------------------------------------