...

Source file src/cloud.google.com/go/internal/fields/fields_test.go

Documentation: cloud.google.com/go/internal/fields

     1  // Copyright 2016 Google LLC
     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  	"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 // annihilates with embed2.Dup
    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 // annihilated because embed3 is in both embed1 and embed2
    44  	embed5
    45  }
    46  
    47  type embed4 struct {
    48  	Em4     int
    49  	Dup     int // annihilation of Dup in embed1, embed2 hides this Dup
    50  	*embed1     // ignored because it occurs at a higher level
    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 // shadows S1.Shadow
    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  	// Demonstrates that this package produces the same set of fields as encoding/json.
   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{} // need this because reflection won't create it
   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  // Tests use of LeafTypes parameter to NewCache
   160  func TestAgainstJSONEncodingEmbeddedTime(t *testing.T) {
   161  	timeLeafFn := func(t reflect.Type) bool {
   162  		return t == reflect.TypeOf(time.Time{})
   163  	}
   164  	// Demonstrates that this package can produce the same set of
   165  	// fields as encoding/json for a struct with an embedded time.Time.
   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"` // tag name takes precedence
   186  	Anonymous `json:"anon"` // anonymous non-structs also get their name from the 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  	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  	// Demonstrates that this package produces the same set of fields as encoding/json.
   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  	// An unexported anonymous non-struct field should not be recorded.
   279  	// This is currently a bug in encoding/json.
   280  	// https://github.com/golang/go/issues/18009
   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  	// An unexported anonymous struct with a tag is ignored.
   294  	// This is currently a bug in encoding/json.
   295  	// https://github.com/golang/go/issues/18009
   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  	// With fields sorted by index length and then by tag presence,
   313  	// the dominant field is always the first. Make sure all error
   314  	// cases are caught.
   315  	for _, test := range []struct {
   316  		fields []Field
   317  		wantOK bool
   318  	}{
   319  		// A single field is OK.
   320  		{[]Field{{Index: []int{0}}}, true},
   321  		{[]Field{{Index: []int{0}, NameFromTag: true}}, true},
   322  		// A single field at top level is OK.
   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  		// A single tagged field is OK.
   327  		{[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}}}, true},
   328  		// Two untagged fields at the same level is an error.
   329  		{[]Field{{Index: []int{0}}, {Index: []int{1}}}, false},
   330  		// Two tagged fields at the same level is an error.
   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  // Need this because Field contains a function, which cannot be compared even
   405  // by testutil.Equal.
   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  // Set the fields of dst from those of src.
   417  // dst must be a pointer to a struct value.
   418  // src must be a struct value.
   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"` // ignored because of top-level 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  		// Exact match wins.
   463  		{"Abc", field("Abc", int(0), 1)},
   464  		{"AbC", field("AbC", int(0), 2)},
   465  		{"ABc", field("ABc", int(0), 0, 0)},
   466  		// If there are multiple matches but no exact match or tag,
   467  		// the first field wins, lexicographically by index.
   468  		// Here, "ABc" is at a deeper embedding level, but since S4 appears
   469  		// first in S3, its index precedes the other fields of S3.
   470  		{"abc", field("ABc", int(0), 0, 0)},
   471  		// Tag name takes precedence over untagged field of the same name.
   472  		{"Tag", tfield("Tag", int(0), 4)},
   473  		// Unexported fields disappear.
   474  		{"unexported", nil},
   475  		// Untagged embedded structs disappear.
   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  	// Second time, we should cache the error.
   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