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

     1  /*
     2   Copyright 2020 Qiniu Limited (qiniu.com)
     3  
     4   Licensed under the Apache License, Version 2.0 (the "License");
     5   you may not use this file except in compliance with the License.
     6   You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10   Unless required by applicable law or agreed to in writing, software
    11   distributed under the License is distributed on an "AS IS" BASIS,
    12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   See the License for the specific language governing permissions and
    14   limitations under the License.
    15  */
    16  
    17  // Package ts provides Go packages testing utilities.
    18  // See https://github.com/qiniu/x/wiki/How-to-write-a-TestCase for details.
    19  package ts
    20  
    21  import (
    22  	"reflect"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/qiniu/x/errors"
    27  )
    28  
    29  // ----------------------------------------------------------------------------
    30  
    31  // Testing represents a testing object.
    32  type Testing struct {
    33  	t *testing.T
    34  }
    35  
    36  // New creates a testing object.
    37  func New(t *testing.T) *Testing {
    38  	return &Testing{t: t}
    39  }
    40  
    41  // New creates a test case.
    42  func (p *Testing) New(name string) *TestCase {
    43  	return &TestCase{name: name, t: p.t}
    44  }
    45  
    46  // Call creates a test case, and then calls a function.
    47  func (p *Testing) Call(fn interface{}, args ...interface{}) *TestCase {
    48  	return p.New("").Call(fn, args...)
    49  }
    50  
    51  // Case creates a test case and sets its output parameters.
    52  func (p *Testing) Case(name string, result ...interface{}) *TestCase {
    53  	return p.New(name).Init(result...)
    54  }
    55  
    56  // ----------------------------------------------------------------------------
    57  
    58  // TestCase represents a test case.
    59  type TestCase struct {
    60  	t    *testing.T
    61  	name string
    62  	msg  []byte
    63  	rcov interface{}
    64  	cstk *stack
    65  	out  []reflect.Value
    66  	idx  int
    67  }
    68  
    69  func (p *TestCase) newMsg() []byte {
    70  	msg := make([]byte, 0, 16)
    71  	if p.name != "" {
    72  		msg = append(msg, p.name...)
    73  		msg = append(msg, ' ')
    74  	}
    75  	return msg
    76  }
    77  
    78  // Init sets output parameters.
    79  func (p *TestCase) Init(result ...interface{}) *TestCase {
    80  	out := make([]reflect.Value, len(result))
    81  	for i, ret := range result {
    82  		out[i] = reflect.ValueOf(ret)
    83  	}
    84  	p.msg = p.newMsg()
    85  	p.rcov = nil
    86  	p.out = out
    87  	p.idx = 0
    88  	return p
    89  }
    90  
    91  // Call calls a function.
    92  func (p *TestCase) Call(fn interface{}, args ...interface{}) (e *TestCase) {
    93  	e = p
    94  	e.msg = errors.CallDetail(e.newMsg(), fn, args...)
    95  	defer func() {
    96  		if e.rcov = recover(); e.rcov != nil {
    97  			e.cstk = callers(3)
    98  		}
    99  	}()
   100  	e.rcov = nil
   101  	e.out = reflect.ValueOf(fn).Call(makeArgs(args))
   102  	e.idx = 0
   103  	return
   104  }
   105  
   106  func makeArgs(args []interface{}) []reflect.Value {
   107  	in := make([]reflect.Value, len(args))
   108  	for i, arg := range args {
   109  		in[i] = reflect.ValueOf(arg)
   110  	}
   111  	return in
   112  }
   113  
   114  // Next sets current output value to next output parameter.
   115  func (p *TestCase) Next() *TestCase {
   116  	p.idx++
   117  	return p
   118  }
   119  
   120  // With sets current output value to check.
   121  func (p *TestCase) With(i int) *TestCase {
   122  	p.idx = i
   123  	return p
   124  }
   125  
   126  // Panic checks if function call panics or not. Panic(v) means
   127  // function call panics with `v`. If v == nil, it means we don't
   128  // care any detail information about panic.
   129  func (p *TestCase) Panic(panicMsg ...interface{}) *TestCase {
   130  	if panicMsg == nil {
   131  		p.assertNotPanic()
   132  	} else {
   133  		assertPanic(p.t, p.msg, p.rcov, panicMsg[0])
   134  	}
   135  	return p
   136  }
   137  
   138  func assertPanic(t *testing.T, msg []byte, rcov interface{}, panicMsg interface{}) {
   139  	if rcov == nil {
   140  		t.Fatalf("%s:\nPanic checks: no panic, expected: panic\n", string(msg))
   141  	}
   142  	if panicMsg != nil {
   143  		if !reflect.DeepEqual(rcov, panicMsg) {
   144  			t.Fatalf("%s:\nPanic checks: %v, expected: %v\n", string(msg), rcov, panicMsg)
   145  		}
   146  	}
   147  }
   148  
   149  func (p *TestCase) assertNotPanic() {
   150  	if p.rcov != nil {
   151  		p.t.Fatalf("panic: %v\n%+v\n", p.rcov, p.cstk)
   152  	}
   153  }
   154  
   155  // Equal checks current output value.
   156  func (p *TestCase) Equal(v interface{}) *TestCase {
   157  	p.assertNotPanic()
   158  	p.assertEq(p.out[p.idx].Interface(), v)
   159  	return p
   160  }
   161  
   162  // PropEqual checks property of current output value.
   163  func (p *TestCase) PropEqual(prop string, v interface{}) *TestCase {
   164  	p.assertNotPanic()
   165  	o := PropVal(p.out[p.idx], prop)
   166  	p.assertEq(o.Interface(), v)
   167  	return p
   168  }
   169  
   170  func (p *TestCase) assertEq(a, b interface{}) {
   171  	if !reflect.DeepEqual(a, b) {
   172  		p.t.Fatalf("%s:\nassertEq failed: %v, expected: %v\n", string(p.msg), a, b)
   173  	}
   174  }
   175  
   176  // PropVal returns property value of an object.
   177  func PropVal(o reflect.Value, prop string) reflect.Value {
   178  start:
   179  	switch o.Kind() {
   180  	case reflect.Struct:
   181  		if ret := o.FieldByName(prop); ret.IsValid() {
   182  			return ret
   183  		}
   184  	case reflect.Interface:
   185  		o = o.Elem()
   186  		goto start
   187  	}
   188  	if m := o.MethodByName(strings.Title(prop)); m.IsValid() {
   189  		out := m.Call([]reflect.Value{})
   190  		if len(out) != 1 {
   191  			panic("invalid PropVal: " + prop)
   192  		}
   193  		return out[0]
   194  	}
   195  	panic(o.Type().String() + " object hasn't property: " + prop)
   196  }
   197  
   198  // ----------------------------------------------------------------------------