github.com/aaronlehmann/figtree@v1.0.1/figtree_test.go (about)

     1  package figtree
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"reflect"
     9  	"runtime"
    10  	"testing"
    11  
    12  	yaml "gopkg.in/yaml.v3"
    13  	logging "gopkg.in/op/go-logging.v1"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  type info struct {
    20  	name string
    21  	line string
    22  }
    23  
    24  func line() string {
    25  	_, file, line, _ := runtime.Caller(1)
    26  	return fmt.Sprintf("%s:%d", path.Base(file), line)
    27  }
    28  
    29  func init() {
    30  	StringifyValue = false
    31  	logging.SetLevel(logging.NOTICE, "")
    32  }
    33  
    34  func newFigTreeFromEnv(opts ...Option) *FigTree {
    35  	cwd, _ := os.Getwd()
    36  	opts = append([]Option{
    37  		WithHome(os.Getenv("HOME")),
    38  		WithCwd(cwd),
    39  		WithEnvPrefix("FIGTREE"),
    40  	}, opts...)
    41  
    42  	return NewFigTree(opts...)
    43  }
    44  
    45  type TestOptions struct {
    46  	String1    StringOption     `json:"str1,omitempty" yaml:"str1,omitempty"`
    47  	LeaveEmpty StringOption     `json:"leave-empty,omitempty" yaml:"leave-empty,omitempty"`
    48  	Array1     ListStringOption `json:"arr1,omitempty" yaml:"arr1,omitempty"`
    49  	Map1       MapStringOption  `json:"map1,omitempty" yaml:"map1,omitempty"`
    50  	Int1       IntOption        `json:"int1,omitempty" yaml:"int1,omitempty"`
    51  	Float1     Float32Option    `json:"float1,omitempty" yaml:"float1,omitempty"`
    52  	Bool1      BoolOption       `json:"bool1,omitempty" yaml:"bool1,omitempty"`
    53  }
    54  
    55  type TestBuiltin struct {
    56  	String1    string            `yaml:"str1,omitempty"`
    57  	LeaveEmpty string            `yaml:"leave-empty,omitempty"`
    58  	Array1     []string          `yaml:"arr1,omitempty"`
    59  	Map1       map[string]string `yaml:"map1,omitempty"`
    60  	Int1       int               `yaml:"int1,omitempty"`
    61  	Float1     float32           `yaml:"float1,omitempty"`
    62  	Bool1      bool              `yaml:"bool1,omitempty"`
    63  }
    64  
    65  func TestOptionsLoadConfigD3(t *testing.T) {
    66  	opts := TestOptions{}
    67  	os.Chdir("d1/d2/d3")
    68  	defer os.Chdir("../../..")
    69  
    70  	arr1 := []StringOption{}
    71  	arr1 = append(arr1, StringOption{"figtree.yml", true, "d3arr1val1"})
    72  	arr1 = append(arr1, StringOption{"figtree.yml", true, "d3arr1val2"})
    73  	arr1 = append(arr1, StringOption{"figtree.yml", true, "dupval"})
    74  	arr1 = append(arr1, StringOption{"../figtree.yml", true, "d2arr1val1"})
    75  	arr1 = append(arr1, StringOption{"../figtree.yml", true, "d2arr1val2"})
    76  	arr1 = append(arr1, StringOption{"../../figtree.yml", true, "d1arr1val1"})
    77  	arr1 = append(arr1, StringOption{"../../figtree.yml", true, "d1arr1val2"})
    78  
    79  	expected := TestOptions{
    80  		String1:    StringOption{"figtree.yml", true, "d3str1val1"},
    81  		LeaveEmpty: StringOption{},
    82  		Array1:     arr1,
    83  		Map1: map[string]StringOption{
    84  			"key0": StringOption{"../../figtree.yml", true, "d1map1val0"},
    85  			"key1": StringOption{"../figtree.yml", true, "d2map1val1"},
    86  			"key2": StringOption{"figtree.yml", true, "d3map1val2"},
    87  			"key3": StringOption{"figtree.yml", true, "d3map1val3"},
    88  			"dup":  StringOption{"figtree.yml", true, "d3dupval"},
    89  		},
    90  		Int1:   IntOption{"figtree.yml", true, 333},
    91  		Float1: Float32Option{"figtree.yml", true, 3.33},
    92  		Bool1:  BoolOption{"figtree.yml", true, true},
    93  	}
    94  
    95  	fig := newFigTreeFromEnv()
    96  	err := fig.LoadAllConfigs("figtree.yml", &opts)
    97  	assert.Nil(t, err)
    98  	assert.Exactly(t, expected, opts)
    99  }
   100  
   101  func TestOptionsLoadConfigD2(t *testing.T) {
   102  	opts := TestOptions{}
   103  	os.Chdir("d1/d2")
   104  	defer os.Chdir("../..")
   105  
   106  	arr1 := []StringOption{}
   107  	arr1 = append(arr1, StringOption{"figtree.yml", true, "d2arr1val1"})
   108  	arr1 = append(arr1, StringOption{"figtree.yml", true, "d2arr1val2"})
   109  	arr1 = append(arr1, StringOption{"figtree.yml", true, "dupval"})
   110  	arr1 = append(arr1, StringOption{"../figtree.yml", true, "d1arr1val1"})
   111  	arr1 = append(arr1, StringOption{"../figtree.yml", true, "d1arr1val2"})
   112  
   113  	expected := TestOptions{
   114  		String1:    StringOption{"figtree.yml", true, "d2str1val1"},
   115  		LeaveEmpty: StringOption{},
   116  		Array1:     arr1,
   117  		Map1: map[string]StringOption{
   118  			"key0": StringOption{"../figtree.yml", true, "d1map1val0"},
   119  			"key1": StringOption{"figtree.yml", true, "d2map1val1"},
   120  			"key2": StringOption{"figtree.yml", true, "d2map1val2"},
   121  			"dup":  StringOption{"figtree.yml", true, "d2dupval"},
   122  		},
   123  		Int1:   IntOption{"figtree.yml", true, 222},
   124  		Float1: Float32Option{"figtree.yml", true, 2.22},
   125  		Bool1:  BoolOption{"figtree.yml", true, false},
   126  	}
   127  
   128  	fig := newFigTreeFromEnv()
   129  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   130  	assert.Nil(t, err)
   131  	assert.Exactly(t, expected, opts)
   132  }
   133  
   134  func TestOptionsLoadConfigD1(t *testing.T) {
   135  	opts := TestOptions{}
   136  	os.Chdir("d1")
   137  	defer os.Chdir("..")
   138  
   139  	arr1 := []StringOption{}
   140  	arr1 = append(arr1, StringOption{"figtree.yml", true, "d1arr1val1"})
   141  	arr1 = append(arr1, StringOption{"figtree.yml", true, "d1arr1val2"})
   142  	arr1 = append(arr1, StringOption{"figtree.yml", true, "dupval"})
   143  
   144  	expected := TestOptions{
   145  		String1:    StringOption{"figtree.yml", true, "d1str1val1"},
   146  		LeaveEmpty: StringOption{},
   147  		Array1:     arr1,
   148  		Map1: map[string]StringOption{
   149  			"key0": StringOption{"figtree.yml", true, "d1map1val0"},
   150  			"key1": StringOption{"figtree.yml", true, "d1map1val1"},
   151  			"dup":  StringOption{"figtree.yml", true, "d1dupval"},
   152  		},
   153  		Int1:   IntOption{"figtree.yml", true, 111},
   154  		Float1: Float32Option{"figtree.yml", true, 1.11},
   155  		Bool1:  BoolOption{"figtree.yml", true, true},
   156  	}
   157  
   158  	fig := newFigTreeFromEnv()
   159  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   160  	assert.Nil(t, err)
   161  	assert.Exactly(t, expected, opts)
   162  }
   163  
   164  func TestOptionsCorrupt(t *testing.T) {
   165  	opts := TestOptions{}
   166  	os.Chdir("d1")
   167  	defer os.Chdir("..")
   168  
   169  	fig := newFigTreeFromEnv()
   170  	err := fig.LoadAllConfigs("corrupt.yml", &opts)
   171  	assert.NotNil(t, err)
   172  }
   173  
   174  func TestBuiltinLoadConfigD3(t *testing.T) {
   175  	opts := TestBuiltin{}
   176  	os.Chdir("d1/d2/d3")
   177  	defer os.Chdir("../../..")
   178  
   179  	arr1 := []string{}
   180  	arr1 = append(arr1, "d3arr1val1")
   181  	arr1 = append(arr1, "d3arr1val2")
   182  	arr1 = append(arr1, "dupval")
   183  	arr1 = append(arr1, "d2arr1val1")
   184  	arr1 = append(arr1, "d2arr1val2")
   185  	arr1 = append(arr1, "d1arr1val1")
   186  	arr1 = append(arr1, "d1arr1val2")
   187  
   188  	expected := TestBuiltin{
   189  		String1:    "d3str1val1",
   190  		LeaveEmpty: "",
   191  		Array1:     arr1,
   192  		Map1: map[string]string{
   193  			"key0": "d1map1val0",
   194  			"key1": "d2map1val1",
   195  			"key2": "d3map1val2",
   196  			"key3": "d3map1val3",
   197  			"dup":  "d3dupval",
   198  		},
   199  		Int1:   333,
   200  		Float1: 3.33,
   201  		Bool1:  true,
   202  	}
   203  
   204  	fig := newFigTreeFromEnv()
   205  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   206  	assert.Nil(t, err)
   207  	assert.Exactly(t, expected, opts)
   208  }
   209  
   210  func TestBuiltinLoadConfigD2(t *testing.T) {
   211  	opts := TestBuiltin{}
   212  	os.Chdir("d1/d2")
   213  	defer os.Chdir("../..")
   214  
   215  	arr1 := []string{}
   216  	arr1 = append(arr1, "d2arr1val1")
   217  	arr1 = append(arr1, "d2arr1val2")
   218  	arr1 = append(arr1, "dupval")
   219  	arr1 = append(arr1, "d1arr1val1")
   220  	arr1 = append(arr1, "d1arr1val2")
   221  
   222  	expected := TestBuiltin{
   223  		String1:    "d2str1val1",
   224  		LeaveEmpty: "",
   225  		Array1:     arr1,
   226  		Map1: map[string]string{
   227  			"key0": "d1map1val0",
   228  			"key1": "d2map1val1",
   229  			"key2": "d2map1val2",
   230  			"dup":  "d2dupval",
   231  		},
   232  		Int1:   222,
   233  		Float1: 2.22,
   234  		// note this will be true from d1/figtree.yml since the
   235  		// d1/d2/figtree.yml set it to false which is a zero value
   236  		Bool1: true,
   237  	}
   238  
   239  	fig := newFigTreeFromEnv()
   240  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   241  	assert.Nil(t, err)
   242  	assert.Exactly(t, expected, opts)
   243  }
   244  
   245  func TestBuiltinLoadConfigD1(t *testing.T) {
   246  	opts := TestBuiltin{}
   247  	os.Chdir("d1")
   248  	defer os.Chdir("..")
   249  
   250  	arr1 := []string{}
   251  	arr1 = append(arr1, "d1arr1val1")
   252  	arr1 = append(arr1, "d1arr1val2")
   253  	arr1 = append(arr1, "dupval")
   254  
   255  	expected := TestBuiltin{
   256  		String1:    "d1str1val1",
   257  		LeaveEmpty: "",
   258  		Array1:     arr1,
   259  		Map1: map[string]string{
   260  			"key0": "d1map1val0",
   261  			"key1": "d1map1val1",
   262  			"dup":  "d1dupval",
   263  		},
   264  		Int1:   111,
   265  		Float1: 1.11,
   266  		Bool1:  true,
   267  	}
   268  
   269  	fig := newFigTreeFromEnv()
   270  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   271  	assert.Nil(t, err)
   272  	assert.Exactly(t, expected, opts)
   273  }
   274  
   275  func TestBuiltinCorrupt(t *testing.T) {
   276  	opts := TestBuiltin{}
   277  	os.Chdir("d1")
   278  	defer os.Chdir("..")
   279  
   280  	fig := newFigTreeFromEnv()
   281  	err := fig.LoadAllConfigs("corrupt.yml", &opts)
   282  	assert.NotNil(t, err)
   283  }
   284  
   285  func TestOptionsLoadConfigDefaults(t *testing.T) {
   286  	opts := TestOptions{
   287  		String1:    NewStringOption("defaultVal1"),
   288  		LeaveEmpty: NewStringOption("emptyVal1"),
   289  		Int1:       NewIntOption(999),
   290  		Float1:     NewFloat32Option(9.99),
   291  		Bool1:      NewBoolOption(true),
   292  	}
   293  	os.Chdir("d1/d2")
   294  	defer os.Chdir("../..")
   295  
   296  	arr1 := []StringOption{}
   297  	arr1 = append(arr1, StringOption{"figtree.yml", true, "d2arr1val1"})
   298  	arr1 = append(arr1, StringOption{"figtree.yml", true, "d2arr1val2"})
   299  	arr1 = append(arr1, StringOption{"figtree.yml", true, "dupval"})
   300  	arr1 = append(arr1, StringOption{"../figtree.yml", true, "d1arr1val1"})
   301  	arr1 = append(arr1, StringOption{"../figtree.yml", true, "d1arr1val2"})
   302  
   303  	expected := TestOptions{
   304  		String1:    StringOption{"figtree.yml", true, "d2str1val1"},
   305  		LeaveEmpty: StringOption{"default", true, "emptyVal1"},
   306  		Array1:     arr1,
   307  		Map1: map[string]StringOption{
   308  			"key0": StringOption{"../figtree.yml", true, "d1map1val0"},
   309  			"key1": StringOption{"figtree.yml", true, "d2map1val1"},
   310  			"key2": StringOption{"figtree.yml", true, "d2map1val2"},
   311  			"dup":  StringOption{"figtree.yml", true, "d2dupval"},
   312  		},
   313  		Int1:   IntOption{"figtree.yml", true, 222},
   314  		Float1: Float32Option{"figtree.yml", true, 2.22},
   315  		Bool1:  BoolOption{"figtree.yml", true, false},
   316  	}
   317  
   318  	fig := newFigTreeFromEnv()
   319  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   320  	assert.Nil(t, err)
   321  	require.Exactly(t, expected, opts)
   322  }
   323  
   324  func TestMergeMapsWithNull(t *testing.T) {
   325  	dest := map[string]interface{}{
   326  		"requires": map[string]interface{}{
   327  			"pkgA": nil,
   328  			"pkgB": ">1.2.3",
   329  		},
   330  	}
   331  
   332  	src := map[string]interface{}{
   333  		"requires": map[string]interface{}{
   334  			"pkgC": "<1.2.3",
   335  			"pkgD": nil,
   336  		},
   337  	}
   338  
   339  	Merge(dest, src)
   340  
   341  	expected := map[string]interface{}{
   342  		"requires": map[string]interface{}{
   343  			"pkgA": nil,
   344  			"pkgB": ">1.2.3",
   345  			"pkgC": "<1.2.3",
   346  			"pkgD": nil,
   347  		},
   348  	}
   349  	assert.Equal(t, expected, dest)
   350  }
   351  
   352  func TestMergeMapsIntoStructWithNull(t *testing.T) {
   353  	src1 := map[string]interface{}{
   354  		"requires": map[string]interface{}{
   355  			"pkgA": nil,
   356  			"pkgB": ">1.2.3",
   357  		},
   358  	}
   359  
   360  	src2 := map[string]interface{}{
   361  		"requires": map[string]interface{}{
   362  			"pkgC": "<1.2.3",
   363  			"pkgD": nil,
   364  		},
   365  	}
   366  
   367  	dest := MakeMergeStruct(src1, src2)
   368  	Merge(dest, src1)
   369  	Merge(dest, src2)
   370  
   371  	expected := &struct {
   372  		Requires struct {
   373  			PkgA interface{} `json:"pkgA" yaml:"pkgA"`
   374  			PkgB string      `json:"pkgB" yaml:"pkgB"`
   375  			PkgC string      `json:"pkgC" yaml:"pkgC"`
   376  			PkgD interface{} `json:"pkgD" yaml:"pkgD"`
   377  		} `json:"requires" yaml:"requires"`
   378  	}{
   379  		struct {
   380  			PkgA interface{} `json:"pkgA" yaml:"pkgA"`
   381  			PkgB string      `json:"pkgB" yaml:"pkgB"`
   382  			PkgC string      `json:"pkgC" yaml:"pkgC"`
   383  			PkgD interface{} `json:"pkgD" yaml:"pkgD"`
   384  		}{
   385  			PkgA: nil,
   386  			PkgB: ">1.2.3",
   387  			PkgC: "<1.2.3",
   388  			PkgD: nil,
   389  		},
   390  	}
   391  	assert.Equal(t, expected, dest)
   392  }
   393  
   394  func TestMergeStringIntoStringOption(t *testing.T) {
   395  	src1 := struct {
   396  		Value StringOption
   397  	}{}
   398  
   399  	src2 := struct {
   400  		Value string
   401  	}{"val1"}
   402  
   403  	dest := MakeMergeStruct(src1, src2)
   404  
   405  	Merge(dest, src1)
   406  	Merge(dest, src2)
   407  
   408  	expected := &struct {
   409  		Value StringOption
   410  	}{StringOption{"merge", true, "val1"}}
   411  	assert.Equal(t, expected, dest)
   412  }
   413  
   414  func TestMergeStringOptions(t *testing.T) {
   415  	src1 := struct {
   416  		Value StringOption
   417  	}{}
   418  
   419  	src2 := struct {
   420  		Value StringOption
   421  	}{NewStringOption("val1")}
   422  
   423  	dest := MakeMergeStruct(src1, src2)
   424  
   425  	Merge(dest, src1)
   426  	Merge(dest, src2)
   427  
   428  	expected := &struct {
   429  		Value StringOption
   430  	}{StringOption{"default", true, "val1"}}
   431  	assert.Equal(t, expected, dest)
   432  }
   433  
   434  func TestMergeMapStringIntoStringOption(t *testing.T) {
   435  	src1 := map[string]interface{}{
   436  		"map": MapStringOption{},
   437  	}
   438  
   439  	src2 := map[string]interface{}{
   440  		"map": MapStringOption{
   441  			"key": NewStringOption("val1"),
   442  		},
   443  	}
   444  	dest := MakeMergeStruct(src1, src2)
   445  
   446  	Merge(dest, src1)
   447  	Merge(dest, src2)
   448  
   449  	expected := &struct {
   450  		Map struct {
   451  			Key StringOption `json:"key" yaml:"key"`
   452  		} `json:"map" yaml:"map"`
   453  	}{
   454  		Map: struct {
   455  			Key StringOption `json:"key" yaml:"key"`
   456  		}{StringOption{"default", true, "val1"}},
   457  	}
   458  	assert.Equal(t, expected, dest)
   459  }
   460  
   461  func TestMergeMapStringOptions(t *testing.T) {
   462  	src1 := struct {
   463  		Value StringOption
   464  	}{}
   465  
   466  	src2 := struct {
   467  		Value StringOption
   468  	}{NewStringOption("val1")}
   469  
   470  	dest := MakeMergeStruct(src1, src2)
   471  
   472  	Merge(dest, src1)
   473  	Merge(dest, src2)
   474  
   475  	expected := &struct {
   476  		Value StringOption
   477  	}{StringOption{"default", true, "val1"}}
   478  	assert.Equal(t, expected, dest)
   479  }
   480  
   481  func TestMergeMapWithStruct(t *testing.T) {
   482  	dest := map[string]interface{}{
   483  		"mapkey": "mapval1",
   484  		"map": map[string]interface{}{
   485  			"mapkey":  "mapval2",
   486  			"nullkey": nil,
   487  		},
   488  	}
   489  
   490  	src := struct {
   491  		StructField string
   492  		Map         struct {
   493  			StructField string
   494  		}
   495  	}{
   496  		StructField: "field1",
   497  		Map: struct {
   498  			StructField string
   499  		}{
   500  			StructField: "field2",
   501  		},
   502  	}
   503  
   504  	m := NewMerger()
   505  	m.mergeStructs(reflect.ValueOf(&dest), reflect.ValueOf(&src))
   506  
   507  	expected := map[string]interface{}{
   508  		"mapkey":       "mapval1",
   509  		"struct-field": "field1",
   510  		"map": map[string]interface{}{
   511  			"mapkey":       "mapval2",
   512  			"nullkey":      nil,
   513  			"struct-field": "field2",
   514  		},
   515  	}
   516  	assert.Equal(t, expected, dest)
   517  
   518  }
   519  
   520  func TestMergeStructWithMap(t *testing.T) {
   521  	dest := struct {
   522  		StructField string
   523  		Mapkey      string
   524  		Map         struct {
   525  			StructField string
   526  			Mapkey      string
   527  		}
   528  	}{
   529  		StructField: "field1",
   530  		Map: struct {
   531  			StructField string
   532  			Mapkey      string
   533  		}{
   534  			StructField: "field2",
   535  		},
   536  	}
   537  
   538  	src := map[string]interface{}{
   539  		"mapkey": "mapval1",
   540  		"map": map[string]interface{}{
   541  			"mapkey":  "mapval2",
   542  			"nullkey": nil,
   543  		},
   544  	}
   545  
   546  	merged := MakeMergeStruct(&dest, &src)
   547  	Merge(merged, &dest)
   548  	Merge(merged, &src)
   549  
   550  	expected := struct {
   551  		Map struct {
   552  			Mapkey      string
   553  			Nullkey     interface{} `json:"nullkey" yaml:"nullkey"`
   554  			StructField string
   555  		}
   556  		Mapkey      string
   557  		StructField string
   558  	}{
   559  		Map: struct {
   560  			Mapkey      string
   561  			Nullkey     interface{} `json:"nullkey" yaml:"nullkey"`
   562  			StructField string
   563  		}{
   564  			Mapkey:      "mapval2",
   565  			StructField: "field2",
   566  		},
   567  		Mapkey:      "mapval1",
   568  		StructField: "field1",
   569  	}
   570  	assert.Equal(t, &expected, merged)
   571  }
   572  
   573  func TestMergeStructUsingOptionsWithMap(t *testing.T) {
   574  	dest := struct {
   575  		Bool    BoolOption
   576  		Byte    ByteOption
   577  		Float32 Float32Option
   578  		Float64 Float64Option
   579  		Int16   Int16Option
   580  		Int32   Int32Option
   581  		Int64   Int64Option
   582  		Int8    Int8Option
   583  		Int     IntOption
   584  		Rune    RuneOption
   585  		String  StringOption
   586  		Uint16  Uint16Option
   587  		Uint32  Uint32Option
   588  		Uint64  Uint64Option
   589  		Uint8   Uint8Option
   590  		Uint    UintOption
   591  	}{}
   592  
   593  	src := map[string]interface{}{
   594  		"bool":    true,
   595  		"byte":    byte(10),
   596  		"float32": float32(1.23),
   597  		"float64": float64(2.34),
   598  		"int16":   int16(123),
   599  		"int32":   int32(234),
   600  		"int64":   int64(345),
   601  		"int8":    int8(127),
   602  		"int":     int(456),
   603  		"rune":    rune('a'),
   604  		"string":  "stringval",
   605  		"uint16":  uint16(123),
   606  		"uint32":  uint32(234),
   607  		"uint64":  uint64(345),
   608  		"uint8":   uint8(255),
   609  		"uint":    uint(456),
   610  	}
   611  
   612  	Merge(&dest, &src)
   613  
   614  	expected := struct {
   615  		Bool    BoolOption
   616  		Byte    ByteOption
   617  		Float32 Float32Option
   618  		Float64 Float64Option
   619  		Int16   Int16Option
   620  		Int32   Int32Option
   621  		Int64   Int64Option
   622  		Int8    Int8Option
   623  		Int     IntOption
   624  		Rune    RuneOption
   625  		String  StringOption
   626  		Uint16  Uint16Option
   627  		Uint32  Uint32Option
   628  		Uint64  Uint64Option
   629  		Uint8   Uint8Option
   630  		Uint    UintOption
   631  	}{
   632  		Bool:    BoolOption{"merge", true, true},
   633  		Byte:    ByteOption{"merge", true, byte(10)},
   634  		Float32: Float32Option{"merge", true, float32(1.23)},
   635  		Float64: Float64Option{"merge", true, float64(2.34)},
   636  		Int16:   Int16Option{"merge", true, int16(123)},
   637  		Int32:   Int32Option{"merge", true, int32(234)},
   638  		Int64:   Int64Option{"merge", true, int64(345)},
   639  		Int8:    Int8Option{"merge", true, int8(127)},
   640  		Int:     IntOption{"merge", true, int(456)},
   641  		Rune:    RuneOption{"merge", true, rune('a')},
   642  		String:  StringOption{"merge", true, "stringval"},
   643  		Uint16:  Uint16Option{"merge", true, uint16(123)},
   644  		Uint32:  Uint32Option{"merge", true, uint32(234)},
   645  		Uint64:  Uint64Option{"merge", true, uint64(345)},
   646  		Uint8:   Uint8Option{"merge", true, uint8(255)},
   647  		Uint:    UintOption{"merge", true, uint(456)},
   648  	}
   649  	assert.Equal(t, expected, dest)
   650  }
   651  
   652  func TestMergeMapWithStructUsingOptions(t *testing.T) {
   653  	dest := map[string]interface{}{
   654  		"bool":    false,
   655  		"byte":    byte(0),
   656  		"float32": float32(0),
   657  		"float64": float64(0),
   658  		"int16":   int16(0),
   659  		"int32":   int32(0),
   660  		"int64":   int64(0),
   661  		"int8":    int8(0),
   662  		"int":     int(0),
   663  		"rune":    rune(0),
   664  		"string":  "",
   665  		"uint16":  uint16(0),
   666  		"uint32":  uint32(0),
   667  		"uint64":  uint64(0),
   668  		"uint8":   uint8(0),
   669  		"uint":    uint(0),
   670  	}
   671  
   672  	src := struct {
   673  		Bool    BoolOption
   674  		Byte    ByteOption
   675  		Float32 Float32Option `yaml:"float32"`
   676  		Float64 Float64Option `yaml:"float64"`
   677  		Int16   Int16Option   `yaml:"int16"`
   678  		Int32   Int32Option   `yaml:"int32"`
   679  		Int64   Int64Option   `yaml:"int64"`
   680  		Int8    Int8Option    `yaml:"int8"`
   681  		Int     IntOption
   682  		Rune    RuneOption
   683  		String  StringOption
   684  		Uint16  Uint16Option `yaml:"uint16"`
   685  		Uint32  Uint32Option `yaml:"uint32"`
   686  		Uint64  Uint64Option `yaml:"uint64"`
   687  		Uint8   Uint8Option  `yaml:"uint8"`
   688  		Uint    UintOption
   689  	}{
   690  		Bool:    NewBoolOption(true),
   691  		Byte:    NewByteOption(10),
   692  		Float32: NewFloat32Option(1.23),
   693  		Float64: NewFloat64Option(2.34),
   694  		Int16:   NewInt16Option(123),
   695  		Int32:   NewInt32Option(234),
   696  		Int64:   NewInt64Option(345),
   697  		Int8:    NewInt8Option(127),
   698  		Int:     NewIntOption(456),
   699  		Rune:    NewRuneOption('a'),
   700  		String:  NewStringOption("stringval"),
   701  		Uint16:  NewUint16Option(123),
   702  		Uint32:  NewUint32Option(234),
   703  		Uint64:  NewUint64Option(345),
   704  		Uint8:   NewUint8Option(255),
   705  		Uint:    NewUintOption(456),
   706  	}
   707  
   708  	Merge(&dest, &src)
   709  	expected := map[string]interface{}{
   710  		"bool":    true,
   711  		"byte":    byte(10),
   712  		"float32": float32(1.23),
   713  		"float64": float64(2.34),
   714  		"int16":   int16(123),
   715  		"int32":   int32(234),
   716  		"int64":   int64(345),
   717  		"int8":    int8(127),
   718  		"int":     int(456),
   719  		"rune":    rune('a'),
   720  		"string":  "stringval",
   721  		"uint16":  uint16(123),
   722  		"uint32":  uint32(234),
   723  		"uint64":  uint64(345),
   724  		"uint8":   uint8(255),
   725  		"uint":    uint(456),
   726  	}
   727  	assert.Equal(t, expected, dest)
   728  }
   729  
   730  func TestMergeStructUsingListOptionsWithMap(t *testing.T) {
   731  	dest := struct {
   732  		Strings ListStringOption
   733  	}{
   734  		Strings: ListStringOption{
   735  			NewStringOption("abc"),
   736  		},
   737  	}
   738  
   739  	src := map[string]interface{}{
   740  		"strings": []string{
   741  			"abc",
   742  			"def",
   743  		},
   744  	}
   745  
   746  	Merge(&dest, &src)
   747  
   748  	expected := struct {
   749  		Strings ListStringOption
   750  	}{
   751  		ListStringOption{
   752  			StringOption{"default", true, "abc"},
   753  			StringOption{"merge", true, "def"},
   754  		},
   755  	}
   756  	assert.Equal(t, expected, dest)
   757  }
   758  
   759  func TestMergeMapWithStructUsingListOptions(t *testing.T) {
   760  	dest := map[string]interface{}{
   761  		"strings": []string{"abc"},
   762  	}
   763  
   764  	src := struct {
   765  		Strings ListStringOption
   766  	}{
   767  		Strings: ListStringOption{
   768  			NewStringOption("abc"),
   769  			NewStringOption("def"),
   770  		},
   771  	}
   772  
   773  	Merge(&dest, &src)
   774  	expected := map[string]interface{}{
   775  		"strings": []string{"abc", "def"},
   776  	}
   777  	assert.Equal(t, expected, dest)
   778  }
   779  
   780  func TestMergeStructWithListUsingListOptions(t *testing.T) {
   781  	dest := struct {
   782  		Property []interface{}
   783  	}{
   784  		Property: []interface{}{
   785  			"abc",
   786  		},
   787  	}
   788  
   789  	src := struct {
   790  		Property ListStringOption
   791  	}{
   792  		Property: ListStringOption{
   793  			NewStringOption("abc"),
   794  			NewStringOption("def"),
   795  		},
   796  	}
   797  
   798  	Merge(&dest, &src)
   799  	expected := struct {
   800  		Property []interface{}
   801  	}{
   802  		Property: []interface{}{
   803  			"abc",
   804  			NewStringOption("def"),
   805  		},
   806  	}
   807  	assert.Equal(t, expected, dest)
   808  }
   809  
   810  func TestMergeStructUsingMapOptionsWithMap(t *testing.T) {
   811  	dest := struct {
   812  		Strings MapStringOption
   813  	}{}
   814  
   815  	src := map[string]interface{}{
   816  		"strings": map[string]interface{}{
   817  			"key1": "val1",
   818  			"key2": "val2",
   819  		},
   820  	}
   821  
   822  	Merge(&dest, &src)
   823  
   824  	expected := struct {
   825  		Strings MapStringOption
   826  	}{
   827  		Strings: MapStringOption{
   828  			"key1": StringOption{"merge", true, "val1"},
   829  			"key2": StringOption{"merge", true, "val2"},
   830  		},
   831  	}
   832  	assert.Equal(t, expected, dest)
   833  }
   834  
   835  func TestMergeMapWithStructUsingMapOptions(t *testing.T) {
   836  	dest := map[string]interface{}{
   837  		"strings": map[string]string{},
   838  	}
   839  
   840  	src := struct {
   841  		Strings MapStringOption
   842  	}{
   843  		Strings: MapStringOption{
   844  			"key1": NewStringOption("val1"),
   845  			"key2": NewStringOption("val2"),
   846  		},
   847  	}
   848  
   849  	Merge(&dest, &src)
   850  	expected := map[string]interface{}{
   851  		"strings": map[string]string{
   852  			"key1": "val1",
   853  			"key2": "val2",
   854  		},
   855  	}
   856  	assert.Equal(t, expected, dest)
   857  }
   858  
   859  func TestMergeStructsWithSrcEmbedded(t *testing.T) {
   860  	dest := struct {
   861  		FieldName string
   862  	}{}
   863  
   864  	type embedded struct {
   865  		FieldName string
   866  	}
   867  
   868  	src := struct {
   869  		embedded
   870  	}{
   871  		embedded: embedded{
   872  			FieldName: "field1",
   873  		},
   874  	}
   875  
   876  	m := NewMerger()
   877  	m.mergeStructs(reflect.ValueOf(&dest), reflect.ValueOf(&src))
   878  
   879  	expected := struct {
   880  		FieldName string
   881  	}{
   882  		FieldName: "field1",
   883  	}
   884  	assert.Equal(t, expected, dest)
   885  }
   886  
   887  func TestMergeStructsWithDestEmbedded(t *testing.T) {
   888  	type embedded struct {
   889  		FieldName string
   890  	}
   891  
   892  	dest := struct {
   893  		embedded
   894  	}{}
   895  
   896  	src := struct {
   897  		FieldName string
   898  	}{
   899  		FieldName: "field1",
   900  	}
   901  
   902  	m := NewMerger()
   903  	m.mergeStructs(reflect.ValueOf(&dest), reflect.ValueOf(&src))
   904  
   905  	expected := struct {
   906  		embedded
   907  	}{
   908  		embedded: embedded{
   909  			FieldName: "field1",
   910  		},
   911  	}
   912  	assert.Equal(t, expected, dest)
   913  }
   914  
   915  func TestMakeMergeStruct(t *testing.T) {
   916  	input := map[string]interface{}{
   917  		"mapkey": "mapval1",
   918  		"map": map[string]interface{}{
   919  			"mapkey": "mapval2",
   920  		},
   921  		"nilmap": nil,
   922  	}
   923  
   924  	got := MakeMergeStruct(input)
   925  
   926  	Merge(got, &input)
   927  
   928  	assert.Equal(t, input["mapkey"], reflect.ValueOf(got).Elem().FieldByName("Mapkey").Interface())
   929  	assert.Equal(t, struct {
   930  		Mapkey string `json:"mapkey" yaml:"mapkey"`
   931  	}{"mapval2"}, reflect.ValueOf(got).Elem().FieldByName("Map").Interface())
   932  	assert.Equal(t, input["map"].(map[string]interface{})["mapkey"], reflect.ValueOf(got).Elem().FieldByName("Map").FieldByName("Mapkey").Interface())
   933  }
   934  
   935  func TestMakeMergeStructWithDups(t *testing.T) {
   936  	input := map[string]interface{}{
   937  		"mapkey": "mapval1",
   938  	}
   939  
   940  	s := struct {
   941  		Mapkey string
   942  	}{
   943  		Mapkey: "mapval2",
   944  	}
   945  
   946  	got := MakeMergeStruct(input, s)
   947  	Merge(got, &input)
   948  	assert.Equal(t, &struct {
   949  		Mapkey string `json:"mapkey" yaml:"mapkey"`
   950  	}{"mapval1"}, got)
   951  
   952  	got = MakeMergeStruct(s, input)
   953  	Merge(got, &s)
   954  	assert.Equal(t, &struct{ Mapkey string }{"mapval2"}, got)
   955  }
   956  
   957  func TestMakeMergeStructWithInline(t *testing.T) {
   958  	type Inner struct {
   959  		InnerString string
   960  	}
   961  
   962  	outer := struct {
   963  		Inner       `figtree:",inline"`
   964  		OuterString string
   965  	}{}
   966  
   967  	other := struct {
   968  		InnerString string
   969  		OtherString string
   970  	}{}
   971  
   972  	got := MakeMergeStruct(outer, other)
   973  	assert.IsType(t, (*struct {
   974  		InnerString string
   975  		OtherString string
   976  		OuterString string
   977  	})(nil), got)
   978  
   979  	otherMap := map[string]interface{}{
   980  		"inner-string": "inner",
   981  		"other-string": "other",
   982  	}
   983  
   984  	got = MakeMergeStruct(outer, otherMap)
   985  	assert.IsType(t, (*struct {
   986  		InnerString string `json:"inner-string" yaml:"inner-string"`
   987  		OtherString string `json:"other-string" yaml:"other-string"`
   988  		OuterString string
   989  	})(nil), got)
   990  }
   991  
   992  func TestMakeMergeStructWithYaml(t *testing.T) {
   993  	input := "foo-bar: foo-val\n"
   994  	data := map[string]interface{}{}
   995  	err := yaml.Unmarshal([]byte(input), &data)
   996  	assert.NoError(t, err)
   997  
   998  	// turn map data into a struct
   999  	got := MakeMergeStruct(data)
  1000  	// then assign the data back into that struct
  1001  	Merge(got, data)
  1002  
  1003  	expected := &struct {
  1004  		FooBar string `json:"foo-bar" yaml:"foo-bar"`
  1005  	}{
  1006  		"foo-val",
  1007  	}
  1008  	assert.Equal(t, expected, got)
  1009  
  1010  	// make sure the new structure serializes back to the original document
  1011  	output, err := yaml.Marshal(expected)
  1012  	assert.NoError(t, err)
  1013  	assert.Equal(t, input, string(output))
  1014  }
  1015  
  1016  func TestMakeMergeStructWithJson(t *testing.T) {
  1017  	input := `{"foo-bar":"foo-val"}`
  1018  	data := map[string]interface{}{}
  1019  	err := json.Unmarshal([]byte(input), &data)
  1020  	assert.NoError(t, err)
  1021  
  1022  	// turn map data into a struct
  1023  	got := MakeMergeStruct(data)
  1024  	// then assign the data back into that struct
  1025  	Merge(got, data)
  1026  
  1027  	expected := &struct {
  1028  		FooBar string `json:"foo-bar" yaml:"foo-bar"`
  1029  	}{
  1030  		"foo-val",
  1031  	}
  1032  	assert.Equal(t, expected, got)
  1033  
  1034  	// make sure the new structure serializes back to the original document
  1035  	output, err := json.Marshal(expected)
  1036  	assert.NoError(t, err)
  1037  	assert.Equal(t, input, string(output))
  1038  }
  1039  
  1040  func TestMergeWithZeros(t *testing.T) {
  1041  	var zero interface{}
  1042  	tests := []struct {
  1043  		info info
  1044  		dest map[string]interface{}
  1045  		src  map[string]interface{}
  1046  		want map[string]interface{}
  1047  	}{
  1048  		{
  1049  			info: info{"zero to nil", line()},
  1050  			dest: map[string]interface{}{},
  1051  			src: map[string]interface{}{
  1052  				"value": zero,
  1053  			},
  1054  			want: map[string]interface{}{
  1055  				"value": zero,
  1056  			},
  1057  		},
  1058  		{
  1059  			info: info{"zero to zero", line()},
  1060  			dest: map[string]interface{}{
  1061  				"value": zero,
  1062  			},
  1063  			src: map[string]interface{}{
  1064  				"value": zero,
  1065  			},
  1066  			want: map[string]interface{}{
  1067  				"value": zero,
  1068  			},
  1069  		},
  1070  		{
  1071  			info: info{"zero to StringOption", line()},
  1072  			dest: map[string]interface{}{
  1073  				"value": StringOption{},
  1074  			},
  1075  			src: map[string]interface{}{
  1076  				"value": zero,
  1077  			},
  1078  			want: map[string]interface{}{
  1079  				"value": StringOption{},
  1080  			},
  1081  		},
  1082  		{
  1083  			info: info{"StringOption to zero", line()},
  1084  			dest: map[string]interface{}{
  1085  				"value": zero,
  1086  			},
  1087  			src: map[string]interface{}{
  1088  				"value": StringOption{},
  1089  			},
  1090  			want: map[string]interface{}{
  1091  				"value": StringOption{},
  1092  			},
  1093  		},
  1094  		{
  1095  			info: info{"list zero to nil", line()},
  1096  			dest: map[string]interface{}{
  1097  				"value": nil,
  1098  			},
  1099  			src: map[string]interface{}{
  1100  				"value": []interface{}{zero},
  1101  			},
  1102  			want: map[string]interface{}{
  1103  				"value": []interface{}{zero},
  1104  			},
  1105  		},
  1106  		{
  1107  			info: info{"list zero to empty", line()},
  1108  			dest: map[string]interface{}{
  1109  				"value": []interface{}{},
  1110  			},
  1111  			src: map[string]interface{}{
  1112  				"value": []interface{}{zero},
  1113  			},
  1114  			want: map[string]interface{}{
  1115  				"value": []interface{}{},
  1116  			},
  1117  		},
  1118  		{
  1119  			info: info{"list zero to StringOption", line()},
  1120  			dest: map[string]interface{}{
  1121  				"value": []interface{}{StringOption{}},
  1122  			},
  1123  			src: map[string]interface{}{
  1124  				"value": []interface{}{zero},
  1125  			},
  1126  			want: map[string]interface{}{
  1127  				"value": []interface{}{StringOption{}},
  1128  			},
  1129  		},
  1130  		{
  1131  			info: info{"list StringOption to zero", line()},
  1132  			dest: map[string]interface{}{
  1133  				"value": []interface{}{zero},
  1134  			},
  1135  			src: map[string]interface{}{
  1136  				"value": []interface{}{StringOption{}},
  1137  			},
  1138  			want: map[string]interface{}{
  1139  				"value": []interface{}{zero, StringOption{}},
  1140  			},
  1141  		},
  1142  		{
  1143  			info: info{"list StringOption to empty", line()},
  1144  			dest: map[string]interface{}{
  1145  				"value": []interface{}{},
  1146  			},
  1147  			src: map[string]interface{}{
  1148  				"value": []interface{}{StringOption{}},
  1149  			},
  1150  			want: map[string]interface{}{
  1151  				"value": []interface{}{StringOption{}},
  1152  			},
  1153  		},
  1154  		{
  1155  			info: info{"zero to ListStringOption", line()},
  1156  			dest: map[string]interface{}{
  1157  				"value": ListStringOption{StringOption{}},
  1158  			},
  1159  			src: map[string]interface{}{
  1160  				"value": []interface{}{zero},
  1161  			},
  1162  			want: map[string]interface{}{
  1163  				"value": ListStringOption{StringOption{}},
  1164  			},
  1165  		},
  1166  		{
  1167  			info: info{"ListStringOption to zero", line()},
  1168  			dest: map[string]interface{}{
  1169  				"value": []interface{}{zero},
  1170  			},
  1171  			src: map[string]interface{}{
  1172  				"value": ListStringOption{StringOption{}},
  1173  			},
  1174  			want: map[string]interface{}{
  1175  				"value": []interface{}{zero},
  1176  			},
  1177  		},
  1178  		{
  1179  			info: info{"map zero to nil", line()},
  1180  			dest: map[string]interface{}{
  1181  				"value": nil,
  1182  			},
  1183  			src: map[string]interface{}{
  1184  				"value": map[string]interface{}{
  1185  					"key": zero,
  1186  				},
  1187  			},
  1188  			want: map[string]interface{}{
  1189  				"value": map[string]interface{}{
  1190  					"key": zero,
  1191  				},
  1192  			},
  1193  		},
  1194  		{
  1195  			info: info{"map zero to empty", line()},
  1196  			dest: map[string]interface{}{
  1197  				"value": map[string]interface{}{},
  1198  			},
  1199  			src: map[string]interface{}{
  1200  				"value": map[string]interface{}{
  1201  					"key": zero,
  1202  				},
  1203  			},
  1204  			want: map[string]interface{}{
  1205  				"value": map[string]interface{}{
  1206  					"key": zero,
  1207  				},
  1208  			},
  1209  		},
  1210  		{
  1211  			info: info{"MapStringOption to zero", line()},
  1212  			dest: map[string]interface{}{
  1213  				"value": zero,
  1214  			},
  1215  			src: map[string]interface{}{
  1216  				"value": MapStringOption{
  1217  					"key": StringOption{},
  1218  				},
  1219  			},
  1220  			want: map[string]interface{}{
  1221  				"value": MapStringOption{
  1222  					"key": StringOption{},
  1223  				},
  1224  			},
  1225  		},
  1226  		{
  1227  			info: info{"map zero to StringOption", line()},
  1228  			dest: map[string]interface{}{
  1229  				"value": MapStringOption{
  1230  					"key": StringOption{},
  1231  				},
  1232  			},
  1233  			src: map[string]interface{}{
  1234  				"value": zero,
  1235  			},
  1236  			want: map[string]interface{}{
  1237  				"value": MapStringOption{
  1238  					"key": StringOption{},
  1239  				},
  1240  			},
  1241  		},
  1242  		{
  1243  			info: info{"map zero key to StringOption", line()},
  1244  			dest: map[string]interface{}{
  1245  				"value": MapStringOption{
  1246  					"key": StringOption{},
  1247  				},
  1248  			},
  1249  			src: map[string]interface{}{
  1250  				"value": map[string]interface{}{
  1251  					"key": zero,
  1252  				},
  1253  			},
  1254  			want: map[string]interface{}{
  1255  				"value": MapStringOption{
  1256  					"key": StringOption{},
  1257  				},
  1258  			},
  1259  		},
  1260  		{
  1261  			info: info{"map StringOption to zero key", line()},
  1262  			dest: map[string]interface{}{
  1263  				"value": map[string]interface{}{
  1264  					"key": zero,
  1265  				},
  1266  			},
  1267  			src: map[string]interface{}{
  1268  				"value": MapStringOption{
  1269  					"key": StringOption{},
  1270  				},
  1271  			},
  1272  			want: map[string]interface{}{
  1273  				"value": map[string]interface{}{
  1274  					"key": StringOption{},
  1275  				},
  1276  			},
  1277  		},
  1278  	}
  1279  
  1280  	for _, tt := range tests {
  1281  		require.True(t,
  1282  			t.Run(tt.info.name, func(t *testing.T) {
  1283  				// assert.NotPanics(t, func() {
  1284  				Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
  1285  				Log.Debugf("%s", tt.info.name)
  1286  				Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
  1287  				Merge(&tt.dest, &tt.src)
  1288  				// })
  1289  				assert.Equal(t, tt.want, tt.dest, tt.info.line)
  1290  
  1291  				got := MakeMergeStruct(tt.dest)
  1292  				Merge(got, tt.dest)
  1293  				Merge(got, tt.src)
  1294  
  1295  				expected := MakeMergeStruct(tt.want)
  1296  				Merge(expected, tt.want)
  1297  
  1298  				assert.Equal(t, expected, got, tt.info.line)
  1299  			}),
  1300  		)
  1301  	}
  1302  }
  1303  
  1304  func TestMergeStructsWithZeros(t *testing.T) {
  1305  	var zero interface{}
  1306  	tests := []struct {
  1307  		info info
  1308  		dest interface{}
  1309  		src  interface{}
  1310  		want interface{}
  1311  		line string
  1312  	}{
  1313  		{
  1314  			info: info{"bare nil", line()},
  1315  			dest: struct {
  1316  				Value interface{}
  1317  			}{},
  1318  			src: struct {
  1319  				Value interface{}
  1320  			}{zero},
  1321  			want: struct {
  1322  				Value interface{}
  1323  			}{zero},
  1324  		},
  1325  		{
  1326  			info: info{"bare zero", line()},
  1327  			dest: struct {
  1328  				Value interface{}
  1329  			}{zero},
  1330  			src: struct {
  1331  				Value interface{}
  1332  			}{zero},
  1333  			want: struct {
  1334  				Value interface{}
  1335  			}{zero},
  1336  		},
  1337  		{
  1338  			info: info{"bare StringOption", line()},
  1339  			dest: struct {
  1340  				Value interface{}
  1341  			}{StringOption{}},
  1342  			src: struct {
  1343  				Value interface{}
  1344  			}{StringOption{}},
  1345  			want: struct {
  1346  				Value interface{}
  1347  			}{StringOption{}},
  1348  		},
  1349  		{
  1350  			info: info{"bare StringOptions to zero", line()},
  1351  			dest: struct {
  1352  				Value interface{}
  1353  			}{zero},
  1354  			src: struct {
  1355  				Value StringOption
  1356  			}{StringOption{}},
  1357  			want: struct {
  1358  				Value interface{}
  1359  			}{zero},
  1360  		},
  1361  		{
  1362  			info: info{"list zero to nil", line()},
  1363  			dest: struct {
  1364  				Value interface{}
  1365  			}{},
  1366  			src: struct {
  1367  				Value interface{}
  1368  			}{[]interface{}{zero}},
  1369  			want: struct {
  1370  				Value interface{}
  1371  			}{[]interface{}{zero}},
  1372  		},
  1373  		{
  1374  			info: info{"list zero to empty", line()},
  1375  			dest: struct {
  1376  				Value interface{}
  1377  			}{[]interface{}{}},
  1378  			src: struct {
  1379  				Value interface{}
  1380  			}{[]interface{}{zero}},
  1381  			want: struct {
  1382  				Value interface{}
  1383  			}{[]interface{}{}},
  1384  		},
  1385  		{
  1386  			info: info{"list zero to StringOption", line()},
  1387  			dest: struct {
  1388  				Value interface{}
  1389  			}{[]interface{}{StringOption{}}},
  1390  			src: struct {
  1391  				Value interface{}
  1392  			}{[]interface{}{zero}},
  1393  			want: struct {
  1394  				Value interface{}
  1395  			}{[]interface{}{StringOption{}}},
  1396  		},
  1397  		{
  1398  			info: info{"list StringOption to zero", line()},
  1399  			dest: struct {
  1400  				Value interface{}
  1401  			}{[]interface{}{zero}},
  1402  			src: struct {
  1403  				Value interface{}
  1404  			}{[]interface{}{StringOption{}}},
  1405  			want: struct {
  1406  				Value interface{}
  1407  			}{[]interface{}{zero, StringOption{}}},
  1408  		},
  1409  		{
  1410  			info: info{"list ListStringOption to empty list", line()},
  1411  			line: line(),
  1412  			dest: struct {
  1413  				Value interface{}
  1414  			}{[]interface{}{}},
  1415  			src: struct {
  1416  				Value ListStringOption
  1417  			}{ListStringOption{StringOption{}}},
  1418  			want: struct {
  1419  				Value interface{}
  1420  			}{[]interface{}{}},
  1421  		},
  1422  		{
  1423  
  1424  			info: info{"list zero list to ListStringOption", line()},
  1425  			dest: struct {
  1426  				Value ListStringOption
  1427  			}{ListStringOption{StringOption{}}},
  1428  			src: struct {
  1429  				Value interface{}
  1430  			}{[]interface{}{zero}},
  1431  			want: struct {
  1432  				Value ListStringOption
  1433  			}{ListStringOption{StringOption{}}},
  1434  		},
  1435  		{
  1436  			info: info{"list ListStringOption to zero list", line()},
  1437  			dest: struct {
  1438  				Value interface{}
  1439  			}{[]interface{}{zero}},
  1440  			src: struct {
  1441  				Value ListStringOption
  1442  			}{ListStringOption{StringOption{}}},
  1443  			want: struct {
  1444  				Value interface{}
  1445  			}{[]interface{}{zero}},
  1446  		},
  1447  		{
  1448  			info: info{"map zero to nil", line()},
  1449  			dest: struct {
  1450  				Value interface{}
  1451  			}{},
  1452  			src: struct {
  1453  				Value interface{}
  1454  			}{map[string]interface{}{"key": zero}},
  1455  			want: struct {
  1456  				Value interface{}
  1457  			}{map[string]interface{}{"key": zero}},
  1458  		},
  1459  		{
  1460  			info: info{"map zero to empty", line()},
  1461  			dest: struct {
  1462  				Value interface{}
  1463  			}{map[string]interface{}{}},
  1464  			src: struct {
  1465  				Value interface{}
  1466  			}{map[string]interface{}{"key": zero}},
  1467  			want: struct {
  1468  				Value interface{}
  1469  			}{map[string]interface{}{"key": zero}},
  1470  		},
  1471  		{
  1472  			info: info{"map StringOption to zero", line()},
  1473  			dest: struct {
  1474  				Value interface{}
  1475  			}{zero},
  1476  			src: struct {
  1477  				Value interface{}
  1478  			}{map[string]interface{}{"key": zero}},
  1479  			want: struct {
  1480  				Value interface{}
  1481  			}{map[string]interface{}{"key": zero}},
  1482  		},
  1483  		{
  1484  			info: info{"MapStringOption StringOption to zero", line()},
  1485  			dest: struct {
  1486  				Value interface{}
  1487  			}{zero},
  1488  			src: struct {
  1489  				Value interface{}
  1490  			}{MapStringOption{
  1491  				"key": StringOption{},
  1492  			}},
  1493  			want: struct {
  1494  				Value interface{}
  1495  			}{MapStringOption{
  1496  				"key": StringOption{},
  1497  			}},
  1498  		},
  1499  		{
  1500  			info: info{"zero to MapStringOption StringOption", line()},
  1501  			dest: struct {
  1502  				Value interface{}
  1503  			}{MapStringOption{
  1504  				"key": StringOption{},
  1505  			}},
  1506  			src: struct {
  1507  				Value interface{}
  1508  			}{zero},
  1509  			want: struct {
  1510  				Value interface{}
  1511  			}{MapStringOption{
  1512  				"key": StringOption{},
  1513  			}},
  1514  		},
  1515  		{
  1516  			info: info{"map zero to MapStringOption StringOption", line()},
  1517  			dest: struct {
  1518  				Value interface{}
  1519  			}{MapStringOption{
  1520  				"key": StringOption{},
  1521  			}},
  1522  			src: struct {
  1523  				Value interface{}
  1524  			}{map[string]interface{}{
  1525  				"key": zero,
  1526  			}},
  1527  			want: struct {
  1528  				Value interface{}
  1529  			}{MapStringOption{
  1530  				"key": StringOption{},
  1531  			}},
  1532  		},
  1533  	}
  1534  	for _, tt := range tests {
  1535  		require.True(t,
  1536  			t.Run(tt.info.name, func(t *testing.T) {
  1537  				// assert.NotPanics(t, func() {
  1538  				Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
  1539  				Log.Debugf("%s", tt.info.name)
  1540  				Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
  1541  				Merge(&tt.dest, &tt.src)
  1542  				// })
  1543  				assert.Equal(t, tt.want, tt.dest, tt.info.line)
  1544  
  1545  				got := MakeMergeStruct(tt.dest)
  1546  				Merge(got, tt.dest)
  1547  				Merge(got, tt.src)
  1548  
  1549  				expected := MakeMergeStruct(tt.want)
  1550  				Merge(expected, tt.want)
  1551  
  1552  				assert.Equal(t, expected, got, tt.info.line)
  1553  
  1554  			}),
  1555  		)
  1556  	}
  1557  }
  1558  
  1559  func TestMergeStructsWithPreservedMaps(t *testing.T) {
  1560  	tests := []struct {
  1561  		info   info
  1562  		src    interface{}
  1563  		want   interface{}
  1564  		merger *Merger
  1565  	}{
  1566  		{
  1567  			info: info{"convert map to struct by default", line()},
  1568  			src: map[string]interface{}{
  1569  				"map": map[string]string{"key": "value"},
  1570  			},
  1571  			want: &struct {
  1572  				Map struct {
  1573  					Key string `json:"key" yaml:"key"`
  1574  				} `json:"map" yaml:"map"`
  1575  			}{},
  1576  			merger: NewMerger(),
  1577  		}, {
  1578  			info: info{"preserve map when converting to struct", line()},
  1579  			src: map[string]interface{}{
  1580  				"map":   map[string]string{"key": "value"},
  1581  				"other": map[string]string{"key": "value"},
  1582  			},
  1583  			want: &struct {
  1584  				Map   map[string]string `json:"map" yaml:"map"`
  1585  				Other struct {
  1586  					Key string `json:"key" yaml:"key"`
  1587  				} `json:"other" yaml:"other"`
  1588  			}{},
  1589  			merger: NewMerger(PreserveMap("map")),
  1590  		},
  1591  	}
  1592  
  1593  	for _, tt := range tests {
  1594  		require.True(t,
  1595  			t.Run(tt.info.name, func(t *testing.T) {
  1596  				got := tt.merger.MakeMergeStruct(tt.src)
  1597  				assert.Equal(t, tt.want, got)
  1598  			}),
  1599  		)
  1600  	}
  1601  }
  1602  
  1603  func TestFigtreePreProcessor(t *testing.T) {
  1604  	input := []byte(`
  1605  bad-name: good-value
  1606  good-name: bad-value
  1607  ok-name: want-array
  1608  `)
  1609  
  1610  	pp := func(in []byte) ([]byte, error) {
  1611  		raw := map[string]interface{}{}
  1612  		if err := yaml.Unmarshal(in, &raw); err != nil {
  1613  			return in, err
  1614  		}
  1615  
  1616  		// rename "bad-name" key to "fixed-name"
  1617  		if val, ok := raw["bad-name"]; ok {
  1618  			delete(raw, "bad-name")
  1619  			raw["fixed-name"] = val
  1620  		}
  1621  
  1622  		// reset "bad-value" key to "good-value"
  1623  		if val, ok := raw["good-name"]; ok {
  1624  			if t, ok := val.(string); ok && t != "fixed-value" {
  1625  				raw["good-name"] = "fixed-value"
  1626  			}
  1627  		}
  1628  
  1629  		// migrate "ok-name" value from string to list of strings
  1630  		if val, ok := raw["ok-name"]; ok {
  1631  			if t, ok := val.(string); ok {
  1632  				raw["ok-name"] = []string{t}
  1633  			}
  1634  		}
  1635  		return yaml.Marshal(raw)
  1636  	}
  1637  
  1638  	fig := newFigTreeFromEnv(WithPreProcessor(pp))
  1639  
  1640  	dest := struct {
  1641  		FixedName string   `yaml:"fixed-name"`
  1642  		GoodName  string   `yaml:"good-name"`
  1643  		OkName    []string `yaml:"ok-name"`
  1644  	}{}
  1645  
  1646  	want := struct {
  1647  		FixedName string   `yaml:"fixed-name"`
  1648  		GoodName  string   `yaml:"good-name"`
  1649  		OkName    []string `yaml:"ok-name"`
  1650  	}{"good-value", "fixed-value", []string{"want-array"}}
  1651  
  1652  	err := fig.LoadConfigBytes(input, "test", &dest)
  1653  	assert.NoError(t, err)
  1654  	assert.Equal(t, want, dest)
  1655  }
  1656  
  1657  func TestMergeMapWithCopy(t *testing.T) {
  1658  	type mss = map[string]string
  1659  
  1660  	dest := struct {
  1661  		Map mss
  1662  	}{}
  1663  
  1664  	src1 := struct {
  1665  		Map mss
  1666  	}{
  1667  		mss{
  1668  			"key": "value",
  1669  		},
  1670  	}
  1671  
  1672  	src2 := struct {
  1673  		Map mss
  1674  	}{
  1675  		mss{
  1676  			"otherkey": "othervalue",
  1677  		},
  1678  	}
  1679  
  1680  	Merge(&dest, &src1)
  1681  	assert.Equal(t, mss{"key": "value"}, dest.Map)
  1682  
  1683  	Merge(&dest, &src2)
  1684  	assert.Equal(t, mss{"key": "value", "otherkey": "othervalue"}, dest.Map)
  1685  
  1686  	// verify that src1 was unmodified
  1687  	assert.Equal(t, mss{"key": "value"}, src1.Map)
  1688  }
  1689  
  1690  func TestMergeBoolString(t *testing.T) {
  1691  	src1 := struct {
  1692  		EnableThing BoolOption
  1693  	}{NewBoolOption(true)}
  1694  
  1695  	src2 := map[string]interface{}{
  1696  		"enable-thing": "true",
  1697  	}
  1698  
  1699  	dest := MakeMergeStruct(src1, src2)
  1700  	Merge(dest, src1)
  1701  	Merge(dest, src2)
  1702  
  1703  	expected := &struct {
  1704  		EnableThing BoolOption
  1705  	}{BoolOption{Source: "merge", Defined: true, Value: true}}
  1706  
  1707  	assert.Equal(t, expected, dest)
  1708  }
  1709  
  1710  func TestMergeStringBool(t *testing.T) {
  1711  	src1 := struct {
  1712  		EnableThing StringOption
  1713  	}{NewStringOption("true")}
  1714  
  1715  	src2 := map[string]interface{}{
  1716  		"enable-thing": true,
  1717  	}
  1718  
  1719  	dest := MakeMergeStruct(src1, src2)
  1720  	Merge(dest, src1)
  1721  	Merge(dest, src2)
  1722  
  1723  	expected := &struct {
  1724  		EnableThing StringOption
  1725  	}{StringOption{Source: "merge", Defined: true, Value: "true"}}
  1726  
  1727  	assert.Equal(t, expected, dest)
  1728  }
  1729  
  1730  func TestMergeStringFloat64(t *testing.T) {
  1731  	src1 := struct {
  1732  		SomeThing StringOption
  1733  	}{NewStringOption("true")}
  1734  
  1735  	src2 := map[string]interface{}{
  1736  		"some-thing": 42.0,
  1737  	}
  1738  
  1739  	dest := MakeMergeStruct(src1, src2)
  1740  	Merge(dest, src1)
  1741  	Merge(dest, src2)
  1742  
  1743  	expected := &struct {
  1744  		SomeThing StringOption
  1745  	}{StringOption{Source: "merge", Defined: true, Value: "42"}}
  1746  
  1747  	assert.Equal(t, expected, dest)
  1748  }
  1749  
  1750  func TestMergeDefaults(t *testing.T) {
  1751  	src1 := &struct {
  1752  		SomeThing StringOption
  1753  	}{NewStringOption("foo")}
  1754  
  1755  	src2 := &struct {
  1756  		SomeThing StringOption
  1757  	}{NewStringOption("bar")}
  1758  
  1759  	dest := MakeMergeStruct(src1, src2)
  1760  	Merge(dest, src1)
  1761  
  1762  	expected := &struct {
  1763  		SomeThing StringOption
  1764  	}{StringOption{Source: "default", Defined: true, Value: "foo"}}
  1765  
  1766  	assert.Equal(t, expected, dest)
  1767  
  1768  	Merge(dest, src2)
  1769  
  1770  	assert.Equal(t, expected, dest)
  1771  }