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