func deepEqual()

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
	}
}