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  }