1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package fields
16
17 import (
18 "encoding/json"
19 "errors"
20 "fmt"
21 "reflect"
22 "testing"
23 "time"
24
25 "cloud.google.com/go/internal/testutil"
26 "github.com/google/go-cmp/cmp"
27 )
28
29 type embed1 struct {
30 Em1 int
31 Dup int
32 Shadow int
33 embed3
34 }
35
36 type embed2 struct {
37 Dup int
38 embed3
39 embed4
40 }
41
42 type embed3 struct {
43 Em3 int
44 embed5
45 }
46
47 type embed4 struct {
48 Em4 int
49 Dup int
50 *embed1
51 }
52
53 type embed5 struct {
54 x int
55 }
56
57 type Anonymous int
58
59 type S1 struct {
60 Exported int
61 unexported int
62 Shadow int
63 embed1
64 *embed2
65 Anonymous
66 }
67
68 type Time struct {
69 time.Time
70 }
71
72 var intType = reflect.TypeOf(int(0))
73
74 func field(name string, tval interface{}, index ...int) *Field {
75 return &Field{
76 Name: name,
77 Type: reflect.TypeOf(tval),
78 Index: index,
79 ParsedTag: []string(nil),
80 }
81 }
82
83 func tfield(name string, tval interface{}, index ...int) *Field {
84 return &Field{
85 Name: name,
86 Type: reflect.TypeOf(tval),
87 Index: index,
88 NameFromTag: true,
89 ParsedTag: []string(nil),
90 }
91 }
92
93 func TestFieldsNoTags(t *testing.T) {
94 c := NewCache(nil, nil, nil)
95 got, err := c.Fields(reflect.TypeOf(S1{}))
96 if err != nil {
97 t.Fatal(err)
98 }
99 want := []*Field{
100 field("Exported", int(0), 0),
101 field("Shadow", int(0), 2),
102 field("Em1", int(0), 3, 0),
103 field("Em4", int(0), 4, 2, 0),
104 field("Anonymous", Anonymous(0), 5),
105 }
106 for _, f := range want {
107 f.ParsedTag = nil
108 }
109 if msg, ok := compareFields(got, want); !ok {
110 t.Error(msg)
111 }
112 }
113
114 func TestAgainstJSONEncodingNoTags(t *testing.T) {
115
116 s1 := S1{
117 Exported: 1,
118 unexported: 2,
119 Shadow: 3,
120 embed1: embed1{
121 Em1: 4,
122 Dup: 5,
123 Shadow: 6,
124 embed3: embed3{
125 Em3: 7,
126 embed5: embed5{x: 8},
127 },
128 },
129 embed2: &embed2{
130 Dup: 9,
131 embed3: embed3{
132 Em3: 10,
133 embed5: embed5{x: 11},
134 },
135 embed4: embed4{
136 Em4: 12,
137 Dup: 13,
138 embed1: &embed1{Em1: 14},
139 },
140 },
141 Anonymous: Anonymous(15),
142 }
143 var want S1
144 want.embed2 = &embed2{}
145 jsonRoundTrip(t, s1, &want)
146 var got S1
147 got.embed2 = &embed2{}
148 fields, err := NewCache(nil, nil, nil).Fields(reflect.TypeOf(got))
149 if err != nil {
150 t.Fatal(err)
151 }
152 setFields(fields, &got, s1)
153 if !testutil.Equal(got, want,
154 cmp.AllowUnexported(S1{}, embed1{}, embed2{}, embed3{}, embed4{}, embed5{})) {
155 t.Errorf("got\n%+v\nwant\n%+v", got, want)
156 }
157 }
158
159
160 func TestAgainstJSONEncodingEmbeddedTime(t *testing.T) {
161 timeLeafFn := func(t reflect.Type) bool {
162 return t == reflect.TypeOf(time.Time{})
163 }
164
165
166 now := time.Now().UTC()
167 myt := Time{
168 now,
169 }
170 var want Time
171 jsonRoundTrip(t, myt, &want)
172 var got Time
173 fields, err := NewCache(nil, nil, timeLeafFn).Fields(reflect.TypeOf(got))
174 if err != nil {
175 t.Fatal(err)
176 }
177 setFields(fields, &got, myt)
178 if !testutil.Equal(got, want) {
179 t.Errorf("got\n%+v\nwant\n%+v", got, want)
180 }
181 }
182
183 type S2 struct {
184 NoTag int
185 XXX int `json:"tag"`
186 Anonymous `json:"anon"`
187 Embed `json:"em"`
188 Tag int
189 YYY int `json:"Tag"`
190 Empty int `json:""`
191 tEmbed1
192 tEmbed2
193 }
194
195 type Embed struct {
196 Em int
197 }
198
199 type tEmbed1 struct {
200 Dup int
201 X int `json:"Dup2"`
202 }
203
204 type tEmbed2 struct {
205 Y int `json:"Dup"`
206 Z int `json:"Dup2"`
207 }
208
209 func jsonTagParser(t reflect.StructTag) (name string, keep bool, other interface{}, err error) {
210 return ParseStandardTag("json", t)
211 }
212
213 func validateFunc(t reflect.Type) (err error) {
214 if t.Kind() != reflect.Struct {
215 return errors.New("non-struct type used")
216 }
217
218 for i := 0; i < t.NumField(); i++ {
219 if t.Field(i).Type.Kind() == reflect.Slice {
220 return fmt.Errorf("slice field found at field %s on struct %s", t.Field(i).Name, t.Name())
221 }
222 }
223
224 return nil
225 }
226
227 func TestFieldsWithTags(t *testing.T) {
228 got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S2{}))
229 if err != nil {
230 t.Fatal(err)
231 }
232 want := []*Field{
233 field("NoTag", int(0), 0),
234 tfield("tag", int(0), 1),
235 tfield("anon", Anonymous(0), 2),
236 tfield("em", Embed{}, 4),
237 tfield("Tag", int(0), 6),
238 field("Empty", int(0), 7),
239 tfield("Dup", int(0), 8, 0),
240 }
241 if msg, ok := compareFields(got, want); !ok {
242 t.Error(msg)
243 }
244 }
245
246 func TestAgainstJSONEncodingWithTags(t *testing.T) {
247
248 s2 := S2{
249 NoTag: 1,
250 XXX: 2,
251 Anonymous: 3,
252 Embed: Embed{
253 Em: 4,
254 },
255 tEmbed1: tEmbed1{
256 Dup: 5,
257 X: 6,
258 },
259 tEmbed2: tEmbed2{
260 Y: 7,
261 Z: 8,
262 },
263 }
264 var want S2
265 jsonRoundTrip(t, s2, &want)
266 var got S2
267 fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(got))
268 if err != nil {
269 t.Fatal(err)
270 }
271 setFields(fields, &got, s2)
272 if !testutil.Equal(got, want, cmp.AllowUnexported(S2{})) {
273 t.Errorf("got\n%+v\nwant\n%+v", got, want)
274 }
275 }
276
277 func TestUnexportedAnonymousNonStruct(t *testing.T) {
278
279
280
281 type S struct{}
282
283 got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{}))
284 if err != nil {
285 t.Fatal(err)
286 }
287 if len(got) != 0 {
288 t.Errorf("got %d fields, want 0", len(got))
289 }
290 }
291
292 func TestUnexportedAnonymousStruct(t *testing.T) {
293
294
295
296 type (
297 s1 struct{ X int }
298 S2 struct {
299 s1 `json:"Y"`
300 }
301 )
302 got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S2{}))
303 if err != nil {
304 t.Fatal(err)
305 }
306 if len(got) != 0 {
307 t.Errorf("got %d fields, want 0", len(got))
308 }
309 }
310
311 func TestDominantField(t *testing.T) {
312
313
314
315 for _, test := range []struct {
316 fields []Field
317 wantOK bool
318 }{
319
320 {[]Field{{Index: []int{0}}}, true},
321 {[]Field{{Index: []int{0}, NameFromTag: true}}, true},
322
323 {[]Field{{Index: []int{0}}, {Index: []int{1, 0}}}, true},
324 {[]Field{{Index: []int{0}}, {Index: []int{1, 0}, NameFromTag: true}}, true},
325 {[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1, 0}, NameFromTag: true}}, true},
326
327 {[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}}}, true},
328
329 {[]Field{{Index: []int{0}}, {Index: []int{1}}}, false},
330
331 {[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}, NameFromTag: true}}, false},
332 } {
333 _, gotOK := dominantField(test.fields)
334 if gotOK != test.wantOK {
335 t.Errorf("%v: got %t, want %t", test.fields, gotOK, test.wantOK)
336 }
337 }
338 }
339
340 func TestIgnore(t *testing.T) {
341 type S struct {
342 X int `json:"-"`
343 }
344 got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{}))
345 if err != nil {
346 t.Fatal(err)
347 }
348 if len(got) != 0 {
349 t.Errorf("got %d fields, want 0", len(got))
350 }
351 }
352
353 func TestParsedTag(t *testing.T) {
354 type S struct {
355 X int `json:"name,omitempty"`
356 }
357 got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{}))
358 if err != nil {
359 t.Fatal(err)
360 }
361 want := []*Field{
362 {Name: "name", NameFromTag: true, Type: intType,
363 Index: []int{0}, ParsedTag: []string{"omitempty"}},
364 }
365 if msg, ok := compareFields(got, want); !ok {
366 t.Error(msg)
367 }
368 }
369
370 func TestValidateFunc(t *testing.T) {
371 type MyInvalidStruct struct {
372 A string
373 B []int
374 }
375
376 _, err := NewCache(nil, validateFunc, nil).Fields(reflect.TypeOf(MyInvalidStruct{}))
377 if err == nil {
378 t.Fatal("expected error, got nil")
379 }
380
381 type MyValidStruct struct {
382 A string
383 B int
384 }
385 _, err = NewCache(nil, validateFunc, nil).Fields(reflect.TypeOf(MyValidStruct{}))
386 if err != nil {
387 t.Fatalf("expected nil, got error: %s\n", err)
388 }
389 }
390
391 func compareFields(got []Field, want []*Field) (msg string, ok bool) {
392 if len(got) != len(want) {
393 return fmt.Sprintf("got %d fields, want %d", len(got), len(want)), false
394 }
395 for i, g := range got {
396 w := *want[i]
397 if !fieldsEqual(&g, &w) {
398 return fmt.Sprintf("got\n%+v\nwant\n%+v", g, w), false
399 }
400 }
401 return "", true
402 }
403
404
405
406 func fieldsEqual(f1, f2 *Field) bool {
407 if f1 == nil || f2 == nil {
408 return f1 == f2
409 }
410 return f1.Name == f2.Name &&
411 f1.NameFromTag == f2.NameFromTag &&
412 f1.Type == f2.Type &&
413 testutil.Equal(f1.ParsedTag, f2.ParsedTag)
414 }
415
416
417
418
419 func setFields(fields []Field, dst, src interface{}) {
420 vsrc := reflect.ValueOf(src)
421 vdst := reflect.ValueOf(dst).Elem()
422 for _, f := range fields {
423 fdst := vdst.FieldByIndex(f.Index)
424 fsrc := vsrc.FieldByIndex(f.Index)
425 fdst.Set(fsrc)
426 }
427 }
428
429 func jsonRoundTrip(t *testing.T, in, out interface{}) {
430 bytes, err := json.Marshal(in)
431 if err != nil {
432 t.Fatal(err)
433 }
434 if err := json.Unmarshal(bytes, out); err != nil {
435 t.Fatal(err)
436 }
437 }
438
439 type S3 struct {
440 S4
441 Abc int
442 AbC int
443 Tag int
444 X int `json:"Tag"`
445 unexported int
446 }
447
448 type S4 struct {
449 ABc int
450 Y int `json:"Abc"`
451 }
452
453 func TestMatchingField(t *testing.T) {
454 fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{}))
455 if err != nil {
456 t.Fatal(err)
457 }
458 for _, test := range []struct {
459 name string
460 want *Field
461 }{
462
463 {"Abc", field("Abc", int(0), 1)},
464 {"AbC", field("AbC", int(0), 2)},
465 {"ABc", field("ABc", int(0), 0, 0)},
466
467
468
469
470 {"abc", field("ABc", int(0), 0, 0)},
471
472 {"Tag", tfield("Tag", int(0), 4)},
473
474 {"unexported", nil},
475
476 {"S4", nil},
477 } {
478 if got := fields.Match(test.name); !fieldsEqual(got, test.want) {
479 t.Errorf("match %q:\ngot %+v\nwant %+v", test.name, got, test.want)
480 }
481 }
482 }
483
484 func TestAgainstJSONMatchingField(t *testing.T) {
485 s3 := S3{
486 S4: S4{ABc: 1, Y: 2},
487 Abc: 3,
488 AbC: 4,
489 Tag: 5,
490 X: 6,
491 unexported: 7,
492 }
493 var want S3
494 jsonRoundTrip(t, s3, &want)
495 v := reflect.ValueOf(want)
496 fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{}))
497 if err != nil {
498 t.Fatal(err)
499 }
500 for _, test := range []struct {
501 name string
502 got int
503 }{
504 {"Abc", 3},
505 {"AbC", 4},
506 {"ABc", 1},
507 {"abc", 1},
508 {"Tag", 6},
509 } {
510 f := fields.Match(test.name)
511 if f == nil {
512 t.Fatalf("%s: no match", test.name)
513 }
514 w := v.FieldByIndex(f.Index).Interface()
515 if test.got != w {
516 t.Errorf("%s: got %d, want %d", test.name, test.got, w)
517 }
518 }
519 }
520
521 func TestTagErrors(t *testing.T) {
522 called := false
523 c := NewCache(func(t reflect.StructTag) (string, bool, interface{}, error) {
524 called = true
525 s := t.Get("f")
526 if s == "bad" {
527 return "", false, nil, errors.New("error")
528 }
529 return s, true, nil, nil
530 }, nil, nil)
531
532 type T struct {
533 X int `f:"ok"`
534 Y int `f:"bad"`
535 }
536
537 _, err := c.Fields(reflect.TypeOf(T{}))
538 if !called {
539 t.Fatal("tag parser not called")
540 }
541 if err == nil {
542 t.Error("want error, got nil")
543 }
544
545 called = false
546 _, err = c.Fields(reflect.TypeOf(T{}))
547 if called {
548 t.Fatal("tag parser called on second time")
549 }
550 if err == nil {
551 t.Error("want error, got nil")
552 }
553 }
554
View as plain text