package verify import ( "fmt" "reflect" "strings" "testing" ) // Values verifies that got has all the content, and only the content, defined by want. // Note that NaN always results in a mismatch. func Values(tb testing.TB, name string, got, want interface{}) (ok bool) { t := travel{} t.values(reflect.ValueOf(got), reflect.ValueOf(want), nil) fail := t.report(name) if fail != "" { tb.Helper() tb.Error(fail) return false } return true } func (t *travel) values(got, want reflect.Value, path []*segment) { if !want.IsValid() { if got.IsValid() { t.differ(path, "Unwanted %s", got.Type()) } return } if !got.IsValid() { t.differ(path, "Missing %s", want.Type()) return } if got.Type() != want.Type() { t.differ(path, "Got type %s, want %s", got.Type(), want.Type()) return } switch got.Kind() { case reflect.Struct: seg := &segment{format: "/%s"} path = append(path, seg) var unexp []string for i, n := 0, got.NumField(); i < n; i++ { field := got.Type().Field(i) if field.PkgPath != "" { unexp = append(unexp, field.Name) } else { seg.x = field.Name t.values(got.Field(i), want.Field(i), path) } } path = path[:len(path)-1] if len(unexp) != 0 && !reflect.DeepEqual(got.Interface(), want.Interface()) { t.differ(path, "Type %s with unexported fields %q not equal", got.Type(), unexp) } case reflect.Slice, reflect.Array: n := got.Len() if n != want.Len() { t.differ(path, "Got %d elements, want %d", n, want.Len()) return } seg := &segment{format: "[%d]"} path = append(path, seg) for i := 0; i < n; i++ { seg.x = i t.values(got.Index(i), want.Index(i), path) } path = path[:len(path)-1] case reflect.Ptr: if got.Pointer() != want.Pointer() { t.values(got.Elem(), want.Elem(), path) } case reflect.Interface: t.values(got.Elem(), want.Elem(), path) case reflect.Map: seg := &segment{} path = append(path, seg) for _, key := range want.MapKeys() { applyKeySeg(seg, key) t.values(got.MapIndex(key), want.MapIndex(key), path) } for _, key := range got.MapKeys() { v := want.MapIndex(key) if v.IsValid() { continue } applyKeySeg(seg, key) t.values(got.MapIndex(key), v, path) } path = path[:len(path)-1] case reflect.Func: if !(got.IsNil() && want.IsNil()) { t.differ(path, "Can't compare functions") } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if a, b := got.Int(), want.Int(); a != b { if a < 0xA && a > -0xA && b < 0xA && b > -0xA { t.differ(path, fmt.Sprintf("Got %d, want %d", a, b)) } else { t.differ(path, fmt.Sprintf("Got %d (0x%x), want %d (0x%x)", a, a, b, b)) } } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if a, b := got.Uint(), want.Uint(); a != b { if a < 0xA && b < 0xA { t.differ(path, fmt.Sprintf("Got %d, want %d", a, b)) } else { t.differ(path, fmt.Sprintf("Got %d (0x%x), want %d (0x%x)", a, a, b, b)) } } case reflect.String: if a, b := got.String(), want.String(); a != b { t.differ(path, differMsg(a, b)) } default: if a, b := got.Interface(), want.Interface(); a != b { t.differ(path, fmt.Sprintf("Got %v, want %v", a, b)) } } } func applyKeySeg(dst *segment, key reflect.Value) { if key.Kind() == reflect.String { dst.format = "[%q]" } else { dst.format = "[%v]" } dst.x = key.Interface() } func differMsg(got, want string) string { if len(got) < 9 || len(want) < 9 { return fmt.Sprintf("Got %q, want %q", got, want) } got, want = fmt.Sprintf("%q", got), fmt.Sprintf("%q", want) // find first character which differs var i int a, b := []rune(got), []rune(want) for i = 0; i < len(a); i++ { if i >= len(b) || a[i] != b[i] { break } } return fmt.Sprintf("Got %s, want %s\n %s^", got, want, strings.Repeat(" ", i)) }