summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <m.shulhan@gmail.com>2020-11-16 20:18:04 +0700
committerShulhan <m.shulhan@gmail.com>2020-11-23 00:34:11 +0700
commitdcf00280684899154b9633c946dc4efeede2ebf9 (patch)
tree12c68878a2aec16ecf9076f40007631d434480b7
parent06c7987909976d799aa0ebb086fe22e34d44c13b (diff)
downloadpakakeh.go-dcf00280684899154b9633c946dc4efeede2ebf9.tar.xz
test: refactoring Assert with better error message
The new Assert function use the reflect.DoEqual that return an error which describe which field have unmatched value. This changes affect other test that use "false" as the last parameter.
-rw-r--r--lib/floats64/floats64_test.go2
-rw-r--r--lib/ints/ints_test.go2
-rw-r--r--lib/ints64/ints64_test.go2
-rw-r--r--lib/math/big/rat_test.go12
-rw-r--r--lib/reflect/reflect.go266
-rw-r--r--lib/tabula/columns_test.go26
-rw-r--r--lib/tabula/row_test.go1
-rw-r--r--lib/tabula/rows_test.go3
-rw-r--r--lib/test/test.go43
9 files changed, 306 insertions, 51 deletions
diff --git a/lib/floats64/floats64_test.go b/lib/floats64/floats64_test.go
index 1b53d2c5..5f8a1c67 100644
--- a/lib/floats64/floats64_test.go
+++ b/lib/floats64/floats64_test.go
@@ -175,8 +175,6 @@ func TestSwap(t *testing.T) {
Swap(in, 0, len(in)-1)
- test.Assert(t, "", exp, in, false)
-
tmp := exp[0]
exp[0] = exp[len(exp)-1]
exp[len(exp)-1] = tmp
diff --git a/lib/ints/ints_test.go b/lib/ints/ints_test.go
index 65ae6fa7..6393cd01 100644
--- a/lib/ints/ints_test.go
+++ b/lib/ints/ints_test.go
@@ -175,8 +175,6 @@ func TestSwap(t *testing.T) {
Swap(in, 0, len(in)-1)
- test.Assert(t, "", exp, in, false)
-
tmp := exp[0]
exp[0] = exp[len(exp)-1]
exp[len(exp)-1] = tmp
diff --git a/lib/ints64/ints64_test.go b/lib/ints64/ints64_test.go
index ee31f109..e0af44ca 100644
--- a/lib/ints64/ints64_test.go
+++ b/lib/ints64/ints64_test.go
@@ -174,8 +174,6 @@ func TestSwap(t *testing.T) {
Swap(in, 0, len(in)-1)
- test.Assert(t, "", exp, in, false)
-
tmp := exp[0]
exp[0] = exp[len(exp)-1]
exp[len(exp)-1] = tmp
diff --git a/lib/math/big/rat_test.go b/lib/math/big/rat_test.go
index 90267fa4..e40028ce 100644
--- a/lib/math/big/rat_test.go
+++ b/lib/math/big/rat_test.go
@@ -297,23 +297,15 @@ func TestRat_IsEqual_unexported(t *testing.T) {
}
cases := []struct {
- got *A
- expEqual bool
+ got *A
}{{
got: &A{
r: NewRat(10),
},
- expEqual: true,
- }, {
- got: &A{
- r: NewRat(11),
- },
- expEqual: false,
}}
for x, c := range cases {
- test.Assert(t, fmt.Sprintf("unexported field %d", x),
- exp, c.got, c.expEqual)
+ test.Assert(t, fmt.Sprintf("unexported field %d", x), exp, c.got, false)
}
}
diff --git a/lib/reflect/reflect.go b/lib/reflect/reflect.go
index 167188a6..b0d06218 100644
--- a/lib/reflect/reflect.go
+++ b/lib/reflect/reflect.go
@@ -8,7 +8,9 @@
package reflect
import (
+ "fmt"
"reflect"
+ "unsafe"
)
//
@@ -26,6 +28,21 @@ func IsNil(v interface{}) bool {
}
//
+// DoEqual is a naive interfaces comparison that check and use Equaler
+// interface and return an error if its not match.
+//
+func DoEqual(x, y interface{}) (err error) {
+ if x == nil && y == nil {
+ return nil
+ }
+
+ v1 := reflect.ValueOf(x)
+ v2 := reflect.ValueOf(y)
+
+ return doEqual(v1, v2)
+}
+
+//
// IsEqual is a naive interfaces comparison that check and use Equaler
// interface.
//
@@ -40,6 +57,255 @@ func IsEqual(x, y interface{}) bool {
return isEqual(v1, v2)
}
+//
+// doEqual compare two kind of objects and return nils if both are equal.
+//
+// If its not equal, it will return the interface{} value of v1 and v2 and
+// additional error message which describe the type and value where its not
+// matched.
+//
+func doEqual(v1, v2 reflect.Value) (err error) {
+ var in1, in2 interface{}
+
+ if !v1.IsValid() || !v2.IsValid() {
+ if v1.IsValid() == v2.IsValid() {
+ return nil
+ }
+ return fmt.Errorf("IsValid: expecting %s(%v), got %s(%v)",
+ v1.String(), v1.IsValid(), v2.String(), v2.IsValid())
+ }
+
+ t1 := v1.Type()
+ t2 := v2.Type()
+ name1 := t1.Name()
+ name2 := t2.Name()
+ if t1 != t2 {
+ return fmt.Errorf("Type: expecting %s(%v), got %s(%v)",
+ name1, t1.String(), name2, t2.String())
+ }
+
+ if v1.CanSet() {
+ in1 = v1.Interface()
+ } else if v1.CanAddr() {
+ in1 = reflect.NewAt(t1, unsafe.Pointer(v1.UnsafeAddr())).Elem()
+ }
+
+ if v2.CanSet() {
+ in2 = v2.Interface()
+ } else if v2.CanAddr() {
+ in2 = reflect.NewAt(t2, unsafe.Pointer(v2.UnsafeAddr())).Elem()
+ }
+
+ k1 := v1.Kind()
+ k2 := v2.Kind()
+ if k1 != k2 {
+ return fmt.Errorf("Kind: expecting %s(%v), got %s(%v)",
+ name1, v1.String(), name2, v2.String())
+ }
+
+ // For debugging.
+ //log.Printf("v1:%v(%s(%v)) v2:%v(%s(%v))", k1, t1.String(), v1,
+ // k2, t2.String(), v2)
+
+ switch k1 {
+ case reflect.Bool:
+ if v1.Bool() == v2.Bool() {
+ return nil
+ }
+ return fmt.Errorf("expecting %s(%v), got %s(%v)",
+ name1, v1.Bool(), name2, v2.Bool())
+
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
+ reflect.Int64:
+ if v1.Int() == v2.Int() {
+ return nil
+ }
+ return fmt.Errorf("expecting %s(%v), got %s(%v)",
+ name1, v1.Int(), name2, v2.Int())
+
+ case reflect.Uint, reflect.Uint8, reflect.Uint16,
+ reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ if v1.Uint() == v2.Uint() {
+ return nil
+ }
+ return fmt.Errorf("expecting %s(%v), got %s(%v)",
+ name1, v1.Uint(), name2, v2.Uint())
+
+ case reflect.Float32, reflect.Float64:
+ if v1.Float() == v2.Float() {
+ return nil
+ }
+ return fmt.Errorf("expecting %s(%v), got %s(%v)",
+ name1, v1.Float(), name2, v2.Float())
+
+ case reflect.Complex64, reflect.Complex128:
+ if v1.Complex() == v2.Complex() {
+ return nil
+ }
+ return fmt.Errorf("expecting %s(%v), got %s(%v)",
+ name1, v1.Complex(), name2, v2.Complex())
+
+ case reflect.Array:
+ if v1.Len() != v2.Len() {
+ return fmt.Errorf("len(%s): expecting %v, got %v",
+ name1, v1.Len(), v2.Len())
+ }
+ for x := 0; x < v1.Len(); x++ {
+ err = doEqual(v1.Index(x), v2.Index(x))
+ if err != nil {
+ return fmt.Errorf("%s[%d]: %s", name1, x, err)
+ }
+ }
+ return nil
+
+ case reflect.Chan:
+ if v1.IsNil() && v2.IsNil() {
+ return nil
+ }
+ if t1 == t2 {
+ return nil
+ }
+
+ case reflect.Func:
+ if v1.IsNil() && v2.IsNil() {
+ return nil
+ }
+ if v2.IsNil() {
+ return fmt.Errorf("%s(%v): expecting non-nil, got nil",
+ name1, v1.String())
+ }
+ if t1 == t2 {
+ return nil
+ }
+
+ case reflect.Interface:
+ if v1.IsNil() && v2.IsNil() {
+ return nil
+ }
+ if v2.IsNil() {
+ return fmt.Errorf("%s(%v): expecting non-nil, got nil", name1, in1)
+ }
+ return doEqual(v1.Elem(), v2.Elem())
+
+ case reflect.Map:
+ return doEqualMap(v1, v2)
+
+ case reflect.Ptr:
+ if v1.IsNil() && v2.IsNil() {
+ return nil
+ }
+ if v2.IsNil() {
+ return fmt.Errorf("%s(%v): expecting non-nil got nil",
+ name1, in1)
+ }
+ if v1.Pointer() == v2.Pointer() {
+ return nil
+ }
+ return doEqual(v1.Elem(), v2.Elem())
+
+ case reflect.Slice:
+ if v1.IsNil() && v2.IsNil() {
+ return nil
+ }
+ if v2.IsNil() {
+ return fmt.Errorf("%s(%v): expecting non-nil, got nil",
+ name1, in1)
+ }
+
+ l1 := v1.Len()
+ l2 := v2.Len()
+ if l1 != l2 {
+ return fmt.Errorf("len(%s): expecting %v, got %v",
+ name1, l1, l2)
+ }
+
+ for x := 0; x < l1; x++ {
+ s1 := v1.Index(x)
+ s2 := v2.Index(x)
+ err = doEqual(s1, s2)
+ if err != nil {
+ return fmt.Errorf("%s[%d]: %s", name1, x, err)
+ }
+ }
+ return nil
+
+ case reflect.String:
+ if v1.String() == v2.String() {
+ return nil
+ }
+ return fmt.Errorf("expecting %s(%v), got %s(%v)",
+ name1, v1.String(), name2, v2.String())
+
+ case reflect.Struct:
+ return doEqualStruct(v1, v2)
+
+ case reflect.UnsafePointer:
+ if v1.UnsafeAddr() == v2.UnsafeAddr() {
+ return nil
+ }
+ }
+
+ return fmt.Errorf("expecting %s(%v), got %s(%v)", name1, in1, name2, in2)
+}
+
+func doEqualMap(v1, v2 reflect.Value) (err error) {
+ if v1.IsNil() && v2.IsNil() {
+ return nil
+ }
+ if v2.IsNil() {
+ return fmt.Errorf("Map(%s) expecting non-nil, got nil", v1.String())
+ }
+
+ name1 := v1.Type().Name()
+
+ if v1.Len() != v2.Len() {
+ return fmt.Errorf("len(map(%s)): expecting %d, got %d",
+ name1, v1.Len(), v2.Len())
+ }
+ keys := v1.MapKeys()
+ for x := 0; x < len(keys); x++ {
+ tipe := keys[x].Type()
+ name := tipe.Name()
+ err = doEqual(v1.MapIndex(keys[x]), v2.MapIndex(keys[x]))
+ if err != nil {
+ return fmt.Errorf("Map[%s(%v)] %s", name,
+ keys[x].Interface(), err)
+ }
+ }
+ return nil
+}
+
+func doEqualStruct(v1, v2 reflect.Value) (err error) {
+ m1 := v1.MethodByName("IsEqual")
+ if m1.IsValid() {
+ res := m1.Call([]reflect.Value{
+ v2.Addr(),
+ })
+ if len(res) == 1 && res[0].Kind() == reflect.Bool {
+ if res[0].Bool() {
+ return nil
+ }
+ return fmt.Errorf("IsEqual: %s.IsEqual(%s) return false",
+ v1.String(), v2.String())
+ }
+ }
+
+ t1 := v1.Type()
+ v1Name := t1.Name()
+
+ n := v1.NumField()
+ for x := 0; x < n; x++ {
+ f1 := v1.Field(x)
+ f1Name := t1.Field(x).Name
+ f2 := v2.Field(x)
+ err = doEqual(f1, f2)
+ if err != nil {
+ return fmt.Errorf("%s.%s: %s", v1Name, f1Name, err)
+ }
+ }
+ return nil
+}
+
func isEqual(v1, v2 reflect.Value) bool {
if !v1.IsValid() || !v2.IsValid() {
return v1.IsValid() == v2.IsValid()
diff --git a/lib/tabula/columns_test.go b/lib/tabula/columns_test.go
index 43b30028..cbec00ad 100644
--- a/lib/tabula/columns_test.go
+++ b/lib/tabula/columns_test.go
@@ -7,7 +7,7 @@ package tabula
import (
"testing"
- "github.com/shuLhan/share/lib/test"
+ "github.com/shuLhan/share/lib/reflect"
)
func TestRandomPickColumns(t *testing.T) {
@@ -23,25 +23,10 @@ func TestRandomPickColumns(t *testing.T) {
dataset.TransposeToColumns()
- // random pick with duplicate
+ // random pick without duplicate
+ dup := false
ncols := 6
- dup := true
excludeIdx := []int{3}
-
- for i := 0; i < 5; i++ {
- picked, unpicked, _, _ :=
- dataset.Columns.RandomPick(ncols, dup, excludeIdx)
-
- // check if unpicked item exist in picked items.
- for _, un := range unpicked {
- for _, pick := range picked {
- test.Assert(t, "", un, pick, false)
- }
- }
- }
-
- // random pick without duplicate
- dup = false
for i := 0; i < 5; i++ {
picked, unpicked, _, _ :=
dataset.Columns.RandomPick(ncols, dup, excludeIdx)
@@ -49,7 +34,10 @@ func TestRandomPickColumns(t *testing.T) {
// check if unpicked item exist in picked items.
for _, un := range unpicked {
for _, pick := range picked {
- test.Assert(t, "", un, pick, false)
+ err := reflect.DoEqual(un, pick)
+ if err == nil {
+ t.Fatalf("unpicked column exist in picked: %v", un)
+ }
}
}
}
diff --git a/lib/tabula/row_test.go b/lib/tabula/row_test.go
index 1003969f..e40a1cf0 100644
--- a/lib/tabula/row_test.go
+++ b/lib/tabula/row_test.go
@@ -28,5 +28,4 @@ func TestClone(t *testing.T) {
// changing the clone value should not change the original copy.
(*rowClone2)[0].SetFloat(0)
test.Assert(t, "", &row, rowClone, true)
- test.Assert(t, "", &row, rowClone2, false)
}
diff --git a/lib/tabula/rows_test.go b/lib/tabula/rows_test.go
index 174dd10f..56e1b5cc 100644
--- a/lib/tabula/rows_test.go
+++ b/lib/tabula/rows_test.go
@@ -134,9 +134,6 @@ func TestRandomPick(t *testing.T) {
picked, unpicked, pickedIdx, unpickedIdx := rows.RandomPick(3,
false)
- // check if picked rows is duplicate
- test.Assert(t, "", picked[0], picked[1], false)
-
// check if unpicked item exist in picked items.
isin, _ := picked.Contains(unpicked)
diff --git a/lib/test/test.go b/lib/test/test.go
index a4b5f04e..a8343bd2 100644
--- a/lib/test/test.go
+++ b/lib/test/test.go
@@ -37,27 +37,32 @@ func printStackTrace(t testing.TB, trace []byte) {
}
//
-// Assert will compare two interfaces: `exp` and `got` whether its same with
-// `equal` value.
+// Assert will compare two interfaces: `exp` and `got` for equality.
+// If both are not equal, the test will throw panic parameter describe the
+// position (type and value) where both are not matched.
//
// If `exp` implement the extended `reflect.Equaler`, then it will use the
// method `IsEqual()` with `got` as parameter.
//
-// If comparison result is not same with `equal`, it will print the result and
-// expectation and then terminate the test routine.
+// If debug parameter is true it will print the stack trace of testing.T
+// instance.
//
-func Assert(t *testing.T, name string, exp, got interface{}, equal bool) {
- if reflect.IsEqual(exp, got) == equal {
+// WARNING: this method does not support recursive pointer, for example node
+// that point to parent and parent that point back to node.
+//
+func Assert(t *testing.T, name string, exp, got interface{}, debug bool) {
+ err := reflect.DoEqual(exp, got)
+ if err == nil {
return
}
- trace := make([]byte, 1024)
- runtime.Stack(trace, false)
-
- printStackTrace(t, trace)
+ if debug {
+ trace := make([]byte, 1024)
+ runtime.Stack(trace, false)
+ printStackTrace(t, trace)
+ }
- t.Fatalf(">>> Got %s:\n\t'%+v';\n"+
- " want:\n\t'%+v'\n", name, got, exp)
+ t.Fatalf("!!! %s: %s", name, err)
}
//
@@ -80,3 +85,17 @@ func AssertBench(b *testing.B, name string, exp, got interface{}, equal bool) {
b.Fatalf(">>> Got %s:\n\t'%+v';\n"+
" want:\n\t'%+v'\n", name, got, exp)
}
+
+func AssertBench2(b *testing.B, name string, exp, got interface{}) {
+ err := reflect.DoEqual(exp, got)
+ if err == nil {
+ return
+ }
+
+ trace := make([]byte, 1024)
+ runtime.Stack(trace, false)
+
+ printStackTrace(b, trace)
+
+ b.Fatalf("!!! %s: %s", name, err)
+}