mirror of
https://github.com/robertkrimen/otto
synced 2025-10-19 19:55:30 +08:00
1167 lines
30 KiB
Go
1167 lines
30 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
|
|
|
|
import (
|
|
"testing"
|
|
. "github.com/robertkrimen/terst"
|
|
)
|
|
|
|
func Test(t *testing.T) {
|
|
Terst(t) // Associate terst methods with t (the current testing.T)
|
|
|
|
Is(getApple(), "apple") // Pass
|
|
Is(getOrange(), "orange") // Fail: emits nice-looking diagnostic
|
|
|
|
Compare(1, ">", 0) // Pass
|
|
Compare(1, "==", 1.0) // Pass
|
|
}
|
|
|
|
func getApple() string {
|
|
return "apple"
|
|
}
|
|
|
|
func getOrange() string {
|
|
return "apple" // Intentional mistake
|
|
}
|
|
|
|
At the top of your testing function, call Terst(), passing the testing.T you receive as the first argument:
|
|
|
|
func TestExample(t *testing.T) {
|
|
Terst(t)
|
|
...
|
|
}
|
|
|
|
After you initialize with the given *testing.T, you can use the following to test:
|
|
|
|
Is
|
|
IsNot
|
|
Equal
|
|
Unequal
|
|
IsTrue
|
|
IsFalse
|
|
Like
|
|
Unlike
|
|
Compare
|
|
|
|
Each of the methods above can take an additional (optional) argument,
|
|
which is a string describing the test. If the test fails, this
|
|
description will be included with the test output For example:
|
|
|
|
Is(2 + 2, float32(5), "This result is Doubleplusgood")
|
|
|
|
--- FAIL: Test (0.00 seconds)
|
|
test.go:17: This result is Doubleplusgood
|
|
Failed test (Is)
|
|
got: 4 (int)
|
|
expected: 5 (float32)
|
|
|
|
Future
|
|
|
|
- Add Catch() for testing panic()
|
|
- Add Same() for testing via .DeepEqual && == (without panicking?)
|
|
- Add StrictCompare to use {}= scoping
|
|
- Add BigCompare for easier math/big.Int testing?
|
|
- Support the complex type in Compare()
|
|
- Equality test for NaN?
|
|
- Better syntax for At*
|
|
- Need IsType/TypeIs
|
|
|
|
*/
|
|
package terst
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
func (self *Tester) hadResult(result bool, test *test, onFail func()) bool {
|
|
if self.selfTesting {
|
|
expect := true
|
|
if self.failIsPassing {
|
|
expect = false
|
|
}
|
|
if expect != result {
|
|
self.Log(fmt.Sprintf("Expect %v but got %v (%v) (%v) (%v)\n", expect, result, test.kind, test.have, test.want))
|
|
onFail()
|
|
self._fail()
|
|
}
|
|
return result
|
|
}
|
|
if !result {
|
|
onFail()
|
|
self._fail()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// IsTrue is DEPRECATED by:
|
|
//
|
|
// Is(..., true)
|
|
//
|
|
func IsTrue(have bool, description ...interface{}) bool {
|
|
return terstTester().IsTrue(have, description...)
|
|
}
|
|
|
|
// IsTrue is DEPRECATED by:
|
|
//
|
|
// Is(..., true)
|
|
//
|
|
func (self *Tester) IsTrue(have bool, description ...interface{}) bool {
|
|
return self.trueOrFalse(true, have, description...)
|
|
}
|
|
|
|
// IsFalse is DEPRECATED by:
|
|
//
|
|
// Is(..., false)
|
|
//
|
|
func IsFalse(have bool, description ...interface{}) bool {
|
|
return terstTester().IsFalse(have, description...)
|
|
}
|
|
|
|
// IsFalse is DEPRECATED by:
|
|
//
|
|
// Is(..., false)
|
|
//
|
|
func (self *Tester) IsFalse(have bool, description ...interface{}) bool {
|
|
return self.trueOrFalse(false, have, description...)
|
|
}
|
|
|
|
func (self *Tester) trueOrFalse(want bool, have bool, description ...interface{}) bool {
|
|
kind := "IsTrue"
|
|
if want == false {
|
|
kind = "IsFalse"
|
|
}
|
|
test := newTest(kind, have, want, description)
|
|
didPass := have == want
|
|
return self.hadResult(didPass, test, func() {
|
|
self.Log(self.failMessageForIsTrue(test))
|
|
})
|
|
}
|
|
|
|
// Fail will fail immediately, reporting a test failure with the (optional) description
|
|
func Fail(description ...interface{}) bool {
|
|
return terstTester().Fail(description...)
|
|
}
|
|
|
|
// Fail will fail immediately, reporting a test failure with the (optional) description
|
|
func (self *Tester) Fail(description ...interface{}) bool {
|
|
return self.fail(description...)
|
|
}
|
|
|
|
func (self *Tester) fail(description ...interface{}) bool {
|
|
kind := "Fail"
|
|
test := newTest(kind, false, false, description)
|
|
didPass := false
|
|
return self.hadResult(didPass, test, func() {
|
|
self.Log(self.failMessageForFail(test))
|
|
})
|
|
}
|
|
|
|
// FailNow will fail immediately, triggering testing.FailNow() and optionally reporting a test failure with description
|
|
func FailNow(description ...interface{}) bool {
|
|
return terstTester().FailNow(description...)
|
|
}
|
|
|
|
// FailNow will fail immediately, triggering testing.FailNow() and optionally reporting a test failure with description
|
|
func (self *Tester) FailNow(description ...interface{}) bool {
|
|
return self.failNow(description...)
|
|
}
|
|
|
|
func (self *Tester) failNow(description ...interface{}) bool {
|
|
if len(description) > 0 {
|
|
kind := "FailNow"
|
|
test := newTest(kind, false, false, description)
|
|
didPass := false
|
|
self.hadResult(didPass, test, func() {
|
|
self.Log(self.failMessageForFail(test))
|
|
})
|
|
}
|
|
self.TestingT.FailNow()
|
|
return false
|
|
}
|
|
|
|
// Equal tests have against want via ==:
|
|
//
|
|
// Equal(have, want) // Pass if have == want
|
|
//
|
|
// No special coercion or type inspection is done.
|
|
//
|
|
// If the type is incomparable (e.g. type mismatch) this will panic.
|
|
func Equal(have, want interface{}, description ...interface{}) bool {
|
|
return terstTester().Equal(have, want, description...)
|
|
}
|
|
|
|
func (self *Tester) Equal(have, want interface{}, description ...interface{}) bool {
|
|
return self.equal(have, want, description...)
|
|
}
|
|
|
|
func (self *Tester) equal(have, want interface{}, description ...interface{}) bool {
|
|
test := newTest("==", have, want, description)
|
|
didPass := have == want
|
|
return self.hadResult(didPass, test, func() {
|
|
self.Log(self.failMessageForEqual(test))
|
|
})
|
|
}
|
|
|
|
// Unequal tests have against want via !=:
|
|
//
|
|
// Unequal(have, want) // Pass if have != want
|
|
//
|
|
// No special coercion or type inspection is done.
|
|
//
|
|
// If the type is incomparable (e.g. type mismatch) this will panic.
|
|
func Unequal(have, want interface{}, description ...interface{}) bool {
|
|
return terstTester().Unequal(have, want, description...)
|
|
}
|
|
|
|
func (self *Tester) Unequal(have, want interface{}, description ...interface{}) bool {
|
|
return self.unequal(have, want, description...)
|
|
}
|
|
|
|
func (self *Tester) unequal(have, want interface{}, description ...interface{}) bool {
|
|
test := newTest("!=", have, want, description)
|
|
didPass := have != want
|
|
return self.hadResult(didPass, test, func() {
|
|
self.Log(self.failMessageForIs(test))
|
|
})
|
|
}
|
|
|
|
// Is tests <have> against <want> in different ways, depending on the
|
|
// type of <want>.
|
|
//
|
|
// If <want> is a string, then it will first convert
|
|
// <have> to a string before doing the comparison:
|
|
//
|
|
// Is(fmt.Sprintf("%v", have), want) // Pass if have == want
|
|
//
|
|
// Otherwise, Is is a shortcut for:
|
|
//
|
|
// Compare(have, "==", want)
|
|
//
|
|
// If <want> is a slice, struct, or similar, Is will perform a reflect.DeepEqual() comparison.
|
|
func Is(have, want interface{}, description ...interface{}) bool {
|
|
return terstTester().Is(have, want, description...)
|
|
}
|
|
|
|
// TODO "slice, struct, or similar" What is similar?
|
|
|
|
func (self *Tester) Is(have, want interface{}, description ...interface{}) bool {
|
|
return self.isOrIsNot(true, have, want, description...)
|
|
}
|
|
|
|
// IsNot tests <have> against <want> in different ways, depending on the
|
|
// type of <want>.
|
|
//
|
|
// If <want> is a string, then it will first convert
|
|
// <have> to a string before doing the comparison:
|
|
//
|
|
// IsNot(fmt.Sprintf("%v", have), want) // Pass if have != want
|
|
//
|
|
// Otherwise, Is is a shortcut for:
|
|
//
|
|
// Compare(have, "!=", want)
|
|
//
|
|
// If <want> is a slice, struct, or similar, Is will perform a reflect.DeepEqual() comparison.
|
|
func IsNot(have, want interface{}, description ...interface{}) bool {
|
|
return terstTester().IsNot(have, want, description...)
|
|
}
|
|
|
|
// TODO "slice, struct, or similar" What is similar?
|
|
|
|
func (self *Tester) IsNot(have, want interface{}, description ...interface{}) bool {
|
|
return self.isOrIsNot(false, have, want, description...)
|
|
}
|
|
|
|
func (self *Tester) isOrIsNot(wantIs bool, have, want interface{}, description ...interface{}) bool {
|
|
test := newTest("Is", have, want, description)
|
|
if !wantIs {
|
|
test.kind = "IsNot"
|
|
}
|
|
didPass := false
|
|
switch want.(type) {
|
|
case string:
|
|
didPass = stringValue(have) == want
|
|
default:
|
|
didPass, _ = compare(have, "{}* ==", want)
|
|
}
|
|
if !wantIs {
|
|
didPass = !didPass
|
|
}
|
|
return self.hadResult(didPass, test, func() {
|
|
self.Log(self.failMessageForIs(test))
|
|
})
|
|
}
|
|
|
|
// Like tests <have> against <want> in different ways, depending on the
|
|
// type of <want>.
|
|
//
|
|
// If <want> is a string, then it will first convert
|
|
// <have> to a string before doing a regular expression comparison:
|
|
//
|
|
// Like(fmt.Sprintf("%v", have), want) // Pass if regexp.Match(want, have)
|
|
//
|
|
// Otherwise, Like is a shortcut for:
|
|
//
|
|
// Compare(have, "{}~ ==", want)
|
|
//
|
|
// If <want> is a slice, struct, or similar, Like will perform a reflect.DeepEqual() comparison.
|
|
func Like(have, want interface{}, description ...interface{}) bool {
|
|
return terstTester().Like(have, want, description...)
|
|
}
|
|
|
|
func (self *Tester) Like(have, want interface{}, description ...interface{}) bool {
|
|
return self.likeOrUnlike(true, have, want, description...)
|
|
}
|
|
|
|
// Unlike tests <have> against <want> in different ways, depending on the
|
|
// type of <want>.
|
|
//
|
|
// If <want> is a string, then it will first convert
|
|
// <have> to a string before doing a regular expression comparison:
|
|
//
|
|
// Unlike(fmt.Sprintf("%v", have), want) // Pass if !regexp.Match(want, have)
|
|
//
|
|
// Otherwise, Unlike is a shortcut for:
|
|
//
|
|
// Compare(have, "{}~ !=", want)
|
|
//
|
|
// If <want> is a slice, struct, or similar, Unlike will perform a reflect.DeepEqual() comparison.
|
|
func Unlike(have, want interface{}, description ...interface{}) bool {
|
|
return terstTester().Unlike(have, want, description...)
|
|
}
|
|
|
|
func (self *Tester) Unlike(have, want interface{}, description ...interface{}) bool {
|
|
return self.likeOrUnlike(false, have, want, description...)
|
|
}
|
|
|
|
func (self *Tester) likeOrUnlike(wantLike bool, have, want interface{}, description ...interface{}) bool {
|
|
test := newTest("Like", have, want, description)
|
|
if !wantLike {
|
|
test.kind = "Unlike"
|
|
}
|
|
didPass := false
|
|
switch want0 := want.(type) {
|
|
case string:
|
|
haveString := stringValue(have)
|
|
didPass, error := regexp.Match(want0, []byte(haveString))
|
|
if !wantLike {
|
|
didPass = !didPass
|
|
}
|
|
if error != nil {
|
|
panic("regexp.Match(" + want0 + ", ...): " + error.Error())
|
|
}
|
|
want = fmt.Sprintf("(?:%v)", want) // Make it look like a regular expression
|
|
return self.hadResult(didPass, test, func() {
|
|
self.Log(self.failMessageForMatch(test, stringValue(have), stringValue(want), wantLike))
|
|
})
|
|
}
|
|
didPass, operator := compare(have, "{}~ ==", want)
|
|
if !wantLike {
|
|
didPass = !didPass
|
|
}
|
|
test.operator = operator
|
|
return self.hadResult(didPass, test, func() {
|
|
self.Log(self.failMessageForLike(test, stringValue(have), stringValue(want), wantLike))
|
|
})
|
|
}
|
|
|
|
// Compare will compare <have> to <want> with the given operator. The operator can be one of the following:
|
|
//
|
|
// ==
|
|
// !=
|
|
// <
|
|
// <=
|
|
// >
|
|
// >=
|
|
//
|
|
// Compare is not strict when comparing numeric types,
|
|
// and will make a best effort to promote <have> and <want> to the
|
|
// same type.
|
|
//
|
|
// Compare will promote int and uint to big.Int for testing
|
|
// against each other.
|
|
//
|
|
// Compare will promote int, uint, and float to float64 for
|
|
// float testing.
|
|
//
|
|
// For example:
|
|
//
|
|
// Compare(float32(1.0), "<", int8(2)) // A valid test
|
|
//
|
|
// result := float32(1.0) < int8(2) // Will not compile because of the type mismatch
|
|
//
|
|
func Compare(have interface{}, operator string, want interface{}, description ...interface{}) bool {
|
|
return terstTester().Compare(have, operator, want, description...)
|
|
}
|
|
|
|
func (self *Tester) Compare(have interface{}, operator string, want interface{}, description ...interface{}) bool {
|
|
return self.compare(have, operator, want, description...)
|
|
}
|
|
|
|
func (self *Tester) compare(left interface{}, operatorString string, right interface{}, description ...interface{}) bool {
|
|
operatorString = strings.TrimSpace(operatorString)
|
|
test := newTest("Compare "+operatorString, left, right, description)
|
|
didPass, operator := compare(left, operatorString, right)
|
|
test.operator = operator
|
|
return self.hadResult(didPass, test, func() {
|
|
self.Log(self.failMessageForCompare(test))
|
|
})
|
|
}
|
|
|
|
type (
|
|
compareScope int
|
|
)
|
|
|
|
const (
|
|
compareScopeEqual compareScope = iota
|
|
compareScopeTilde
|
|
compareScopeAsterisk
|
|
)
|
|
|
|
type compareOperator struct {
|
|
scope compareScope
|
|
comparison string
|
|
}
|
|
|
|
var newCompareOperatorRE *regexp.Regexp = regexp.MustCompile(`^\s*(?:((?:{}|#)[*~=])\s+)?(==|!=|<|<=|>|>=)\s*$`)
|
|
|
|
func newCompareOperator(operatorString string) compareOperator {
|
|
|
|
if operatorString == "" {
|
|
return compareOperator{compareScopeEqual, ""}
|
|
}
|
|
|
|
result := newCompareOperatorRE.FindStringSubmatch(operatorString)
|
|
if result == nil {
|
|
panic(fmt.Errorf("Unable to parse %v into a compareOperator", operatorString))
|
|
}
|
|
|
|
scope := compareScopeAsterisk
|
|
switch result[1] {
|
|
case "#*", "{}*":
|
|
scope = compareScopeAsterisk
|
|
case "#~", "{}~":
|
|
scope = compareScopeTilde
|
|
case "#=", "{}=":
|
|
scope = compareScopeEqual
|
|
}
|
|
|
|
comparison := result[2]
|
|
|
|
return compareOperator{scope, comparison}
|
|
}
|
|
|
|
func compare(left interface{}, operatorString string, right interface{}) (bool, compareOperator) {
|
|
pass := true
|
|
operator := newCompareOperator(operatorString)
|
|
comparator := newComparator(left, operator, right)
|
|
// FIXME Confusing
|
|
switch operator.comparison {
|
|
case "==":
|
|
pass = comparator.IsEqual()
|
|
case "!=":
|
|
pass = !comparator.IsEqual()
|
|
default:
|
|
if comparator.HasOrder() {
|
|
switch operator.comparison {
|
|
case "<":
|
|
pass = comparator.Compare() == -1
|
|
case "<=":
|
|
pass = comparator.Compare() <= 0
|
|
case ">":
|
|
pass = comparator.Compare() == 1
|
|
case ">=":
|
|
pass = comparator.Compare() >= 0
|
|
default:
|
|
panic(fmt.Errorf("Compare operator (%v) is invalid", operator.comparison))
|
|
}
|
|
} else {
|
|
pass = false
|
|
}
|
|
}
|
|
return pass, operator
|
|
}
|
|
|
|
// Compare / Comparator
|
|
|
|
type compareKind int
|
|
|
|
const (
|
|
kindInterface compareKind = iota
|
|
kindInteger
|
|
kindUnsignedInteger
|
|
kindFloat
|
|
kindString
|
|
kindBoolean
|
|
)
|
|
|
|
func comparatorValue(value interface{}) (reflect.Value, compareKind) {
|
|
reflectValue := reflect.ValueOf(value)
|
|
kind := kindInterface
|
|
switch value.(type) {
|
|
case int, int8, int16, int32, int64:
|
|
kind = kindInteger
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
kind = kindUnsignedInteger
|
|
case float32, float64:
|
|
kind = kindFloat
|
|
case string:
|
|
kind = kindString
|
|
case bool:
|
|
kind = kindBoolean
|
|
}
|
|
return reflectValue, kind
|
|
}
|
|
|
|
func toFloat(value reflect.Value) float64 {
|
|
switch value.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return float64(value.Int())
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return float64(value.Uint())
|
|
case reflect.Float32, reflect.Float64:
|
|
return float64(value.Float())
|
|
}
|
|
panic(fmt.Errorf("toFloat( %v <%[1]T> )", value.Interface()))
|
|
}
|
|
|
|
func toInteger(value reflect.Value) *big.Int {
|
|
switch value.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return big.NewInt(value.Int())
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16:
|
|
return big.NewInt(int64(value.Uint()))
|
|
case reflect.Uint32, reflect.Uint64:
|
|
tmp := big.NewInt(0)
|
|
tmp.SetString(fmt.Sprintf("%v", value.Uint()), 10)
|
|
return tmp
|
|
}
|
|
panic(fmt.Errorf("toInteger( %v <%[1]T> )", value.Interface()))
|
|
}
|
|
|
|
func toString(value reflect.Value) string {
|
|
switch value.Kind() {
|
|
case reflect.String:
|
|
return value.String()
|
|
}
|
|
panic(fmt.Errorf("toString( %v <%[1]T> )", value.Interface()))
|
|
}
|
|
|
|
func toBoolean(value reflect.Value) bool {
|
|
switch value.Kind() {
|
|
case reflect.Bool:
|
|
return value.Bool()
|
|
}
|
|
panic(fmt.Errorf("toBoolean( %v <%[1]T> )", value.Interface()))
|
|
}
|
|
|
|
type aComparator interface {
|
|
Compare() int
|
|
HasOrder() bool
|
|
IsEqual() bool
|
|
CompareScope() compareScope
|
|
}
|
|
|
|
type baseComparator struct {
|
|
hasOrder bool
|
|
operator compareOperator
|
|
}
|
|
|
|
func (self *baseComparator) Compare() int {
|
|
panic(fmt.Errorf("Invalid .Compare()"))
|
|
}
|
|
func (self *baseComparator) HasOrder() bool {
|
|
return self.hasOrder
|
|
}
|
|
func (self *baseComparator) CompareScope() compareScope {
|
|
return self.operator.scope
|
|
}
|
|
func comparatorWithOrder(operator compareOperator) *baseComparator {
|
|
return &baseComparator{true, operator}
|
|
}
|
|
func comparatorWithoutOrder(operator compareOperator) *baseComparator {
|
|
return &baseComparator{false, operator}
|
|
}
|
|
|
|
type interfaceComparator struct {
|
|
*baseComparator
|
|
left interface{}
|
|
right interface{}
|
|
}
|
|
|
|
func (self *interfaceComparator) IsEqual() bool {
|
|
if self.CompareScope() != compareScopeEqual {
|
|
return reflect.DeepEqual(self.left, self.right)
|
|
}
|
|
return self.left == self.right
|
|
}
|
|
|
|
type floatComparator struct {
|
|
*baseComparator
|
|
left float64
|
|
right float64
|
|
}
|
|
|
|
func (self *floatComparator) Compare() int {
|
|
if self.left == self.right {
|
|
return 0
|
|
} else if self.left < self.right {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
func (self *floatComparator) IsEqual() bool {
|
|
return self.left == self.right
|
|
}
|
|
|
|
type integerComparator struct {
|
|
*baseComparator
|
|
left *big.Int
|
|
right *big.Int
|
|
}
|
|
|
|
func (self *integerComparator) Compare() int {
|
|
return self.left.Cmp(self.right)
|
|
}
|
|
func (self *integerComparator) IsEqual() bool {
|
|
return 0 == self.left.Cmp(self.right)
|
|
}
|
|
|
|
type stringComparator struct {
|
|
*baseComparator
|
|
left string
|
|
right string
|
|
}
|
|
|
|
func (self *stringComparator) Compare() int {
|
|
if self.left == self.right {
|
|
return 0
|
|
} else if self.left < self.right {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
func (self *stringComparator) IsEqual() bool {
|
|
return self.left == self.right
|
|
}
|
|
|
|
type booleanComparator struct {
|
|
*baseComparator
|
|
left bool
|
|
right bool
|
|
}
|
|
|
|
func (self *booleanComparator) IsEqual() bool {
|
|
return self.left == self.right
|
|
}
|
|
|
|
func newComparator(left interface{}, operator compareOperator, right interface{}) aComparator {
|
|
leftValue, _ := comparatorValue(left)
|
|
rightValue, rightKind := comparatorValue(right)
|
|
|
|
// The simplest comparator is comparing interface{} =? interface{}
|
|
targetKind := kindInterface
|
|
// Are left and right of the same kind?
|
|
// (reflect.Value.Kind() is different from compareKind)
|
|
scopeEqual := leftValue.Kind() == rightValue.Kind()
|
|
scopeTilde := false
|
|
scopeAsterisk := false
|
|
if scopeEqual {
|
|
targetKind = rightKind // Since left and right are the same, the targetKind is Integer/Float/String/Boolean
|
|
} else {
|
|
// Examine the prefix of reflect.Value.Kind().String() to see if there is a similarity of
|
|
// the left value to right value
|
|
lk := leftValue.Kind().String()
|
|
hasPrefix := func(prefix string) bool {
|
|
return strings.HasPrefix(lk, prefix)
|
|
}
|
|
|
|
switch right.(type) {
|
|
case float32, float64:
|
|
// Right is float*
|
|
if hasPrefix("float") {
|
|
// Left is also float*
|
|
targetKind = kindFloat
|
|
scopeTilde = true
|
|
} else if hasPrefix("int") || hasPrefix("uint") {
|
|
// Left is a kind of numeric (int* or uint*)
|
|
targetKind = kindFloat
|
|
scopeAsterisk = true
|
|
} else {
|
|
// Otherwise left is a non-numeric
|
|
}
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
// Right is uint*
|
|
if hasPrefix("uint") {
|
|
// Left is also uint*
|
|
targetKind = kindInteger
|
|
scopeTilde = true
|
|
} else if hasPrefix("int") {
|
|
// Left is an int* (a numeric)
|
|
targetKind = kindInteger
|
|
scopeAsterisk = true
|
|
} else if hasPrefix("float") {
|
|
// Left is an float* (a numeric)
|
|
targetKind = kindFloat
|
|
scopeAsterisk = true
|
|
} else {
|
|
// Otherwise left is a non-numeric
|
|
}
|
|
case int, int8, int16, int32, int64:
|
|
// Right is int*
|
|
if hasPrefix("int") {
|
|
// Left is also int*
|
|
targetKind = kindInteger
|
|
scopeTilde = true
|
|
} else if hasPrefix("uint") {
|
|
// Left is a uint* (a numeric)
|
|
targetKind = kindInteger
|
|
scopeAsterisk = true
|
|
} else if hasPrefix("float") {
|
|
// Left is an float* (a numeric)
|
|
targetKind = kindFloat
|
|
scopeAsterisk = true
|
|
} else {
|
|
// Otherwise left is a non-numeric
|
|
}
|
|
default:
|
|
// Right is a non-numeric
|
|
// Can only really compare string to string or boolean to boolean, so
|
|
// we will either have a string/boolean/interfaceComparator
|
|
}
|
|
}
|
|
|
|
/*fmt.Println("%v %v %v %v %s %s", operator.scope, same, sibling, family, leftValue, rightValue)*/
|
|
{
|
|
mismatch := false
|
|
switch operator.scope {
|
|
case compareScopeEqual:
|
|
mismatch = !scopeEqual
|
|
case compareScopeTilde:
|
|
mismatch = !scopeEqual && !scopeTilde
|
|
case compareScopeAsterisk:
|
|
mismatch = !scopeEqual && !scopeTilde && !scopeAsterisk
|
|
}
|
|
if mismatch {
|
|
targetKind = kindInterface
|
|
}
|
|
}
|
|
|
|
switch targetKind {
|
|
case kindFloat:
|
|
return &floatComparator{
|
|
comparatorWithOrder(operator),
|
|
toFloat(leftValue),
|
|
toFloat(rightValue),
|
|
}
|
|
case kindInteger:
|
|
return &integerComparator{
|
|
comparatorWithOrder(operator),
|
|
toInteger(leftValue),
|
|
toInteger(rightValue),
|
|
}
|
|
case kindString:
|
|
return &stringComparator{
|
|
comparatorWithOrder(operator),
|
|
toString(leftValue),
|
|
toString(rightValue),
|
|
}
|
|
case kindBoolean:
|
|
return &booleanComparator{
|
|
comparatorWithoutOrder(operator),
|
|
toBoolean(leftValue),
|
|
toBoolean(rightValue),
|
|
}
|
|
}
|
|
|
|
// As a last resort, we can always compare left (interface{}) to right (interface{})
|
|
return &interfaceComparator{
|
|
comparatorWithoutOrder(operator),
|
|
left,
|
|
right,
|
|
}
|
|
}
|
|
|
|
// failMessage*
|
|
|
|
func (self *Tester) failMessageForIsTrue(test *test) string {
|
|
test.findFileLineFunction(self)
|
|
return formatMessage(`
|
|
%s:%d: %s
|
|
Failed test (%s)
|
|
got: %s
|
|
expected: %s
|
|
`, test.file, test.line, test.Description(), test.kind, stringValue(test.have), stringValue(test.want))
|
|
}
|
|
|
|
func (self *Tester) failMessageForFail(test *test) string {
|
|
test.findFileLineFunction(self)
|
|
return formatMessage(`
|
|
%s:%d: %s
|
|
Failed test (%s)
|
|
`, test.file, test.line, test.Description(), test.kind)
|
|
}
|
|
|
|
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 (self *Tester) failMessageForCompare(test *test) string {
|
|
test.findFileLineFunction(self)
|
|
return formatMessage(`
|
|
%s:%d: %s
|
|
Failed test (%s)
|
|
%v%s
|
|
%s
|
|
%v%s
|
|
`, test.file, test.line, test.Description(), test.kind, test.have, typeKindString(test.have), test.operator.comparison, test.want, typeKindString(test.want))
|
|
}
|
|
|
|
func (self *Tester) failMessageForEqual(test *test) string {
|
|
return self.failMessageForIs(test)
|
|
}
|
|
|
|
func (self *Tester) failMessageForIs(test *test) string {
|
|
test.findFileLineFunction(self)
|
|
return formatMessage(`
|
|
%s:%d: %v
|
|
Failed test (%s)
|
|
got: %v%s
|
|
expected: %v%s
|
|
`, test.file, test.line, test.Description(), test.kind, test.have, typeKindString(test.have), test.want, typeKindString(test.want))
|
|
}
|
|
|
|
func (self *Tester) failMessageForMatch(test *test, have, want string, wantMatch bool) string {
|
|
test.findFileLineFunction(self)
|
|
expect := " like"
|
|
if !wantMatch {
|
|
expect = "unlike"
|
|
}
|
|
return formatMessage(`
|
|
%s:%d: %s
|
|
Failed test (%s)
|
|
got: %v%s
|
|
%s: %s
|
|
`, test.file, test.line, test.Description(), test.kind, have, typeKindString(have), expect, want)
|
|
}
|
|
|
|
func (self *Tester) failMessageForLike(test *test, have, want string, wantLike bool) string {
|
|
test.findFileLineFunction(self)
|
|
if !wantLike {
|
|
want = "Anything else"
|
|
}
|
|
return formatMessage(`
|
|
%s:%d: %s
|
|
Failed test (%s)
|
|
got: %v%s
|
|
expected: %v%s
|
|
`, test.file, test.line, test.Description(), test.kind, have, typeKindString(have), want, typeKindString(want))
|
|
}
|
|
|
|
// ...
|
|
|
|
type Tester struct {
|
|
TestingT *testing.T
|
|
|
|
sanityChecking bool
|
|
selfTesting bool
|
|
failIsPassing bool
|
|
|
|
testEntry uintptr
|
|
focusEntry uintptr
|
|
}
|
|
|
|
var _terstTester *Tester = nil
|
|
|
|
func findTestEntry() uintptr {
|
|
height := 2
|
|
for {
|
|
functionPC, _, _, ok := runtime.Caller(height)
|
|
function := runtime.FuncForPC(functionPC)
|
|
functionName := function.Name()
|
|
if !ok {
|
|
return 0
|
|
}
|
|
if index := strings.LastIndex(functionName, ".Test"); index >= 0 {
|
|
// Assume we have an instance of TestXyzzy in a _test file
|
|
return function.Entry()
|
|
}
|
|
height += 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Focus will focus the entry point of the test to the current method.
|
|
//
|
|
// This is important for test failures in getting feedback on which line was at fault.
|
|
//
|
|
// Consider the following scenario:
|
|
//
|
|
// func testingMethod( ... ) {
|
|
// Is( ..., ... )
|
|
// }
|
|
//
|
|
// func TestExample(t *testing.T) {
|
|
// Terst(t)
|
|
//
|
|
// testingMethod( ... )
|
|
// testingMethod( ... ) // If something in testingMethod fails, this line number will come up
|
|
// testingMethod( ... )
|
|
// }
|
|
//
|
|
// By default, when a test fails, terst will report the outermost line that led to the failure.
|
|
// Usually this is what you want, but if you need to drill down, you can by inserting a special
|
|
// call at the top of your testing method:
|
|
//
|
|
// func testingMethod( ... ) {
|
|
// Terst().Focus() // Grab the global Tester and tell it to focus on this method
|
|
// Is( ..., ... ) // Now if this test fails, this line number will come up
|
|
// }
|
|
//
|
|
func (self *Tester) Focus() {
|
|
pc, _, _, ok := runtime.Caller(1)
|
|
if ok {
|
|
function := runtime.FuncForPC(pc)
|
|
self.focusEntry = function.Entry()
|
|
}
|
|
}
|
|
|
|
// Terst(*testing.T)
|
|
//
|
|
// Create a new terst Tester and return it. Associate calls to Is, Compare, Like, etc. with the newly created terst.
|
|
//
|
|
// Terst()
|
|
//
|
|
// Return the current Tester (if any).
|
|
//
|
|
// Terst(nil)
|
|
//
|
|
// Clear out the current Tester (if any).
|
|
func Terst(terst ...interface{}) *Tester {
|
|
if len(terst) == 0 {
|
|
return terstTester()
|
|
} else {
|
|
if terst[0] == nil {
|
|
_terstTester = nil
|
|
return nil
|
|
}
|
|
_terstTester = newTester(terst[0].(*testing.T))
|
|
_terstTester.enableSanityChecking()
|
|
_terstTester.testEntry = findTestEntry()
|
|
_terstTester.focusEntry = _terstTester.testEntry
|
|
}
|
|
return _terstTester
|
|
}
|
|
|
|
func terstTester() *Tester {
|
|
if _terstTester == nil {
|
|
panic("_terstTester == nil")
|
|
}
|
|
return _terstTester.checkSanity()
|
|
}
|
|
|
|
func newTester(t *testing.T) *Tester {
|
|
return &Tester{
|
|
TestingT: t,
|
|
}
|
|
}
|
|
|
|
func formatMessage(message string, argumentList ...interface{}) string {
|
|
message = fmt.Sprintf(message, argumentList...)
|
|
message = strings.TrimLeft(message, "\n")
|
|
message = strings.TrimRight(message, " \n")
|
|
return message + "\n\n"
|
|
}
|
|
|
|
// Log is a utility method that will append the given output to the normal output stream.
|
|
func (self *Tester) Log(output string) {
|
|
outputValue := reflect.ValueOf(self.TestingT).Elem().FieldByName("output")
|
|
output_ := outputValue.Bytes()
|
|
output_ = append(output_, output...)
|
|
*(*[]byte)(unsafe.Pointer(outputValue.UnsafeAddr())) = output_
|
|
}
|
|
|
|
func (self *Tester) _fail() {
|
|
self.TestingT.Fail()
|
|
}
|
|
|
|
func (self *Tester) enableSanityChecking() *Tester {
|
|
self.sanityChecking = true
|
|
return self
|
|
}
|
|
|
|
func (self *Tester) disableSanityChecking() *Tester {
|
|
self.sanityChecking = false
|
|
return self
|
|
}
|
|
|
|
func (self *Tester) enableSelfTesting() *Tester {
|
|
self.selfTesting = true
|
|
return self
|
|
}
|
|
|
|
func (self *Tester) disableSelfTesting() *Tester {
|
|
self.selfTesting = false
|
|
return self
|
|
}
|
|
|
|
func (self *Tester) failIsPass() *Tester {
|
|
self.failIsPassing = true
|
|
return self
|
|
}
|
|
|
|
func (self *Tester) passIsPass() *Tester {
|
|
self.failIsPassing = false
|
|
return self
|
|
}
|
|
|
|
func (self *Tester) checkSanity() *Tester {
|
|
if self.sanityChecking && self.testEntry != 0 {
|
|
foundEntryPoint := findTestEntry()
|
|
if self.testEntry != foundEntryPoint {
|
|
panic(fmt.Errorf("TestEntry(%v) does not match foundEntry(%v): Did you call Terst when entering a new Test* function?", self.testEntry, foundEntryPoint))
|
|
}
|
|
}
|
|
return self
|
|
}
|
|
|
|
func (self *Tester) findDepth() int {
|
|
height := 1 // Skip us
|
|
for {
|
|
pc, _, _, ok := runtime.Caller(height)
|
|
function := runtime.FuncForPC(pc)
|
|
if !ok {
|
|
// Got too close to the sun
|
|
if false {
|
|
for ; height > 0; height-- {
|
|
pc, _, _, ok := runtime.Caller(height)
|
|
fmt.Printf("[%d %v %v]", height, pc, ok)
|
|
if ok {
|
|
function := runtime.FuncForPC(pc)
|
|
fmt.Printf(" => [%s]", function.Name())
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
}
|
|
return 1
|
|
}
|
|
functionEntry := function.Entry()
|
|
if functionEntry == self.focusEntry || functionEntry == self.testEntry {
|
|
return height - 1 // Not the surrounding test function, but within it
|
|
}
|
|
height += 1
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// test
|
|
|
|
type test struct {
|
|
kind string
|
|
have interface{}
|
|
want interface{}
|
|
description []interface{}
|
|
operator compareOperator
|
|
|
|
file string
|
|
line int
|
|
functionPC uintptr
|
|
function string
|
|
}
|
|
|
|
func newTest(kind string, have, want interface{}, description []interface{}) *test {
|
|
operator := newCompareOperator("")
|
|
return &test{
|
|
kind: kind,
|
|
have: have,
|
|
want: want,
|
|
description: description,
|
|
operator: operator,
|
|
}
|
|
}
|
|
|
|
func (self *test) findFileLineFunction(tester *Tester) {
|
|
self.file, self.line, self.functionPC, self.function, _ = atFileLineFunction(tester.findDepth())
|
|
}
|
|
|
|
func (self *test) Description() string {
|
|
description := ""
|
|
if len(self.description) > 0 {
|
|
description = fmt.Sprintf("%v", self.description[0])
|
|
}
|
|
return description
|
|
}
|
|
|
|
func findPathForFile(file string) string {
|
|
terstBase := os.ExpandEnv("$TERST_BASE")
|
|
if len(terstBase) > 0 && strings.HasPrefix(file, terstBase) {
|
|
file = file[len(terstBase):]
|
|
if file[0] == '/' || file[0] == '\\' {
|
|
file = file[1:]
|
|
}
|
|
return file
|
|
}
|
|
|
|
if index := strings.LastIndex(file, "/"); index >= 0 {
|
|
file = file[index+1:]
|
|
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
|
|
file = file[index+1:]
|
|
}
|
|
|
|
return file
|
|
}
|
|
|
|
func atFileLineFunction(callDepth int) (string, int, uintptr, string, bool) {
|
|
pc, file, line, ok := runtime.Caller(callDepth + 1)
|
|
function := runtime.FuncForPC(pc).Name()
|
|
if ok {
|
|
file = findPathForFile(file)
|
|
if index := strings.LastIndex(function, ".Test"); index >= 0 {
|
|
function = function[index+1:]
|
|
}
|
|
} else {
|
|
pc = 0
|
|
file = "?"
|
|
line = 1
|
|
}
|
|
return file, line, pc, function, ok
|
|
}
|
|
|
|
// Conversion
|
|
|
|
func integerValue(value interface{}) int64 {
|
|
return reflect.ValueOf(value).Int()
|
|
}
|
|
|
|
func unsignedIntegerValue(value interface{}) uint64 {
|
|
return reflect.ValueOf(value).Uint()
|
|
}
|
|
|
|
func floatValue(value interface{}) float64 {
|
|
return reflect.ValueOf(value).Float()
|
|
}
|
|
|
|
func stringValue(value interface{}) string {
|
|
return fmt.Sprintf("%v", value)
|
|
}
|