github.com/thiagoyeds/go-cloud@v0.26.0/docstore/internal/fields/fields_test.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fields 16 17 import ( 18 "encoding/json" 19 "errors" 20 "fmt" 21 "reflect" 22 "testing" 23 "time" 24 25 "github.com/google/go-cmp/cmp" 26 ) 27 28 type embed1 struct { 29 Em1 int 30 Dup int // annihilates with embed2.Dup 31 Shadow int 32 embed3 33 } 34 35 type embed2 struct { 36 Dup int 37 embed3 38 embed4 39 } 40 41 type embed3 struct { 42 Em3 int // annihilated because embed3 is in both embed1 and embed2 43 embed5 44 } 45 46 type embed4 struct { 47 Em4 int 48 Dup int // annihilation of Dup in embed1, embed2 hides this Dup 49 *embed1 // ignored because it occurs at a higher level 50 } 51 52 type embed5 struct { 53 x int 54 } 55 56 type Anonymous int 57 58 type S1 struct { 59 Exported int 60 unexported int 61 Shadow int // shadows S1.Shadow 62 embed1 63 *embed2 64 Anonymous 65 } 66 67 type Time struct { 68 time.Time 69 } 70 71 var intType = reflect.TypeOf(int(0)) 72 73 func field(name string, tval interface{}, index ...int) *Field { 74 return &Field{ 75 Name: name, 76 Type: reflect.TypeOf(tval), 77 Index: index, 78 ParsedTag: []string(nil), 79 } 80 } 81 82 func tfield(name string, tval interface{}, index ...int) *Field { 83 return &Field{ 84 Name: name, 85 Type: reflect.TypeOf(tval), 86 Index: index, 87 NameFromTag: true, 88 ParsedTag: []string(nil), 89 } 90 } 91 92 func TestFieldsNoTags(t *testing.T) { 93 c := NewCache(nil, nil, nil) 94 got, err := c.Fields(reflect.TypeOf(S1{})) 95 if err != nil { 96 t.Fatal(err) 97 } 98 want := []*Field{ 99 field("Exported", int(0), 0), 100 field("Shadow", int(0), 2), 101 field("Em1", int(0), 3, 0), 102 field("Em4", int(0), 4, 2, 0), 103 field("Anonymous", Anonymous(0), 5), 104 } 105 for _, f := range want { 106 f.ParsedTag = nil 107 } 108 if msg, ok := compareFields(got, want); !ok { 109 t.Error(msg) 110 } 111 } 112 113 func TestAgainstJSONEncodingNoTags(t *testing.T) { 114 // Demonstrates that this package produces the same set of fields as encoding/json. 115 s1 := S1{ 116 Exported: 1, 117 unexported: 2, 118 Shadow: 3, 119 embed1: embed1{ 120 Em1: 4, 121 Dup: 5, 122 Shadow: 6, 123 embed3: embed3{ 124 Em3: 7, 125 embed5: embed5{x: 8}, 126 }, 127 }, 128 embed2: &embed2{ 129 Dup: 9, 130 embed3: embed3{ 131 Em3: 10, 132 embed5: embed5{x: 11}, 133 }, 134 embed4: embed4{ 135 Em4: 12, 136 Dup: 13, 137 embed1: &embed1{Em1: 14}, 138 }, 139 }, 140 Anonymous: Anonymous(15), 141 } 142 var want S1 143 want.embed2 = &embed2{} // need this because reflection won't create it 144 jsonRoundTrip(t, s1, &want) 145 var got S1 146 got.embed2 = &embed2{} 147 fields, err := NewCache(nil, nil, nil).Fields(reflect.TypeOf(got)) 148 if err != nil { 149 t.Fatal(err) 150 } 151 setFields(fields, &got, s1) 152 if !cmp.Equal(got, want, 153 cmp.AllowUnexported(S1{}, embed1{}, embed2{}, embed3{}, embed4{}, embed5{})) { 154 t.Errorf("got\n%+v\nwant\n%+v", got, want) 155 } 156 } 157 158 // Tests use of LeafTypes parameter to NewCache 159 func TestAgainstJSONEncodingEmbeddedTime(t *testing.T) { 160 timeLeafFn := func(t reflect.Type) bool { 161 return t == reflect.TypeOf(time.Time{}) 162 } 163 // Demonstrates that this package can produce the same set of 164 // fields as encoding/json for a struct with an embedded time.Time. 165 now := time.Now().UTC() 166 myt := Time{ 167 now, 168 } 169 var want Time 170 jsonRoundTrip(t, myt, &want) 171 var got Time 172 fields, err := NewCache(nil, nil, timeLeafFn).Fields(reflect.TypeOf(got)) 173 if err != nil { 174 t.Fatal(err) 175 } 176 setFields(fields, &got, myt) 177 if !cmp.Equal(got, want) { 178 t.Errorf("got\n%+v\nwant\n%+v", got, want) 179 } 180 } 181 182 type S2 struct { 183 NoTag int 184 XXX int `json:"tag"` // tag name takes precedence 185 Anonymous `json:"anon"` // anonymous non-structs also get their name from the tag 186 unexported int `json:"tag"` 187 Embed `json:"em"` // embedded structs with tags become fields 188 Tag int 189 YYY int `json:"Tag"` // tag takes precedence over untagged field of the same name 190 Empty int `json:""` // empty tag is noop 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"` // takes precedence over tEmbed1.Dup because it is tagged 206 Z int `json:"Dup2"` // same name as tEmbed1.X and both tagged, so ignored 207 } 208 209 func jsonTagParser(t reflect.StructTag) (name string, keep bool, other interface{}, err error) { 210 n, k, o := ParseStandardTag("json", t) 211 return n, k, o, nil 212 } 213 214 func validateFunc(t reflect.Type) (err error) { 215 if t.Kind() != reflect.Struct { 216 return errors.New("non-struct type used") 217 } 218 219 for i := 0; i < t.NumField(); i++ { 220 if t.Field(i).Type.Kind() == reflect.Slice { 221 return fmt.Errorf("slice field found at field %s on struct %s", t.Field(i).Name, t.Name()) 222 } 223 } 224 225 return nil 226 } 227 228 func TestFieldsWithTags(t *testing.T) { 229 got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S2{})) 230 if err != nil { 231 t.Fatal(err) 232 } 233 want := []*Field{ 234 field("NoTag", int(0), 0), 235 tfield("tag", int(0), 1), 236 tfield("anon", Anonymous(0), 2), 237 tfield("em", Embed{}, 4), 238 tfield("Tag", int(0), 6), 239 field("Empty", int(0), 7), 240 tfield("Dup", int(0), 8, 0), 241 } 242 if msg, ok := compareFields(got, want); !ok { 243 t.Error(msg) 244 } 245 } 246 247 func TestAgainstJSONEncodingWithTags(t *testing.T) { 248 // Demonstrates that this package produces the same set of fields as encoding/json. 249 s2 := S2{ 250 NoTag: 1, 251 XXX: 2, 252 Anonymous: 3, 253 Embed: Embed{ 254 Em: 4, 255 }, 256 tEmbed1: tEmbed1{ 257 Dup: 5, 258 X: 6, 259 }, 260 tEmbed2: tEmbed2{ 261 Y: 7, 262 Z: 8, 263 }, 264 } 265 var want S2 266 jsonRoundTrip(t, s2, &want) 267 var got S2 268 fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(got)) 269 if err != nil { 270 t.Fatal(err) 271 } 272 setFields(fields, &got, s2) 273 if !cmp.Equal(got, want, cmp.AllowUnexported(S2{})) { 274 t.Errorf("got\n%+v\nwant\n%+v", got, want) 275 } 276 } 277 278 func TestUnexportedAnonymousNonStruct(t *testing.T) { 279 // An unexported anonymous non-struct field should not be recorded. 280 // This is currently a bug in encoding/json. 281 // https://github.com/golang/go/issues/18009 282 type ( 283 u int 284 v int 285 S struct { 286 u 287 v `json:"x"` 288 int 289 } 290 ) 291 292 got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{})) 293 if err != nil { 294 t.Fatal(err) 295 } 296 if len(got) != 0 { 297 t.Errorf("got %d fields, want 0", len(got)) 298 } 299 } 300 301 func TestUnexportedAnonymousStruct(t *testing.T) { 302 // An unexported anonymous struct with a tag is ignored. 303 // This is currently a bug in encoding/json. 304 // https://github.com/golang/go/issues/18009 305 type ( 306 s1 struct{ X int } 307 S2 struct { 308 s1 `json:"Y"` 309 } 310 ) 311 got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S2{})) 312 if err != nil { 313 t.Fatal(err) 314 } 315 if len(got) != 0 { 316 t.Errorf("got %d fields, want 0", len(got)) 317 } 318 } 319 320 func TestDominantField(t *testing.T) { 321 // With fields sorted by index length and then by tag presence, 322 // the dominant field is always the first. Make sure all error 323 // cases are caught. 324 for _, test := range []struct { 325 fields []Field 326 wantOK bool 327 }{ 328 // A single field is OK. 329 {[]Field{{Index: []int{0}}}, true}, 330 {[]Field{{Index: []int{0}, NameFromTag: true}}, true}, 331 // A single field at top level is OK. 332 {[]Field{{Index: []int{0}}, {Index: []int{1, 0}}}, true}, 333 {[]Field{{Index: []int{0}}, {Index: []int{1, 0}, NameFromTag: true}}, true}, 334 {[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1, 0}, NameFromTag: true}}, true}, 335 // A single tagged field is OK. 336 {[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}}}, true}, 337 // Two untagged fields at the same level is an error. 338 {[]Field{{Index: []int{0}}, {Index: []int{1}}}, false}, 339 // Two tagged fields at the same level is an error. 340 {[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}, NameFromTag: true}}, false}, 341 } { 342 _, gotOK := dominantField(test.fields) 343 if gotOK != test.wantOK { 344 t.Errorf("%v: got %t, want %t", test.fields, gotOK, test.wantOK) 345 } 346 } 347 } 348 349 func TestIgnore(t *testing.T) { 350 type S struct { 351 X int `json:"-"` 352 Y int `json:"-,"` // field with name "-" 353 } 354 got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{})) 355 if err != nil { 356 t.Fatal(err) 357 } 358 if len(got) != 1 { 359 t.Errorf("got %d fields, want 1", len(got)) 360 } 361 } 362 363 func TestParsedTag(t *testing.T) { 364 type S struct { 365 X int `json:"name,omitempty"` 366 } 367 got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{})) 368 if err != nil { 369 t.Fatal(err) 370 } 371 want := []*Field{ 372 {Name: "name", NameFromTag: true, Type: intType, 373 Index: []int{0}, ParsedTag: []string{"omitempty"}}, 374 } 375 if msg, ok := compareFields(got, want); !ok { 376 t.Error(msg) 377 } 378 } 379 380 func TestValidateFunc(t *testing.T) { 381 type MyInvalidStruct struct { 382 A string 383 B []int 384 } 385 386 _, err := NewCache(nil, validateFunc, nil).Fields(reflect.TypeOf(MyInvalidStruct{})) 387 if err == nil { 388 t.Fatal("expected error, got nil") 389 } 390 391 type MyValidStruct struct { 392 A string 393 B int 394 } 395 _, err = NewCache(nil, validateFunc, nil).Fields(reflect.TypeOf(MyValidStruct{})) 396 if err != nil { 397 t.Fatalf("expected nil, got error: %s\n", err) 398 } 399 } 400 401 func compareFields(got []Field, want []*Field) (msg string, ok bool) { 402 if len(got) != len(want) { 403 return fmt.Sprintf("got %d fields, want %d", len(got), len(want)), false 404 } 405 for i, g := range got { 406 w := *want[i] 407 if !fieldsEqual(&g, &w) { 408 return fmt.Sprintf("got\n%+v\nwant\n%+v", g, w), false 409 } 410 } 411 return "", true 412 } 413 414 // Need this because Field contains a function, which cannot be compared. 415 func fieldsEqual(f1, f2 *Field) bool { 416 if f1 == nil || f2 == nil { 417 return f1 == f2 418 } 419 return f1.Name == f2.Name && 420 f1.NameFromTag == f2.NameFromTag && 421 f1.Type == f2.Type && 422 cmp.Equal(f1.ParsedTag, f2.ParsedTag) 423 } 424 425 // Set the fields of dst from those of src. 426 // dst must be a pointer to a struct value. 427 // src must be a struct value. 428 func setFields(fields []Field, dst, src interface{}) { 429 vsrc := reflect.ValueOf(src) 430 vdst := reflect.ValueOf(dst).Elem() 431 for _, f := range fields { 432 fdst := vdst.FieldByIndex(f.Index) 433 fsrc := vsrc.FieldByIndex(f.Index) 434 fdst.Set(fsrc) 435 } 436 } 437 438 func jsonRoundTrip(t *testing.T, in, out interface{}) { 439 bytes, err := json.Marshal(in) 440 if err != nil { 441 t.Fatal(err) 442 } 443 if err := json.Unmarshal(bytes, out); err != nil { 444 t.Fatal(err) 445 } 446 } 447 448 type S3 struct { 449 S4 450 Abc int 451 AbC int 452 Tag int 453 X int `json:"Tag"` 454 unexported int 455 } 456 457 type S4 struct { 458 ABc int 459 Y int `json:"Abc"` // ignored because of top-level Abc 460 } 461 462 func TestMatchExact(t *testing.T) { 463 fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{})) 464 if err != nil { 465 t.Fatal(err) 466 } 467 for _, test := range []struct { 468 name string 469 want *Field 470 }{ 471 // Exact match wins. 472 {"Abc", field("Abc", int(0), 1)}, 473 {"AbC", field("AbC", int(0), 2)}, 474 {"ABc", field("ABc", int(0), 0, 0)}, 475 // Matches must be exact. 476 {"abc", nil}, 477 // Tag name takes precedence over untagged field of the same name. 478 {"Tag", tfield("Tag", int(0), 4)}, 479 // Unexported fields disappear. 480 {"unexported", nil}, 481 // Untagged embedded structs disappear. 482 {"S4", nil}, 483 } { 484 if got := fields.MatchExact(test.name); !fieldsEqual(got, test.want) { 485 t.Errorf("match %q:\ngot %+v\nwant %+v", test.name, got, test.want) 486 } 487 } 488 } 489 490 func TestMatchFold(t *testing.T) { 491 fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{})) 492 if err != nil { 493 t.Fatal(err) 494 } 495 for _, test := range []struct { 496 name string 497 want *Field 498 }{ 499 // Exact match wins. 500 {"Abc", field("Abc", int(0), 1)}, 501 {"AbC", field("AbC", int(0), 2)}, 502 {"ABc", field("ABc", int(0), 0, 0)}, 503 // If there are multiple matches but no exact match or tag, 504 // the first field wins, lexicographically by index. 505 // Here, "ABc" is at a deeper embedding level, but since S4 appears 506 // first in S3, its index precedes the other fields of S3. 507 {"abc", field("ABc", int(0), 0, 0)}, 508 // Tag name takes precedence over untagged field of the same name. 509 {"Tag", tfield("Tag", int(0), 4)}, 510 // Unexported fields disappear. 511 {"unexported", nil}, 512 // Untagged embedded structs disappear. 513 {"S4", nil}, 514 } { 515 if got := fields.MatchFold(test.name); !fieldsEqual(got, test.want) { 516 t.Errorf("match %q:\ngot %+v\nwant %+v", test.name, got, test.want) 517 } 518 } 519 } 520 521 func TestAgainstJSONMatchingField(t *testing.T) { 522 s3 := S3{ 523 S4: S4{ABc: 1, Y: 2}, 524 Abc: 3, 525 AbC: 4, 526 Tag: 5, 527 X: 6, 528 unexported: 7, 529 } 530 var want S3 531 jsonRoundTrip(t, s3, &want) 532 v := reflect.ValueOf(want) 533 fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{})) 534 if err != nil { 535 t.Fatal(err) 536 } 537 for _, test := range []struct { 538 name string 539 got int 540 }{ 541 {"Abc", 3}, 542 {"AbC", 4}, 543 {"ABc", 1}, 544 {"abc", 1}, 545 {"Tag", 6}, 546 } { 547 f := fields.MatchFold(test.name) 548 if f == nil { 549 t.Fatalf("%s: no match", test.name) 550 } 551 w := v.FieldByIndex(f.Index).Interface() 552 if test.got != w { 553 t.Errorf("%s: got %d, want %d", test.name, test.got, w) 554 } 555 } 556 } 557 558 func TestTagErrors(t *testing.T) { 559 called := false 560 c := NewCache(func(t reflect.StructTag) (string, bool, interface{}, error) { 561 called = true 562 s := t.Get("f") 563 if s == "bad" { 564 return "", false, nil, errors.New("error") 565 } 566 return s, true, nil, nil 567 }, nil, nil) 568 569 type T struct { 570 X int `f:"ok"` 571 Y int `f:"bad"` 572 } 573 574 _, err := c.Fields(reflect.TypeOf(T{})) 575 if !called { 576 t.Fatal("tag parser not called") 577 } 578 if err == nil { 579 t.Error("want error, got nil") 580 } 581 // Second time, we should cache the error. 582 called = false 583 _, err = c.Fields(reflect.TypeOf(T{})) 584 if called { 585 t.Fatal("tag parser called on second time") 586 } 587 if err == nil { 588 t.Error("want error, got nil") 589 } 590 }