massert: implement Subset, Has, Fatal, and Error
This commit is contained in:
parent
c297e09446
commit
69e3019fbf
@ -9,6 +9,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"testing"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,23 +27,31 @@ func fmtBlock(str string) string {
|
|||||||
return "\n\t" + strings.Replace(str, "\n", "\n\t", -1) + "\n"
|
return "\n\t" + strings.Replace(str, "\n", "\n\t", -1) + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
func fmtMultiDescr(prefix string, aa ...Assertion) string {
|
func fmtMultiBlock(prefix string, elems ...string) string {
|
||||||
if len(aa) == 0 {
|
if len(elems) == 0 {
|
||||||
return prefix + "()"
|
return prefix + "()"
|
||||||
} else if len(aa) == 1 {
|
} else if len(elems) == 1 {
|
||||||
return prefix + "(" + fmtBlock(aa[0].Description()) + ")"
|
return prefix + "(" + fmtBlock(elems[0]) + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
fmt.Fprintf(buf, "%s(\n", prefix)
|
fmt.Fprintf(buf, "%s(\n", prefix)
|
||||||
for _, a := range aa {
|
for _, el := range elems {
|
||||||
descrStr := "\t" + strings.Replace(a.Description(), "\n", "\n\t", -1)
|
elStr := "\t" + strings.Replace(el, "\n", "\n\t", -1)
|
||||||
fmt.Fprintf(buf, "%s,\n", descrStr)
|
fmt.Fprintf(buf, "%s,\n", elStr)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(buf, ")")
|
fmt.Fprintf(buf, ")")
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fmtMultiDescr(prefix string, aa ...Assertion) string {
|
||||||
|
descrs := make([]string, len(aa))
|
||||||
|
for i := range aa {
|
||||||
|
descrs[i] = aa[i].Description()
|
||||||
|
}
|
||||||
|
return fmtMultiBlock(prefix, descrs...)
|
||||||
|
}
|
||||||
|
|
||||||
func fmtStack(frames []runtime.Frame) string {
|
func fmtStack(frames []runtime.Frame) string {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
tw := tabwriter.NewWriter(buf, 0, 4, 2, ' ', 0)
|
tw := tabwriter.NewWriter(buf, 0, 4, 2, ' ', 0)
|
||||||
@ -134,6 +143,24 @@ func (a *assertion) Stack() []runtime.Frame {
|
|||||||
return a.stack
|
return a.stack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Fatal is a convenience function which performs the Assertion and calls Fatal
|
||||||
|
// on the testing.T instance if the assertion fails.
|
||||||
|
func Fatal(t *testing.T, a Assertion) {
|
||||||
|
if err := a.Assert(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is a convenience function which performs the Assertion and calls Error
|
||||||
|
// on the testing.T instance if the assertion fails.
|
||||||
|
func Error(t *testing.T, a Assertion) {
|
||||||
|
if err := a.Assert(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Assertion wrappers
|
// Assertion wrappers
|
||||||
|
|
||||||
@ -256,48 +283,95 @@ func None(aa ...Assertion) Assertion {
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
var typeOfInt64 = reflect.TypeOf(int64(0))
|
|
||||||
|
|
||||||
func toStr(i interface{}) string {
|
func toStr(i interface{}) string {
|
||||||
return fmt.Sprintf("%T(%#v)", i, i)
|
return fmt.Sprintf("%T(%#v)", i, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal asserts that the two values given are equal. The equality checking
|
// Equal asserts that the two values are exactly equal, and uses the
|
||||||
// done is to some degree fuzzy in the following ways:
|
// reflect.DeepEqual function to determine if they are.
|
||||||
//
|
|
||||||
// * All pointers are dereferenced.
|
|
||||||
// * All ints and uints are converted to int64.
|
|
||||||
//
|
|
||||||
func Equal(a, b interface{}) Assertion {
|
func Equal(a, b interface{}) Assertion {
|
||||||
normalize := func(v reflect.Value) reflect.Value {
|
|
||||||
v = reflect.Indirect(v)
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
|
||||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
v = v.Convert(typeOfInt64)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := func() error {
|
|
||||||
aV, bV := reflect.ValueOf(a), reflect.ValueOf(b)
|
|
||||||
aV, bV = normalize(aV), normalize(bV)
|
|
||||||
if !reflect.DeepEqual(aV.Interface(), bV.Interface()) {
|
|
||||||
return errors.New("not equal")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return newAssertion(fn, toStr(a)+" == "+toStr(b), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exactly asserts that the two values are exactly equal, and uses the
|
|
||||||
// reflect.DeepEquals function to determine if they are.
|
|
||||||
func Exactly(a, b interface{}) Assertion {
|
|
||||||
return newAssertion(func() error {
|
return newAssertion(func() error {
|
||||||
if !reflect.DeepEqual(a, b) {
|
if !reflect.DeepEqual(a, b) {
|
||||||
return errors.New("not exactly equal")
|
return errors.New("not exactly equal")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, toStr(a)+" === "+toStr(b), 0)
|
}, toStr(a)+" == "+toStr(b), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSet(i interface{}, keyedMap bool) ([]interface{}, error) {
|
||||||
|
v := reflect.ValueOf(i)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
vv := make([]interface{}, v.Len())
|
||||||
|
for i := range vv {
|
||||||
|
vv[i] = v.Index(i).Interface()
|
||||||
|
}
|
||||||
|
return vv, nil
|
||||||
|
case reflect.Map:
|
||||||
|
keys := v.MapKeys()
|
||||||
|
vv := make([]interface{}, len(keys))
|
||||||
|
for i := range keys {
|
||||||
|
if keyedMap {
|
||||||
|
vv[i] = struct{ k, v interface{} }{
|
||||||
|
k: keys[i].Interface(),
|
||||||
|
v: v.MapIndex(keys[i]).Interface(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vv[i] = v.MapIndex(keys[i]).Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vv, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("cannot turn value of type %s into a set", v.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subset asserts that the given subset is a subset of the given set. Both must
|
||||||
|
// be of the same type and may be arrays, slices, or maps.
|
||||||
|
func Subset(set, subset interface{}) Assertion {
|
||||||
|
return newAssertion(func() error {
|
||||||
|
if reflect.TypeOf(set) != reflect.TypeOf(subset) {
|
||||||
|
return errors.New("set and subset aren't of same type")
|
||||||
|
}
|
||||||
|
|
||||||
|
setVV, err := toSet(set, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
subsetVV, err := toSet(subset, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is obviously not the most efficient way to do this
|
||||||
|
outer:
|
||||||
|
for i := range subsetVV {
|
||||||
|
for j := range setVV {
|
||||||
|
if reflect.DeepEqual(setVV[j], subsetVV[i]) {
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("missing element %s", toStr(subsetVV[i]))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, toStr(set)+" has subset "+toStr(subset), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has asserts that the given set has the given element as a value in it. The
|
||||||
|
// set may be an array, a slice, or a map, and if it's a map then the elem will
|
||||||
|
// need to be a value in it.
|
||||||
|
func Has(set, elem interface{}) Assertion {
|
||||||
|
return newAssertion(func() error {
|
||||||
|
setVV, err := toSet(set, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range setVV {
|
||||||
|
if reflect.DeepEqual(setVV[i], elem) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("value not in set")
|
||||||
|
}, toStr(set)+" has value "+toStr(elem), 0)
|
||||||
}
|
}
|
||||||
|
@ -95,41 +95,65 @@ func TestNone(t *T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO pointers, structs, slices, maps, nils
|
|
||||||
func TestEqual(t *T) {
|
func TestEqual(t *T) {
|
||||||
if err := All(
|
Fatal(t, All(
|
||||||
Equal(1, 1),
|
Equal(1, 1),
|
||||||
|
Equal("foo", "foo"),
|
||||||
|
))
|
||||||
|
|
||||||
|
Fatal(t, None(
|
||||||
|
Equal(1, 2),
|
||||||
Equal(1, int64(1)),
|
Equal(1, int64(1)),
|
||||||
Equal(1, uint64(1)),
|
Equal(1, uint64(1)),
|
||||||
Equal("foo", "foo"),
|
|
||||||
).Assert(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := None(
|
|
||||||
Equal(1, 2),
|
|
||||||
Equal(1, int64(2)),
|
|
||||||
Equal(1, uint64(2)),
|
|
||||||
Equal("foo", "bar"),
|
Equal("foo", "bar"),
|
||||||
).Assert(); err != nil {
|
))
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExactly(t *T) {
|
func TestSubset(t *T) {
|
||||||
if err := All(
|
Fatal(t, All(
|
||||||
Exactly(1, 1),
|
Subset([]int{1, 2, 3}, []int{}),
|
||||||
Exactly("foo", "foo"),
|
Subset([]int{1, 2, 3}, []int{1}),
|
||||||
).Assert(); err != nil {
|
Subset([]int{1, 2, 3}, []int{2}),
|
||||||
t.Fatal(err)
|
Subset([]int{1, 2, 3}, []int{1, 2}),
|
||||||
}
|
Subset([]int{1, 2, 3}, []int{2, 1}),
|
||||||
|
Subset([]int{1, 2, 3}, []int{1, 2, 3}),
|
||||||
|
Subset([]int{1, 2, 3}, []int{1, 3, 2}),
|
||||||
|
|
||||||
if err := None(
|
Subset(map[int]int{1: 1, 2: 2}, map[int]int{}),
|
||||||
Exactly(1, 2),
|
Subset(map[int]int{1: 1, 2: 2}, map[int]int{1: 1}),
|
||||||
Exactly(1, int64(1)),
|
Subset(map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 2: 2}),
|
||||||
Exactly(1, uint64(1)),
|
))
|
||||||
Exactly("foo", "bar"),
|
|
||||||
).Assert(); err != nil {
|
Fatal(t, None(
|
||||||
t.Fatal(err)
|
Subset([]int64{1, 2, 3}, []int{1}),
|
||||||
}
|
Subset([]int{}, []int{1, 2, 3}),
|
||||||
|
Subset([]int{1, 2, 3}, []int{4}),
|
||||||
|
Subset([]int{1, 2, 3}, []int{1, 3, 2, 4}),
|
||||||
|
|
||||||
|
Subset(map[int]int{1: 1, 2: 2}, map[int]int64{1: 1}),
|
||||||
|
Subset(map[int]int{1: 1, 2: 2}, map[int]int{1: 2}),
|
||||||
|
Subset(map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 3: 3}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHas(t *T) {
|
||||||
|
Fatal(t, All(
|
||||||
|
Has([]int{1}, 1),
|
||||||
|
Has([]int{1, 2}, 1),
|
||||||
|
Has([]int{2, 1}, 1),
|
||||||
|
Has(map[int]int{1: 1}, 1),
|
||||||
|
Has(map[int]int{1: 2}, 2),
|
||||||
|
Has(map[int]int{1: 2, 2: 1}, 1),
|
||||||
|
Has(map[int]int{1: 2, 2: 2}, 2),
|
||||||
|
))
|
||||||
|
|
||||||
|
Fatal(t, None(
|
||||||
|
Has([]int{}, 1),
|
||||||
|
Has([]int{1}, 2),
|
||||||
|
Has([]int{2, 1}, 3),
|
||||||
|
Has(map[int]int{}, 1),
|
||||||
|
Has(map[int]int{1: 1}, 2),
|
||||||
|
Has(map[int]int{1: 2}, 1),
|
||||||
|
Has(map[int]int{1: 2, 2: 1}, 3),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user