670 lines
15 KiB
Go
670 lines
15 KiB
Go
// This file was AUTOMATICALLY GENERATED by terst-import (smuggol) from github.com/robertkrimen/terst
|
|
|
|
/*
|
|
Package terst is a terse (terst = test + terse), easy-to-use testing library for Go.
|
|
|
|
terst is compatible with (and works via) the standard testing package: http://golang.org/pkg/testing
|
|
|
|
var is = terst.Is
|
|
|
|
func Test(t *testing.T) {
|
|
terst.Terst(t, func() {
|
|
is("abc", "abc")
|
|
|
|
is(1, ">", 0)
|
|
|
|
var abc []int
|
|
is(abc, nil)
|
|
}
|
|
}
|
|
|
|
Do not import terst directly, instead use `terst-import` to copy it into your testing environment:
|
|
|
|
https://github.com/robertkrimen/terst/tree/master/terst-import
|
|
|
|
$ go get github.com/robertkrimen/terst/terst-import
|
|
|
|
$ terst-import
|
|
|
|
*/
|
|
package terst
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Is compares two values (got & expect) and returns true if the comparison is true,
|
|
// false otherwise. In addition, if the comparison is false, Is will report the error
|
|
// in a manner similar to testing.T.Error(...). Is also takes an optional argument,
|
|
// a comparator, that changes how the comparison is made. The following
|
|
// comparators are available:
|
|
//
|
|
// == # got == expect (default)
|
|
// != # got != expect
|
|
//
|
|
// > # got > expect (float32, uint, uint16, int, int64, ...)
|
|
// >= # got >= expect
|
|
// < # got < expect
|
|
// <= # got <= expect
|
|
//
|
|
// =~ # regexp.MustCompile(expect).Match{String}(got)
|
|
// !~ # !regexp.MustCompile(expect).Match{String}(got)
|
|
//
|
|
// Basic usage with the default comparator (==):
|
|
//
|
|
// Is(<got>, <expect>)
|
|
//
|
|
// Specifying a different comparator:
|
|
//
|
|
// Is(<got>, <comparator>, <expect>)
|
|
//
|
|
// A simple comparison:
|
|
//
|
|
// Is(2 + 2, 4)
|
|
//
|
|
// A bit trickier:
|
|
//
|
|
// Is(1, ">", 0)
|
|
// Is(2 + 2, "!=", 5)
|
|
// Is("Nothing happens.", "=~", `ing(\s+)happens\.$`)
|
|
//
|
|
// Is should only be called under a Terst(t, ...) call. For a standalone version,
|
|
// use IsErr. If no scope is found and the comparison is false, then Is will panic the error.
|
|
//
|
|
func Is(arguments ...interface{}) bool {
|
|
err := IsErr(arguments...)
|
|
if err != nil {
|
|
call := Caller()
|
|
if call == nil {
|
|
panic(err)
|
|
}
|
|
call.Error(err)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
type (
|
|
// ErrFail indicates a comparison failure (e.g. 0 > 1).
|
|
ErrFail error
|
|
|
|
// ErrInvalid indicates an invalid comparison (e.g. bool == string).
|
|
ErrInvalid error
|
|
)
|
|
|
|
var errInvalid = errors.New("invalid")
|
|
|
|
var registry = struct {
|
|
table map[uintptr]*_scope
|
|
lock sync.RWMutex
|
|
}{
|
|
table: map[uintptr]*_scope{},
|
|
}
|
|
|
|
func registerScope(pc uintptr, scope *_scope) {
|
|
registry.lock.Lock()
|
|
defer registry.lock.Unlock()
|
|
registry.table[pc] = scope
|
|
}
|
|
|
|
func scope() *_scope {
|
|
scope, _ := findScope()
|
|
return scope
|
|
}
|
|
|
|
func floatCompare(a float64, b float64) int {
|
|
if a > b {
|
|
return 1
|
|
} else if a < b {
|
|
return -1
|
|
}
|
|
// NaN == NaN
|
|
return 0
|
|
}
|
|
|
|
func bigIntCompare(a *big.Int, b *big.Int) int {
|
|
return a.Cmp(b)
|
|
}
|
|
|
|
func bigInt(value int64) *big.Int {
|
|
return big.NewInt(value)
|
|
}
|
|
|
|
func bigUint(value uint64) *big.Int {
|
|
return big.NewInt(0).SetUint64(value)
|
|
}
|
|
|
|
type _toString interface {
|
|
String() string
|
|
}
|
|
|
|
func toString(value interface{}) (string, error) {
|
|
switch value := value.(type) {
|
|
case string:
|
|
return value, nil
|
|
case _toString:
|
|
return value.String(), nil
|
|
case error:
|
|
return value.Error(), nil
|
|
}
|
|
return "", errInvalid
|
|
}
|
|
|
|
func matchString(got string, expect *regexp.Regexp) (int, error) {
|
|
if expect.MatchString(got) {
|
|
return 0, nil
|
|
}
|
|
return -1, nil
|
|
}
|
|
|
|
func match(got []byte, expect *regexp.Regexp) (int, error) {
|
|
if expect.Match(got) {
|
|
return 0, nil
|
|
}
|
|
return -1, nil
|
|
}
|
|
|
|
func compareMatch(got, expect interface{}) (int, error) {
|
|
switch got := got.(type) {
|
|
case []byte:
|
|
switch expect := expect.(type) {
|
|
case string:
|
|
matcher, err := regexp.Compile(expect)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return match(got, matcher)
|
|
case *regexp.Regexp:
|
|
return match(got, expect)
|
|
}
|
|
default:
|
|
if got, err := toString(got); err == nil {
|
|
switch expect := expect.(type) {
|
|
case string:
|
|
matcher, err := regexp.Compile(expect)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return matchString(got, matcher)
|
|
case *regexp.Regexp:
|
|
return matchString(got, expect)
|
|
}
|
|
} else {
|
|
return 0, err
|
|
}
|
|
}
|
|
return 0, errInvalid
|
|
}
|
|
|
|
func floatPromote(value reflect.Value) (float64, error) {
|
|
kind := value.Kind()
|
|
if reflect.Int <= kind && kind <= reflect.Int64 {
|
|
return float64(value.Int()), nil
|
|
}
|
|
if reflect.Uint <= kind && kind <= reflect.Uint64 {
|
|
return float64(value.Uint()), nil
|
|
}
|
|
if reflect.Float32 <= kind && kind <= reflect.Float64 {
|
|
return value.Float(), nil
|
|
}
|
|
return 0, errInvalid
|
|
}
|
|
|
|
func bigIntPromote(value reflect.Value) (*big.Int, error) {
|
|
kind := value.Kind()
|
|
if reflect.Int <= kind && kind <= reflect.Int64 {
|
|
return bigInt(value.Int()), nil
|
|
}
|
|
if reflect.Uint <= kind && kind <= reflect.Uint64 {
|
|
return bigUint(value.Uint()), nil
|
|
}
|
|
return nil, errInvalid
|
|
}
|
|
|
|
func compareOther(got, expect interface{}) (int, error) {
|
|
{
|
|
switch expect.(type) {
|
|
case float32, float64:
|
|
return compareNumber(got, expect)
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
return compareNumber(got, expect)
|
|
case int, int8, int16, int32, int64:
|
|
return compareNumber(got, expect)
|
|
case string:
|
|
var err error
|
|
got, err = toString(got)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
case nil:
|
|
got := reflect.ValueOf(got)
|
|
switch got.Kind() {
|
|
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface:
|
|
if got.IsNil() {
|
|
return 0, nil
|
|
}
|
|
return -1, nil
|
|
case reflect.Invalid: // reflect.Invalid: var abc interface{} = nil
|
|
return 0, nil
|
|
}
|
|
return 0, errInvalid
|
|
}
|
|
}
|
|
|
|
if reflect.ValueOf(got).Type() != reflect.ValueOf(expect).Type() {
|
|
return 0, errInvalid
|
|
}
|
|
|
|
if reflect.DeepEqual(got, expect) {
|
|
return 0, nil
|
|
}
|
|
return -1, nil
|
|
}
|
|
|
|
func compareNumber(got, expect interface{}) (int, error) {
|
|
{
|
|
got := reflect.ValueOf(got)
|
|
k0 := got.Kind()
|
|
expect := reflect.ValueOf(expect)
|
|
k1 := expect.Kind()
|
|
if reflect.Float32 <= k0 && k0 <= reflect.Float64 ||
|
|
reflect.Float32 <= k1 && k1 <= reflect.Float64 {
|
|
got, err := floatPromote(got)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
expect, err := floatPromote(expect)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return floatCompare(got, expect), nil
|
|
} else {
|
|
got, err := bigIntPromote(got)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
expect, err := bigIntPromote(expect)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return got.Cmp(expect), nil
|
|
}
|
|
}
|
|
|
|
return 0, errInvalid
|
|
}
|
|
|
|
// IsErr compares two values (got & expect) and returns nil if the comparison is true, an ErrFail if
|
|
// the comparison is false, or an ErrInvalid if the comparison is invalid. IsErr also
|
|
// takes an optional argument, a comparator, that changes how the comparison is made.
|
|
//
|
|
// Is & IsErr are similar but different:
|
|
//
|
|
// Is(...) // Should only be called within a Terst(...) call
|
|
// IsErr(...) // A standalone comparator, the same as Is, just without the automatic reporting
|
|
//
|
|
func IsErr(arguments ...interface{}) error {
|
|
var got, expect interface{}
|
|
comparator := "=="
|
|
switch len(arguments) {
|
|
case 0, 1:
|
|
return fmt.Errorf("invalid number of arguments to IsErr: %d", len(arguments))
|
|
case 2:
|
|
got, expect = arguments[0], arguments[1]
|
|
default:
|
|
if value, ok := arguments[1].(string); ok {
|
|
comparator = value
|
|
} else {
|
|
return fmt.Errorf("invalid comparator: %v", arguments[1])
|
|
}
|
|
got, expect = arguments[0], arguments[2]
|
|
}
|
|
|
|
var result int
|
|
var err error
|
|
|
|
switch comparator {
|
|
case "<", "<=", ">", ">=":
|
|
result, err = compareNumber(got, expect)
|
|
case "=~", "!~":
|
|
result, err = compareMatch(got, expect)
|
|
case "==", "!=":
|
|
result, err = compareOther(got, expect)
|
|
default:
|
|
return fmt.Errorf("invalid comparator: %s", comparator)
|
|
}
|
|
|
|
if err == errInvalid {
|
|
return ErrInvalid(fmt.Errorf(
|
|
"\nINVALID (%s):\n got: %v (%T)\n expected: %v (%T)",
|
|
comparator,
|
|
got, got,
|
|
expect, expect,
|
|
))
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
equality, pass := false, false
|
|
|
|
switch comparator {
|
|
case "==", "=~":
|
|
equality = true
|
|
pass = result == 0
|
|
case "!=", "!~":
|
|
equality = true
|
|
pass = result != 0
|
|
case "<":
|
|
pass = result < 0
|
|
case "<=":
|
|
pass = result <= 0
|
|
case ">":
|
|
pass = result > 0
|
|
case ">=":
|
|
pass = result >= 0
|
|
}
|
|
|
|
if !pass {
|
|
if equality {
|
|
if comparator[1] == '~' {
|
|
if value, ok := got.([]byte); ok {
|
|
return ErrFail(fmt.Errorf(
|
|
"\nFAIL (%s)\n got: %s %v%s\nexpected: %v%s",
|
|
comparator,
|
|
value, got, typeKindString(got),
|
|
expect, typeKindString(expect),
|
|
))
|
|
}
|
|
}
|
|
return ErrFail(fmt.Errorf(
|
|
"\nFAIL (%s)\n got: %v%s\nexpected: %v%s",
|
|
comparator,
|
|
got, typeKindString(got),
|
|
expect, typeKindString(expect),
|
|
))
|
|
}
|
|
return ErrFail(fmt.Errorf(
|
|
"\nFAIL (%s)\n got: %v%s\nexpected: %s %v%s",
|
|
comparator,
|
|
got, typeKindString(got),
|
|
comparator, expect, typeKindString(expect),
|
|
))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func typeKindString(value interface{}) string {
|
|
reflectValue := reflect.ValueOf(value)
|
|
kind := reflectValue.Kind().String()
|
|
result := fmt.Sprintf("%T", value)
|
|
if kind == result {
|
|
if kind == "string" {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf(" (%T)", value)
|
|
}
|
|
return fmt.Sprintf(" (%T=%s)", value, kind)
|
|
}
|
|
|
|
func (scope *_scope) reset() {
|
|
scope.name = ""
|
|
scope.output = scope.output[:]
|
|
scope.start = time.Time{}
|
|
scope.duration = 0
|
|
}
|
|
|
|
// Terst creates a testing scope, where Is can be called and errors will be reported
|
|
// according to the top-level location of the comparison, and not where the Is call
|
|
// actually takes place. For example:
|
|
//
|
|
// func test(value int) {
|
|
// Is(value, 5) // <--- This failure is reported below.
|
|
// }
|
|
//
|
|
// Terst(t, func(){
|
|
//
|
|
// Is(2, ">", 3) // <--- An error is reported here.
|
|
//
|
|
// test(5) // <--- An error is reported here.
|
|
//
|
|
// })
|
|
//
|
|
func Terst(t *testing.T, arguments ...func()) {
|
|
scope := &_scope{
|
|
t: t,
|
|
}
|
|
|
|
pc, _, _, ok := runtime.Caller(1) // TODO Associate with the Test... func
|
|
if !ok {
|
|
panic("Here be dragons.")
|
|
}
|
|
|
|
_, scope.testFunc = findTestFunc()
|
|
|
|
registerScope(pc, scope)
|
|
|
|
for _, fn := range arguments {
|
|
func() {
|
|
scope.reset()
|
|
name := scope.testFunc.Name()
|
|
index := strings.LastIndex(scope.testFunc.Name(), ".")
|
|
if index >= 0 {
|
|
name = name[index+1:] + "(Terst)"
|
|
} else {
|
|
name = "(Terst)"
|
|
}
|
|
name = "(Terst)"
|
|
scope.name = name
|
|
scope.start = time.Now()
|
|
defer func() {
|
|
scope.duration = time.Now().Sub(scope.start)
|
|
if err := recover(); err != nil {
|
|
scope.t.Fail()
|
|
scope.report()
|
|
panic(err)
|
|
}
|
|
scope.report()
|
|
}()
|
|
fn()
|
|
}()
|
|
}
|
|
}
|
|
|
|
// From "testing"
|
|
func (scope *_scope) report() {
|
|
format := "~~~ %s: (Terst)\n%s"
|
|
if scope.t.Failed() {
|
|
fmt.Printf(format, "FAIL", scope.output)
|
|
} else if testing.Verbose() && len(scope.output) > 0 {
|
|
fmt.Printf(format, "PASS", scope.output)
|
|
}
|
|
}
|
|
|
|
func (scope *_scope) log(call _entry, str string) {
|
|
scope.mu.Lock()
|
|
defer scope.mu.Unlock()
|
|
scope.output = append(scope.output, decorate(call, str)...)
|
|
}
|
|
|
|
// decorate prefixes the string with the file and line of the call site
|
|
// and inserts the final newline if needed and indentation tabs for formascing.
|
|
func decorate(call _entry, s string) string {
|
|
|
|
file, line := call.File, call.Line
|
|
if call.PC > 0 {
|
|
// Truncate file name at last file name separator.
|
|
if index := strings.LastIndex(file, "/"); index >= 0 {
|
|
file = file[index+1:]
|
|
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
|
|
file = file[index+1:]
|
|
}
|
|
} else {
|
|
file = "???"
|
|
line = 1
|
|
}
|
|
buf := new(bytes.Buffer)
|
|
// Every line is indented at least one tab.
|
|
buf.WriteByte('\t')
|
|
fmt.Fprintf(buf, "%s:%d: ", file, line)
|
|
lines := strings.Split(s, "\n")
|
|
if l := len(lines); l > 1 && lines[l-1] == "" {
|
|
lines = lines[:l-1]
|
|
}
|
|
for i, line := range lines {
|
|
if i > 0 {
|
|
// Second and subsequent lines are indented an extra tab.
|
|
buf.WriteString("\n\t\t")
|
|
}
|
|
buf.WriteString(line)
|
|
}
|
|
buf.WriteByte('\n')
|
|
return buf.String()
|
|
}
|
|
|
|
func findScope() (*_scope, _entry) {
|
|
registry.lock.RLock()
|
|
defer registry.lock.RUnlock()
|
|
table := registry.table
|
|
depth := 2 // Starting depth
|
|
call := _entry{}
|
|
for {
|
|
pc, _, _, ok := runtime.Caller(depth)
|
|
if !ok {
|
|
break
|
|
}
|
|
if scope, exists := table[pc]; exists {
|
|
pc, file, line, _ := runtime.Caller(depth - 3) // Terst(...) + func(){}() + fn() => ???()
|
|
call.PC = pc
|
|
call.File = file
|
|
call.Line = line
|
|
return scope, call
|
|
}
|
|
depth++
|
|
}
|
|
return nil, _entry{}
|
|
}
|
|
|
|
// Call is a reference to a line immediately under a Terst testing scope.
|
|
type Call struct {
|
|
scope *_scope
|
|
entry _entry
|
|
}
|
|
|
|
// Caller will search the stack, looking for a Terst testing scope. If a scope
|
|
// is found, then Caller returns a Call for logging errors, accessing testing.T, etc.
|
|
// If no scope is found, Caller returns nil.
|
|
func Caller() *Call {
|
|
scope, entry := findScope()
|
|
if scope == nil {
|
|
return nil
|
|
}
|
|
return &Call{
|
|
scope: scope,
|
|
entry: entry,
|
|
}
|
|
}
|
|
|
|
// TestFunc returns the *runtime.Func entry for the top-level Test...(t testing.T)
|
|
// function.
|
|
func (cl *Call) TestFunc() *runtime.Func {
|
|
return cl.scope.testFunc
|
|
}
|
|
|
|
// T returns the original testing.T passed to Terst(...)
|
|
func (cl *Call) T() *testing.T {
|
|
return cl.scope.t
|
|
}
|
|
|
|
// Log is the terst version of `testing.T.Log`
|
|
func (cl *Call) Log(arguments ...interface{}) {
|
|
cl.scope.log(cl.entry, fmt.Sprintln(arguments...))
|
|
}
|
|
|
|
// Logf is the terst version of `testing.T.Logf`
|
|
func (cl *Call) Logf(format string, arguments ...interface{}) {
|
|
cl.scope.log(cl.entry, fmt.Sprintf(format, arguments...))
|
|
}
|
|
|
|
// Error is the terst version of `testing.T.Error`
|
|
func (cl *Call) Error(arguments ...interface{}) {
|
|
cl.scope.log(cl.entry, fmt.Sprintln(arguments...))
|
|
cl.scope.t.Fail()
|
|
}
|
|
|
|
// Errorf is the terst version of `testing.T.Errorf`
|
|
func (cl *Call) Errorf(format string, arguments ...interface{}) {
|
|
cl.scope.log(cl.entry, fmt.Sprintf(format, arguments...))
|
|
cl.scope.t.Fail()
|
|
}
|
|
|
|
// Skip is the terst version of `testing.T.Skip`
|
|
func (cl *Call) Skip(arguments ...interface{}) {
|
|
cl.scope.log(cl.entry, fmt.Sprintln(arguments...))
|
|
cl.scope.t.SkipNow()
|
|
}
|
|
|
|
// Skipf is the terst version of `testing.T.Skipf`
|
|
func (cl *Call) Skipf(format string, arguments ...interface{}) {
|
|
cl.scope.log(cl.entry, fmt.Sprintf(format, arguments...))
|
|
cl.scope.t.SkipNow()
|
|
}
|
|
|
|
type _scope struct {
|
|
t *testing.T
|
|
testFunc *runtime.Func
|
|
name string
|
|
mu sync.RWMutex
|
|
output []byte
|
|
start time.Time
|
|
duration time.Duration
|
|
}
|
|
|
|
type _entry struct {
|
|
PC uintptr
|
|
File string
|
|
Line int
|
|
Func *runtime.Func
|
|
}
|
|
|
|
func _findFunc(match string) (_entry, *runtime.Func) {
|
|
depth := 2 // Starting depth
|
|
for {
|
|
pc, file, line, ok := runtime.Caller(depth)
|
|
if !ok {
|
|
break
|
|
}
|
|
fn := runtime.FuncForPC(pc)
|
|
name := fn.Name()
|
|
if index := strings.LastIndex(name, match); index >= 0 {
|
|
// Assume we have an instance of TestXyzzy in a _test file
|
|
return _entry{
|
|
PC: pc,
|
|
File: file,
|
|
Line: line,
|
|
Func: fn,
|
|
}, fn
|
|
}
|
|
depth++
|
|
}
|
|
return _entry{}, nil
|
|
}
|
|
|
|
func findTestFunc() (_entry, *runtime.Func) {
|
|
return _findFunc(".Test")
|
|
}
|
|
|
|
func findTerstFunc() (_entry, *runtime.Func) {
|
|
return _findFunc(".Terst")
|
|
}
|