package pretty import ( "fmt" "io" "reflect" ) type sbuf []string func (s *sbuf) Write(b []byte) (int, error) { *s = append(*s, string(b)) return len(b), nil } // Diff returns a slice where each element describes // a difference between a and b. func Diff(a, b interface{}) (desc []string) { Fdiff((*sbuf)(&desc), a, b) return desc } // Fdiff writes to w a description of the differences between a and b. func Fdiff(w io.Writer, a, b interface{}) { diffWriter{w: w}.diff(reflect.ValueOf(a), reflect.ValueOf(b)) } type diffWriter struct { w io.Writer l string // label } func (w diffWriter) printf(f string, a ...interface{}) { var l string if w.l != "" { l = w.l + ": " } fmt.Fprintf(w.w, l+f, a...) } func (w diffWriter) diff(av, bv reflect.Value) { if !av.IsValid() && bv.IsValid() { w.printf("nil != %#v", bv.Interface()) return } if av.IsValid() && !bv.IsValid() { w.printf("%#v != nil", av.Interface()) return } if !av.IsValid() && !bv.IsValid() { return } at := av.Type() bt := bv.Type() if at != bt { w.printf("%v != %v", at, bt) return } // numeric types, including bool if at.Kind() < reflect.Array { a, b := av.Interface(), bv.Interface() if a != b { w.printf("%#v != %#v", a, b) } return } switch at.Kind() { case reflect.String: a, b := av.Interface(), bv.Interface() if a != b { w.printf("%q != %q", a, b) } case reflect.Ptr: switch { case av.IsNil() && !bv.IsNil(): w.printf("nil != %v", bv.Interface()) case !av.IsNil() && bv.IsNil(): w.printf("%v != nil", av.Interface()) case !av.IsNil() && !bv.IsNil(): w.diff(av.Elem(), bv.Elem()) } case reflect.Struct: for i := 0; i < av.NumField(); i++ { w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i)) } case reflect.Slice: lenA := av.Len() lenB := bv.Len() if lenA != lenB { w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB) break } for i := 0; i < lenA; i++ { w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i)) } case reflect.Map: ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys()) for _, k := range ak { w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) w.printf("%q != (missing)", av.MapIndex(k)) } for _, k := range both { w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) w.diff(av.MapIndex(k), bv.MapIndex(k)) } for _, k := range bk { w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) w.printf("(missing) != %q", bv.MapIndex(k)) } case reflect.Interface: w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface())) default: if !reflect.DeepEqual(av.Interface(), bv.Interface()) { w.printf("%# v != %# v", Formatter(av.Interface()), Formatter(bv.Interface())) } } } func (d diffWriter) relabel(name string) (d1 diffWriter) { d1 = d if d.l != "" && name[0] != '[' { d1.l += "." } d1.l += name return d1 } func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) { for _, av := range a { inBoth := false for _, bv := range b { if reflect.DeepEqual(av.Interface(), bv.Interface()) { inBoth = true both = append(both, av) break } } if !inBoth { ak = append(ak, av) } } for _, bv := range b { inBoth := false for _, av := range a { if reflect.DeepEqual(av.Interface(), bv.Interface()) { inBoth = true break } } if !inBoth { bk = append(bk, bv) } } return }