in structmatcher/structmatcher.go [27:104]
func structMatcher(expected, actual reflect.Value, fieldPath string, shouldFilter bool, filterInclude bool, filterFields ...string) []string {
// Add field names for the top-level struct to a filter map, and split off nested field names to pass down to nested structs
filterMap := make(map[string]bool)
nestedFilterFields := make([]string, 0)
for i := 0; i < len(filterFields); i++ {
fieldNames := strings.Split(filterFields[i], ".")
if len(fieldNames) == 2 {
nestedFilterFields = append(nestedFilterFields, fieldNames[1])
// If we include a nested struct field, we also need to include the nested struct
if filterInclude {
filterMap[fieldNames[0]] = true
}
} else {
filterMap[filterFields[i]] = true
}
}
expectedStruct := reflect.Indirect(expected)
actualStruct := reflect.Indirect(actual)
mismatches := []string{}
mismatches = append(mismatches, InterceptGomegaFailures(func() {
structCanInterface := true
for i := 0; i < expectedStruct.NumField(); i++ {
expectedField := reflect.Indirect(expectedStruct.Field(i))
actualField := reflect.Indirect(actualStruct.Field(i))
fieldName := actualStruct.Type().Field(i).Name
// If we're including, skip this field if the name doesn't match; if we're excluding, skip if it does match
if shouldFilter && ((filterInclude && !filterMap[fieldName]) || (!filterInclude && filterMap[fieldName])) {
continue
}
actualFieldIsNonemptySlice := actualField.Kind() == reflect.Slice && !actualField.IsNil() && actualField.Len() > 0
expectedFieldIsNonemptySlice := expectedField.Kind() == reflect.Slice && !expectedField.IsNil() && expectedField.Len() > 0
fieldIsStructSlice := actualFieldIsNonemptySlice && expectedFieldIsNonemptySlice && actualField.Len() == expectedField.Len() && actualField.Index(0).Kind() == reflect.Struct
expectedFieldIsNilPtr := expectedStruct.Field(i).Kind() == reflect.Ptr && expectedStruct.Field(i).IsNil()
actualFieldIsNilPtr := actualStruct.Field(i).Kind() == reflect.Ptr && actualStruct.Field(i).IsNil()
if fieldIsStructSlice {
for j := 0; j < actualField.Len(); j++ {
expectedStructField := expectedStruct.Field(i).Index(j)
actualStructField := actualStruct.Field(i).Index(j)
subFieldPath := fmt.Sprintf("%s%s[%d].", fieldPath, fieldName, j)
mismatches = append(mismatches, structMatcher(expectedStructField, actualStructField, subFieldPath, shouldFilter, filterInclude, nestedFilterFields...)...)
}
} else if actualFieldIsNilPtr != expectedFieldIsNilPtr {
expectedValue := expectedStruct.Field(i).Interface()
actualValue := actualStruct.Field(i).Interface()
Expect(actualValue).To(Equal(expectedValue), "Mismatch on field %s%s", fieldPath, fieldName)
} else if expectedStruct.Field(i).CanInterface() {
if actualField.Kind() == reflect.Struct {
expectedStructField := expectedStruct.Field(i)
actualStructField := actualStruct.Field(i)
subFieldPath := fmt.Sprintf("%s%s.", fieldPath, fieldName)
mismatches = append(mismatches, structMatcher(expectedStructField, actualStructField, subFieldPath, shouldFilter, filterInclude, nestedFilterFields...)...)
} else {
expectedValue := expectedStruct.Field(i).Interface()
actualValue := actualStruct.Field(i).Interface()
Expect(actualValue).To(Equal(expectedValue), "Mismatch on field %s%s", fieldPath, fieldName)
}
} else {
structCanInterface = false
}
}
if !structCanInterface {
extra := []interface{}{
"Mismatch on unexported field within top level struct",
}
if fieldPath != "" {
structName := fieldPath[0 : len(fieldPath)-1] // remove trailing dot.
extra = []interface{}{
"Mismatch on unexported field within %s", structName,
}
}
Expect(actualStruct.Interface()).To(Equal(expectedStruct.Interface()), extra...)
}
})...)
return mismatches
}