github.com/Psiphon-Labs/goarista@v0.0.0-20160825065156-d002785f4c67/test/diff.go (about) 1 // Copyright (C) 2015 Arista Networks, Inc. 2 // Use of this source code is governed by the Apache License 2.0 3 // that can be found in the COPYING file. 4 5 package test 6 7 import ( 8 "bytes" 9 "fmt" 10 "reflect" 11 "sort" 12 "strings" 13 14 "github.com/aristanetworks/goarista/areflect" 15 "github.com/aristanetworks/goarista/key" 16 ) 17 18 // diffable types have a method that returns the diff 19 // of two objects 20 type diffable interface { 21 // Diff returns a human readable string of the diff of the two objects 22 // an empty string means that the two objects are equal 23 Diff(other interface{}) string 24 } 25 26 // Diff returns the difference of two objects in a human readable format. 27 // An empty string is returned when there is no difference. 28 // To avoid confusing diffs, make sure you pass the expected value first. 29 func Diff(expected, actual interface{}) string { 30 if DeepEqual(expected, actual) { 31 return "" 32 } 33 34 return diffImpl(expected, actual, nil) 35 } 36 37 func diffImpl(a, b interface{}, seen map[edge]struct{}) string { 38 av := reflect.ValueOf(a) 39 bv := reflect.ValueOf(b) 40 // Check if nil 41 if !av.IsValid() { 42 if !bv.IsValid() { 43 return "" // Both are "nil" with no type 44 } 45 return fmt.Sprintf("expected nil but got a %T: %#v", b, b) 46 } else if !bv.IsValid() { 47 return fmt.Sprintf("expected a %T (%#v) but got nil", a, a) 48 } 49 if av.Type() != bv.Type() { 50 return fmt.Sprintf("expected a %T but got a %T", a, b) 51 } 52 53 switch a := a.(type) { 54 case string, bool, 55 int8, int16, int32, int64, 56 uint8, uint16, uint32, uint64, 57 float32, float64, 58 complex64, complex128, 59 int, uint, uintptr: 60 if a != b { 61 typ := reflect.TypeOf(a).Name() 62 return fmt.Sprintf("%s(%v) != %s(%v)", typ, a, typ, b) 63 } 64 return "" 65 case []byte: 66 if !bytes.Equal(a, b.([]byte)) { 67 return fmt.Sprintf("[]byte(%q) != []byte(%q)", a, b) 68 } 69 } 70 71 if ac, ok := a.(diffable); ok { 72 return ac.Diff(b.(diffable)) 73 } 74 75 if ac, ok := a.(key.Comparable); ok { 76 if ac.Equal(b.(key.Comparable)) { 77 return "" 78 } 79 return fmt.Sprintf("Comparable types are different: %s vs %s", 80 PrettyPrint(a), PrettyPrint(b)) 81 } 82 83 switch av.Kind() { 84 case reflect.Array, reflect.Slice: 85 l := av.Len() 86 if l != bv.Len() { 87 return fmt.Sprintf("Expected an array of size %d but got %d", 88 l, bv.Len()) 89 } 90 for i := 0; i < l; i++ { 91 diff := diffImpl(av.Index(i).Interface(), bv.Index(i).Interface(), 92 seen) 93 if len(diff) > 0 { 94 return fmt.Sprintf("In arrays, values are different at index %d: %s", i, diff) 95 } 96 } 97 98 case reflect.Map: 99 if c, d := isNilCheck(av, bv); c { 100 return d 101 } 102 if av.Len() != bv.Len() { 103 return fmt.Sprintf("Maps have different size: %d != %d (%s)", 104 av.Len(), bv.Len(), diffMapKeys(av, bv)) 105 } 106 for _, ka := range av.MapKeys() { 107 ae := av.MapIndex(ka) 108 if k := ka.Kind(); k == reflect.Ptr || k == reflect.Interface { 109 return diffComplexKeyMap(av, bv, seen) 110 } 111 be := bv.MapIndex(ka) 112 if !be.IsValid() { 113 return fmt.Sprintf( 114 "key %s in map is missing in the actual map", 115 prettyPrint(ka, ptrSet{}, prettyPrintDepth)) 116 } 117 if !ae.CanInterface() { 118 return fmt.Sprintf( 119 "for key %s in map, value can't become an interface: %s", 120 prettyPrint(ka, ptrSet{}, prettyPrintDepth), 121 prettyPrint(ae, ptrSet{}, prettyPrintDepth)) 122 } 123 if !be.CanInterface() { 124 return fmt.Sprintf( 125 "for key %s in map, value can't become an interface: %s", 126 prettyPrint(ka, ptrSet{}, prettyPrintDepth), 127 prettyPrint(be, ptrSet{}, prettyPrintDepth)) 128 } 129 if diff := diffImpl(ae.Interface(), be.Interface(), seen); len(diff) > 0 { 130 return fmt.Sprintf( 131 "for key %s in map, values are different: %s", 132 prettyPrint(ka, ptrSet{}, prettyPrintDepth), diff) 133 } 134 } 135 136 case reflect.Ptr, reflect.Interface: 137 if c, d := isNilCheck(av, bv); c { 138 return d 139 } 140 av = av.Elem() 141 bv = bv.Elem() 142 143 if av.CanAddr() && bv.CanAddr() { 144 e := edge{from: av.UnsafeAddr(), to: bv.UnsafeAddr()} 145 // Detect and prevent cycles. 146 if seen == nil { 147 seen = make(map[edge]struct{}) 148 } else if _, ok := seen[e]; ok { 149 return "" 150 } 151 seen[e] = struct{}{} 152 } 153 return diffImpl(av.Interface(), bv.Interface(), seen) 154 155 case reflect.Struct: 156 typ := av.Type() 157 for i, n := 0, av.NumField(); i < n; i++ { 158 if typ.Field(i).Tag.Get("deepequal") == "ignore" { 159 continue 160 } 161 af := areflect.ForceExport(av.Field(i)) 162 bf := areflect.ForceExport(bv.Field(i)) 163 if diff := diffImpl(af.Interface(), bf.Interface(), seen); len(diff) > 0 { 164 return fmt.Sprintf("attributes %q are different: %s", 165 av.Type().Field(i).Name, diff) 166 } 167 } 168 169 // The following cases are here to handle named types (aka type aliases). 170 case reflect.String: 171 if as, bs := av.String(), bv.String(); as != bs { 172 return fmt.Sprintf("%s(%q) != %s(%q)", av.Type().Name(), as, bv.Type().Name(), bs) 173 } 174 case reflect.Bool: 175 if ab, bb := av.Bool(), bv.Bool(); ab != bb { 176 return fmt.Sprintf("%s(%t) != %s(%t)", av.Type().Name(), ab, bv.Type().Name(), bb) 177 } 178 case reflect.Uint, reflect.Uintptr, 179 reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 180 if ai, bi := av.Uint(), bv.Uint(); ai != bi { 181 return fmt.Sprintf("%s(%d) != %s(%d)", av.Type().Name(), ai, bv.Type().Name(), bi) 182 } 183 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 184 if ai, bi := av.Int(), bv.Int(); ai != bi { 185 return fmt.Sprintf("%s(%d) != %s(%d)", av.Type().Name(), ai, bv.Type().Name(), bi) 186 } 187 case reflect.Float32, reflect.Float64: 188 if af, bf := av.Float(), bv.Float(); af != bf { 189 return fmt.Sprintf("%s(%f) != %s(%f)", av.Type().Name(), af, bv.Type().Name(), bf) 190 } 191 case reflect.Complex64, reflect.Complex128: 192 if ac, bc := av.Complex(), bv.Complex(); ac != bc { 193 return fmt.Sprintf("%s(%f) != %s(%f)", av.Type().Name(), ac, bv.Type().Name(), bc) 194 } 195 196 default: 197 return fmt.Sprintf("Unknown or unsupported type: %T: %#v", a, a) 198 } 199 200 return "" 201 } 202 203 func diffComplexKeyMap(av, bv reflect.Value, seen map[edge]struct{}) string { 204 ok, ka, be := complexKeyMapEqual(av, bv, seen) 205 if ok { 206 return "" 207 } else if be.IsValid() { 208 return fmt.Sprintf("for complex key %s in map, values are different: %s", 209 prettyPrint(ka, ptrSet{}, prettyPrintDepth), 210 diffImpl(av.MapIndex(ka).Interface(), be.Interface(), seen)) 211 } 212 return fmt.Sprintf("complex key %s in map is missing in the actual map", 213 prettyPrint(ka, ptrSet{}, prettyPrintDepth)) 214 } 215 216 func diffMapKeys(av, bv reflect.Value) string { 217 var diffs []string 218 // TODO: We produce extraneous diffs for composite keys. 219 for _, ka := range av.MapKeys() { 220 be := bv.MapIndex(ka) 221 if !be.IsValid() { 222 diffs = append(diffs, fmt.Sprintf("missing key: %s", 223 PrettyPrint(ka.Interface()))) 224 } 225 } 226 for _, kb := range bv.MapKeys() { 227 ae := av.MapIndex(kb) 228 if !ae.IsValid() { 229 diffs = append(diffs, fmt.Sprintf("extra key: %s", 230 PrettyPrint(kb.Interface()))) 231 } 232 } 233 sort.Strings(diffs) 234 return strings.Join(diffs, ", ") 235 } 236 237 func isNilCheck(a, b reflect.Value) (bool /*checked*/, string) { 238 if a.IsNil() { 239 if b.IsNil() { 240 return true, "" 241 } 242 return true, fmt.Sprintf("expected nil but got %s", 243 prettyPrint(b, ptrSet{}, prettyPrintDepth)) 244 } else if b.IsNil() { 245 return true, fmt.Sprintf("got nil instead of %s", 246 prettyPrint(a, ptrSet{}, prettyPrintDepth)) 247 } 248 return false, "" 249 } 250 251 type mapEntry struct { 252 k, v string 253 } 254 255 type mapEntries struct { 256 entries []*mapEntry 257 } 258 259 func (t *mapEntries) Len() int { 260 return len(t.entries) 261 } 262 func (t *mapEntries) Less(i, j int) bool { 263 if t.entries[i].k > t.entries[j].k { 264 return false 265 } else if t.entries[i].k < t.entries[j].k { 266 return true 267 } 268 return t.entries[i].v <= t.entries[j].v 269 } 270 func (t *mapEntries) Swap(i, j int) { 271 t.entries[i], t.entries[j] = t.entries[j], t.entries[i] 272 }