8e5b17cf13
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
826 lines
22 KiB
Go
826 lines
22 KiB
Go
/*
|
|
Copyright 2014 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package conversion
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/gofuzz"
|
|
flag "github.com/spf13/pflag"
|
|
|
|
"k8s.io/apimachinery/pkg/util/diff"
|
|
)
|
|
|
|
var fuzzIters = flag.Int("fuzz-iters", 50, "How many fuzzing iterations to do.")
|
|
|
|
// Test a weird version/kind embedding format.
|
|
type MyWeirdCustomEmbeddedVersionKindField struct {
|
|
ID string `json:"ID,omitempty"`
|
|
APIVersion string `json:"myVersionKey,omitempty"`
|
|
ObjectKind string `json:"myKindKey,omitempty"`
|
|
Z string `json:"Z,omitempty"`
|
|
Y uint64 `json:"Y,omitempty"`
|
|
}
|
|
|
|
type TestType1 struct {
|
|
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
|
|
A string `json:"A,omitempty"`
|
|
B int `json:"B,omitempty"`
|
|
C int8 `json:"C,omitempty"`
|
|
D int16 `json:"D,omitempty"`
|
|
E int32 `json:"E,omitempty"`
|
|
F int64 `json:"F,omitempty"`
|
|
G uint `json:"G,omitempty"`
|
|
H uint8 `json:"H,omitempty"`
|
|
I uint16 `json:"I,omitempty"`
|
|
J uint32 `json:"J,omitempty"`
|
|
K uint64 `json:"K,omitempty"`
|
|
L bool `json:"L,omitempty"`
|
|
M map[string]int `json:"M,omitempty"`
|
|
N map[string]TestType2 `json:"N,omitempty"`
|
|
O *TestType2 `json:"O,omitempty"`
|
|
P []TestType2 `json:"Q,omitempty"`
|
|
}
|
|
|
|
type TestType2 struct {
|
|
A string `json:"A,omitempty"`
|
|
B int `json:"B,omitempty"`
|
|
}
|
|
|
|
type ExternalTestType2 struct {
|
|
A string `json:"A,omitempty"`
|
|
B int `json:"B,omitempty"`
|
|
}
|
|
type ExternalTestType1 struct {
|
|
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
|
|
A string `json:"A,omitempty"`
|
|
B int `json:"B,omitempty"`
|
|
C int8 `json:"C,omitempty"`
|
|
D int16 `json:"D,omitempty"`
|
|
E int32 `json:"E,omitempty"`
|
|
F int64 `json:"F,omitempty"`
|
|
G uint `json:"G,omitempty"`
|
|
H uint8 `json:"H,omitempty"`
|
|
I uint16 `json:"I,omitempty"`
|
|
J uint32 `json:"J,omitempty"`
|
|
K uint64 `json:"K,omitempty"`
|
|
L bool `json:"L,omitempty"`
|
|
M map[string]int `json:"M,omitempty"`
|
|
N map[string]ExternalTestType2 `json:"N,omitempty"`
|
|
O *ExternalTestType2 `json:"O,omitempty"`
|
|
P []ExternalTestType2 `json:"Q,omitempty"`
|
|
}
|
|
|
|
func testLogger(t *testing.T) DebugLogger {
|
|
// We don't set logger to eliminate rubbish logs in tests.
|
|
// If you want to switch it, simply switch it to: "return t"
|
|
return nil
|
|
}
|
|
|
|
func TestConverter_byteSlice(t *testing.T) {
|
|
c := NewConverter(DefaultNameFunc)
|
|
src := []byte{1, 2, 3}
|
|
dest := []byte{}
|
|
err := c.Convert(&src, &dest, 0, nil)
|
|
if err != nil {
|
|
t.Fatalf("expected no error")
|
|
}
|
|
if e, a := src, dest; !reflect.DeepEqual(e, a) {
|
|
t.Errorf("expected %#v, got %#v", e, a)
|
|
}
|
|
}
|
|
|
|
func TestConverter_MismatchedTypes(t *testing.T) {
|
|
c := NewConverter(DefaultNameFunc)
|
|
|
|
err := c.RegisterConversionFunc(
|
|
func(in *[]string, out *int, s Scope) error {
|
|
if str, err := strconv.Atoi((*in)[0]); err != nil {
|
|
return err
|
|
} else {
|
|
*out = str
|
|
return nil
|
|
}
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
src := []string{"5"}
|
|
var dest *int
|
|
err = c.Convert(&src, &dest, 0, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if e, a := 5, *dest; e != a {
|
|
t.Errorf("expected %#v, got %#v", e, a)
|
|
}
|
|
}
|
|
|
|
func TestConverter_DefaultConvert(t *testing.T) {
|
|
type A struct {
|
|
Foo string
|
|
Baz int
|
|
}
|
|
type B struct {
|
|
Bar string
|
|
Baz int
|
|
}
|
|
c := NewConverter(DefaultNameFunc)
|
|
c.Debug = testLogger(t)
|
|
c.nameFunc = func(t reflect.Type) string { return "MyType" }
|
|
|
|
// Ensure conversion funcs can call DefaultConvert to get default behavior,
|
|
// then fixup remaining fields manually
|
|
err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
|
|
if err := s.DefaultConvert(in, out, IgnoreMissingFields); err != nil {
|
|
return err
|
|
}
|
|
out.Bar = in.Foo
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
|
|
x := A{"hello, intrepid test reader!", 3}
|
|
y := B{}
|
|
|
|
err = c.Convert(&x, &y, 0, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if e, a := x.Foo, y.Bar; e != a {
|
|
t.Errorf("expected %v, got %v", e, a)
|
|
}
|
|
if e, a := x.Baz, y.Baz; e != a {
|
|
t.Errorf("expected %v, got %v", e, a)
|
|
}
|
|
}
|
|
|
|
func TestConverter_DeepCopy(t *testing.T) {
|
|
type A struct {
|
|
Foo *string
|
|
Bar []string
|
|
Baz interface{}
|
|
Qux map[string]string
|
|
}
|
|
c := NewConverter(DefaultNameFunc)
|
|
c.Debug = testLogger(t)
|
|
|
|
foo, baz := "foo", "baz"
|
|
x := A{
|
|
Foo: &foo,
|
|
Bar: []string{"bar"},
|
|
Baz: &baz,
|
|
Qux: map[string]string{"qux": "qux"},
|
|
}
|
|
y := A{}
|
|
|
|
if err := c.Convert(&x, &y, 0, nil); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
*x.Foo = "foo2"
|
|
x.Bar[0] = "bar2"
|
|
*x.Baz.(*string) = "baz2"
|
|
x.Qux["qux"] = "qux2"
|
|
if e, a := *x.Foo, *y.Foo; e == a {
|
|
t.Errorf("expected difference between %v and %v", e, a)
|
|
}
|
|
if e, a := x.Bar, y.Bar; reflect.DeepEqual(e, a) {
|
|
t.Errorf("expected difference between %v and %v", e, a)
|
|
}
|
|
if e, a := *x.Baz.(*string), *y.Baz.(*string); e == a {
|
|
t.Errorf("expected difference between %v and %v", e, a)
|
|
}
|
|
if e, a := x.Qux, y.Qux; reflect.DeepEqual(e, a) {
|
|
t.Errorf("expected difference between %v and %v", e, a)
|
|
}
|
|
}
|
|
|
|
func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
|
type A struct {
|
|
Foo string
|
|
Baz int
|
|
}
|
|
type B struct {
|
|
Bar string
|
|
Baz int
|
|
}
|
|
type C struct{}
|
|
c := NewConverter(DefaultNameFunc)
|
|
c.Debug = testLogger(t)
|
|
err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
|
|
out.Bar = in.Foo
|
|
return s.Convert(&in.Baz, &out.Baz, 0)
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
err = c.RegisterConversionFunc(func(in *B, out *A, s Scope) error {
|
|
out.Foo = in.Bar
|
|
return s.Convert(&in.Baz, &out.Baz, 0)
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
|
|
x := A{"hello, intrepid test reader!", 3}
|
|
y := B{}
|
|
|
|
err = c.Convert(&x, &y, 0, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if e, a := x.Foo, y.Bar; e != a {
|
|
t.Errorf("expected %v, got %v", e, a)
|
|
}
|
|
if e, a := x.Baz, y.Baz; e != a {
|
|
t.Errorf("expected %v, got %v", e, a)
|
|
}
|
|
|
|
z := B{"all your test are belong to us", 42}
|
|
w := A{}
|
|
|
|
err = c.Convert(&z, &w, 0, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if e, a := z.Bar, w.Foo; e != a {
|
|
t.Errorf("expected %v, got %v", e, a)
|
|
}
|
|
if e, a := z.Baz, w.Baz; e != a {
|
|
t.Errorf("expected %v, got %v", e, a)
|
|
}
|
|
|
|
err = c.RegisterConversionFunc(func(in *A, out *C, s Scope) error {
|
|
return fmt.Errorf("C can't store an A, silly")
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
err = c.Convert(&A{}, &C{}, 0, nil)
|
|
if err == nil {
|
|
t.Errorf("unexpected non-error")
|
|
}
|
|
}
|
|
|
|
func TestConverter_IgnoredConversion(t *testing.T) {
|
|
type A struct{}
|
|
type B struct{}
|
|
|
|
count := 0
|
|
c := NewConverter(DefaultNameFunc)
|
|
if err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
|
|
count++
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if err := c.RegisterIgnoredConversion(&A{}, &B{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a := A{}
|
|
b := B{}
|
|
if err := c.Convert(&a, &b, 0, nil); err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
if count != 0 {
|
|
t.Errorf("unexpected number of conversion invocations")
|
|
}
|
|
}
|
|
|
|
func TestConverter_IgnoredConversionNested(t *testing.T) {
|
|
type C string
|
|
type A struct {
|
|
C C
|
|
}
|
|
type B struct {
|
|
C C
|
|
}
|
|
|
|
c := NewConverter(DefaultNameFunc)
|
|
typed := C("")
|
|
if err := c.RegisterIgnoredConversion(&typed, &typed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a := A{C: C("test")}
|
|
b := B{C: C("other")}
|
|
if err := c.Convert(&a, &b, AllowDifferentFieldTypeNames, nil); err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
if b.C != C("other") {
|
|
t.Errorf("expected no conversion of field C: %#v", b)
|
|
}
|
|
}
|
|
|
|
func TestConverter_GeneratedConversionOverriden(t *testing.T) {
|
|
type A struct{}
|
|
type B struct{}
|
|
c := NewConverter(DefaultNameFunc)
|
|
if err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if err := c.RegisterGeneratedConversionFunc(func(in *A, out *B, s Scope) error {
|
|
return fmt.Errorf("generated function should be overriden")
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
|
|
a := A{}
|
|
b := B{}
|
|
if err := c.Convert(&a, &b, 0, nil); err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestConverter_WithConversionOverriden(t *testing.T) {
|
|
type A struct{}
|
|
type B struct{}
|
|
c := NewConverter(DefaultNameFunc)
|
|
if err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
|
|
return fmt.Errorf("conversion function should be overriden")
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if err := c.RegisterGeneratedConversionFunc(func(in *A, out *B, s Scope) error {
|
|
return fmt.Errorf("generated function should be overriden")
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
|
|
ext := NewConversionFuncs()
|
|
ext.Add(func(in *A, out *B, s Scope) error {
|
|
return nil
|
|
})
|
|
newc := c.WithConversions(ext)
|
|
|
|
a := A{}
|
|
b := B{}
|
|
if err := c.Convert(&a, &b, 0, nil); err == nil || err.Error() != "conversion function should be overriden" {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
if err := newc.Convert(&a, &b, 0, nil); err != nil {
|
|
t.Errorf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestConverter_MapsStringArrays(t *testing.T) {
|
|
type A struct {
|
|
Foo string
|
|
Baz int
|
|
Other string
|
|
}
|
|
c := NewConverter(DefaultNameFunc)
|
|
c.Debug = testLogger(t)
|
|
if err := c.RegisterConversionFunc(func(input *[]string, out *string, s Scope) error {
|
|
if len(*input) == 0 {
|
|
*out = ""
|
|
}
|
|
*out = (*input)[0]
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
|
|
x := map[string][]string{
|
|
"Foo": {"bar"},
|
|
"Baz": {"1"},
|
|
"Other": {"", "test"},
|
|
"other": {"wrong"},
|
|
}
|
|
y := A{"test", 2, "something"}
|
|
|
|
if err := c.Convert(&x, &y, AllowDifferentFieldTypeNames, nil); err == nil {
|
|
t.Error("unexpected non-error")
|
|
}
|
|
|
|
if err := c.RegisterConversionFunc(func(input *[]string, out *int, s Scope) error {
|
|
if len(*input) == 0 {
|
|
*out = 0
|
|
}
|
|
str := (*input)[0]
|
|
i, err := strconv.Atoi(str)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*out = i
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
|
|
if err := c.Convert(&x, &y, AllowDifferentFieldTypeNames, nil); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if !reflect.DeepEqual(y, A{"bar", 1, ""}) {
|
|
t.Errorf("unexpected result: %#v", y)
|
|
}
|
|
}
|
|
|
|
func TestConverter_MapsStringArraysWithMappingKey(t *testing.T) {
|
|
type A struct {
|
|
Foo string `json:"test"`
|
|
Baz int
|
|
Other string
|
|
}
|
|
c := NewConverter(DefaultNameFunc)
|
|
c.Debug = testLogger(t)
|
|
if err := c.RegisterConversionFunc(func(input *[]string, out *string, s Scope) error {
|
|
if len(*input) == 0 {
|
|
*out = ""
|
|
}
|
|
*out = (*input)[0]
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
|
|
x := map[string][]string{
|
|
"Foo": {"bar"},
|
|
"test": {"baz"},
|
|
}
|
|
y := A{"", 0, ""}
|
|
|
|
if err := c.Convert(&x, &y, AllowDifferentFieldTypeNames|IgnoreMissingFields, &Meta{}); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if !reflect.DeepEqual(y, A{"bar", 0, ""}) {
|
|
t.Errorf("unexpected result: %#v", y)
|
|
}
|
|
|
|
mapping := func(key string, sourceTag, destTag reflect.StructTag) (source string, dest string) {
|
|
if s := destTag.Get("json"); len(s) > 0 {
|
|
return strings.SplitN(s, ",", 2)[0], key
|
|
}
|
|
return key, key
|
|
}
|
|
|
|
if err := c.Convert(&x, &y, AllowDifferentFieldTypeNames|IgnoreMissingFields, &Meta{KeyNameMapping: mapping}); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if !reflect.DeepEqual(y, A{"baz", 0, ""}) {
|
|
t.Errorf("unexpected result: %#v", y)
|
|
}
|
|
}
|
|
|
|
func TestConverter_fuzz(t *testing.T) {
|
|
// Use the same types from the scheme test.
|
|
table := []struct {
|
|
from, to, check interface{}
|
|
}{
|
|
{&TestType1{}, &ExternalTestType1{}, &TestType1{}},
|
|
{&ExternalTestType1{}, &TestType1{}, &ExternalTestType1{}},
|
|
}
|
|
|
|
f := fuzz.New().NilChance(.5).NumElements(0, 100)
|
|
c := NewConverter(DefaultNameFunc)
|
|
c.nameFunc = func(t reflect.Type) string {
|
|
// Hide the fact that we don't have separate packages for these things.
|
|
return map[reflect.Type]string{
|
|
reflect.TypeOf(TestType1{}): "TestType1",
|
|
reflect.TypeOf(ExternalTestType1{}): "TestType1",
|
|
reflect.TypeOf(TestType2{}): "TestType2",
|
|
reflect.TypeOf(ExternalTestType2{}): "TestType2",
|
|
}[t]
|
|
}
|
|
c.Debug = testLogger(t)
|
|
|
|
for i, item := range table {
|
|
for j := 0; j < *fuzzIters; j++ {
|
|
f.Fuzz(item.from)
|
|
err := c.Convert(item.from, item.to, 0, nil)
|
|
if err != nil {
|
|
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
|
continue
|
|
}
|
|
err = c.Convert(item.to, item.check, 0, nil)
|
|
if err != nil {
|
|
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
|
continue
|
|
}
|
|
if e, a := item.from, item.check; !reflect.DeepEqual(e, a) {
|
|
t.Errorf("(%v, %v): unexpected diff: %v", i, j, diff.ObjectDiff(e, a))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConverter_MapElemAddr(t *testing.T) {
|
|
type Foo struct {
|
|
A map[int]int
|
|
}
|
|
type Bar struct {
|
|
A map[string]string
|
|
}
|
|
c := NewConverter(DefaultNameFunc)
|
|
c.Debug = testLogger(t)
|
|
err := c.RegisterConversionFunc(
|
|
func(in *int, out *string, s Scope) error {
|
|
*out = fmt.Sprintf("%v", *in)
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
err = c.RegisterConversionFunc(
|
|
func(in *string, out *int, s Scope) error {
|
|
if str, err := strconv.Atoi(*in); err != nil {
|
|
return err
|
|
} else {
|
|
*out = str
|
|
return nil
|
|
}
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
f := fuzz.New().NilChance(0).NumElements(3, 3)
|
|
first := Foo{}
|
|
second := Bar{}
|
|
f.Fuzz(&first)
|
|
err = c.Convert(&first, &second, AllowDifferentFieldTypeNames, nil)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
third := Foo{}
|
|
err = c.Convert(&second, &third, AllowDifferentFieldTypeNames, nil)
|
|
if e, a := first, third; !reflect.DeepEqual(e, a) {
|
|
t.Errorf("Unexpected diff: %v", diff.ObjectDiff(e, a))
|
|
}
|
|
}
|
|
|
|
func TestConverter_tags(t *testing.T) {
|
|
type Foo struct {
|
|
A string `test:"foo"`
|
|
}
|
|
type Bar struct {
|
|
A string `test:"bar"`
|
|
}
|
|
c := NewConverter(DefaultNameFunc)
|
|
c.Debug = testLogger(t)
|
|
err := c.RegisterConversionFunc(
|
|
func(in *string, out *string, s Scope) error {
|
|
if e, a := "foo", s.SrcTag().Get("test"); e != a {
|
|
t.Errorf("expected %v, got %v", e, a)
|
|
}
|
|
if e, a := "bar", s.DestTag().Get("test"); e != a {
|
|
t.Errorf("expected %v, got %v", e, a)
|
|
}
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
err = c.Convert(&Foo{}, &Bar{}, AllowDifferentFieldTypeNames, nil)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestConverter_meta(t *testing.T) {
|
|
type Foo struct{ A string }
|
|
type Bar struct{ A string }
|
|
c := NewConverter(DefaultNameFunc)
|
|
c.Debug = testLogger(t)
|
|
checks := 0
|
|
err := c.RegisterConversionFunc(
|
|
func(in *Foo, out *Bar, s Scope) error {
|
|
if s.Meta() == nil {
|
|
t.Errorf("Meta did not get passed!")
|
|
}
|
|
checks++
|
|
s.Convert(&in.A, &out.A, 0)
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
err = c.RegisterConversionFunc(
|
|
func(in *string, out *string, s Scope) error {
|
|
if s.Meta() == nil {
|
|
t.Errorf("Meta did not get passed a second time!")
|
|
}
|
|
checks++
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
err = c.Convert(&Foo{}, &Bar{}, 0, &Meta{})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if checks != 2 {
|
|
t.Errorf("Registered functions did not get called.")
|
|
}
|
|
}
|
|
|
|
func TestConverter_flags(t *testing.T) {
|
|
type Foo struct{ A string }
|
|
type Bar struct{ A string }
|
|
table := []struct {
|
|
from, to interface{}
|
|
flags FieldMatchingFlags
|
|
shouldSucceed bool
|
|
}{
|
|
// Check that DestFromSource allows extra fields only in source.
|
|
{
|
|
from: &struct{ A string }{},
|
|
to: &struct{ A, B string }{},
|
|
flags: DestFromSource,
|
|
shouldSucceed: false,
|
|
}, {
|
|
from: &struct{ A, B string }{},
|
|
to: &struct{ A string }{},
|
|
flags: DestFromSource,
|
|
shouldSucceed: true,
|
|
},
|
|
|
|
// Check that SourceToDest allows for extra fields only in dest.
|
|
{
|
|
from: &struct{ A string }{},
|
|
to: &struct{ A, B string }{},
|
|
flags: SourceToDest,
|
|
shouldSucceed: true,
|
|
}, {
|
|
from: &struct{ A, B string }{},
|
|
to: &struct{ A string }{},
|
|
flags: SourceToDest,
|
|
shouldSucceed: false,
|
|
},
|
|
|
|
// Check that IgnoreMissingFields makes the above failure cases pass.
|
|
{
|
|
from: &struct{ A string }{},
|
|
to: &struct{ A, B string }{},
|
|
flags: DestFromSource | IgnoreMissingFields,
|
|
shouldSucceed: true,
|
|
}, {
|
|
from: &struct{ A, B string }{},
|
|
to: &struct{ A string }{},
|
|
flags: SourceToDest | IgnoreMissingFields,
|
|
shouldSucceed: true,
|
|
},
|
|
|
|
// Check that the field type name must match unless
|
|
// AllowDifferentFieldTypeNames is specified.
|
|
{
|
|
from: &struct{ A, B Foo }{},
|
|
to: &struct{ A Bar }{},
|
|
flags: DestFromSource,
|
|
shouldSucceed: false,
|
|
}, {
|
|
from: &struct{ A Foo }{},
|
|
to: &struct{ A, B Bar }{},
|
|
flags: SourceToDest,
|
|
shouldSucceed: false,
|
|
}, {
|
|
from: &struct{ A, B Foo }{},
|
|
to: &struct{ A Bar }{},
|
|
flags: DestFromSource | AllowDifferentFieldTypeNames,
|
|
shouldSucceed: true,
|
|
}, {
|
|
from: &struct{ A Foo }{},
|
|
to: &struct{ A, B Bar }{},
|
|
flags: SourceToDest | AllowDifferentFieldTypeNames,
|
|
shouldSucceed: true,
|
|
},
|
|
}
|
|
f := fuzz.New().NilChance(.5).NumElements(0, 100)
|
|
c := NewConverter(DefaultNameFunc)
|
|
c.Debug = testLogger(t)
|
|
|
|
for i, item := range table {
|
|
for j := 0; j < *fuzzIters; j++ {
|
|
f.Fuzz(item.from)
|
|
err := c.Convert(item.from, item.to, item.flags, nil)
|
|
if item.shouldSucceed && err != nil {
|
|
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
|
continue
|
|
}
|
|
if !item.shouldSucceed && err == nil {
|
|
t.Errorf("(%v, %v): unexpected non-error", i, j)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConverter_FieldRename(t *testing.T) {
|
|
type WeirdMeta struct {
|
|
Name string
|
|
Type string
|
|
}
|
|
type NameMeta struct {
|
|
Name string
|
|
}
|
|
type TypeMeta struct {
|
|
Type string
|
|
}
|
|
type A struct {
|
|
WeirdMeta
|
|
}
|
|
type B struct {
|
|
TypeMeta
|
|
NameMeta
|
|
}
|
|
|
|
c := NewConverter(DefaultNameFunc)
|
|
err := c.SetStructFieldCopy(WeirdMeta{}, "WeirdMeta", TypeMeta{}, "TypeMeta")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
err = c.SetStructFieldCopy(WeirdMeta{}, "WeirdMeta", NameMeta{}, "NameMeta")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
err = c.SetStructFieldCopy(TypeMeta{}, "TypeMeta", WeirdMeta{}, "WeirdMeta")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
err = c.SetStructFieldCopy(NameMeta{}, "NameMeta", WeirdMeta{}, "WeirdMeta")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
c.Debug = testLogger(t)
|
|
|
|
aVal := &A{
|
|
WeirdMeta: WeirdMeta{
|
|
Name: "Foo",
|
|
Type: "Bar",
|
|
},
|
|
}
|
|
|
|
bVal := &B{
|
|
TypeMeta: TypeMeta{"Bar"},
|
|
NameMeta: NameMeta{"Foo"},
|
|
}
|
|
|
|
table := map[string]struct {
|
|
from, to, expect interface{}
|
|
flags FieldMatchingFlags
|
|
}{
|
|
"to": {
|
|
aVal,
|
|
&B{},
|
|
bVal,
|
|
AllowDifferentFieldTypeNames | SourceToDest | IgnoreMissingFields,
|
|
},
|
|
"from": {
|
|
bVal,
|
|
&A{},
|
|
aVal,
|
|
AllowDifferentFieldTypeNames | SourceToDest,
|
|
},
|
|
"toDestFirst": {
|
|
aVal,
|
|
&B{},
|
|
bVal,
|
|
AllowDifferentFieldTypeNames,
|
|
},
|
|
"fromDestFirst": {
|
|
bVal,
|
|
&A{},
|
|
aVal,
|
|
AllowDifferentFieldTypeNames | IgnoreMissingFields,
|
|
},
|
|
}
|
|
|
|
for name, item := range table {
|
|
err := c.Convert(item.from, item.to, item.flags, nil)
|
|
if err != nil {
|
|
t.Errorf("%v: unexpected error: %v", name, err)
|
|
continue
|
|
}
|
|
if e, a := item.expect, item.to; !reflect.DeepEqual(e, a) {
|
|
t.Errorf("%v: unexpected diff: %v", name, diff.ObjectDiff(e, a))
|
|
}
|
|
}
|
|
}
|