diff options
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.go | 279 |
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 | |||
5 | package cmp | ||
6 | |||
7 | import ( | ||
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 | |||
18 | type 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. | ||
35 | func (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. | ||
77 | func (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. | ||
208 | func 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. | ||
218 | func 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. | ||
238 | func 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. | ||
262 | func 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 | |||
270 | type 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. | ||
274 | func (m visitedPointers) Visit(v reflect.Value) bool { | ||
275 | p := value.PointerOf(v) | ||
276 | _, visited := m[p] | ||
277 | m[p] = struct{}{} | ||
278 | return visited | ||
279 | } | ||