356 lines
8.9 KiB
Go
356 lines
8.9 KiB
Go
package assert
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/d5/tengo/compiler"
|
|
"github.com/d5/tengo/compiler/source"
|
|
"github.com/d5/tengo/compiler/token"
|
|
"github.com/d5/tengo/objects"
|
|
)
|
|
|
|
// NoError asserts err is not an error.
|
|
func NoError(t *testing.T, err error, msg ...interface{}) bool {
|
|
if err == nil {
|
|
return true
|
|
}
|
|
|
|
return failExpectedActual(t, "no error", err, msg...)
|
|
}
|
|
|
|
// Error asserts err is an error.
|
|
func Error(t *testing.T, err error, msg ...interface{}) bool {
|
|
if err != nil {
|
|
return true
|
|
}
|
|
|
|
return failExpectedActual(t, "error", err, msg...)
|
|
}
|
|
|
|
// Nil asserts v is nil.
|
|
func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
|
|
if isNil(v) {
|
|
return true
|
|
}
|
|
|
|
return failExpectedActual(t, "nil", v, msg...)
|
|
}
|
|
|
|
// True asserts v is true.
|
|
func True(t *testing.T, v bool, msg ...interface{}) bool {
|
|
if v {
|
|
return true
|
|
}
|
|
|
|
return failExpectedActual(t, "true", v, msg...)
|
|
}
|
|
|
|
// False asserts vis false.
|
|
func False(t *testing.T, v bool, msg ...interface{}) bool {
|
|
if !v {
|
|
return true
|
|
}
|
|
|
|
return failExpectedActual(t, "false", v, msg...)
|
|
}
|
|
|
|
// NotNil asserts v is not nil.
|
|
func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool {
|
|
if !isNil(v) {
|
|
return true
|
|
}
|
|
|
|
return failExpectedActual(t, "not nil", v, msg...)
|
|
}
|
|
|
|
// IsType asserts expected and actual are of the same type.
|
|
func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
|
if reflect.TypeOf(expected) == reflect.TypeOf(actual) {
|
|
return true
|
|
}
|
|
|
|
return failExpectedActual(t, reflect.TypeOf(expected), reflect.TypeOf(actual), msg...)
|
|
}
|
|
|
|
// Equal asserts expected and actual are equal.
|
|
func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
|
if isNil(expected) {
|
|
return Nil(t, actual, "expected nil, but got not nil")
|
|
}
|
|
if !NotNil(t, actual, "expected not nil, but got nil") {
|
|
return false
|
|
}
|
|
if !IsType(t, expected, actual, msg...) {
|
|
return false
|
|
}
|
|
|
|
switch expected := expected.(type) {
|
|
case int:
|
|
if expected != actual.(int) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case int64:
|
|
if expected != actual.(int64) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case float64:
|
|
if expected != actual.(float64) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case string:
|
|
if expected != actual.(string) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case []byte:
|
|
if !bytes.Equal(expected, actual.([]byte)) {
|
|
return failExpectedActual(t, string(expected), string(actual.([]byte)), msg...)
|
|
}
|
|
case []string:
|
|
if !equalStringSlice(expected, actual.([]string)) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case []int:
|
|
if !equalIntSlice(expected, actual.([]int)) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case bool:
|
|
if expected != actual.(bool) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case rune:
|
|
if expected != actual.(rune) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case *compiler.Symbol:
|
|
if !equalSymbol(expected, actual.(*compiler.Symbol)) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case source.Pos:
|
|
if expected != actual.(source.Pos) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case token.Token:
|
|
if expected != actual.(token.Token) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case []objects.Object:
|
|
return equalObjectSlice(t, expected, actual.([]objects.Object), msg...)
|
|
case *objects.Int:
|
|
return Equal(t, expected.Value, actual.(*objects.Int).Value, msg...)
|
|
case *objects.Float:
|
|
return Equal(t, expected.Value, actual.(*objects.Float).Value, msg...)
|
|
case *objects.String:
|
|
return Equal(t, expected.Value, actual.(*objects.String).Value, msg...)
|
|
case *objects.Char:
|
|
return Equal(t, expected.Value, actual.(*objects.Char).Value, msg...)
|
|
case *objects.Bool:
|
|
if expected != actual {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case *objects.Array:
|
|
return equalObjectSlice(t, expected.Value, actual.(*objects.Array).Value, msg...)
|
|
case *objects.ImmutableArray:
|
|
return equalObjectSlice(t, expected.Value, actual.(*objects.ImmutableArray).Value, msg...)
|
|
case *objects.Bytes:
|
|
if !bytes.Equal(expected.Value, actual.(*objects.Bytes).Value) {
|
|
return failExpectedActual(t, string(expected.Value), string(actual.(*objects.Bytes).Value), msg...)
|
|
}
|
|
case *objects.Map:
|
|
return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value, msg...)
|
|
case *objects.ImmutableMap:
|
|
return equalObjectMap(t, expected.Value, actual.(*objects.ImmutableMap).Value, msg...)
|
|
case *objects.CompiledFunction:
|
|
return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction), msg...)
|
|
case *objects.Closure:
|
|
return equalClosure(t, expected, actual.(*objects.Closure), msg...)
|
|
case *objects.Undefined:
|
|
if expected != actual {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case *objects.Error:
|
|
return Equal(t, expected.Value, actual.(*objects.Error).Value, msg...)
|
|
case objects.Object:
|
|
if !expected.Equals(actual.(objects.Object)) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
case *source.FileSet:
|
|
return equalFileSet(t, expected, actual.(*source.FileSet), msg...)
|
|
case *source.File:
|
|
return Equal(t, expected.Name, actual.(*source.File).Name, msg...) &&
|
|
Equal(t, expected.Base, actual.(*source.File).Base, msg...) &&
|
|
Equal(t, expected.Size, actual.(*source.File).Size, msg...) &&
|
|
True(t, equalIntSlice(expected.Lines, actual.(*source.File).Lines), msg...)
|
|
case error:
|
|
if expected != actual.(error) {
|
|
return failExpectedActual(t, expected, actual, msg...)
|
|
}
|
|
default:
|
|
panic(fmt.Errorf("type not implemented: %T", expected))
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Fail marks the function as having failed but continues execution.
|
|
func Fail(t *testing.T, msg ...interface{}) bool {
|
|
t.Logf("\nError trace:\n\t%s\n%s", strings.Join(errorTrace(), "\n\t"), message(msg...))
|
|
|
|
t.Fail()
|
|
|
|
return false
|
|
}
|
|
|
|
func failExpectedActual(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
|
var addMsg string
|
|
if len(msg) > 0 {
|
|
addMsg = "\nMessage: " + message(msg...)
|
|
}
|
|
|
|
t.Logf("\nError trace:\n\t%s\nExpected: %v\nActual: %v%s",
|
|
strings.Join(errorTrace(), "\n\t"),
|
|
expected, actual,
|
|
addMsg)
|
|
|
|
t.Fail()
|
|
|
|
return false
|
|
}
|
|
|
|
func message(formatArgs ...interface{}) string {
|
|
var format string
|
|
var args []interface{}
|
|
if len(formatArgs) > 0 {
|
|
format = formatArgs[0].(string)
|
|
}
|
|
if len(formatArgs) > 1 {
|
|
args = formatArgs[1:]
|
|
}
|
|
|
|
return fmt.Sprintf(format, args...)
|
|
}
|
|
|
|
func equalIntSlice(a, b []int) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < len(a); i++ {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func equalStringSlice(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < len(a); i++ {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func equalSymbol(a, b *compiler.Symbol) bool {
|
|
return a.Name == b.Name &&
|
|
a.Index == b.Index &&
|
|
a.Scope == b.Scope
|
|
}
|
|
|
|
func equalObjectSlice(t *testing.T, expected, actual []objects.Object, msg ...interface{}) bool {
|
|
if !Equal(t, len(expected), len(actual), msg...) {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < len(expected); i++ {
|
|
if !Equal(t, expected[i], actual[i], msg...) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func equalFileSet(t *testing.T, expected, actual *source.FileSet, msg ...interface{}) bool {
|
|
if !Equal(t, len(expected.Files), len(actual.Files), msg...) {
|
|
return false
|
|
}
|
|
for i, f := range expected.Files {
|
|
if !Equal(t, f, actual.Files[i], msg...) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return Equal(t, expected.Base, actual.Base) &&
|
|
Equal(t, expected.LastFile, actual.LastFile)
|
|
}
|
|
|
|
func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object, msg ...interface{}) bool {
|
|
if !Equal(t, len(expected), len(actual), msg...) {
|
|
return false
|
|
}
|
|
|
|
for key, expectedVal := range expected {
|
|
actualVal := actual[key]
|
|
|
|
if !Equal(t, expectedVal, actualVal, msg...) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func equalCompiledFunction(t *testing.T, expected, actual objects.Object, msg ...interface{}) bool {
|
|
expectedT := expected.(*objects.CompiledFunction)
|
|
actualT := actual.(*objects.CompiledFunction)
|
|
|
|
return Equal(t,
|
|
compiler.FormatInstructions(expectedT.Instructions, 0),
|
|
compiler.FormatInstructions(actualT.Instructions, 0), msg...)
|
|
}
|
|
|
|
func equalClosure(t *testing.T, expected, actual objects.Object, msg ...interface{}) bool {
|
|
expectedT := expected.(*objects.Closure)
|
|
actualT := actual.(*objects.Closure)
|
|
|
|
if !Equal(t, expectedT.Fn, actualT.Fn, msg...) {
|
|
return false
|
|
}
|
|
|
|
if !Equal(t, len(expectedT.Free), len(actualT.Free), msg...) {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < len(expectedT.Free); i++ {
|
|
if !Equal(t, *expectedT.Free[i], *actualT.Free[i], msg...) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func isNil(v interface{}) bool {
|
|
if v == nil {
|
|
return true
|
|
}
|
|
|
|
value := reflect.ValueOf(v)
|
|
kind := value.Kind()
|
|
if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|