From 498b988a6be1b8f931ce3191f0553d96f4a29cfd Mon Sep 17 00:00:00 2001 From: Christopher Poile Date: Tue, 19 Mar 2019 09:45:20 -0400 Subject: [PATCH] MM-14442: Merge function, to enable merging of configs (#10423) * MM-14442: Merge function, to enable merging of configs Merge will return a new struct of the same type as base and patch, with patch merged into base. Specifically, patch's values will be preferred except when patch's value is `nil`. Restrictions/guarantees: - base or patch will not be modified - base and patch can be pointers or values - base and patch must be the same type - base and patch must have no unexported types (at the moment) - if maps or slices are different, the entire map or slice will be replaced (at the moment) * License header * major rewrite addressing PR comments from Jesse * MM-14442: merge function for config files - simplified merge for slices and maps - fixed many problems with nested pointers/maps/slices - all references are cloned - 54 new tests, simplified tests for specific problems * MM-14442: fixing formatting, comments --- config/common.go | 12 + config/common_test.go | 61 ++ config/export_test.go | 5 + utils/merge.go | 165 +++++ utils/merge_test.go | 1649 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1892 insertions(+) create mode 100644 utils/merge.go create mode 100644 utils/merge_test.go diff --git a/config/common.go b/config/common.go index 4e0e89b7d3..de0444f5ec 100644 --- a/config/common.go +++ b/config/common.go @@ -4,6 +4,7 @@ package config import ( + "github.com/mattermost/mattermost-server/utils" "io" "sync" @@ -140,3 +141,14 @@ func (cs *commonStore) validate(cfg *model.Config) error { return nil } + +// mergeConfig merges two configs together. The receiver's values are overwritten with the patch's +// values except when the patch's values are nil. +func (cs *commonStore) mergeConfig(patch *model.Config) (*model.Config, error) { + ret, err := utils.Merge(cs.config, patch) + if err != nil { + return nil, err + } + retC := ret.(model.Config) + return &retC, nil +} diff --git a/config/common_test.go b/config/common_test.go index 108f7dc6af..1a373bbb69 100644 --- a/config/common_test.go +++ b/config/common_test.go @@ -1,6 +1,9 @@ package config_test import ( + "github.com/mattermost/mattermost-server/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "testing" "github.com/mattermost/mattermost-server/model" @@ -74,3 +77,61 @@ func prepareExpectedConfig(t *testing.T, expectedCfg *model.Config) *model.Confi return expectedCfg } + +func TestMergeConfigs(t *testing.T) { + t.Run("merge two default configs with different salts/keys", func(t *testing.T) { + base, err := config.NewMemoryStore() + require.NoError(t, err) + patch, err := config.NewMemoryStore() + require.NoError(t, err) + + merged, err := base.MergeConfig(patch.Get()) + require.NoError(t, err) + + assert.Equal(t, patch.Get(), merged) + }) + t.Run("merge identical configs", func(t *testing.T) { + base, err := config.NewMemoryStore() + require.NoError(t, err) + patch := base.Get().Clone() + + merged, err := base.MergeConfig(patch) + require.NoError(t, err) + + assert.Equal(t, base.Get(), merged) + assert.Equal(t, patch, merged) + }) + t.Run("merge configs with a different setting", func(t *testing.T) { + base, err := config.NewMemoryStore() + require.NoError(t, err) + patch := base.Get().Clone() + patch.ServiceSettings.SiteURL = newString("http://newhost.ca") + + merged, err := base.MergeConfig(patch) + require.NoError(t, err) + + assert.NotEqual(t, base.Get(), merged) + assert.Equal(t, patch, merged) + }) + t.Run("merge default config with changes from a mostly nil patch", func(t *testing.T) { + base, err := config.NewMemoryStore() + require.NoError(t, err) + patch := &model.Config{} + patch.ServiceSettings.SiteURL = newString("http://newhost.ca") + patch.GoogleSettings.Enable = newBool(true) + + expected := base.Get().Clone() + expected.ServiceSettings.SiteURL = newString("http://newhost.ca") + expected.GoogleSettings.Enable = newBool(true) + + merged, err := base.MergeConfig(patch) + require.NoError(t, err) + + assert.NotEqual(t, base.Get(), merged) + assert.NotEqual(t, patch, merged) + assert.Equal(t, expected, merged) + }) +} + +func newBool(b bool) *bool { return &b } +func newString(s string) *string { return &s } diff --git a/config/export_test.go b/config/export_test.go index b8ef523982..7e5392ef09 100644 --- a/config/export_test.go +++ b/config/export_test.go @@ -26,3 +26,8 @@ func InitializeConfigurationsTable(db *sqlx.DB) error { func ResolveConfigFilePath(path string) (string, error) { return resolveConfigFilePath(path) } + +// GetCommonStore exposes the internal commonStore to test only. +func (ms *memoryStore) MergeConfig(patch *model.Config) (*model.Config, error) { + return ms.mergeConfig(patch) +} diff --git a/utils/merge.go b/utils/merge.go new file mode 100644 index 0000000000..8ac86dbda7 --- /dev/null +++ b/utils/merge.go @@ -0,0 +1,165 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package utils + +import ( + "fmt" + "reflect" +) + +// Merge will return a new struct/map/slice of the same type as base and patch, with patch merged into base. +// Specifically, patch's values will be preferred except when patch's value is `nil`. +// Note: a referenced value (eg. *bool) will only be `nil` if the pointer is nil. If the value is a zero value, +// then that is considered a legitimate value. Eg, *bool(false) will overwrite *bool(true). +// +// Restrictions/guarantees: +// - base and patch will not be modified +// - base and patch can be pointers or values +// - base and patch must be the same type +// - if slices are different, this rule applies: +// - if patch is not nil, overwrite the base slice. +// - otherwise, keep the base slice +// - maps will be merged according to the following rules: +// - if patch is not nil, replace the base map completely +// - otherwise, keep the base map +// - reference values (eg. slice/ptr/map) will be cloned +// - channel values are not supported at the moment +// +// Usage: callers need to cast the returned interface back into the original type, eg: +// func mergeTestStruct(base, patch *testStruct) (*testStruct, error) { +// ret, err := merge(base, patch) +// if err != nil { +// return nil, err +// } +// retTS := ret.(testStruct) +// return &retTS, nil +// } +func Merge(base interface{}, patch interface{}) (interface{}, error) { + if reflect.TypeOf(base) != reflect.TypeOf(patch) { + return nil, fmt.Errorf("cannot merge different types. base type: %s, patch type: %s", + reflect.TypeOf(base), reflect.TypeOf(patch)) + } + + commonType := reflect.TypeOf(base) + baseVal := reflect.ValueOf(base) + patchVal := reflect.ValueOf(patch) + if commonType.Kind() == reflect.Ptr { + commonType = commonType.Elem() + baseVal = baseVal.Elem() + patchVal = patchVal.Elem() + } + + ret := reflect.New(commonType) + + val, ok := merge(baseVal, patchVal) + if ok { + ret.Elem().Set(val) + } + return ret.Elem().Interface(), nil +} + +// merge recursively merges patch into base and returns the new struct, ptr, slice/map, or value +func merge(base, patch reflect.Value) (reflect.Value, bool) { + commonType := base.Type() + + switch commonType.Kind() { + case reflect.Struct: + merged := reflect.New(commonType).Elem() + for i := 0; i < base.NumField(); i++ { + if !merged.Field(i).CanSet() { + continue + } + val, ok := merge(base.Field(i), patch.Field(i)) + if ok { + merged.Field(i).Set(val) + } + } + return merged, true + + case reflect.Ptr: + mergedPtr := reflect.New(commonType.Elem()) + if base.IsNil() && patch.IsNil() { + return mergedPtr, false + } + + // clone reference values (if any) + if base.IsNil() { + val, _ := merge(patch.Elem(), patch.Elem()) + mergedPtr.Elem().Set(val) + } else if patch.IsNil() { + val, _ := merge(base.Elem(), base.Elem()) + mergedPtr.Elem().Set(val) + } else { + val, _ := merge(base.Elem(), patch.Elem()) + mergedPtr.Elem().Set(val) + } + return mergedPtr, true + + case reflect.Slice: + if base.IsNil() && patch.IsNil() { + return reflect.Zero(commonType), false + } + if !patch.IsNil() { + // use patch + merged := reflect.MakeSlice(commonType, 0, patch.Len()) + for i := 0; i < patch.Len(); i++ { + // recursively merge patch with itself. This will clone reference values. + val, _ := merge(patch.Index(i), patch.Index(i)) + merged = reflect.Append(merged, val) + } + return merged, true + } + // use base + merged := reflect.MakeSlice(commonType, 0, base.Len()) + for i := 0; i < base.Len(); i++ { + + // recursively merge base with itself. This will clone reference values. + val, _ := merge(base.Index(i), base.Index(i)) + merged = reflect.Append(merged, val) + } + return merged, true + + case reflect.Map: + // maps are merged according to these rules: + // - if patch is not nil, replace the base map completely + // - otherwise, keep the base map + // - reference values (eg. slice/ptr/map) will be cloned + if base.IsNil() && patch.IsNil() { + return reflect.Zero(commonType), false + } + merged := reflect.MakeMap(commonType) + mapPtr := base + if !patch.IsNil() { + mapPtr = patch + } + for _, key := range mapPtr.MapKeys() { + // clone reference values + val, ok := merge(mapPtr.MapIndex(key), mapPtr.MapIndex(key)) + if !ok { + val = reflect.New(mapPtr.MapIndex(key).Type()).Elem() + } + merged.SetMapIndex(key, val) + } + return merged, true + + case reflect.Interface: + var val reflect.Value + if base.IsNil() && patch.IsNil() { + return reflect.Zero(commonType), false + } + + // clone reference values (if any) + if base.IsNil() { + val, _ = merge(patch.Elem(), patch.Elem()) + } else if patch.IsNil() { + val, _ = merge(base.Elem(), base.Elem()) + } else { + val, _ = merge(base.Elem(), patch.Elem()) + } + return val, true + + default: + return patch, true + } +} diff --git a/utils/merge_test.go b/utils/merge_test.go new file mode 100644 index 0000000000..0a66dfbba9 --- /dev/null +++ b/utils/merge_test.go @@ -0,0 +1,1649 @@ +package utils + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Test merging maps alone. This isolates the complexity of merging maps from merging maps recursively in +// a struct/ptr/etc. +// Remember that for our purposes, "merging" means replacing base with patch if patch is /anything/ other than nil. +func TestMergeWithMaps(t *testing.T) { + t.Run("merge maps where patch is longer", func(t *testing.T) { + m1 := map[string]int{"this": 1, "is": 2, "a map": 3} + m2 := map[string]int{"this": 1, "is": 3, "a second map": 3, "another key": 4} + + expected := map[string]int{"this": 1, "is": 3, "a second map": 3, "another key": 4} + merged, err := mergeStringIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge maps where base is longer", func(t *testing.T) { + m1 := map[string]int{"this": 1, "is": 2, "a map": 3, "with": 4, "more keys": -12} + m2 := map[string]int{"this": 1, "is": 3, "a second map": 3} + expected := map[string]int{"this": 1, "is": 3, "a second map": 3} + + merged, err := mergeStringIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge maps where base is empty", func(t *testing.T) { + m1 := make(map[string]int) + m2 := map[string]int{"this": 1, "is": 3, "a second map": 3, "another key": 4} + + expected := map[string]int{"this": 1, "is": 3, "a second map": 3, "another key": 4} + merged, err := mergeStringIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge maps where patch is empty", func(t *testing.T) { + m1 := map[string]int{"this": 1, "is": 3, "a map": 3, "another key": 4} + var m2 map[string]int + expected := map[string]int{"this": 1, "is": 3, "a map": 3, "another key": 4} + + merged, err := mergeStringIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]*int patch with different keys and values", func(t *testing.T) { + m1 := map[string]*int{"this": newInt(1), "is": newInt(3), "a key": newInt(3)} + m2 := map[string]*int{"this": newInt(2), "is": newInt(3), "a key": newInt(4)} + expected := map[string]*int{"this": newInt(2), "is": newInt(3), "a key": newInt(4)} + + merged, err := mergeStringPtrIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]*int patch has nil keys -- doesn't matter, maps overwrite completely", func(t *testing.T) { + m1 := map[string]*int{"this": newInt(1), "is": newInt(3), "a key": newInt(3)} + m2 := map[string]*int{"this": newInt(1), "is": nil, "a key": newInt(3)} + expected := map[string]*int{"this": newInt(1), "is": nil, "a key": newInt(3)} + + merged, err := mergeStringPtrIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]*int base has nil vals -- overwrite base with patch", func(t *testing.T) { + m1 := map[string]*int{"this": newInt(1), "is": nil, "base key": newInt(4)} + m2 := map[string]*int{"this": newInt(1), "is": newInt(3), "a key": newInt(3)} + expected := map[string]*int{"this": newInt(1), "is": newInt(3), "a key": newInt(3)} + + merged, err := mergeStringPtrIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]*int base has nil vals -- but patch is nil, so keep base", func(t *testing.T) { + m1 := map[string]*int{"this": newInt(1), "is": nil, "base key": newInt(4)} + var m2 map[string]*int + expected := map[string]*int{"this": newInt(1), "is": nil, "base key": newInt(4)} + + merged, err := mergeStringPtrIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]*int pointers are not copied - change in base do not affect merged", func(t *testing.T) { + // that should never happen, since patch overwrites completely + m1 := map[string]*int{"this": newInt(1), "is": newInt(3), "a key": newInt(4)} + m2 := map[string]*int{"this": newInt(1), "a key": newInt(5)} + expected := map[string]*int{"this": newInt(1), "a key": newInt(5)} + + merged, err := mergeStringPtrIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + *m1["this"] = 6 + assert.Equal(t, 1, *merged["this"]) + }) + + t.Run("merge map[string]*int pointers are not copied - change in patched do not affect merged", func(t *testing.T) { + m1 := map[string]*int{"this": newInt(1), "is": newInt(3), "a key": newInt(4)} + m2 := map[string]*int{"this": newInt(2), "a key": newInt(5)} + expected := map[string]*int{"this": newInt(2), "a key": newInt(5)} + + merged, err := mergeStringPtrIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + *m2["this"] = 6 + assert.Equal(t, 2, *merged["this"]) + }) + + t.Run("merge map[string][]int overwrite base with patch", func(t *testing.T) { + m1 := map[string][]int{"this": {1, 2, 3}, "is": {4, 5, 6}} + m2 := map[string][]int{"this": {1, 2, 3}, "new": {7, 8, 9}} + expected := map[string][]int{"this": {1, 2, 3}, "new": {7, 8, 9}} + + merged, err := mergeStringSliceIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string][]int nil in patch /does/ overwrite base", func(t *testing.T) { + m1 := map[string][]int{"this": {1, 2, 3}, "is": {4, 5, 6}} + m2 := map[string][]int{"this": {1, 2, 3}, "is": nil} + expected := map[string][]int{"this": {1, 2, 3}, "is": nil} + + merged, err := mergeStringSliceIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string][]int nil in base is overwritten", func(t *testing.T) { + m1 := map[string][]int{"this": {1, 2, 3}, "is": nil} + m2 := map[string][]int{"this": {1, 2, 3}, "is": {4, 5, 6}} + expected := map[string][]int{"this": {1, 2, 3}, "is": {4, 5, 6}} + merged, err := mergeStringSliceIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string][]int nil in base is overwritten even without matching key", func(t *testing.T) { + m1 := map[string][]int{"this": {1, 2, 3}, "is": nil} + m2 := map[string][]int{"this": {1, 2, 3}, "new": {4, 5, 6}} + expected := map[string][]int{"this": {1, 2, 3}, "new": {4, 5, 6}} + + merged, err := mergeStringSliceIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string][]int slice is cloned - change in base does not affect merged", func(t *testing.T) { + // shouldn't, is patch clobbers + m1 := map[string][]int{"this": {1, 2, 3}, "is": {4, 5, 6}} + m2 := map[string][]int{"this": {1, 2, 3}, "new": {7, 8, 9}} + expected := map[string][]int{"this": {1, 2, 3}, "new": {7, 8, 9}} + + merged, err := mergeStringSliceIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + m1["this"][0] = 99 + assert.Equal(t, 1, merged["this"][0]) + }) + + t.Run("merge map[string][]int slice is cloned - change in patch does not affect merged", func(t *testing.T) { + m1 := map[string][]int{"this": {1, 2, 3}, "is": {4, 5, 6}} + m2 := map[string][]int{"this": {1, 2, 3}, "new": {7, 8, 9}} + expected := map[string][]int{"this": {1, 2, 3}, "new": {7, 8, 9}} + + merged, err := mergeStringSliceIntMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + m2["new"][1] = 0 + assert.Equal(t, 8, merged["new"][1]) + }) + + t.Run("merge map[string]map[string]*int", func(t *testing.T) { + m1 := map[string]map[string]*int{"this": {"second": newInt(99)}, "base": {"level": newInt(10)}} + m2 := map[string]map[string]*int{"this": {"second": newInt(77)}, "patch": {"level": newInt(15)}} + expected := map[string]map[string]*int{"this": {"second": newInt(77)}, "patch": {"level": newInt(15)}} + + merged, err := mergeMapOfMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]map[string]*int, patch has nil keys -- /do/ overwrite base with nil", func(t *testing.T) { + m1 := map[string]map[string]*int{"this": {"second": newInt(99)}, "base": {"level": newInt(10)}} + m2 := map[string]map[string]*int{"this": {"second": nil}, "base": nil, "patch": {"level": newInt(15)}} + expected := map[string]map[string]*int{"this": {"second": nil}, "base": nil, "patch": {"level": newInt(15)}} + + merged, err := mergeMapOfMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]map[string]*int, base has nil vals -- overwrite base with patch", func(t *testing.T) { + m1 := map[string]map[string]*int{"this": {"second": nil}, "base": nil} + m2 := map[string]map[string]*int{"this": {"second": newInt(77)}, "base": {"level": newInt(10)}, "patch": {"level": newInt(15)}} + expected := map[string]map[string]*int{"this": {"second": newInt(77)}, "base": {"level": newInt(10)}, "patch": {"level": newInt(15)}} + + merged, err := mergeMapOfMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]map[string]*int, pointers are not copied - change in base does not affect merged", func(t *testing.T) { + // shouldn't, if we're overwriting completely + m1 := map[string]map[string]*int{"this": {"second": newInt(99)}, "base": {"level": newInt(10)}, "are belong": {"to us": newInt(23)}} + m2 := map[string]map[string]*int{"base": {"level": newInt(10)}} + expected := map[string]map[string]*int{"base": {"level": newInt(10)}} + + merged, err := mergeMapOfMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + + // test changing the map entry's referenced value + *m1["base"]["level"] = 347 + assert.Equal(t, 10, *merged["base"]["level"]) + + // test replacing map entry + m1["base"]["level"] = newInt(12) + assert.Equal(t, 10, *merged["base"]["level"]) + + // test replacing a referenced map + m1["base"] = map[string]*int{"third": newInt(777)} + assert.Equal(t, 10, *merged["base"]["level"]) + + }) + + t.Run("merge map[string]map[string]*int, pointers are not copied - change in patch do not affect merged", func(t *testing.T) { + m1 := map[string]map[string]*int{"base": {"level": newInt(15)}} + m2 := map[string]map[string]*int{"this": {"second": newInt(99)}, "patch": {"level": newInt(10)}, + "are belong": {"to us": newInt(23)}} + expected := map[string]map[string]*int{"this": {"second": newInt(99)}, "patch": {"level": newInt(10)}, + "are belong": {"to us": newInt(23)}} + + merged, err := mergeMapOfMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + + // test replacing a referenced map + m2["this"] = map[string]*int{"third": newInt(777)} + assert.Equal(t, 99, *merged["this"]["second"]) + + // test replacing map entry + m2["patch"]["level"] = newInt(12) + assert.Equal(t, 10, *merged["patch"]["level"]) + + // test changing the map entry's referenced value + *m2["are belong"]["to us"] = 347 + assert.Equal(t, 23, *merged["are belong"]["to us"]) + }) + + t.Run("merge map[string]interface{}", func(t *testing.T) { + m1 := map[string]interface{}{"this": map[string]*int{"second": newInt(99)}, + "base": map[string]*int{"level": newInt(10)}} + m2 := map[string]interface{}{"this": map[string]*int{"second": newInt(77)}, + "patch": map[string]*int{"level": newInt(15)}} + expected := map[string]interface{}{"this": map[string]*int{"second": newInt(77)}, + "patch": map[string]*int{"level": newInt(15)}} + + merged, err := mergeInterfaceMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]interface{}, patch has nil keys -- /do/ overwrite base with nil", func(t *testing.T) { + m1 := map[string]interface{}{"this": map[string]*int{"second": newInt(99)}} + m2 := map[string]interface{}{"this": map[string]*int{"second": nil}} + expected := map[string]interface{}{"this": map[string]*int{"second": nil}} + + merged, err := mergeInterfaceMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]interface{}, patch has nil keys -- /do/ overwrite base with nil (more complex)", func(t *testing.T) { + m1 := map[string]interface{}{"this": map[string]*int{"second": newInt(99)}, + "base": map[string]*int{"level": newInt(10)}} + m2 := map[string]interface{}{"this": map[string]*int{"second": nil}, + "base": nil, "patch": map[string]*int{"level": newInt(15)}} + expected := map[string]interface{}{"this": map[string]*int{"second": nil}, + "base": nil, "patch": map[string]*int{"level": newInt(15)}} + + merged, err := mergeInterfaceMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]map[string]*int, base has nil vals -- overwrite base with patch", func(t *testing.T) { + m1 := map[string]interface{}{"base": nil} + m2 := map[string]interface{}{"base": map[string]*int{"level": newInt(10)}} + expected := map[string]interface{}{"base": map[string]*int{"level": newInt(10)}} + + merged, err := mergeInterfaceMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]map[string]*int, base has nil vals -- overwrite base with patch (more complex)", func(t *testing.T) { + m1 := map[string]interface{}{"this": map[string]*int{"second": nil}, "base": nil} + m2 := map[string]interface{}{"this": map[string]*int{"second": newInt(77)}, + "base": map[string]*int{"level": newInt(10)}, "patch": map[string]*int{"level": newInt(15)}} + expected := map[string]interface{}{"this": map[string]*int{"second": newInt(77)}, + "base": map[string]*int{"level": newInt(10)}, "patch": map[string]*int{"level": newInt(15)}} + + merged, err := mergeInterfaceMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("merge map[string]interface{}, pointers are not copied - changes in base do not affect merged", func(t *testing.T) { + m1 := map[string]interface{}{"this": map[string]*int{"second": newInt(99)}, + "base": map[string]*int{"level": newInt(10)}, "are belong": map[string]*int{"to us": newInt(23)}} + m2 := map[string]interface{}{"this": map[string]*int{"second": newInt(99)}, + "base": map[string]*int{"level": newInt(10)}, "are belong": map[string]*int{"to us": newInt(23)}} + expected := map[string]interface{}{"this": map[string]*int{"second": newInt(99)}, + "base": map[string]*int{"level": newInt(10)}, "are belong": map[string]*int{"to us": newInt(23)}} + + merged, err := mergeInterfaceMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + + // test replacing a referenced map + m1["this"] = map[string]*int{"third": newInt(777)} + assert.Equal(t, 99, *merged["this"].(map[string]*int)["second"]) + + // test replacing map entry + m1["base"].(map[string]*int)["level"] = newInt(12) + assert.Equal(t, 10, *merged["base"].(map[string]*int)["level"]) + + // test changing the map entry's referenced value + *m1["are belong"].(map[string]*int)["to us"] = 347 + assert.Equal(t, 23, *merged["are belong"].(map[string]*int)["to us"]) + }) + + t.Run("merge map[string]interface{}, pointers are not copied - change in patch do not affect merged", func(t *testing.T) { + m1 := map[string]interface{}{"base": map[string]*int{"level": newInt(15)}} + m2 := map[string]interface{}{"this": map[string]*int{"second": newInt(99)}, + "patch": map[string]*int{"level": newInt(10)}, "are belong": map[string]*int{"to us": newInt(23)}} + expected := map[string]interface{}{"this": map[string]*int{"second": newInt(99)}, + "patch": map[string]*int{"level": newInt(10)}, "are belong": map[string]*int{"to us": newInt(23)}} + + merged, err := mergeInterfaceMap(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + + // test replacing a referenced map + m2["this"] = map[string]*int{"third": newInt(777)} + assert.Equal(t, 99, *merged["this"].(map[string]*int)["second"]) + + // test replacing map entry + m2["patch"].(map[string]*int)["level"] = newInt(12) + assert.Equal(t, 10, *merged["patch"].(map[string]*int)["level"]) + + // test changing the map entry's referenced value + *m2["are belong"].(map[string]*int)["to us"] = 347 + assert.Equal(t, 23, *merged["are belong"].(map[string]*int)["to us"]) + }) +} + +// Test merging slices alone. This isolates the complexity of merging slices from merging slices +// recursively in a struct/ptr/etc. +func TestMergeWithSlices(t *testing.T) { + t.Run("patch overwrites base slice", func(t *testing.T) { + m1 := []string{"this", "will", "be", "overwritten"} + m2 := []string{"this one", "will", "replace the other", "one", "and", "is", "longer"} + expected := []string{"this one", "will", "replace the other", "one", "and", "is", "longer"} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("patch overwrites base even when base is longer", func(t *testing.T) { + m1 := []string{"this", "will", "be", "overwritten", "but", "not", "this"} + m2 := []string{"this one", "will", "replace the other", "one"} + expected := []string{"this one", "will", "replace the other", "one"} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("patch overwrites when base is empty slice", func(t *testing.T) { + m1 := []string{} + m2 := []string{"this one", "will", "replace the other", "one"} + expected := []string{"this one", "will", "replace the other", "one"} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("patch overwrites when base is nil", func(t *testing.T) { + var m1 []string + m2 := []string{"this one", "will", "replace the other", "one"} + expected := []string{"this one", "will", "replace the other", "one"} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("patch overwites when patch is empty struct", func(t *testing.T) { + m1 := []string{"this", "will", "be", "overwritten"} + m2 := []string{} + expected := []string{} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("use base where patch is nil", func(t *testing.T) { + m1 := []string{"this", "will", "not", "be", "overwritten"} + var m2 []string + expected := []string{"this", "will", "not", "be", "overwritten"} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("return nil where both are nil", func(t *testing.T) { + var m1 []string + var m2 []string + expected := []string(nil) + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("return empty struct where both are empty", func(t *testing.T) { + m1 := []string{} + m2 := []string{} + expected := []string{} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + }) + + t.Run("patch is nil, slice is not copied. change in base will not affect merged", func(t *testing.T) { + m1 := []string{"this", "will", "not", "be", "overwritten"} + var m2 []string + expected := []string{"this", "will", "not", "be", "overwritten"} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + m1[0] = "THAT" + assert.Equal(t, "this", merged[0]) + }) + + t.Run("patch empty, slice is not copied. change in patch will not affect merged", func(t *testing.T) { + m1 := []string{"this", "will", "not", "be", "overwritten"} + m2 := []string{} + expected := []string{} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + fmt.Println("expeted: ", expected, " merged: ", merged) + assert.Equal(t, expected, merged) + // of course this won't change merged, even if it did copy... but just in case. + m2 = append(m2, "test") + assert.Equal(t, 0, len(merged)) + }) + + t.Run("slice is not copied. change in patch will not affect merged", func(t *testing.T) { + var m1 []string + m2 := []string{"this", "will", "not", "be", "overwritten"} + expected := []string{"this", "will", "not", "be", "overwritten"} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + m2[0] = "THAT" + assert.Equal(t, "this", merged[0]) + }) + + t.Run("base overwritten, slice is not copied. change in patch will not affect merged", func(t *testing.T) { + m1 := []string{"this", "will", "be", "overwritten"} + m2 := []string{"that", "overwrote", "it"} + expected := []string{"that", "overwrote", "it"} + + merged, err := mergeStringSlices(m1, m2) + require.NoError(t, err) + + assert.Equal(t, expected, merged) + m2[0] = "THAT!!" + assert.Equal(t, "that", merged[0]) + }) +} + +type evenSimpler struct { + B *bool + ES2 *evenSimpler2 +} + +func (e *evenSimpler) String() string { + if e == nil { + return "nil" + } + sb := "nil" + if e.B != nil { + sb = fmt.Sprintf("%t", *e.B) + } + return fmt.Sprintf("ES{B: %s, ES2: %s}", sb, e.ES2.String()) +} + +type evenSimpler2 struct { + S *string +} + +func (e *evenSimpler2) String() string { + if e == nil { + return "nil" + } + var s string + if e.S == nil { + s = "nil" + } else { + s = *e.S + } + return fmt.Sprintf("ES2{S: %s}", s) +} + +func TestMergeWithEvenSimpler(t *testing.T) { + t.Run("evenSimplerStruct: base nils are overwritten by patch", func(t *testing.T) { + t1 := evenSimpler{newBool(true), &evenSimpler2{nil}} + t2 := evenSimpler{newBool(false), &evenSimpler2{newString("patch")}} + expected := evenSimpler{newBool(false), &evenSimpler2{newString("patch")}} + + merged, err := mergeEvenSimpler(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("evenSimplerStruct: patch nils are ignored", func(t *testing.T) { + t1 := evenSimpler{newBool(true), &evenSimpler2{newString("base")}} + t2 := evenSimpler{nil, &evenSimpler2{nil}} + expected := evenSimpler{newBool(true), &evenSimpler2{newString("base")}} + + merged, err := mergeEvenSimpler(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("evenSimplerStruct: can handle both nils, merged will have nil (not zero value)", func(t *testing.T) { + t1 := evenSimpler{nil, &evenSimpler2{nil}} + t2 := evenSimpler{nil, &evenSimpler2{nil}} + expected := evenSimpler{nil, &evenSimpler2{nil}} + + merged, err := mergeEvenSimpler(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("evenSimplerStruct: can handle both nils (ptr to ptr), merged will have nil (not zero value)", func(t *testing.T) { + t1 := evenSimpler{newBool(true), nil} + t2 := evenSimpler{newBool(true), nil} + expected := evenSimpler{newBool(true), nil} + + merged, err := mergeEvenSimpler(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("evenSimplerStruct: base nils (ptr to ptr) are overwritten by patch", func(t *testing.T) { + t1 := evenSimpler{newBool(true), nil} + t2 := evenSimpler{newBool(false), &evenSimpler2{newString("patch")}} + expected := evenSimpler{newBool(false), &evenSimpler2{newString("patch")}} + + merged, err := mergeEvenSimpler(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("evenSimplerStruct: base nils (ptr to ptr) are overwritten by patch, and not copied - changes in patch don't affect merged", func(t *testing.T) { + t1 := evenSimpler{newBool(true), nil} + t2 := evenSimpler{newBool(false), &evenSimpler2{newString("patch")}} + expected := evenSimpler{newBool(false), &evenSimpler2{newString("patch")}} + + merged, err := mergeEvenSimpler(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + *t2.ES2.S = "new patch" + assert.Equal(t, "patch", *merged.ES2.S) + }) + + t.Run("evenSimplerStruct: patch nils (ptr to ptr) do not overwrite base, and are not copied - changes in base don't affect merged", func(t *testing.T) { + t1 := evenSimpler{newBool(true), &evenSimpler2{newString("base")}} + t2 := evenSimpler{newBool(false), nil} + expected := evenSimpler{newBool(false), &evenSimpler2{newString("base")}} + + merged, err := mergeEvenSimpler(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + *t1.ES2.S = "new base" + assert.Equal(t, "base", *merged.ES2.S) + }) + +} + +type sliceStruct struct { + Sls []string +} + +func TestMergeWithSliceStruct(t *testing.T) { + t.Run("patch nils are ignored - sliceStruct", func(t *testing.T) { + t1 := sliceStruct{[]string{"this", "is", "base"}} + t2 := sliceStruct{nil} + expected := sliceStruct{[]string{"this", "is", "base"}} + + merged, err := mergeSliceStruct(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("base nils are overwritten by patch - sliceStruct", func(t *testing.T) { + t1 := sliceStruct{nil} + t2 := sliceStruct{[]string{"this", "is", "patch"}} + expected := sliceStruct{[]string{"this", "is", "patch"}} + + merged, err := mergeSliceStruct(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("slices are not being copied or modified", func(t *testing.T) { + t1 := sliceStruct{[]string{"this", "is", "base"}} + t2 := sliceStruct{nil} + expected := sliceStruct{[]string{"this", "is", "base"}} + + merged, err := mergeSliceStruct(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + + // changes in base do not affect merged + t1.Sls[0] = "test0" + assert.Equal(t, "this", merged.Sls[0]) + + // changes in merged (on slice that was cloned from base) do not affect base + merged.Sls[1] = "test222" + assert.Equal(t, "is", t1.Sls[1]) + }) + + t.Run("slices are not being copied or modified", func(t *testing.T) { + t1 := sliceStruct{nil} + t2 := sliceStruct{[]string{"this", "is", "patch"}} + expected := sliceStruct{[]string{"this", "is", "patch"}} + + merged, err := mergeSliceStruct(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + + // changes in patch do not affect merged + t2.Sls[0] = "test0" + assert.Equal(t, "this", merged.Sls[0]) + + // changes in merged (on slice that was cloned from patch) do not affect patch + merged.Sls[1] = "test222" + assert.Equal(t, "is", t2.Sls[1]) + }) +} + +type mapPtr struct { + MP map[string]*evenSimpler2 +} + +func TestMergeWithMapPtr(t *testing.T) { + t.Run("patch nils overwrite - mapPtr - maps overwrite completely", func(t *testing.T) { + t1 := mapPtr{map[string]*evenSimpler2{"base key": {newString("base")}}} + t2 := mapPtr{map[string]*evenSimpler2{"base key": {nil}}} + expected := mapPtr{map[string]*evenSimpler2{"base key": {nil}}} + + merged, err := mergeMapPtr(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("patch nil structs are ignored - mapPtr - maps overwrite ", func(t *testing.T) { + t1 := mapPtr{map[string]*evenSimpler2{"base key": {newString("base")}}} + t2 := mapPtr{map[string]*evenSimpler2{"base key": nil}} + expected := mapPtr{map[string]*evenSimpler2{"base key": nil}} + + merged, err := mergeMapPtr(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) +} + +type mapPtrState struct { + MP map[string]*state +} +type state struct { + Enable bool +} + +func TestMergeWithMapPtrState(t *testing.T) { + t.Run("inside structs, patch map overwrites completely - mapPtrState", func(t *testing.T) { + t1 := mapPtrState{map[string]*state{"base key": {true}}} + t2 := mapPtrState{map[string]*state{"base key": nil}} + expected := mapPtrState{map[string]*state{"base key": nil}} + + merged, err := mergeMapPtrState(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("merge identical structs - simple", func(t *testing.T) { + t1 := simple{42, 42.2, newFloat64(932.2), newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + t2 := simple{42, 42.2, newFloat64(932.2), newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + expected := simple{42, 0, nil, newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + + merged, err := mergeSimple(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("base nils are overwritten by patch", func(t *testing.T) { + t1 := simple{42, 42.2, newFloat64(932.2), newInt(45), nil, + []int{1, 2, 3}, nil, + simple2{30, nil, nil}, + nil} + t2 := simple{42, 42.2, newFloat64(932.2), newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + expected := simple{42, 0, nil, newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + + merged, err := mergeSimple(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) +} + +type mapPtrState2 struct { + MP map[string]*state2 +} +type state2 struct { + Enable bool + EPtr *bool +} + +func TestMergeWithMapPtrState2(t *testing.T) { + t.Run("inside structs, maps overwrite completely - mapPtrState2", func(t *testing.T) { + t1 := mapPtrState2{map[string]*state2{"base key": {true, newBool(true)}}} + t2 := mapPtrState2{map[string]*state2{"base key": {false, nil}}} + expected := mapPtrState2{map[string]*state2{"base key": {false, nil}}} + + merged, err := mergeMapPtrState2(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("inside structs, maps overwrite completely - mapPtrState2 2", func(t *testing.T) { + t1 := mapPtrState2{map[string]*state2{"base key": {true, newBool(true)}}} // + t2 := mapPtrState2{map[string]*state2{"base key": nil}} + expected := mapPtrState2{map[string]*state2{"base key": nil}} + + merged, err := mergeMapPtrState2(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) +} + +type simple struct { + I int + f float64 + fp *float64 + Ip *int + B *bool + Sli []int + Msi map[string]int + S2 simple2 + S3 *simple2 +} + +type simple2 struct { + I int + S *string + Sls []string +} + +func TestMergeWithSimpleStruct(t *testing.T) { + t.Run("patch nils are ignored", func(t *testing.T) { + t1 := simple{42, 42.2, newFloat64(932.2), newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test base"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + t2 := simple{42, 42.2, newFloat64(932.2), nil, nil, + nil, nil, + simple2{30, nil, nil}, + &simple2{42, nil, nil}} + expected := simple{42, 0, nil, newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test base"), []string{"test1", "test2"}}, + &simple2{42, newString("test2"), []string{"test3", "test4", "test5"}}} + + merged, err := mergeSimple(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("patch nilled structs are ignored", func(t *testing.T) { + t1 := simple{42, 42.2, newFloat64(932.2), newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test base"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + t2 := simple{42, 42.2, newFloat64(932.2), newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test base"), []string{"test1", "test2"}}, + nil} + expected := simple{42, 0, nil, newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test base"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + + merged, err := mergeSimple(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("can handle both nils", func(t *testing.T) { + t1 := simple{42, 42.2, newFloat64(932.2), newInt(45), nil, + []int{1, 2, 3}, nil, + simple2{30, nil, nil}, + nil} + t2 := simple{42, 42.2, newFloat64(932.2), newInt(45), nil, + []int{1, 2, 3}, nil, + simple2{30, nil, nil}, + nil} + expected := simple{42, 0, nil, newInt(45), nil, + []int{1, 2, 3}, nil, + simple2{30, nil, nil}, + nil} + + merged, err := mergeSimple(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) + + t.Run("different base vals are overwritten by patch, and unexported fields are ignored", func(t *testing.T) { + t1 := simple{42, 42.2, newFloat64(932.2), newInt(45), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + t2 := simple{13, 53.1, newFloat64(932.2), newInt(46), newBool(false), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("testpatch"), []string{"test1", "test99"}}, + &simple2{45, nil, []string{"test3", "test123", "test5"}}} + expected := simple{13, 0, nil, newInt(46), newBool(false), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("testpatch"), []string{"test1", "test99"}}, + &simple2{45, newString("test2"), []string{"test3", "test123", "test5"}}} + + merged, err := mergeSimple(t1, t2) + require.NoError(t, err) + + assert.NotEqual(t, t1, *merged) + assert.Equal(t, expected, *merged) + }) + + t.Run("pointers are not being copied or modified", func(t *testing.T) { + t1 := simple{42, 42.2, newFloat64(932.2), newInt(99), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + t2 := simple{13, 53.1, newFloat64(932.2), nil, newBool(false), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("testpatch"), []string{"test1", "test2"}}, + &simple2{45, nil, []string{"test3", "test4", "test5"}}} + expected := simple{13, 0, nil, newInt(99), newBool(false), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("testpatch"), []string{"test1", "test2"}}, + &simple2{45, newString("test2"), []string{"test3", "test4", "test5"}}} + + merged, err := mergeSimple(t1, t2) + require.NoError(t, err) + + assert.NotEqual(t, t1, *merged) + assert.Equal(t, expected, *merged) + + // changes in originals do not affect merged + *t1.S3.S = "testBASE" + assert.Equal(t, "test2", *merged.S3.S) + *t2.B = true + assert.Equal(t, false, *merged.B) + + // changes in base do not affect patched + *t1.S2.S = "test from base" + assert.NotEqual(t, *t1.S2.S, *t2.S2.S) + + // changes in merged (on pointers that were cloned from base or patch) do not affect base or patch + *merged.Ip = 0 + assert.Equal(t, 99, *t1.Ip) + *merged.S2.S = "testMERGED" + assert.NotEqual(t, *t2.S2.S, *merged.S2.S) + }) + + t.Run("slices are not being copied or modified", func(t *testing.T) { + t1 := simple{42, 42.2, newFloat64(932.2), newInt(99), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + t2 := simple{13, 53.1, newFloat64(932.2), nil, newBool(false), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("testpatch"), nil}, + &simple2{45, nil, []string{"test3", "test4", "test99"}}} + expected := simple{13, 0, nil, newInt(99), newBool(false), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("testpatch"), []string{"test1", "test2"}}, + &simple2{45, newString("test2"), []string{"test3", "test4", "test99"}}} + + merged, err := mergeSimple(t1, t2) + require.NoError(t, err) + + assert.NotEqual(t, t1, *merged) + assert.Equal(t, expected, *merged) + + // changes in base do not affect merged + t1.S2.Sls[0] = "test0" + assert.Equal(t, "test1", merged.S2.Sls[0]) + + // changes in patch do not affect merged + t2.S3.Sls[0] = "test0" + assert.Equal(t, "test3", merged.S3.Sls[0]) + + // changes in merged (on slice that was cloned from base) do not affect base + merged.S2.Sls[1] = "test222" + assert.Equal(t, "test2", t1.S2.Sls[1]) + }) + + t.Run("maps are not being copied or modified: base -> merged", func(t *testing.T) { + t1 := simple{42, 42.2, newFloat64(932.2), newInt(99), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("test"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + t2 := simple{13, 53.1, newFloat64(932.2), nil, newBool(false), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("testpatch"), []string{"test1", "test2"}}, + &simple2{45, nil, []string{"test3", "test4", "test99"}}} + expected := simple{13, 0, nil, newInt(99), newBool(false), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2}, + simple2{30, newString("testpatch"), []string{"test1", "test2"}}, + &simple2{45, newString("test2"), []string{"test3", "test4", "test99"}}} + + merged, err := mergeSimple(t1, t2) + require.NoError(t, err) + + assert.NotEqual(t, t1, *merged) + assert.Equal(t, expected, *merged) + + // changes in originals do not affect merged + t1.Msi["key1"] = 3 + assert.Equal(t, 1, merged.Msi["key1"]) + t2.Msi["key5"] = 5 + _, ok := merged.Msi["key5"] + assert.False(t, ok) + }) + + t.Run("patch map overwrites", func(t *testing.T) { + t1 := simple{42, 42.2, newFloat64(932.2), newInt(99), newBool(true), + []int{1, 2, 3}, map[string]int{"key1": 1, "key2": 2, "key4": 4}, + simple2{30, newString("test"), []string{"test1", "test2"}}, + &simple2{40, newString("test2"), []string{"test3", "test4", "test5"}}} + t2 := simple{13, 53.1, newFloat64(932.2), nil, newBool(false), + []int{1, 2, 3}, map[string]int{"key1": 11, "key2": 2, "key3": 3}, + simple2{30, newString("testpatch"), []string{"test1", "test2"}}, + &simple2{45, nil, []string{"test3", "test4", "test99"}}} + expected := simple{13, 0, nil, newInt(99), newBool(false), + []int{1, 2, 3}, map[string]int{"key1": 11, "key2": 2, "key3": 3}, + simple2{30, newString("testpatch"), []string{"test1", "test2"}}, + &simple2{45, newString("test2"), []string{"test3", "test4", "test99"}}} + + merged, err := mergeSimple(t1, t2) + require.NoError(t, err) + + assert.Equal(t, expected, *merged) + }) +} + +// The following are tests to see if multiply nested structs/maps/slice and pointers to structs/maps/slices +// will merge. Probably overkill, but if anything goes wrong here, it is best to isolate the problem and +// make a simplified test (like many of the above tests). +func TestMergeWithVeryComplexStruct(t *testing.T) { + t.Run("merge identical structs", func(t *testing.T) { + setupStructs(t) + + merged, err := mergeTestStructs(base, patch) + require.NoError(t, err) + + assert.Equal(t, expectedMerged, *merged) + }) + + t.Run("merge identical structs as pointers", func(t *testing.T) { + setupStructs(t) + + merged, err := mergeTestStructsPtrs(&base, &patch) + require.NoError(t, err) + + assert.Equal(t, expectedMerged, *merged) + }) + + t.Run("different base vals are overwritten by patch", func(t *testing.T) { + setupStructs(t) + + base.F = 1342.12 + base.Struct1.Pi = newInt(937) + base.Struct1p.Ui = 734 + base.Struct1.Struct2.Sli = []int{123123, 1243123} + + merged, err := mergeTestStructs(base, patch) + require.NoError(t, err) + + assert.NotEqual(t, base, *merged) + assert.Equal(t, patch, *merged) + }) + + t.Run("nil values in patch are ignored", func(t *testing.T) { + setupStructs(t) + + patch.Pi = nil + patch.Struct1.Pi16 = nil + + merged, err := mergeTestStructs(base, patch) + require.NoError(t, err) + + assert.NotEqual(t, patch, *merged) + assert.Equal(t, expectedMerged, *merged) + }) + + t.Run("nil structs in patch are ignored", func(t *testing.T) { + setupStructs(t) + + patch.Struct1p = nil + patch.Struct1.Struct2p = nil + + merged, err := mergeTestStructs(base, patch) + require.NoError(t, err) + + assert.NotEqual(t, patch, *merged) + assert.Equal(t, expectedMerged, *merged) + }) + + t.Run("nil slices in patch are ignored", func(t *testing.T) { + setupStructs(t) + + patch.Sls = nil + patch.Struct1.Sli = nil + patch.Struct1.Struct2p.Slf = nil + + merged, err := mergeTestStructs(base, patch) + require.NoError(t, err) + + assert.NotEqual(t, patch, *merged) + assert.Equal(t, expectedMerged, *merged) + }) + + t.Run("nil maps in patch are ignored", func(t *testing.T) { + setupStructs(t) + + patch.Msi = nil + patch.Mspi = nil + patch.Struct1.Mis = nil + patch.Struct1.Struct2p.Mspi = nil + + merged, err := mergeTestStructs(base, patch) + require.NoError(t, err) + + assert.NotEqual(t, patch, *merged) + assert.Equal(t, expectedMerged, *merged) + }) +} + +type testStruct struct { + I int + I8 int8 + I16 int16 + I32 int32 + I64 int64 + F float64 + F32 float32 + S string + Ui uint + Ui8 uint8 + Ui16 uint32 + Ui32 uint32 + Ui64 uint64 + Pi *int + Pi8 *int8 + Pi16 *int16 + Pi32 *int32 + Pi64 *int64 + Pf *float64 + Pf32 *float32 + Ps *string + Pui *uint + Pui8 *uint8 + Pui16 *uint16 + Pui32 *uint32 + Pui64 *uint64 + Sls []string + Sli []int + Slf []float64 + Msi map[string]int + Mis map[int]string + Mspi map[string]*int + Mips map[int]*string + Struct1 testStructEmbed + Struct1p *testStructEmbed +} + +type testStructEmbed struct { + I int + I8 int8 + I16 int16 + I32 int32 + I64 int64 + F float64 + F32 float32 + S string + Ui uint + Ui8 uint8 + Ui16 uint32 + Ui32 uint32 + Ui64 uint64 + Pi *int + Pi8 *int8 + Pi16 *int16 + Pi32 *int32 + Pi64 *int64 + Pf *float64 + Pf32 *float32 + Ps *string + Pui *uint + Pui8 *uint8 + Pui16 *uint16 + Pui32 *uint32 + Pui64 *uint64 + Sls []string + Sli []int + Slf []float64 + Msi map[string]int + Mis map[int]string + Mspi map[string]*int + Mips map[int]*string + Struct2 testStructEmbed2 + Struct2p *testStructEmbed2 +} + +type testStructEmbed2 struct { + I int + I8 int8 + I16 int16 + I32 int32 + I64 int64 + F float64 + F32 float32 + S string + Ui uint + Ui8 uint8 + Ui16 uint32 + Ui32 uint32 + Ui64 uint64 + Pi *int + Pi8 *int8 + Pi16 *int16 + Pi32 *int32 + Pi64 *int64 + Pf *float64 + Pf32 *float32 + Ps *string + Pui *uint + Pui8 *uint8 + Pui16 *uint16 + Pui32 *uint32 + Pui64 *uint64 + Sls []string + Sli []int + Slf []float64 + Msi map[string]int + Mis map[int]string + Mspi map[string]*int + Mips map[int]*string +} + +// the base structs +var baseStructEmbed2A, baseStructEmbed2B, baseStructEmbed2C, baseStructEmbed2D testStructEmbed2 +var baseStructEmbedBaseA, baseStructEmbedBaseB testStructEmbed +var base testStruct + +// the patch structs +var patchStructEmbed2A, patchStructEmbed2B, patchStructEmbed2C, patchStructEmbed2D testStructEmbed2 +var patchStructEmbedBaseA, patchStructEmbedBaseB testStructEmbed +var patch testStruct + +// The merged structs +var mergeStructEmbed2A, mergeStructEmbed2B, mergeStructEmbed2C, mergeStructEmbed2D testStructEmbed2 +var mergeStructEmbedBaseA, mergeStructEmbedBaseB testStructEmbed +var expectedMerged testStruct + +func setupStructs(t *testing.T) { + t.Helper() + + baseStructEmbed2A = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + baseStructEmbed2B = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + baseStructEmbed2C = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + baseStructEmbed2D = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + baseStructEmbedBaseA = testStructEmbed{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + baseStructEmbed2A, &baseStructEmbed2B, + } + + baseStructEmbedBaseB = testStructEmbed{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + baseStructEmbed2C, &baseStructEmbed2D, + } + + base = testStruct{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + baseStructEmbedBaseA, &baseStructEmbedBaseB, + } + + patchStructEmbed2A = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + patchStructEmbed2B = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + patchStructEmbed2C = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + patchStructEmbed2D = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + patchStructEmbedBaseA = testStructEmbed{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + patchStructEmbed2A, &patchStructEmbed2B, + } + + patchStructEmbedBaseB = testStructEmbed{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + patchStructEmbed2C, &patchStructEmbed2D, + } + + patch = testStruct{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + patchStructEmbedBaseA, &patchStructEmbedBaseB, + } + + mergeStructEmbed2A = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + mergeStructEmbed2B = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + mergeStructEmbed2C = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + mergeStructEmbed2D = testStructEmbed2{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + } + + mergeStructEmbedBaseA = testStructEmbed{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + mergeStructEmbed2A, &mergeStructEmbed2B, + } + + mergeStructEmbedBaseB = testStructEmbed{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + mergeStructEmbed2C, &mergeStructEmbed2D, + } + + expectedMerged = testStruct{1, 2, 3, 4, 5, 1.1, 2.2, "test", 10, 11, 12, 12, 13, + newInt(14), newInt8(15), newInt16(16), newInt32(17), newInt64(18), + newFloat64(19.9), newFloat32(20.1), newString("test pointer"), + newUint(21), newUint8(22), newUint16(23), newUint32(24), newUint64(25), + []string{"test", "slice", "strings"}, []int{1, 2, 3, 4}, []float64{1.1, 2.2, 3.3}, + map[string]int{"this": 1, "is": 2, "a": 3, "map": 4}, map[int]string{1: "this", 2: "is", 3: "another"}, + map[string]*int{"wow": newInt(1), "a map": newInt(2), "of pointers!": newInt(3)}, + map[int]*string{1: newString("Another"), 2: newString("map of"), 3: newString("pointers, wow!")}, + mergeStructEmbedBaseA, &mergeStructEmbedBaseB, + } + +} + +func mergeSimple(base, patch simple) (*simple, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retS := ret.(simple) + return &retS, nil +} + +func mergeEvenSimpler(base, patch evenSimpler) (*evenSimpler, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(evenSimpler) + return &retTS, nil +} + +func mergeSliceStruct(base, patch sliceStruct) (*sliceStruct, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(sliceStruct) + return &retTS, nil +} + +func mergeMapPtr(base, patch mapPtr) (*mapPtr, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(mapPtr) + return &retTS, nil +} + +func mergeMapPtrState(base, patch mapPtrState) (*mapPtrState, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(mapPtrState) + return &retTS, nil +} + +func mergeMapPtrState2(base, patch mapPtrState2) (*mapPtrState2, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(mapPtrState2) + return &retTS, nil +} + +func mergeTestStructs(base, patch testStruct) (*testStruct, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(testStruct) + return &retTS, nil +} + +func mergeStringIntMap(base, patch map[string]int) (map[string]int, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(map[string]int) + return retTS, nil +} + +func mergeStringPtrIntMap(base, patch map[string]*int) (map[string]*int, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(map[string]*int) + return retTS, nil +} + +func mergeStringSliceIntMap(base, patch map[string][]int) (map[string][]int, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(map[string][]int) + return retTS, nil +} + +func mergeMapOfMap(base, patch map[string]map[string]*int) (map[string]map[string]*int, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(map[string]map[string]*int) + return retTS, nil +} + +func mergeInterfaceMap(base, patch map[string]interface{}) (map[string]interface{}, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(map[string]interface{}) + return retTS, nil +} + +func mergeStringSlices(base, patch []string) ([]string, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.([]string) + return retTS, nil +} + +func mergeTestStructsPtrs(base, patch *testStruct) (*testStruct, error) { + ret, err := Merge(base, patch) + if err != nil { + return nil, err + } + retTS := ret.(testStruct) + return &retTS, nil +} + +func newBool(b bool) *bool { return &b } +func newInt(n int) *int { return &n } +func newInt64(n int64) *int64 { return &n } +func newString(s string) *string { return &s } +func newInt8(n int8) *int8 { return &n } +func newInt16(n int16) *int16 { return &n } +func newInt32(n int32) *int32 { return &n } +func newFloat64(f float64) *float64 { return &f } +func newFloat32(f float32) *float32 { return &f } +func newUint(n uint) *uint { return &n } +func newUint8(n uint8) *uint8 { return &n } +func newUint16(n uint16) *uint16 { return &n } +func newUint32(n uint32) *uint32 { return &n } +func newUint64(n uint64) *uint64 { return &n }