You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

564 lines
13 KiB

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