From 69e3019fbf09bc9592c52fc566a356d101fbc168 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sun, 8 Jul 2018 18:19:09 +0000 Subject: [PATCH] massert: implement Subset, Has, Fatal, and Error --- mtest/massert/massert.go | 156 +++++++++++++++++++++++++--------- mtest/massert/massert_test.go | 82 +++++++++++------- 2 files changed, 168 insertions(+), 70 deletions(-) diff --git a/mtest/massert/massert.go b/mtest/massert/massert.go index 968e341..f4ac1b7 100644 --- a/mtest/massert/massert.go +++ b/mtest/massert/massert.go @@ -9,6 +9,7 @@ import ( "reflect" "runtime" "strings" + "testing" "text/tabwriter" ) @@ -26,23 +27,31 @@ func fmtBlock(str string) string { return "\n\t" + strings.Replace(str, "\n", "\n\t", -1) + "\n" } -func fmtMultiDescr(prefix string, aa ...Assertion) string { - if len(aa) == 0 { +func fmtMultiBlock(prefix string, elems ...string) string { + if len(elems) == 0 { return prefix + "()" - } else if len(aa) == 1 { - return prefix + "(" + fmtBlock(aa[0].Description()) + ")" + } else if len(elems) == 1 { + return prefix + "(" + fmtBlock(elems[0]) + ")" } buf := new(bytes.Buffer) fmt.Fprintf(buf, "%s(\n", prefix) - for _, a := range aa { - descrStr := "\t" + strings.Replace(a.Description(), "\n", "\n\t", -1) - fmt.Fprintf(buf, "%s,\n", descrStr) + for _, el := range elems { + elStr := "\t" + strings.Replace(el, "\n", "\n\t", -1) + fmt.Fprintf(buf, "%s,\n", elStr) } fmt.Fprintf(buf, ")") 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 { buf := new(bytes.Buffer) tw := tabwriter.NewWriter(buf, 0, 4, 2, ' ', 0) @@ -134,6 +143,24 @@ func (a *assertion) Stack() []runtime.Frame { 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 @@ -256,48 +283,95 @@ func None(aa ...Assertion) Assertion { //////////////////////////////////////////////////////////////////////////////// -var typeOfInt64 = reflect.TypeOf(int64(0)) - func toStr(i interface{}) string { return fmt.Sprintf("%T(%#v)", i, i) } -// Equal asserts that the two values given are equal. The equality checking -// done is to some degree fuzzy in the following ways: -// -// * All pointers are dereferenced. -// * All ints and uints are converted to int64. -// +// Equal asserts that the two values are exactly equal, and uses the +// reflect.DeepEqual function to determine if they are. 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 { if !reflect.DeepEqual(a, b) { return errors.New("not exactly equal") } 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) } diff --git a/mtest/massert/massert_test.go b/mtest/massert/massert_test.go index a4e5081..4534a91 100644 --- a/mtest/massert/massert_test.go +++ b/mtest/massert/massert_test.go @@ -95,41 +95,65 @@ func TestNone(t *T) { } } -// TODO pointers, structs, slices, maps, nils func TestEqual(t *T) { - if err := All( + Fatal(t, All( Equal(1, 1), + Equal("foo", "foo"), + )) + + Fatal(t, None( + Equal(1, 2), Equal(1, int64(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"), - ).Assert(); err != nil { - t.Fatal(err) - } + )) } -func TestExactly(t *T) { - if err := All( - Exactly(1, 1), - Exactly("foo", "foo"), - ).Assert(); err != nil { - t.Fatal(err) - } +func TestSubset(t *T) { + Fatal(t, All( + Subset([]int{1, 2, 3}, []int{}), + Subset([]int{1, 2, 3}, []int{1}), + Subset([]int{1, 2, 3}, []int{2}), + 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( - Exactly(1, 2), - Exactly(1, int64(1)), - Exactly(1, uint64(1)), - Exactly("foo", "bar"), - ).Assert(); err != nil { - t.Fatal(err) - } + Subset(map[int]int{1: 1, 2: 2}, map[int]int{}), + Subset(map[int]int{1: 1, 2: 2}, map[int]int{1: 1}), + Subset(map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 2: 2}), + )) + + Fatal(t, None( + 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), + )) }