Print more details in ElementsMatch

It is not very helpful to print that the lengths differ when an
assertion fails, since that does not reveal what the cause of the issue
might be.

Let's print which elements are extra in each list, that should convey
the relevant information to the user. Also use spew to format the
objects, similar to what Equal does, to make the output more readable.
pull/579/head
Martin Sucha 2020-02-18 12:38:35 +01:00 committed by Boyan Soubachov
parent 961bfee4b1
commit f6cbfc0d03
2 changed files with 136 additions and 13 deletions

View File

@ -894,27 +894,39 @@ func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface
return true
}
aKind := reflect.TypeOf(listA).Kind()
bKind := reflect.TypeOf(listB).Kind()
if aKind != reflect.Array && aKind != reflect.Slice {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...)
if !isList(t, listA, msgAndArgs...) || !isList(t, listB, msgAndArgs...) {
return false
}
if bKind != reflect.Array && bKind != reflect.Slice {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...)
extraA, extraB := diffLists(listA, listB)
if len(extraA) == 0 && len(extraB) == 0 {
return true
}
return Fail(t, formatListDiff(listA, listB, extraA, extraB), msgAndArgs...)
}
// isList checks that the provided value is array or slice.
func isList(t TestingT, list interface{}, msgAndArgs ...interface{}) (ok bool) {
kind := reflect.TypeOf(list).Kind()
if kind != reflect.Array && kind != reflect.Slice {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s, expecting array or slice", list, kind),
msgAndArgs...)
}
return true
}
// diffLists diffs two arrays/slices and returns slices of elements that are only in A and only in B.
// If some element is present multiple times, each instance is counted separately (e.g. if something is 2x in A and
// 5x in B, it will be 0x in extraA and 3x in extraB). The order of items in both lists is ignored.
func diffLists(listA, listB interface{}) (extraA, extraB []interface{}) {
aValue := reflect.ValueOf(listA)
bValue := reflect.ValueOf(listB)
aLen := aValue.Len()
bLen := bValue.Len()
if aLen != bLen {
return Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...)
}
// Mark indexes in bValue that we already used
visited := make([]bool, bLen)
for i := 0; i < aLen; i++ {
@ -931,11 +943,38 @@ func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface
}
}
if !found {
return Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...)
extraA = append(extraA, element)
}
}
return true
for j := 0; j < bLen; j++ {
if visited[j] {
continue
}
extraB = append(extraB, bValue.Index(j).Interface())
}
return
}
func formatListDiff(listA, listB interface{}, extraA, extraB []interface{}) string {
var msg bytes.Buffer
msg.WriteString("elements differ")
if len(extraA) > 0 {
msg.WriteString("\n\nextra elements in list A:\n")
msg.WriteString(spewConfig.Sdump(extraA))
}
if len(extraB) > 0 {
msg.WriteString("\n\nextra elements in list B:\n")
msg.WriteString(spewConfig.Sdump(extraB))
}
msg.WriteString("\n\nlistA:\n")
msg.WriteString(spewConfig.Sdump(listA))
msg.WriteString("\n\nlistB:\n")
msg.WriteString(spewConfig.Sdump(listB))
return msg.String()
}
// Condition uses a Comparison to assert a complex condition.

View File

@ -801,6 +801,90 @@ func TestElementsMatch(t *testing.T) {
}
}
func TestDiffLists(t *testing.T) {
tests := []struct {
name string
listA interface{}
listB interface{}
extraA []interface{}
extraB []interface{}
}{
{
name: "equal empty",
listA: []string{},
listB: []string{},
extraA: nil,
extraB: nil,
},
{
name: "equal same order",
listA: []string{"hello", "world"},
listB: []string{"hello", "world"},
extraA: nil,
extraB: nil,
},
{
name: "equal different order",
listA: []string{"hello", "world"},
listB: []string{"world", "hello"},
extraA: nil,
extraB: nil,
},
{
name: "extra A",
listA: []string{"hello", "hello", "world"},
listB: []string{"hello", "world"},
extraA: []interface{}{"hello"},
extraB: nil,
},
{
name: "extra A twice",
listA: []string{"hello", "hello", "hello", "world"},
listB: []string{"hello", "world"},
extraA: []interface{}{"hello", "hello"},
extraB: nil,
},
{
name: "extra B",
listA: []string{"hello", "world"},
listB: []string{"hello", "hello", "world"},
extraA: nil,
extraB: []interface{}{"hello"},
},
{
name: "extra B twice",
listA: []string{"hello", "world"},
listB: []string{"hello", "hello", "world", "hello"},
extraA: nil,
extraB: []interface{}{"hello", "hello"},
},
{
name: "integers 1",
listA: []int{1, 2, 3, 4, 5},
listB: []int{5, 4, 3, 2, 1},
extraA: nil,
extraB: nil,
},
{
name: "integers 2",
listA: []int{1, 2, 1, 2, 1},
listB: []int{2, 1, 2, 1, 2},
extraA: []interface{}{1},
extraB: []interface{}{2},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
actualExtraA, actualExtraB := diffLists(test.listA, test.listB)
Equal(t, test.extraA, actualExtraA, "extra A does not match for listA=%v listB=%v",
test.listA, test.listB)
Equal(t, test.extraB, actualExtraB, "extra B does not match for listA=%v listB=%v",
test.listA, test.listB)
})
}
}
func TestCondition(t *testing.T) {
mockT := new(testing.T)