in testing/struct.go [27:127]
func deepEqual(expect, actual reflect.Value, path string) error {
if et, at := expect.Kind(), actual.Kind(); et != at {
return fmt.Errorf("%s: kind %s != %s", path, et, at)
}
// there are a handful of short-circuit cases here within the context of
// operation responses:
// - ResultMetadata (we don't care)
// - document.Interface (check for marshaled []byte equality)
// - io.Reader (check for Read() []byte equality)
ei, ai := expect.Interface(), actual.Interface()
if _, _, ok := asMetadatas(ei, ai); ok {
return nil
}
if e, a, ok := asDocuments(ei, ai); ok {
if !compareDocumentTypes(e, a) {
return fmt.Errorf("%s: document values unequal", path)
}
return nil
}
if e, a, ok := asReaders(ei, ai); ok {
if err := CompareReaders(e, a); err != nil {
return fmt.Errorf("%s: %w", path, err)
}
return nil
}
switch expect.Kind() {
case reflect.Pointer:
if expect.Type() != actual.Type() {
return fmt.Errorf("%s: type mismatch", path)
}
expect = deref(expect)
actual = deref(actual)
ek, ak := expect.Kind(), actual.Kind()
if ek == reflect.Invalid || ak == reflect.Invalid {
// one was a nil pointer, so they both must be nil
if ek == ak {
return nil
}
return fmt.Errorf("%s: %s != %s", path, fmtNil(ek), fmtNil(ak))
}
if err := deepEqual(expect, actual, path); err != nil {
return err
}
return nil
case reflect.Slice:
if expect.Len() != actual.Len() {
return fmt.Errorf("%s: slice length unequal", path)
}
for i := 0; i < expect.Len(); i++ {
ipath := fmt.Sprintf("%s[%d]", path, i)
if err := deepEqual(expect.Index(i), actual.Index(i), ipath); err != nil {
return err
}
}
return nil
case reflect.Map:
if expect.Len() != actual.Len() {
return fmt.Errorf("%s: map length unequal", path)
}
for _, k := range expect.MapKeys() {
kpath := fmt.Sprintf("%s[%q]", path, k.String())
if err := deepEqual(expect.MapIndex(k), actual.MapIndex(k), kpath); err != nil {
return err
}
}
return nil
case reflect.Struct:
for i := 0; i < expect.NumField(); i++ {
if !expect.Field(i).CanInterface() {
continue // unexported
}
fpath := fmt.Sprintf("%s.%s", path, expect.Type().Field(i).Name)
if err := deepEqual(expect.Field(i), actual.Field(i), fpath); err != nil {
return err
}
}
return nil
case reflect.Float32, reflect.Float64:
ef, af := expect.Float(), actual.Float()
ebits, abits := math.Float64bits(ef), math.Float64bits(af)
if enan, anan := math.IsNaN(ef), math.IsNaN(af); enan || anan {
if enan != anan {
return fmt.Errorf("%s: NaN: float64(0x%x) != float64(0x%x)", path, ebits, abits)
}
return nil
}
if ebits != abits {
return fmt.Errorf("%s: float64(0x%x) != float64(0x%x)", path, ebits, abits)
}
return nil
default:
// everything else is just scalars and can be delegated
if !reflect.DeepEqual(ei, ai) {
return fmt.Errorf("%s: %v != %v", path, ei, ai)
}
return nil
}
}