report_compare.go 13 KB

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