1*88d15eacSSasha Smundak// Copyright 2019, The Go Authors. All rights reserved. 2*88d15eacSSasha Smundak// Use of this source code is governed by a BSD-style 3*88d15eacSSasha Smundak// license that can be found in the LICENSE file. 4*88d15eacSSasha Smundak 5*88d15eacSSasha Smundakpackage cmp 6*88d15eacSSasha Smundak 7*88d15eacSSasha Smundakimport ( 8*88d15eacSSasha Smundak "fmt" 9*88d15eacSSasha Smundak "reflect" 10*88d15eacSSasha Smundak) 11*88d15eacSSasha Smundak 12*88d15eacSSasha Smundak// numContextRecords is the number of surrounding equal records to print. 13*88d15eacSSasha Smundakconst numContextRecords = 2 14*88d15eacSSasha Smundak 15*88d15eacSSasha Smundaktype diffMode byte 16*88d15eacSSasha Smundak 17*88d15eacSSasha Smundakconst ( 18*88d15eacSSasha Smundak diffUnknown diffMode = 0 19*88d15eacSSasha Smundak diffIdentical diffMode = ' ' 20*88d15eacSSasha Smundak diffRemoved diffMode = '-' 21*88d15eacSSasha Smundak diffInserted diffMode = '+' 22*88d15eacSSasha Smundak) 23*88d15eacSSasha Smundak 24*88d15eacSSasha Smundaktype typeMode int 25*88d15eacSSasha Smundak 26*88d15eacSSasha Smundakconst ( 27*88d15eacSSasha Smundak // emitType always prints the type. 28*88d15eacSSasha Smundak emitType typeMode = iota 29*88d15eacSSasha Smundak // elideType never prints the type. 30*88d15eacSSasha Smundak elideType 31*88d15eacSSasha Smundak // autoType prints the type only for composite kinds 32*88d15eacSSasha Smundak // (i.e., structs, slices, arrays, and maps). 33*88d15eacSSasha Smundak autoType 34*88d15eacSSasha Smundak) 35*88d15eacSSasha Smundak 36*88d15eacSSasha Smundaktype formatOptions struct { 37*88d15eacSSasha Smundak // DiffMode controls the output mode of FormatDiff. 38*88d15eacSSasha Smundak // 39*88d15eacSSasha Smundak // If diffUnknown, then produce a diff of the x and y values. 40*88d15eacSSasha Smundak // If diffIdentical, then emit values as if they were equal. 41*88d15eacSSasha Smundak // If diffRemoved, then only emit x values (ignoring y values). 42*88d15eacSSasha Smundak // If diffInserted, then only emit y values (ignoring x values). 43*88d15eacSSasha Smundak DiffMode diffMode 44*88d15eacSSasha Smundak 45*88d15eacSSasha Smundak // TypeMode controls whether to print the type for the current node. 46*88d15eacSSasha Smundak // 47*88d15eacSSasha Smundak // As a general rule of thumb, we always print the type of the next node 48*88d15eacSSasha Smundak // after an interface, and always elide the type of the next node after 49*88d15eacSSasha Smundak // a slice or map node. 50*88d15eacSSasha Smundak TypeMode typeMode 51*88d15eacSSasha Smundak 52*88d15eacSSasha Smundak // formatValueOptions are options specific to printing reflect.Values. 53*88d15eacSSasha Smundak formatValueOptions 54*88d15eacSSasha Smundak} 55*88d15eacSSasha Smundak 56*88d15eacSSasha Smundakfunc (opts formatOptions) WithDiffMode(d diffMode) formatOptions { 57*88d15eacSSasha Smundak opts.DiffMode = d 58*88d15eacSSasha Smundak return opts 59*88d15eacSSasha Smundak} 60*88d15eacSSasha Smundakfunc (opts formatOptions) WithTypeMode(t typeMode) formatOptions { 61*88d15eacSSasha Smundak opts.TypeMode = t 62*88d15eacSSasha Smundak return opts 63*88d15eacSSasha Smundak} 64*88d15eacSSasha Smundakfunc (opts formatOptions) WithVerbosity(level int) formatOptions { 65*88d15eacSSasha Smundak opts.VerbosityLevel = level 66*88d15eacSSasha Smundak opts.LimitVerbosity = true 67*88d15eacSSasha Smundak return opts 68*88d15eacSSasha Smundak} 69*88d15eacSSasha Smundakfunc (opts formatOptions) verbosity() uint { 70*88d15eacSSasha Smundak switch { 71*88d15eacSSasha Smundak case opts.VerbosityLevel < 0: 72*88d15eacSSasha Smundak return 0 73*88d15eacSSasha Smundak case opts.VerbosityLevel > 16: 74*88d15eacSSasha Smundak return 16 // some reasonable maximum to avoid shift overflow 75*88d15eacSSasha Smundak default: 76*88d15eacSSasha Smundak return uint(opts.VerbosityLevel) 77*88d15eacSSasha Smundak } 78*88d15eacSSasha Smundak} 79*88d15eacSSasha Smundak 80*88d15eacSSasha Smundakconst maxVerbosityPreset = 6 81*88d15eacSSasha Smundak 82*88d15eacSSasha Smundak// verbosityPreset modifies the verbosity settings given an index 83*88d15eacSSasha Smundak// between 0 and maxVerbosityPreset, inclusive. 84*88d15eacSSasha Smundakfunc verbosityPreset(opts formatOptions, i int) formatOptions { 85*88d15eacSSasha Smundak opts.VerbosityLevel = int(opts.verbosity()) + 2*i 86*88d15eacSSasha Smundak if i > 0 { 87*88d15eacSSasha Smundak opts.AvoidStringer = true 88*88d15eacSSasha Smundak } 89*88d15eacSSasha Smundak if i >= maxVerbosityPreset { 90*88d15eacSSasha Smundak opts.PrintAddresses = true 91*88d15eacSSasha Smundak opts.QualifiedNames = true 92*88d15eacSSasha Smundak } 93*88d15eacSSasha Smundak return opts 94*88d15eacSSasha Smundak} 95*88d15eacSSasha Smundak 96*88d15eacSSasha Smundak// FormatDiff converts a valueNode tree into a textNode tree, where the later 97*88d15eacSSasha Smundak// is a textual representation of the differences detected in the former. 98*88d15eacSSasha Smundakfunc (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) { 99*88d15eacSSasha Smundak if opts.DiffMode == diffIdentical { 100*88d15eacSSasha Smundak opts = opts.WithVerbosity(1) 101*88d15eacSSasha Smundak } else if opts.verbosity() < 3 { 102*88d15eacSSasha Smundak opts = opts.WithVerbosity(3) 103*88d15eacSSasha Smundak } 104*88d15eacSSasha Smundak 105*88d15eacSSasha Smundak // Check whether we have specialized formatting for this node. 106*88d15eacSSasha Smundak // This is not necessary, but helpful for producing more readable outputs. 107*88d15eacSSasha Smundak if opts.CanFormatDiffSlice(v) { 108*88d15eacSSasha Smundak return opts.FormatDiffSlice(v) 109*88d15eacSSasha Smundak } 110*88d15eacSSasha Smundak 111*88d15eacSSasha Smundak var parentKind reflect.Kind 112*88d15eacSSasha Smundak if v.parent != nil && v.parent.TransformerName == "" { 113*88d15eacSSasha Smundak parentKind = v.parent.Type.Kind() 114*88d15eacSSasha Smundak } 115*88d15eacSSasha Smundak 116*88d15eacSSasha Smundak // For leaf nodes, format the value based on the reflect.Values alone. 117*88d15eacSSasha Smundak // As a special case, treat equal []byte as a leaf nodes. 118*88d15eacSSasha Smundak isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == byteType 119*88d15eacSSasha Smundak isEqualBytes := isBytes && v.NumDiff+v.NumIgnored+v.NumTransformed == 0 120*88d15eacSSasha Smundak if v.MaxDepth == 0 || isEqualBytes { 121*88d15eacSSasha Smundak switch opts.DiffMode { 122*88d15eacSSasha Smundak case diffUnknown, diffIdentical: 123*88d15eacSSasha Smundak // Format Equal. 124*88d15eacSSasha Smundak if v.NumDiff == 0 { 125*88d15eacSSasha Smundak outx := opts.FormatValue(v.ValueX, parentKind, ptrs) 126*88d15eacSSasha Smundak outy := opts.FormatValue(v.ValueY, parentKind, ptrs) 127*88d15eacSSasha Smundak if v.NumIgnored > 0 && v.NumSame == 0 { 128*88d15eacSSasha Smundak return textEllipsis 129*88d15eacSSasha Smundak } else if outx.Len() < outy.Len() { 130*88d15eacSSasha Smundak return outx 131*88d15eacSSasha Smundak } else { 132*88d15eacSSasha Smundak return outy 133*88d15eacSSasha Smundak } 134*88d15eacSSasha Smundak } 135*88d15eacSSasha Smundak 136*88d15eacSSasha Smundak // Format unequal. 137*88d15eacSSasha Smundak assert(opts.DiffMode == diffUnknown) 138*88d15eacSSasha Smundak var list textList 139*88d15eacSSasha Smundak outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs) 140*88d15eacSSasha Smundak outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, parentKind, ptrs) 141*88d15eacSSasha Smundak for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ { 142*88d15eacSSasha Smundak opts2 := verbosityPreset(opts, i).WithTypeMode(elideType) 143*88d15eacSSasha Smundak outx = opts2.FormatValue(v.ValueX, parentKind, ptrs) 144*88d15eacSSasha Smundak outy = opts2.FormatValue(v.ValueY, parentKind, ptrs) 145*88d15eacSSasha Smundak } 146*88d15eacSSasha Smundak if outx != nil { 147*88d15eacSSasha Smundak list = append(list, textRecord{Diff: '-', Value: outx}) 148*88d15eacSSasha Smundak } 149*88d15eacSSasha Smundak if outy != nil { 150*88d15eacSSasha Smundak list = append(list, textRecord{Diff: '+', Value: outy}) 151*88d15eacSSasha Smundak } 152*88d15eacSSasha Smundak return opts.WithTypeMode(emitType).FormatType(v.Type, list) 153*88d15eacSSasha Smundak case diffRemoved: 154*88d15eacSSasha Smundak return opts.FormatValue(v.ValueX, parentKind, ptrs) 155*88d15eacSSasha Smundak case diffInserted: 156*88d15eacSSasha Smundak return opts.FormatValue(v.ValueY, parentKind, ptrs) 157*88d15eacSSasha Smundak default: 158*88d15eacSSasha Smundak panic("invalid diff mode") 159*88d15eacSSasha Smundak } 160*88d15eacSSasha Smundak } 161*88d15eacSSasha Smundak 162*88d15eacSSasha Smundak // Register slice element to support cycle detection. 163*88d15eacSSasha Smundak if parentKind == reflect.Slice { 164*88d15eacSSasha Smundak ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, true) 165*88d15eacSSasha Smundak defer ptrs.Pop() 166*88d15eacSSasha Smundak defer func() { out = wrapTrunkReferences(ptrRefs, out) }() 167*88d15eacSSasha Smundak } 168*88d15eacSSasha Smundak 169*88d15eacSSasha Smundak // Descend into the child value node. 170*88d15eacSSasha Smundak if v.TransformerName != "" { 171*88d15eacSSasha Smundak out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs) 172*88d15eacSSasha Smundak out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"} 173*88d15eacSSasha Smundak return opts.FormatType(v.Type, out) 174*88d15eacSSasha Smundak } else { 175*88d15eacSSasha Smundak switch k := v.Type.Kind(); k { 176*88d15eacSSasha Smundak case reflect.Struct, reflect.Array, reflect.Slice: 177*88d15eacSSasha Smundak out = opts.formatDiffList(v.Records, k, ptrs) 178*88d15eacSSasha Smundak out = opts.FormatType(v.Type, out) 179*88d15eacSSasha Smundak case reflect.Map: 180*88d15eacSSasha Smundak // Register map to support cycle detection. 181*88d15eacSSasha Smundak ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false) 182*88d15eacSSasha Smundak defer ptrs.Pop() 183*88d15eacSSasha Smundak 184*88d15eacSSasha Smundak out = opts.formatDiffList(v.Records, k, ptrs) 185*88d15eacSSasha Smundak out = wrapTrunkReferences(ptrRefs, out) 186*88d15eacSSasha Smundak out = opts.FormatType(v.Type, out) 187*88d15eacSSasha Smundak case reflect.Ptr: 188*88d15eacSSasha Smundak // Register pointer to support cycle detection. 189*88d15eacSSasha Smundak ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false) 190*88d15eacSSasha Smundak defer ptrs.Pop() 191*88d15eacSSasha Smundak 192*88d15eacSSasha Smundak out = opts.FormatDiff(v.Value, ptrs) 193*88d15eacSSasha Smundak out = wrapTrunkReferences(ptrRefs, out) 194*88d15eacSSasha Smundak out = &textWrap{Prefix: "&", Value: out} 195*88d15eacSSasha Smundak case reflect.Interface: 196*88d15eacSSasha Smundak out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs) 197*88d15eacSSasha Smundak default: 198*88d15eacSSasha Smundak panic(fmt.Sprintf("%v cannot have children", k)) 199*88d15eacSSasha Smundak } 200*88d15eacSSasha Smundak return out 201*88d15eacSSasha Smundak } 202*88d15eacSSasha Smundak} 203*88d15eacSSasha Smundak 204*88d15eacSSasha Smundakfunc (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode { 205*88d15eacSSasha Smundak // Derive record name based on the data structure kind. 206*88d15eacSSasha Smundak var name string 207*88d15eacSSasha Smundak var formatKey func(reflect.Value) string 208*88d15eacSSasha Smundak switch k { 209*88d15eacSSasha Smundak case reflect.Struct: 210*88d15eacSSasha Smundak name = "field" 211*88d15eacSSasha Smundak opts = opts.WithTypeMode(autoType) 212*88d15eacSSasha Smundak formatKey = func(v reflect.Value) string { return v.String() } 213*88d15eacSSasha Smundak case reflect.Slice, reflect.Array: 214*88d15eacSSasha Smundak name = "element" 215*88d15eacSSasha Smundak opts = opts.WithTypeMode(elideType) 216*88d15eacSSasha Smundak formatKey = func(reflect.Value) string { return "" } 217*88d15eacSSasha Smundak case reflect.Map: 218*88d15eacSSasha Smundak name = "entry" 219*88d15eacSSasha Smundak opts = opts.WithTypeMode(elideType) 220*88d15eacSSasha Smundak formatKey = func(v reflect.Value) string { return formatMapKey(v, false, ptrs) } 221*88d15eacSSasha Smundak } 222*88d15eacSSasha Smundak 223*88d15eacSSasha Smundak maxLen := -1 224*88d15eacSSasha Smundak if opts.LimitVerbosity { 225*88d15eacSSasha Smundak if opts.DiffMode == diffIdentical { 226*88d15eacSSasha Smundak maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... 227*88d15eacSSasha Smundak } else { 228*88d15eacSSasha Smundak maxLen = (1 << opts.verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc... 229*88d15eacSSasha Smundak } 230*88d15eacSSasha Smundak opts.VerbosityLevel-- 231*88d15eacSSasha Smundak } 232*88d15eacSSasha Smundak 233*88d15eacSSasha Smundak // Handle unification. 234*88d15eacSSasha Smundak switch opts.DiffMode { 235*88d15eacSSasha Smundak case diffIdentical, diffRemoved, diffInserted: 236*88d15eacSSasha Smundak var list textList 237*88d15eacSSasha Smundak var deferredEllipsis bool // Add final "..." to indicate records were dropped 238*88d15eacSSasha Smundak for _, r := range recs { 239*88d15eacSSasha Smundak if len(list) == maxLen { 240*88d15eacSSasha Smundak deferredEllipsis = true 241*88d15eacSSasha Smundak break 242*88d15eacSSasha Smundak } 243*88d15eacSSasha Smundak 244*88d15eacSSasha Smundak // Elide struct fields that are zero value. 245*88d15eacSSasha Smundak if k == reflect.Struct { 246*88d15eacSSasha Smundak var isZero bool 247*88d15eacSSasha Smundak switch opts.DiffMode { 248*88d15eacSSasha Smundak case diffIdentical: 249*88d15eacSSasha Smundak isZero = r.Value.ValueX.IsZero() || r.Value.ValueY.IsZero() 250*88d15eacSSasha Smundak case diffRemoved: 251*88d15eacSSasha Smundak isZero = r.Value.ValueX.IsZero() 252*88d15eacSSasha Smundak case diffInserted: 253*88d15eacSSasha Smundak isZero = r.Value.ValueY.IsZero() 254*88d15eacSSasha Smundak } 255*88d15eacSSasha Smundak if isZero { 256*88d15eacSSasha Smundak continue 257*88d15eacSSasha Smundak } 258*88d15eacSSasha Smundak } 259*88d15eacSSasha Smundak // Elide ignored nodes. 260*88d15eacSSasha Smundak if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 { 261*88d15eacSSasha Smundak deferredEllipsis = !(k == reflect.Slice || k == reflect.Array) 262*88d15eacSSasha Smundak if !deferredEllipsis { 263*88d15eacSSasha Smundak list.AppendEllipsis(diffStats{}) 264*88d15eacSSasha Smundak } 265*88d15eacSSasha Smundak continue 266*88d15eacSSasha Smundak } 267*88d15eacSSasha Smundak if out := opts.FormatDiff(r.Value, ptrs); out != nil { 268*88d15eacSSasha Smundak list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) 269*88d15eacSSasha Smundak } 270*88d15eacSSasha Smundak } 271*88d15eacSSasha Smundak if deferredEllipsis { 272*88d15eacSSasha Smundak list.AppendEllipsis(diffStats{}) 273*88d15eacSSasha Smundak } 274*88d15eacSSasha Smundak return &textWrap{Prefix: "{", Value: list, Suffix: "}"} 275*88d15eacSSasha Smundak case diffUnknown: 276*88d15eacSSasha Smundak default: 277*88d15eacSSasha Smundak panic("invalid diff mode") 278*88d15eacSSasha Smundak } 279*88d15eacSSasha Smundak 280*88d15eacSSasha Smundak // Handle differencing. 281*88d15eacSSasha Smundak var numDiffs int 282*88d15eacSSasha Smundak var list textList 283*88d15eacSSasha Smundak var keys []reflect.Value // invariant: len(list) == len(keys) 284*88d15eacSSasha Smundak groups := coalesceAdjacentRecords(name, recs) 285*88d15eacSSasha Smundak maxGroup := diffStats{Name: name} 286*88d15eacSSasha Smundak for i, ds := range groups { 287*88d15eacSSasha Smundak if maxLen >= 0 && numDiffs >= maxLen { 288*88d15eacSSasha Smundak maxGroup = maxGroup.Append(ds) 289*88d15eacSSasha Smundak continue 290*88d15eacSSasha Smundak } 291*88d15eacSSasha Smundak 292*88d15eacSSasha Smundak // Handle equal records. 293*88d15eacSSasha Smundak if ds.NumDiff() == 0 { 294*88d15eacSSasha Smundak // Compute the number of leading and trailing records to print. 295*88d15eacSSasha Smundak var numLo, numHi int 296*88d15eacSSasha Smundak numEqual := ds.NumIgnored + ds.NumIdentical 297*88d15eacSSasha Smundak for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 { 298*88d15eacSSasha Smundak if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 { 299*88d15eacSSasha Smundak break 300*88d15eacSSasha Smundak } 301*88d15eacSSasha Smundak numLo++ 302*88d15eacSSasha Smundak } 303*88d15eacSSasha Smundak for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 { 304*88d15eacSSasha Smundak if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 { 305*88d15eacSSasha Smundak break 306*88d15eacSSasha Smundak } 307*88d15eacSSasha Smundak numHi++ 308*88d15eacSSasha Smundak } 309*88d15eacSSasha Smundak if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 { 310*88d15eacSSasha Smundak numHi++ // Avoid pointless coalescing of a single equal record 311*88d15eacSSasha Smundak } 312*88d15eacSSasha Smundak 313*88d15eacSSasha Smundak // Format the equal values. 314*88d15eacSSasha Smundak for _, r := range recs[:numLo] { 315*88d15eacSSasha Smundak out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs) 316*88d15eacSSasha Smundak list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) 317*88d15eacSSasha Smundak keys = append(keys, r.Key) 318*88d15eacSSasha Smundak } 319*88d15eacSSasha Smundak if numEqual > numLo+numHi { 320*88d15eacSSasha Smundak ds.NumIdentical -= numLo + numHi 321*88d15eacSSasha Smundak list.AppendEllipsis(ds) 322*88d15eacSSasha Smundak for len(keys) < len(list) { 323*88d15eacSSasha Smundak keys = append(keys, reflect.Value{}) 324*88d15eacSSasha Smundak } 325*88d15eacSSasha Smundak } 326*88d15eacSSasha Smundak for _, r := range recs[numEqual-numHi : numEqual] { 327*88d15eacSSasha Smundak out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs) 328*88d15eacSSasha Smundak list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) 329*88d15eacSSasha Smundak keys = append(keys, r.Key) 330*88d15eacSSasha Smundak } 331*88d15eacSSasha Smundak recs = recs[numEqual:] 332*88d15eacSSasha Smundak continue 333*88d15eacSSasha Smundak } 334*88d15eacSSasha Smundak 335*88d15eacSSasha Smundak // Handle unequal records. 336*88d15eacSSasha Smundak for _, r := range recs[:ds.NumDiff()] { 337*88d15eacSSasha Smundak switch { 338*88d15eacSSasha Smundak case opts.CanFormatDiffSlice(r.Value): 339*88d15eacSSasha Smundak out := opts.FormatDiffSlice(r.Value) 340*88d15eacSSasha Smundak list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) 341*88d15eacSSasha Smundak keys = append(keys, r.Key) 342*88d15eacSSasha Smundak case r.Value.NumChildren == r.Value.MaxDepth: 343*88d15eacSSasha Smundak outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs) 344*88d15eacSSasha Smundak outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs) 345*88d15eacSSasha Smundak for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ { 346*88d15eacSSasha Smundak opts2 := verbosityPreset(opts, i) 347*88d15eacSSasha Smundak outx = opts2.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs) 348*88d15eacSSasha Smundak outy = opts2.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs) 349*88d15eacSSasha Smundak } 350*88d15eacSSasha Smundak if outx != nil { 351*88d15eacSSasha Smundak list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx}) 352*88d15eacSSasha Smundak keys = append(keys, r.Key) 353*88d15eacSSasha Smundak } 354*88d15eacSSasha Smundak if outy != nil { 355*88d15eacSSasha Smundak list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy}) 356*88d15eacSSasha Smundak keys = append(keys, r.Key) 357*88d15eacSSasha Smundak } 358*88d15eacSSasha Smundak default: 359*88d15eacSSasha Smundak out := opts.FormatDiff(r.Value, ptrs) 360*88d15eacSSasha Smundak list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) 361*88d15eacSSasha Smundak keys = append(keys, r.Key) 362*88d15eacSSasha Smundak } 363*88d15eacSSasha Smundak } 364*88d15eacSSasha Smundak recs = recs[ds.NumDiff():] 365*88d15eacSSasha Smundak numDiffs += ds.NumDiff() 366*88d15eacSSasha Smundak } 367*88d15eacSSasha Smundak if maxGroup.IsZero() { 368*88d15eacSSasha Smundak assert(len(recs) == 0) 369*88d15eacSSasha Smundak } else { 370*88d15eacSSasha Smundak list.AppendEllipsis(maxGroup) 371*88d15eacSSasha Smundak for len(keys) < len(list) { 372*88d15eacSSasha Smundak keys = append(keys, reflect.Value{}) 373*88d15eacSSasha Smundak } 374*88d15eacSSasha Smundak } 375*88d15eacSSasha Smundak assert(len(list) == len(keys)) 376*88d15eacSSasha Smundak 377*88d15eacSSasha Smundak // For maps, the default formatting logic uses fmt.Stringer which may 378*88d15eacSSasha Smundak // produce ambiguous output. Avoid calling String to disambiguate. 379*88d15eacSSasha Smundak if k == reflect.Map { 380*88d15eacSSasha Smundak var ambiguous bool 381*88d15eacSSasha Smundak seenKeys := map[string]reflect.Value{} 382*88d15eacSSasha Smundak for i, currKey := range keys { 383*88d15eacSSasha Smundak if currKey.IsValid() { 384*88d15eacSSasha Smundak strKey := list[i].Key 385*88d15eacSSasha Smundak prevKey, seen := seenKeys[strKey] 386*88d15eacSSasha Smundak if seen && prevKey.CanInterface() && currKey.CanInterface() { 387*88d15eacSSasha Smundak ambiguous = prevKey.Interface() != currKey.Interface() 388*88d15eacSSasha Smundak if ambiguous { 389*88d15eacSSasha Smundak break 390*88d15eacSSasha Smundak } 391*88d15eacSSasha Smundak } 392*88d15eacSSasha Smundak seenKeys[strKey] = currKey 393*88d15eacSSasha Smundak } 394*88d15eacSSasha Smundak } 395*88d15eacSSasha Smundak if ambiguous { 396*88d15eacSSasha Smundak for i, k := range keys { 397*88d15eacSSasha Smundak if k.IsValid() { 398*88d15eacSSasha Smundak list[i].Key = formatMapKey(k, true, ptrs) 399*88d15eacSSasha Smundak } 400*88d15eacSSasha Smundak } 401*88d15eacSSasha Smundak } 402*88d15eacSSasha Smundak } 403*88d15eacSSasha Smundak 404*88d15eacSSasha Smundak return &textWrap{Prefix: "{", Value: list, Suffix: "}"} 405*88d15eacSSasha Smundak} 406*88d15eacSSasha Smundak 407*88d15eacSSasha Smundak// coalesceAdjacentRecords coalesces the list of records into groups of 408*88d15eacSSasha Smundak// adjacent equal, or unequal counts. 409*88d15eacSSasha Smundakfunc coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) { 410*88d15eacSSasha Smundak var prevCase int // Arbitrary index into which case last occurred 411*88d15eacSSasha Smundak lastStats := func(i int) *diffStats { 412*88d15eacSSasha Smundak if prevCase != i { 413*88d15eacSSasha Smundak groups = append(groups, diffStats{Name: name}) 414*88d15eacSSasha Smundak prevCase = i 415*88d15eacSSasha Smundak } 416*88d15eacSSasha Smundak return &groups[len(groups)-1] 417*88d15eacSSasha Smundak } 418*88d15eacSSasha Smundak for _, r := range recs { 419*88d15eacSSasha Smundak switch rv := r.Value; { 420*88d15eacSSasha Smundak case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0: 421*88d15eacSSasha Smundak lastStats(1).NumIgnored++ 422*88d15eacSSasha Smundak case rv.NumDiff == 0: 423*88d15eacSSasha Smundak lastStats(1).NumIdentical++ 424*88d15eacSSasha Smundak case rv.NumDiff > 0 && !rv.ValueY.IsValid(): 425*88d15eacSSasha Smundak lastStats(2).NumRemoved++ 426*88d15eacSSasha Smundak case rv.NumDiff > 0 && !rv.ValueX.IsValid(): 427*88d15eacSSasha Smundak lastStats(2).NumInserted++ 428*88d15eacSSasha Smundak default: 429*88d15eacSSasha Smundak lastStats(2).NumModified++ 430*88d15eacSSasha Smundak } 431*88d15eacSSasha Smundak } 432*88d15eacSSasha Smundak return groups 433*88d15eacSSasha Smundak} 434