github.com/lyft/flytestdlib@v0.3.12-0.20210213045714-8cdd111ecda1/config/tests/accessor_test.go (about)

     1  package tests
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"encoding/binary"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path"
    12  	"path/filepath"
    13  	"reflect"
    14  	"runtime"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	k8sRand "k8s.io/apimachinery/pkg/util/rand"
    20  
    21  	"github.com/lyft/flytestdlib/config"
    22  	"github.com/lyft/flytestdlib/internal/utils"
    23  	"github.com/spf13/pflag"
    24  
    25  	"github.com/ghodss/yaml"
    26  	"github.com/stretchr/testify/assert"
    27  )
    28  
    29  type accessorCreatorFn func(registry config.Section, configPath string) config.Accessor
    30  
    31  func getRandInt() uint64 {
    32  	c := 10
    33  	b := make([]byte, c)
    34  	_, err := rand.Read(b)
    35  	if err != nil {
    36  		return 0
    37  	}
    38  
    39  	return binary.BigEndian.Uint64(b)
    40  }
    41  
    42  func tempFileName(pattern string) string {
    43  	// TODO: Remove this hack after we use Go1.11 everywhere:
    44  	// https://github.com/golang/go/commit/191efbc419d7e5dec842c20841f6f716da4b561d
    45  
    46  	var prefix, suffix string
    47  	if pos := strings.LastIndex(pattern, "*"); pos != -1 {
    48  		prefix, suffix = pattern[:pos], pattern[pos+1:]
    49  	} else {
    50  		prefix = pattern
    51  	}
    52  
    53  	return filepath.Join(os.TempDir(), prefix+k8sRand.String(6)+suffix)
    54  }
    55  
    56  func populateConfigData(configPath string) (TestConfig, error) {
    57  	expected := TestConfig{
    58  		MyComponentConfig: MyComponentConfig{
    59  			StringValue: fmt.Sprintf("Hello World %v", getRandInt()),
    60  		},
    61  		OtherComponentConfig: OtherComponentConfig{
    62  			StringValue:   fmt.Sprintf("Hello World %v", getRandInt()),
    63  			URLValue:      config.URL{URL: utils.MustParseURL("http://something.com")},
    64  			DurationValue: config.Duration{Duration: time.Second * 20},
    65  		},
    66  	}
    67  
    68  	raw, err := yaml.Marshal(expected)
    69  	if err != nil {
    70  		return TestConfig{}, err
    71  	}
    72  
    73  	return expected, ioutil.WriteFile(configPath, raw, os.ModePerm)
    74  }
    75  
    76  func TestGetEmptySection(t *testing.T) {
    77  	t.Run("empty", func(t *testing.T) {
    78  		r := config.GetSection("Empty")
    79  		assert.Nil(t, r)
    80  	})
    81  }
    82  
    83  type ComplexType struct {
    84  	IntValue int `json:"int-val"`
    85  }
    86  
    87  type ComplexTypeArray []ComplexType
    88  
    89  type ConfigWithLists struct {
    90  	ListOfStuff []ComplexType `json:"list"`
    91  	StringValue string        `json:"string-val"`
    92  }
    93  
    94  type ConfigWithMaps struct {
    95  	MapOfStuff     map[string]ComplexType `json:"m"`
    96  	MapWithoutJSON map[string]ComplexType
    97  }
    98  
    99  type ConfigWithJSONTypes struct {
   100  	Duration config.Duration `json:"duration"`
   101  }
   102  
   103  func TestAccessor_InitializePflags(t *testing.T) {
   104  	for _, provider := range providers {
   105  		t.Run(fmt.Sprintf("[%v] Unused flag", provider(config.Options{}).ID()), func(t *testing.T) {
   106  			reg := config.NewRootSection()
   107  			_, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   108  			assert.NoError(t, err)
   109  			v := provider(config.Options{
   110  				SearchPaths: []string{filepath.Join("testdata", "config.yaml")},
   111  				RootSection: reg,
   112  			})
   113  
   114  			set := pflag.NewFlagSet("test", pflag.ContinueOnError)
   115  			set.String("flag1", "123", "")
   116  			v.InitializePflags(set)
   117  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   118  		})
   119  
   120  		t.Run(fmt.Sprintf("[%v] Override string value", provider(config.Options{}).ID()), func(t *testing.T) {
   121  			reg := config.NewRootSection()
   122  			_, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   123  			assert.NoError(t, err)
   124  
   125  			v := provider(config.Options{
   126  				SearchPaths: []string{filepath.Join("testdata", "config.yaml")},
   127  				RootSection: reg,
   128  			})
   129  
   130  			set := pflag.NewFlagSet("test", pflag.ContinueOnError)
   131  			v.InitializePflags(set)
   132  			key := "MY_COMPONENT.STR2"
   133  			assert.NoError(t, os.Setenv(key, "123"))
   134  			defer func() { assert.NoError(t, os.Unsetenv(key)) }()
   135  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   136  			r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   137  			assert.Equal(t, "123", r.StringValue2)
   138  		})
   139  
   140  		t.Run(fmt.Sprintf("[%v] Parse from config file", provider(config.Options{}).ID()), func(t *testing.T) {
   141  			reg := config.NewRootSection()
   142  			_, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   143  			assert.NoError(t, err)
   144  
   145  			_, err = reg.RegisterSection(OtherComponentSectionKey, &OtherComponentConfig{})
   146  			assert.NoError(t, err)
   147  
   148  			v := provider(config.Options{
   149  				SearchPaths: []string{filepath.Join("testdata", "config.yaml")},
   150  				RootSection: reg,
   151  			})
   152  
   153  			set := pflag.NewFlagSet("test", pflag.ExitOnError)
   154  			v.InitializePflags(set)
   155  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   156  			r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   157  			assert.Equal(t, "Hello World", r.StringValue)
   158  			otherC := reg.GetSection(OtherComponentSectionKey).GetConfig().(*OtherComponentConfig)
   159  			assert.Equal(t, 4, otherC.IntValue)
   160  			assert.Equal(t, []string{"default value"}, otherC.StringArrayWithDefaults)
   161  		})
   162  
   163  		t.Run(fmt.Sprintf("[%v] Sub-sections", provider(config.Options{}).ID()), func(t *testing.T) {
   164  			reg := config.NewRootSection()
   165  			sec, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   166  			assert.NoError(t, err)
   167  
   168  			_, err = sec.RegisterSection("nested", &OtherComponentConfig{})
   169  			assert.NoError(t, err)
   170  
   171  			v := provider(config.Options{
   172  				SearchPaths: []string{filepath.Join("testdata", "nested_config.yaml")},
   173  				RootSection: reg,
   174  			})
   175  
   176  			set := pflag.NewFlagSet("test", pflag.ExitOnError)
   177  			v.InitializePflags(set)
   178  			assert.NoError(t, set.Parse([]string{"--my-component.nested.int-val=3"}))
   179  			assert.True(t, set.Parsed())
   180  
   181  			flagValue, err := set.GetInt("my-component.nested.int-val")
   182  			assert.NoError(t, err)
   183  			assert.Equal(t, 3, flagValue)
   184  
   185  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   186  			r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   187  			assert.Equal(t, "Hello World", r.StringValue)
   188  
   189  			nested := sec.GetSection("nested").GetConfig().(*OtherComponentConfig)
   190  			assert.Equal(t, 3, nested.IntValue)
   191  		})
   192  	}
   193  }
   194  
   195  func TestStrictAccessor(t *testing.T) {
   196  	for _, provider := range providers {
   197  		t.Run(fmt.Sprintf("[%v] Bad config", provider(config.Options{}).ID()), func(t *testing.T) {
   198  			reg := config.NewRootSection()
   199  			v := provider(config.Options{
   200  				StrictMode:  true,
   201  				SearchPaths: []string{filepath.Join("testdata", "bad_config.yaml")},
   202  				RootSection: reg,
   203  			})
   204  
   205  			set := pflag.NewFlagSet("test", pflag.ExitOnError)
   206  			v.InitializePflags(set)
   207  			assert.Error(t, v.UpdateConfig(context.TODO()))
   208  		})
   209  
   210  		t.Run(fmt.Sprintf("[%v] Set through env", provider(config.Options{}).ID()), func(t *testing.T) {
   211  			reg := config.NewRootSection()
   212  			_, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   213  			assert.NoError(t, err)
   214  
   215  			_, err = reg.RegisterSection(OtherComponentSectionKey, &OtherComponentConfig{})
   216  			assert.NoError(t, err)
   217  
   218  			v := provider(config.Options{
   219  				StrictMode:  true,
   220  				SearchPaths: []string{filepath.Join("testdata", "config.yaml")},
   221  				RootSection: reg,
   222  			})
   223  
   224  			set := pflag.NewFlagSet("other-component.string-value", pflag.ExitOnError)
   225  			v.InitializePflags(set)
   226  
   227  			key := "OTHER_COMPONENT.STRING_VALUE"
   228  			assert.NoError(t, os.Setenv(key, "set from env"))
   229  			defer func() { assert.NoError(t, os.Unsetenv(key)) }()
   230  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   231  		})
   232  	}
   233  }
   234  
   235  func TestAccessor_UpdateConfig(t *testing.T) {
   236  	for _, provider := range providers {
   237  		t.Run(fmt.Sprintf("[%v] Static File", provider(config.Options{}).ID()), func(t *testing.T) {
   238  			reg := config.NewRootSection()
   239  			_, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   240  			assert.NoError(t, err)
   241  
   242  			v := provider(config.Options{
   243  				SearchPaths: []string{filepath.Join("testdata", "config.yaml")},
   244  				RootSection: reg,
   245  			})
   246  
   247  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   248  			r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   249  			assert.Equal(t, "Hello World", r.StringValue)
   250  		})
   251  
   252  		t.Run(fmt.Sprintf("[%v] Nested", provider(config.Options{}).ID()), func(t *testing.T) {
   253  			root := config.NewRootSection()
   254  			section, err := root.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   255  			assert.NoError(t, err)
   256  
   257  			_, err = section.RegisterSection("nested", &OtherComponentConfig{})
   258  			assert.NoError(t, err)
   259  
   260  			v := provider(config.Options{
   261  				SearchPaths: []string{filepath.Join("testdata", "nested_config.yaml")},
   262  				RootSection: root,
   263  			})
   264  
   265  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   266  			r := root.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   267  			assert.Equal(t, "Hello World", r.StringValue)
   268  
   269  			nested := section.GetSection("nested").GetConfig().(*OtherComponentConfig)
   270  			assert.Equal(t, "Hey there!", nested.StringValue)
   271  		})
   272  
   273  		t.Run(fmt.Sprintf("[%v] Array Configs", provider(config.Options{}).ID()), func(t *testing.T) {
   274  			root := config.NewRootSection()
   275  			section, err := root.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   276  			assert.NoError(t, err)
   277  
   278  			_, err = section.RegisterSection("nested", &ComplexTypeArray{})
   279  			assert.NoError(t, err)
   280  
   281  			_, err = root.RegisterSection("array-config", &ComplexTypeArray{})
   282  			assert.NoError(t, err)
   283  
   284  			v := provider(config.Options{
   285  				SearchPaths: []string{filepath.Join("testdata", "array_configs.yaml")},
   286  				RootSection: root,
   287  			})
   288  
   289  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   290  			r := root.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   291  			assert.Equal(t, "Hello World", r.StringValue)
   292  
   293  			nested := section.GetSection("nested").GetConfig().(*ComplexTypeArray)
   294  			assert.Len(t, *nested, 1)
   295  			assert.Equal(t, 1, (*nested)[0].IntValue)
   296  
   297  			topLevel := root.GetSection("array-config").GetConfig().(*ComplexTypeArray)
   298  			assert.Len(t, *topLevel, 2)
   299  			assert.Equal(t, 4, (*topLevel)[1].IntValue)
   300  		})
   301  
   302  		t.Run(fmt.Sprintf("[%v] Override in Env Var", provider(config.Options{}).ID()), func(t *testing.T) {
   303  			reg := config.NewRootSection()
   304  			_, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   305  			assert.NoError(t, err)
   306  
   307  			v := provider(config.Options{
   308  				SearchPaths: []string{filepath.Join("testdata", "config.yaml")},
   309  				RootSection: reg,
   310  			})
   311  			key := strings.ToUpper("my-component.str")
   312  			assert.NoError(t, os.Setenv(key, "Set From Env"))
   313  			defer func() { assert.NoError(t, os.Unsetenv(key)) }()
   314  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   315  			r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   316  			assert.Equal(t, "Set From Env", r.StringValue)
   317  		})
   318  
   319  		t.Run(fmt.Sprintf("[%v] Override in Env Var no config file", provider(config.Options{}).ID()), func(t *testing.T) {
   320  			reg := config.NewRootSection()
   321  			_, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   322  			assert.NoError(t, err)
   323  
   324  			v := provider(config.Options{RootSection: reg})
   325  			key := strings.ToUpper("my-component.str3")
   326  			assert.NoError(t, os.Setenv(key, "Set From Env"))
   327  			defer func() { assert.NoError(t, os.Unsetenv(key)) }()
   328  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   329  			r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   330  			assert.Equal(t, "Set From Env", r.StringValue3)
   331  		})
   332  
   333  		t.Run(fmt.Sprintf("[%v] Change handler", provider(config.Options{}).ID()), func(t *testing.T) {
   334  			configFile := tempFileName("config-*.yaml")
   335  			defer func() { assert.NoError(t, os.Remove(configFile)) }()
   336  			cfg, err := populateConfigData(configFile)
   337  			assert.NoError(t, err)
   338  
   339  			reg := config.NewRootSection()
   340  			called := false
   341  			_, err = reg.RegisterSectionWithUpdates(MyComponentSectionKey, &cfg.MyComponentConfig,
   342  				func(ctx context.Context, newValue config.Config) {
   343  					called = true
   344  				})
   345  			assert.NoError(t, err)
   346  
   347  			opts := config.Options{
   348  				SearchPaths: []string{configFile},
   349  				RootSection: reg,
   350  			}
   351  			v := provider(opts)
   352  			err = v.UpdateConfig(context.TODO())
   353  			assert.NoError(t, err)
   354  
   355  			assert.True(t, called)
   356  		})
   357  
   358  		t.Run(fmt.Sprintf("[%v] Change handler on change", provider(config.Options{}).ID()), func(t *testing.T) {
   359  			configFile := tempFileName("config-*.yaml")
   360  			defer func() { assert.NoError(t, os.Remove(configFile)) }()
   361  			_, err := populateConfigData(configFile)
   362  			assert.NoError(t, err)
   363  
   364  			reg := config.NewRootSection()
   365  			_, err = reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   366  			assert.NoError(t, err)
   367  
   368  			opts := config.Options{
   369  				SearchPaths: []string{configFile},
   370  				RootSection: reg,
   371  			}
   372  			v := provider(opts)
   373  			err = v.UpdateConfig(context.TODO())
   374  			assert.NoError(t, err)
   375  
   376  			r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   377  			firstValue := r.StringValue
   378  
   379  			_, err = populateConfigData(configFile)
   380  			assert.NoError(t, err)
   381  
   382  			// Wait enough for the file change notification to propagate.
   383  			time.Sleep(5 * time.Second)
   384  
   385  			r = reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   386  			secondValue := r.StringValue
   387  			assert.NotEqual(t, firstValue, secondValue)
   388  		})
   389  
   390  		t.Run(fmt.Sprintf("[%v] Change handler k8s configmaps", provider(config.Options{}).ID()), func(t *testing.T) {
   391  			reg := config.NewRootSection()
   392  			section, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{})
   393  			assert.NoError(t, err)
   394  
   395  			var firstValue string
   396  
   397  			// 1. Create Dir structure
   398  			watchDir, configFile, cleanup := newSymlinkedConfigFile(t)
   399  			defer cleanup()
   400  
   401  			// 2. Start accessor with the symlink as config location
   402  			opts := config.Options{
   403  				SearchPaths: []string{configFile},
   404  				RootSection: reg,
   405  			}
   406  			v := provider(opts)
   407  			err = v.UpdateConfig(context.TODO())
   408  			assert.NoError(t, err)
   409  
   410  			r := section.GetConfig().(*MyComponentConfig)
   411  			firstValue = r.StringValue
   412  			t.Logf("First value: %v", firstValue)
   413  
   414  			// 3. Now update /data symlink to point to data2
   415  			dataDir2 := path.Join(watchDir, "data2")
   416  			err = os.Mkdir(dataDir2, os.ModePerm)
   417  			assert.NoError(t, err)
   418  
   419  			configFile2 := path.Join(dataDir2, "config.yaml")
   420  			newData, err := populateConfigData(configFile2)
   421  			assert.NoError(t, err)
   422  			t.Logf("New value written to file: %v", newData.MyComponentConfig.StringValue)
   423  
   424  			// change the symlink using the `ln -sfn` command
   425  			err = changeSymLink(dataDir2, path.Join(watchDir, "data"))
   426  			assert.NoError(t, err)
   427  
   428  			t.Logf("New config Location: %v", configFile2)
   429  
   430  			time.Sleep(5 * time.Second)
   431  
   432  			r = section.GetConfig().(*MyComponentConfig)
   433  			secondValue := r.StringValue
   434  			// Make sure values have changed
   435  			assert.NotEqual(t, firstValue, secondValue)
   436  		})
   437  
   438  		t.Run(fmt.Sprintf("[%v] Default variables", provider(config.Options{}).ID()), func(t *testing.T) {
   439  			reg := config.NewRootSection()
   440  			_, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{
   441  				StringValue:  "default value 1",
   442  				StringValue2: "default value 2",
   443  			})
   444  			assert.NoError(t, err)
   445  
   446  			v := provider(config.Options{
   447  				SearchPaths: []string{filepath.Join("testdata", "config.yaml")},
   448  				RootSection: reg,
   449  			})
   450  			key := strings.ToUpper("my-component.str")
   451  			assert.NoError(t, os.Setenv(key, "Set From Env"))
   452  			defer func() { assert.NoError(t, os.Unsetenv(key)) }()
   453  			assert.NoError(t, v.UpdateConfig(context.TODO()))
   454  			r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig)
   455  			assert.Equal(t, "Set From Env", r.StringValue)
   456  			assert.Equal(t, "default value 2", r.StringValue2)
   457  		})
   458  	}
   459  }
   460  
   461  func changeSymLink(targetPath, symLink string) error {
   462  	tmpLink := tempFileName("temp-sym-link-*")
   463  	if runtime.GOOS == "windows" {
   464  		// #nosec G204
   465  		err := exec.Command("mklink", filepath.Clean(tmpLink), filepath.Clean(targetPath)).Run()
   466  		if err != nil {
   467  			return err
   468  		}
   469  
   470  		// #nosec G204
   471  		err = exec.Command("copy", "/l", "/y", filepath.Clean(tmpLink), filepath.Clean(symLink)).Run()
   472  		if err != nil {
   473  			return err
   474  		}
   475  
   476  		// #nosec G204
   477  		return exec.Command("del", filepath.Clean(tmpLink)).Run()
   478  	}
   479  
   480  	//// ln -sfn is not an atomic operation. Under the hood, it first calls the system unlink then symlink calls. During
   481  	//// that, there will be a brief moment when there is no symlink at all.
   482  	// #nosec G204
   483  	return exec.Command("ln", "-sfn", filepath.Clean(targetPath), filepath.Clean(symLink)).Run()
   484  }
   485  
   486  // 1. Create Dir structure:
   487  //    |_ data1
   488  //       |_ config.yaml
   489  //    |_ data (symlink for data1)
   490  //    |_ config.yaml (symlink for data/config.yaml -recursively a symlink of data1/config.yaml)
   491  func newSymlinkedConfigFile(t *testing.T) (watchDir, configFile string, cleanup func()) {
   492  	watchDir, err := ioutil.TempDir("", "config-test-")
   493  	assert.NoError(t, err)
   494  
   495  	dataDir1 := path.Join(watchDir, "data1")
   496  	err = os.Mkdir(dataDir1, os.ModePerm)
   497  	assert.NoError(t, err)
   498  
   499  	realConfigFile := path.Join(dataDir1, "config.yaml")
   500  	t.Logf("Real config file location: %s\n", realConfigFile)
   501  	_, err = populateConfigData(realConfigFile)
   502  	assert.NoError(t, err)
   503  
   504  	cleanup = func() {
   505  		t.Logf("Removing watchDir [%v]", watchDir)
   506  		assert.NoError(t, os.RemoveAll(watchDir))
   507  	}
   508  
   509  	// now, symlink the tm `data1` dir to `data` in the baseDir
   510  	assert.NoError(t, os.Symlink(dataDir1, path.Join(watchDir, "data")))
   511  
   512  	// and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml`
   513  	configFile = path.Join(watchDir, "config.yaml")
   514  	assert.NoError(t, os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile))
   515  
   516  	t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml"))
   517  	return watchDir, configFile, cleanup
   518  }
   519  
   520  func testTypes(accessor accessorCreatorFn) func(t *testing.T) {
   521  	return func(t *testing.T) {
   522  		t.Run("ArrayConfigType", func(t *testing.T) {
   523  			expected := ComplexTypeArray{
   524  				{IntValue: 1},
   525  				{IntValue: 4},
   526  			}
   527  
   528  			runEqualTest(t, accessor, &expected, &ComplexTypeArray{})
   529  		})
   530  
   531  		t.Run("Lists", func(t *testing.T) {
   532  			expected := ConfigWithLists{
   533  				ListOfStuff: []ComplexType{
   534  					{IntValue: 1},
   535  					{IntValue: 4},
   536  				},
   537  			}
   538  
   539  			runEqualTest(t, accessor, &expected, &ConfigWithLists{})
   540  		})
   541  
   542  		t.Run("Maps", func(t *testing.T) {
   543  			expected := ConfigWithMaps{
   544  				MapOfStuff: map[string]ComplexType{
   545  					"item1": {IntValue: 1},
   546  					"item2": {IntValue: 3},
   547  				},
   548  				MapWithoutJSON: map[string]ComplexType{
   549  					"it-1": {IntValue: 5},
   550  				},
   551  			}
   552  
   553  			runEqualTest(t, accessor, &expected, &ConfigWithMaps{})
   554  		})
   555  
   556  		t.Run("JsonUnmarshalableTypes", func(t *testing.T) {
   557  			expected := ConfigWithJSONTypes{
   558  				Duration: config.Duration{
   559  					Duration: time.Second * 10,
   560  				},
   561  			}
   562  
   563  			runEqualTest(t, accessor, &expected, &ConfigWithJSONTypes{})
   564  		})
   565  	}
   566  }
   567  
   568  func runEqualTest(t *testing.T, accessor accessorCreatorFn, expected interface{}, emptyType interface{}) {
   569  	assert.NotPanics(t, func() {
   570  		reflect.TypeOf(expected).Elem()
   571  	}, "expected must be a Pointer type. Instead, it was %v", reflect.TypeOf(expected))
   572  
   573  	assert.Equal(t, reflect.TypeOf(expected), reflect.TypeOf(emptyType))
   574  
   575  	rootSection := config.NewRootSection()
   576  	sectionKey := fmt.Sprintf("rand-key-%v", getRandInt()%2000)
   577  	_, err := rootSection.RegisterSection(sectionKey, emptyType)
   578  	assert.NoError(t, err)
   579  
   580  	m := map[string]interface{}{
   581  		sectionKey: expected,
   582  	}
   583  
   584  	raw, err := yaml.Marshal(m)
   585  	assert.NoError(t, err)
   586  	f := tempFileName("test_type_*.yaml")
   587  	assert.NoError(t, err)
   588  	defer func() { assert.NoError(t, os.Remove(f)) }()
   589  
   590  	assert.NoError(t, ioutil.WriteFile(f, raw, os.ModePerm))
   591  	t.Logf("Generated yaml: %v", string(raw))
   592  	assert.NoError(t, accessor(rootSection, f).UpdateConfig(context.TODO()))
   593  
   594  	res := rootSection.GetSection(sectionKey).GetConfig()
   595  	t.Logf("Expected: %+v", expected)
   596  	t.Logf("Actual: %+v", res)
   597  	assert.True(t, reflect.DeepEqual(res, expected))
   598  }
   599  
   600  func TestAccessor_Integration(t *testing.T) {
   601  	accessorsToTest := make([]accessorCreatorFn, 0, len(providers))
   602  	for _, provider := range providers {
   603  		accessorsToTest = append(accessorsToTest, func(r config.Section, configPath string) config.Accessor {
   604  			return provider(config.Options{
   605  				SearchPaths: []string{configPath},
   606  				RootSection: r,
   607  			})
   608  		})
   609  	}
   610  
   611  	for _, accessor := range accessorsToTest {
   612  		t.Run(fmt.Sprintf(testNameFormatter, accessor(nil, "").ID(), "Types"), testTypes(accessor))
   613  	}
   614  }