diff options
author | Joe Tsai <joetsai@digital-static.net> | 2020-06-11 18:28:52 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-11 18:28:52 -0700 |
commit | f1780cfdde930250f45fbe0bb6e107be5b4e9514 (patch) | |
tree | a5256e2020fb407731bd2ac71d6e8695f861a384 /cmp/report_reflect.go | |
parent | 0d296f9f534978cc25de69216b23b74bbc10fad9 (diff) | |
download | go-cmp-f1780cfdde930250f45fbe0bb6e107be5b4e9514.tar.gz |
Limit verbosity of reporter output (#215)
A common complaint is that the reporter it prints out too much
irrelevant information, resulting in a low signal-to-noise ratio.
Improve this metric by imposing a verbosity limit. For nodes that
are equal, we set the verbosity level is a lower value than when
the nodes are inequal.
Other minor changes:
* Adjust heuristic for triple-quote usage to operate on more cases.
* Elide type more aggressively for equal nodes.
* Printing the address for a slice includes the length and capacity.
* The pointed-at value for map keys are printed.
Diffstat (limited to 'cmp/report_reflect.go')
-rw-r--r-- | cmp/report_reflect.go | 79 |
1 files changed, 70 insertions, 9 deletions
diff --git a/cmp/report_reflect.go b/cmp/report_reflect.go index 9bc5eb3..ecc71f0 100644 --- a/cmp/report_reflect.go +++ b/cmp/report_reflect.go @@ -21,14 +21,23 @@ type formatValueOptions struct { // methods like error.Error or fmt.Stringer.String. AvoidStringer bool - // ShallowPointers controls whether to avoid descending into pointers. + // PrintShallowPointer controls whether to print the next pointer. // Useful when printing map keys, where pointer comparison is performed // on the pointer address rather than the pointed-at value. - ShallowPointers bool + PrintShallowPointer bool // PrintAddresses controls whether to print the address of all pointers, // slice elements, and maps. PrintAddresses bool + + // VerbosityLevel controls the amount of output to produce. + // A higher value produces more output. A value of zero or lower produces + // no output (represented using an ellipsis). + // If LimitVerbosity is false, then the level is treated as infinite. + VerbosityLevel int + + // LimitVerbosity specifies that formatting should respect VerbosityLevel. + LimitVerbosity bool } // FormatType prints the type as if it were wrapping s. @@ -45,6 +54,9 @@ func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode { default: return s } + if opts.DiffMode == diffIdentical { + return s // elide type for identical nodes + } case elideType: return s } @@ -86,11 +98,22 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit // Avoid calling Error or String methods on nil receivers since many // implementations crash when doing so. if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() { + var prefix, strVal string switch v := v.Interface().(type) { case error: - return textLine("e" + formatString(v.Error())) + prefix, strVal = "e", v.Error() case fmt.Stringer: - return textLine("s" + formatString(v.String())) + prefix, strVal = "s", v.String() + } + if prefix != "" { + maxLen := len(strVal) + if opts.LimitVerbosity { + maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc... + } + if len(strVal) > maxLen+len(textEllipsis) { + return textLine(prefix + formatString(strVal[:maxLen]) + string(textEllipsis)) + } + return textLine(prefix + formatString(strVal)) } } } @@ -123,17 +146,33 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit case reflect.Complex64, reflect.Complex128: return textLine(fmt.Sprint(v.Complex())) case reflect.String: + maxLen := v.Len() + if opts.LimitVerbosity { + maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc... + } + if v.Len() > maxLen+len(textEllipsis) { + return textLine(formatString(v.String()[:maxLen]) + string(textEllipsis)) + } return textLine(formatString(v.String())) case reflect.UnsafePointer, reflect.Chan, reflect.Func: return textLine(formatPointer(v)) case reflect.Struct: var list textList v := makeAddressable(v) // needed for retrieveUnexportedField + maxLen := v.NumField() + if opts.LimitVerbosity { + maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... + opts.VerbosityLevel-- + } for i := 0; i < v.NumField(); i++ { vv := v.Field(i) if value.IsZero(vv) { continue // Elide fields with zero values } + if len(list) == maxLen { + list.AppendEllipsis(diffStats{}) + break + } sf := t.Field(i) if supportExporters && !isExported(sf.Name) { vv = retrieveUnexportedField(v, sf, true) @@ -147,12 +186,21 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit return textNil } if opts.PrintAddresses { - ptr = formatPointer(v) + ptr = fmt.Sprintf("⟪ptr:0x%x, len:%d, cap:%d⟫", pointerValue(v), v.Len(), v.Cap()) } fallthrough case reflect.Array: + maxLen := v.Len() + if opts.LimitVerbosity { + maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... + opts.VerbosityLevel-- + } var list textList for i := 0; i < v.Len(); i++ { + if len(list) == maxLen { + list.AppendEllipsis(diffStats{}) + break + } vi := v.Index(i) if vi.CanAddr() { // Check for cyclic elements p := vi.Addr() @@ -177,8 +225,17 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit return textLine(formatPointer(v)) } + maxLen := v.Len() + if opts.LimitVerbosity { + maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... + opts.VerbosityLevel-- + } var list textList for _, k := range value.SortKeys(v.MapKeys()) { + if len(list) == maxLen { + list.AppendEllipsis(diffStats{}) + break + } sk := formatMapKey(k) sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), false, m) list = append(list, textRecord{Key: sk, Value: sv}) @@ -191,11 +248,12 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit if v.IsNil() { return textNil } - if m.Visit(v) || opts.ShallowPointers { + if m.Visit(v) { return textLine(formatPointer(v)) } - if opts.PrintAddresses { + if opts.PrintAddresses || opts.PrintShallowPointer { ptr = formatPointer(v) + opts.PrintShallowPointer = false } skipType = true // Let the underlying value print the type instead return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), false, m), ""} @@ -217,7 +275,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit func formatMapKey(v reflect.Value) string { var opts formatOptions opts.TypeMode = elideType - opts.ShallowPointers = true + opts.PrintShallowPointer = true s := opts.FormatValue(v, false, visitedPointers{}).String() return strings.TrimSpace(s) } @@ -268,11 +326,14 @@ func formatHex(u uint64) string { // formatPointer prints the address of the pointer. func formatPointer(v reflect.Value) string { + return fmt.Sprintf("⟪0x%x⟫", pointerValue(v)) +} +func pointerValue(v reflect.Value) uintptr { p := v.Pointer() if flags.Deterministic { p = 0xdeadf00f // Only used for stable testing purposes } - return fmt.Sprintf("⟪0x%x⟫", p) + return p } type visitedPointers map[value.Pointer]struct{} |