aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/google/go-cmp/cmp/report_reflect.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/google/go-cmp/cmp/report_reflect.go')
-rw-r--r--vendor/github.com/google/go-cmp/cmp/report_reflect.go279
1 files changed, 279 insertions, 0 deletions
diff --git a/vendor/github.com/google/go-cmp/cmp/report_reflect.go b/vendor/github.com/google/go-cmp/cmp/report_reflect.go
new file mode 100644
index 0000000..5521c60
--- /dev/null
+++ b/vendor/github.com/google/go-cmp/cmp/report_reflect.go
@@ -0,0 +1,279 @@
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.md file.
4
5package cmp
6
7import (
8 "fmt"
9 "reflect"
10 "strconv"
11 "strings"
12 "unicode"
13
14 "github.com/google/go-cmp/cmp/internal/flags"
15 "github.com/google/go-cmp/cmp/internal/value"
16)
17
18type formatValueOptions struct {
19 // AvoidStringer controls whether to avoid calling custom stringer
20 // methods like error.Error or fmt.Stringer.String.
21 AvoidStringer bool
22
23 // ShallowPointers controls whether to avoid descending into pointers.
24 // Useful when printing map keys, where pointer comparison is performed
25 // on the pointer address rather than the pointed-at value.
26 ShallowPointers bool
27
28 // PrintAddresses controls whether to print the address of all pointers,
29 // slice elements, and maps.
30 PrintAddresses bool
31}
32
33// FormatType prints the type as if it were wrapping s.
34// This may return s as-is depending on the current type and TypeMode mode.
35func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
36 // Check whether to emit the type or not.
37 switch opts.TypeMode {
38 case autoType:
39 switch t.Kind() {
40 case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
41 if s.Equal(textNil) {
42 return s
43 }
44 default:
45 return s
46 }
47 case elideType:
48 return s
49 }
50
51 // Determine the type label, applying special handling for unnamed types.
52 typeName := t.String()
53 if t.Name() == "" {
54 // According to Go grammar, certain type literals contain symbols that
55 // do not strongly bind to the next lexicographical token (e.g., *T).
56 switch t.Kind() {
57 case reflect.Chan, reflect.Func, reflect.Ptr:
58 typeName = "(" + typeName + ")"
59 }
60 typeName = strings.Replace(typeName, "struct {", "struct{", -1)
61 typeName = strings.Replace(typeName, "interface {", "interface{", -1)
62 }
63
64 // Avoid wrap the value in parenthesis if unnecessary.
65 if s, ok := s.(textWrap); ok {
66 hasParens := strings.HasPrefix(s.Prefix, "(") && strings.HasSuffix(s.Suffix, ")")
67 hasBraces := strings.HasPrefix(s.Prefix, "{") && strings.HasSuffix(s.Suffix, "}")
68 if hasParens || hasBraces {
69 return textWrap{typeName, s, ""}
70 }
71 }
72 return textWrap{typeName + "(", s, ")"}
73}
74
75// FormatValue prints the reflect.Value, taking extra care to avoid descending
76// into pointers already in m. As pointers are visited, m is also updated.
77func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out textNode) {
78 if !v.IsValid() {
79 return nil
80 }
81 t := v.Type()
82
83 // Check whether there is an Error or String method to call.
84 if !opts.AvoidStringer && v.CanInterface() {
85 // Avoid calling Error or String methods on nil receivers since many
86 // implementations crash when doing so.
87 if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
88 switch v := v.Interface().(type) {
89 case error:
90 return textLine("e" + formatString(v.Error()))
91 case fmt.Stringer:
92 return textLine("s" + formatString(v.String()))
93 }
94 }
95 }
96
97 // Check whether to explicitly wrap the result with the type.
98 var skipType bool
99 defer func() {
100 if !skipType {
101 out = opts.FormatType(t, out)
102 }
103 }()
104
105 var ptr string
106 switch t.Kind() {
107 case reflect.Bool:
108 return textLine(fmt.Sprint(v.Bool()))
109 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
110 return textLine(fmt.Sprint(v.Int()))
111 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
112 // Unnamed uints are usually bytes or words, so use hexadecimal.
113 if t.PkgPath() == "" || t.Kind() == reflect.Uintptr {
114 return textLine(formatHex(v.Uint()))
115 }
116 return textLine(fmt.Sprint(v.Uint()))
117 case reflect.Float32, reflect.Float64:
118 return textLine(fmt.Sprint(v.Float()))
119 case reflect.Complex64, reflect.Complex128:
120 return textLine(fmt.Sprint(v.Complex()))
121 case reflect.String:
122 return textLine(formatString(v.String()))
123 case reflect.UnsafePointer, reflect.Chan, reflect.Func:
124 return textLine(formatPointer(v))
125 case reflect.Struct:
126 var list textList
127 for i := 0; i < v.NumField(); i++ {
128 vv := v.Field(i)
129 if value.IsZero(vv) {
130 continue // Elide fields with zero values
131 }
132 s := opts.WithTypeMode(autoType).FormatValue(vv, m)
133 list = append(list, textRecord{Key: t.Field(i).Name, Value: s})
134 }
135 return textWrap{"{", list, "}"}
136 case reflect.Slice:
137 if v.IsNil() {
138 return textNil
139 }
140 if opts.PrintAddresses {
141 ptr = formatPointer(v)
142 }
143 fallthrough
144 case reflect.Array:
145 var list textList
146 for i := 0; i < v.Len(); i++ {
147 vi := v.Index(i)
148 if vi.CanAddr() { // Check for cyclic elements
149 p := vi.Addr()
150 if m.Visit(p) {
151 var out textNode
152 out = textLine(formatPointer(p))
153 out = opts.WithTypeMode(emitType).FormatType(p.Type(), out)
154 out = textWrap{"*", out, ""}
155 list = append(list, textRecord{Value: out})
156 continue
157 }
158 }
159 s := opts.WithTypeMode(elideType).FormatValue(vi, m)
160 list = append(list, textRecord{Value: s})
161 }
162 return textWrap{ptr + "{", list, "}"}
163 case reflect.Map:
164 if v.IsNil() {
165 return textNil
166 }
167 if m.Visit(v) {
168 return textLine(formatPointer(v))
169 }
170
171 var list textList
172 for _, k := range value.SortKeys(v.MapKeys()) {
173 sk := formatMapKey(k)
174 sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), m)
175 list = append(list, textRecord{Key: sk, Value: sv})
176 }
177 if opts.PrintAddresses {
178 ptr = formatPointer(v)
179 }
180 return textWrap{ptr + "{", list, "}"}
181 case reflect.Ptr:
182 if v.IsNil() {
183 return textNil
184 }
185 if m.Visit(v) || opts.ShallowPointers {
186 return textLine(formatPointer(v))
187 }
188 if opts.PrintAddresses {
189 ptr = formatPointer(v)
190 }
191 skipType = true // Let the underlying value print the type instead
192 return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), m), ""}
193 case reflect.Interface:
194 if v.IsNil() {
195 return textNil
196 }
197 // Interfaces accept different concrete types,
198 // so configure the underlying value to explicitly print the type.
199 skipType = true // Print the concrete type instead
200 return opts.WithTypeMode(emitType).FormatValue(v.Elem(), m)
201 default:
202 panic(fmt.Sprintf("%v kind not handled", v.Kind()))
203 }
204}
205
206// formatMapKey formats v as if it were a map key.
207// The result is guaranteed to be a single line.
208func formatMapKey(v reflect.Value) string {
209 var opts formatOptions
210 opts.TypeMode = elideType
211 opts.AvoidStringer = true
212 opts.ShallowPointers = true
213 s := opts.FormatValue(v, visitedPointers{}).String()
214 return strings.TrimSpace(s)
215}
216
217// formatString prints s as a double-quoted or backtick-quoted string.
218func formatString(s string) string {
219 // Use quoted string if it the same length as a raw string literal.
220 // Otherwise, attempt to use the raw string form.
221 qs := strconv.Quote(s)
222 if len(qs) == 1+len(s)+1 {
223 return qs
224 }
225
226 // Disallow newlines to ensure output is a single line.
227 // Only allow printable runes for readability purposes.
228 rawInvalid := func(r rune) bool {
229 return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
230 }
231 if strings.IndexFunc(s, rawInvalid) < 0 {
232 return "`" + s + "`"
233 }
234 return qs
235}
236
237// formatHex prints u as a hexadecimal integer in Go notation.
238func formatHex(u uint64) string {
239 var f string
240 switch {
241 case u <= 0xff:
242 f = "0x%02x"
243 case u <= 0xffff:
244 f = "0x%04x"
245 case u <= 0xffffff:
246 f = "0x%06x"
247 case u <= 0xffffffff:
248 f = "0x%08x"
249 case u <= 0xffffffffff:
250 f = "0x%010x"
251 case u <= 0xffffffffffff:
252 f = "0x%012x"
253 case u <= 0xffffffffffffff:
254 f = "0x%014x"
255 case u <= 0xffffffffffffffff:
256 f = "0x%016x"
257 }
258 return fmt.Sprintf(f, u)
259}
260
261// formatPointer prints the address of the pointer.
262func formatPointer(v reflect.Value) string {
263 p := v.Pointer()
264 if flags.Deterministic {
265 p = 0xdeadf00f // Only used for stable testing purposes
266 }
267 return fmt.Sprintf("⟪0x%x⟫", p)
268}
269
270type visitedPointers map[value.Pointer]struct{}
271
272// Visit inserts pointer v into the visited map and reports whether it had
273// already been visited before.
274func (m visitedPointers) Visit(v reflect.Value) bool {
275 p := value.PointerOf(v)
276 _, visited := m[p]
277 m[p] = struct{}{}
278 return visited
279}