report_compare.go 13 KB

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