github.com/awesome-flow/flow@v0.0.3-0.20190918184116-508d75d68a2c/pkg/cfg/repository_test.go (about)

     1  package cfg
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"testing"
     7  
     8  	"github.com/awesome-flow/flow/pkg/cast"
     9  	"github.com/awesome-flow/flow/pkg/types"
    10  )
    11  
    12  func strptr(v string) *string { return &v }
    13  func boolptr(v bool) *bool    { return &v }
    14  func intptr(v int) *int       { return &v }
    15  
    16  func flushMappers() {
    17  	mappersMx.Lock()
    18  	defer mappersMx.Unlock()
    19  	mappers = cast.NewMapperNode()
    20  }
    21  
    22  type queueItem struct {
    23  	k types.Key
    24  	n *node
    25  }
    26  
    27  // Visits all nodes in the repo and maps flattened keys to provider list.
    28  func flattenRepo(repo *Repository) map[string][]Provider {
    29  	res := make(map[string][]Provider)
    30  	queue := make([]queueItem, 0, 1)
    31  	queue = append(queue, queueItem{nil, repo.root})
    32  	var head queueItem
    33  	for len(queue) > 0 {
    34  		head, queue = queue[0], queue[1:]
    35  		if len(head.n.providers) > 0 {
    36  			res[head.k.String()] = head.n.providers
    37  		} else if len(head.n.children) > 0 {
    38  			for k, n := range head.n.children {
    39  				queue = append(queue, queueItem{append(head.k, k), n})
    40  			}
    41  		}
    42  	}
    43  	return res
    44  }
    45  
    46  type TestProv struct {
    47  	val     types.Value
    48  	weight  int
    49  	isSetUp bool
    50  }
    51  
    52  func NewTestProv(val types.Value, weight int) *TestProv {
    53  	return &TestProv{
    54  		val:     val,
    55  		weight:  weight,
    56  		isSetUp: false,
    57  	}
    58  }
    59  
    60  func (tp *TestProv) SetUp(_ *Repository) error {
    61  	tp.isSetUp = true
    62  	return nil
    63  }
    64  
    65  func (tp *TestProv) TearDown(_ *Repository) error { return nil }
    66  
    67  func (tp *TestProv) Get(key types.Key) (*types.KeyValue, bool) {
    68  	return &types.KeyValue{
    69  		Key:   key,
    70  		Value: tp.val,
    71  	}, true
    72  }
    73  
    74  func (tp *TestProv) Weight() int       { return tp.weight }
    75  func (tp *TestProv) Name() string      { return "test" }
    76  func (tp *TestProv) Depends() []string { return []string{} }
    77  
    78  func TestGetSingleProvider(t *testing.T) {
    79  	repo := NewRepository()
    80  	prov := NewTestProv(42, 10)
    81  	key := types.NewKey("foo.bar.baz")
    82  	repo.RegisterKey(key, prov)
    83  
    84  	tests := []struct {
    85  		key types.Key
    86  		ok  bool
    87  		val types.Value
    88  	}{
    89  		{
    90  			key: types.NewKey("foo"),
    91  			ok:  true,
    92  			val: map[string]types.Value{
    93  				"bar": map[string]types.Value{
    94  					"baz": 42,
    95  				},
    96  			},
    97  		},
    98  		{
    99  			key: types.NewKey("foo.bar"),
   100  			ok:  true,
   101  			val: map[string]types.Value{"baz": 42},
   102  		},
   103  		{
   104  			key: types.NewKey("foo.bar.baz"),
   105  			ok:  true,
   106  			val: 42,
   107  		},
   108  		{
   109  			key: types.NewKey("foo.bar.baz.boo"),
   110  			ok:  false,
   111  			val: nil,
   112  		},
   113  	}
   114  
   115  	for _, testCase := range tests {
   116  		val, ok := repo.Get(testCase.key)
   117  		if ok != testCase.ok {
   118  			t.Fatalf("Unexpected key %q lookup result: want %t, got: %t", testCase.key, testCase.ok, ok)
   119  		}
   120  		if !ok {
   121  			continue
   122  		}
   123  		if !reflect.DeepEqual(val, testCase.val) {
   124  			t.Fatalf("Unexpected value for key %q: want %v, got %v", testCase.key, testCase.val, val)
   125  		}
   126  	}
   127  }
   128  
   129  func TestTrioProviderSingleKey(t *testing.T) {
   130  	repo := NewRepository()
   131  	prov1 := NewTestProv(10, 10)
   132  	prov2 := NewTestProv(20, 20)
   133  	prov3 := NewTestProv(30, 30)
   134  
   135  	key := types.NewKey("foo.bar.baz")
   136  	repo.RegisterKey(key, prov1)
   137  	repo.RegisterKey(key, prov2)
   138  	repo.RegisterKey(key, prov3)
   139  
   140  	tests := []struct {
   141  		key types.Key
   142  		ok  bool
   143  		val types.Value
   144  	}{
   145  		{
   146  			key: types.NewKey("foo"),
   147  			ok:  true,
   148  			val: map[string]types.Value{
   149  				"bar": map[string]types.Value{
   150  					"baz": 30,
   151  				},
   152  			},
   153  		},
   154  		{
   155  			key: types.NewKey("foo.bar"),
   156  			ok:  true,
   157  			val: map[string]types.Value{"baz": 30},
   158  		},
   159  		{
   160  			key: types.NewKey("foo.bar.baz"),
   161  			ok:  true,
   162  			val: 30,
   163  		},
   164  		{
   165  			key: types.NewKey("foo.bar.baz.boo"),
   166  			ok:  false,
   167  			val: nil,
   168  		},
   169  	}
   170  
   171  	for _, testCase := range tests {
   172  		val, ok := repo.Get(testCase.key)
   173  		if ok != testCase.ok {
   174  			t.Fatalf("Unexpected key %q lookup result: want %t, got: %t", testCase.key, testCase.ok, ok)
   175  		}
   176  
   177  		if !reflect.DeepEqual(val, testCase.val) {
   178  			t.Fatalf("Unexpected value for key %q: want %#v, got %#v", testCase.key, testCase.val, val)
   179  		}
   180  	}
   181  }
   182  
   183  func TestTrioProviderThreeKeys(t *testing.T) {
   184  	repo := NewRepository()
   185  	prov1 := NewTestProv(10, 10)
   186  	prov2 := NewTestProv(20, 20)
   187  	prov3 := NewTestProv(30, 30)
   188  
   189  	key1 := types.NewKey("k1.k1.k1")
   190  	key2 := types.NewKey("k2.k2.k2")
   191  	key3 := types.NewKey("k3.k3.k3")
   192  	repo.RegisterKey(key1, prov1)
   193  	repo.RegisterKey(key2, prov2)
   194  	repo.RegisterKey(key3, prov3)
   195  
   196  	tests := []struct {
   197  		key types.Key
   198  		ok  bool
   199  		val types.Value
   200  	}{
   201  		{
   202  			key: types.NewKey("k1.k1.k1"),
   203  			ok:  true,
   204  			val: prov1.val,
   205  		},
   206  		{
   207  			key: types.NewKey("k2.k2.k2"),
   208  			ok:  true,
   209  			val: prov2.val,
   210  		},
   211  		{
   212  			key: types.NewKey("k3.k3.k3"),
   213  			ok:  true,
   214  			val: prov3.val,
   215  		},
   216  		{
   217  			key: types.NewKey(""),
   218  			ok:  false,
   219  			val: nil,
   220  		},
   221  		{
   222  			key: types.NewKey("k1.k2.k3"),
   223  			ok:  false,
   224  			val: nil,
   225  		},
   226  		{
   227  			key: types.NewKey("k1"),
   228  			ok:  true,
   229  			val: map[string]types.Value{
   230  				"k1": map[string]types.Value{
   231  					"k1": prov1.val,
   232  				},
   233  			},
   234  		},
   235  		{
   236  			key: types.NewKey("k2.k2"),
   237  			ok:  true,
   238  			val: map[string]types.Value{"k2": prov2.val},
   239  		},
   240  		{
   241  			key: types.NewKey("k3.k3.k3.k3"),
   242  			ok:  false,
   243  			val: nil,
   244  		},
   245  	}
   246  
   247  	for _, testCase := range tests {
   248  		val, ok := repo.Get(testCase.key)
   249  		if ok != testCase.ok {
   250  			t.Fatalf("Unexpected key %q lookup result: want %t, got: %t", testCase.key, testCase.ok, ok)
   251  		}
   252  
   253  		if !reflect.DeepEqual(val, testCase.val) {
   254  			t.Fatalf("Unexpected value for key %q: want %v, got %v", testCase.key, testCase.val, val)
   255  		}
   256  	}
   257  }
   258  
   259  func TestTrioProviderNestingKey(t *testing.T) {
   260  	repo := NewRepository()
   261  	prov1 := NewTestProv(10, 10)
   262  	prov2 := NewTestProv(20, 20)
   263  	prov3 := NewTestProv(30, 30)
   264  
   265  	key1 := types.NewKey("foo")
   266  	key2 := types.NewKey("foo.bar")
   267  	key3 := types.NewKey("foo.bar.baz")
   268  	repo.RegisterKey(key1, prov1)
   269  	repo.RegisterKey(key2, prov2)
   270  	repo.RegisterKey(key3, prov3)
   271  
   272  	tests := []struct {
   273  		key types.Key
   274  		ok  bool
   275  		val types.Value
   276  	}{
   277  		{
   278  			key: key1,
   279  			ok:  true,
   280  			val: prov1.val,
   281  		},
   282  		{
   283  			key: key2,
   284  			ok:  true,
   285  			val: prov2.val,
   286  		},
   287  		{
   288  			key: key3,
   289  			ok:  true,
   290  			val: prov3.val,
   291  		},
   292  		{
   293  			key: types.NewKey(""),
   294  			ok:  false,
   295  			val: nil,
   296  		},
   297  		{
   298  			key: types.NewKey("foo.bar.baz.boo"),
   299  			ok:  false,
   300  			val: nil,
   301  		},
   302  	}
   303  
   304  	for _, testCase := range tests {
   305  		val, ok := repo.Get(testCase.key)
   306  		if ok != testCase.ok {
   307  			t.Fatalf("Unexpected key %q lookup result: want %t, got: %t", testCase.key, testCase.ok, ok)
   308  		}
   309  
   310  		if !reflect.DeepEqual(val, testCase.val) {
   311  			t.Fatalf("Unexpected value for key %q: want %v, got %v", testCase.key, testCase.val, val)
   312  		}
   313  	}
   314  }
   315  
   316  func Test_getAll(t *testing.T) {
   317  	repo := NewRepository()
   318  	n := &node{
   319  		children: map[string]*node{
   320  			"foo": &node{
   321  				children: map[string]*node{
   322  					"baz": &node{
   323  						providers: []Provider{
   324  							NewTestProv(10, 10),
   325  							NewTestProv(5, 5),
   326  						},
   327  					},
   328  				},
   329  			},
   330  			"bar": &node{
   331  				providers: []Provider{
   332  					NewTestProv(20, 20),
   333  				},
   334  			},
   335  		},
   336  	}
   337  	want := map[string]types.Value{
   338  		"foo": map[string]types.Value{
   339  			"baz": 10,
   340  		},
   341  		"bar": 20,
   342  	}
   343  	got := n.getAll(repo, nil).Value
   344  	if !reflect.DeepEqual(want, got) {
   345  		t.Fatalf("Unexpcted traversal value: want: %#v, got: %#v", want, got)
   346  	}
   347  }
   348  
   349  type admincfg struct {
   350  	enabled bool
   351  }
   352  
   353  type systemcfg struct {
   354  	maxproc  int
   355  	admincfg *admincfg
   356  }
   357  
   358  type admincfgmapper struct{}
   359  
   360  var _ cast.Mapper = (*admincfgmapper)(nil)
   361  
   362  func (acm *admincfgmapper) Map(kv *types.KeyValue) (*types.KeyValue, error) {
   363  	if vmap, ok := kv.Value.(map[string]types.Value); ok {
   364  		res := &admincfg{}
   365  		if enabled, ok := vmap["enabled"]; ok {
   366  			res.enabled = enabled.(bool)
   367  		}
   368  		return &types.KeyValue{Key: kv.Key, Value: res}, nil
   369  	}
   370  	return nil, fmt.Errorf("Conversion to admincfg failed for key: %q value: %#v", kv.Key.String(), kv.Value)
   371  }
   372  
   373  type systemcfgmapper struct{}
   374  
   375  var _ cast.Mapper = (*systemcfgmapper)(nil)
   376  
   377  func (scm *systemcfgmapper) Map(kv *types.KeyValue) (*types.KeyValue, error) {
   378  	if vmap, ok := kv.Value.(map[string]types.Value); ok {
   379  		res := &systemcfg{}
   380  		if ac, ok := vmap["admin"]; ok {
   381  			if acptr, ok := ac.(*admincfg); ok {
   382  				res.admincfg = acptr
   383  			} else {
   384  				return nil, fmt.Errorf("Wrong format for admincfg value: %#v", ac)
   385  			}
   386  		}
   387  		if maxproc, ok := vmap["maxprocs"]; ok {
   388  			res.maxproc = maxproc.(int)
   389  		}
   390  		return &types.KeyValue{Key: kv.Key, Value: res}, nil
   391  	}
   392  	return nil, fmt.Errorf("Conversion to systemcfg failed for key: %q value: %#v", kv.Key.String(), kv.Value)
   393  }
   394  
   395  func Test_DefineSchema_Primitive(t *testing.T) {
   396  	repoSchema := cast.Schema(map[string]cast.Schema{
   397  		"system": map[string]cast.Schema{
   398  			"__self__": nil,
   399  			"maxproc":  cast.ToInt,
   400  			"admin": map[string]cast.Schema{
   401  				"__self__": nil,
   402  				"enabled":  cast.ToBool,
   403  			},
   404  		},
   405  	})
   406  
   407  	tests := []struct {
   408  		name     string
   409  		input    map[string]types.Value
   410  		expected map[string]types.Value
   411  	}{
   412  		{
   413  			name: "No casting",
   414  			input: map[string]types.Value{
   415  				"system.maxproc":       4,
   416  				"system.admin.enabled": true,
   417  			},
   418  			expected: map[string]types.Value{
   419  				"system.maxproc":       4,
   420  				"system.admin.enabled": true,
   421  				"system.admin": map[string]types.Value{
   422  					"enabled": true,
   423  				},
   424  				"system": map[string]types.Value{
   425  					"maxproc": 4,
   426  					"admin": map[string]types.Value{
   427  						"enabled": true,
   428  					},
   429  				},
   430  			},
   431  		},
   432  		{
   433  			name: "Casting from all-strings",
   434  			input: map[string]types.Value{
   435  				"system.maxproc":       "4",
   436  				"system.admin.enabled": "true",
   437  			},
   438  			expected: map[string]types.Value{
   439  				"system.maxproc":       4,
   440  				"system.admin.enabled": true,
   441  				"system.admin": map[string]types.Value{
   442  					"enabled": true,
   443  				},
   444  				"system": map[string]types.Value{
   445  					"maxproc": 4,
   446  					"admin": map[string]types.Value{
   447  						"enabled": true,
   448  					},
   449  				},
   450  			},
   451  		},
   452  		{
   453  			name: "Casting from ptrs",
   454  			input: map[string]types.Value{
   455  				"system.maxproc":       intptr(4),
   456  				"system.admin.enabled": boolptr(true),
   457  			},
   458  			expected: map[string]types.Value{
   459  				"system.maxproc":       4,
   460  				"system.admin.enabled": true,
   461  				"system.admin": map[string]types.Value{
   462  					"enabled": true,
   463  				},
   464  				"system": map[string]types.Value{
   465  					"maxproc": 4,
   466  					"admin": map[string]types.Value{
   467  						"enabled": true,
   468  					},
   469  				},
   470  			},
   471  		},
   472  	}
   473  
   474  	t.Parallel()
   475  
   476  	for _, testCase := range tests {
   477  		t.Run(testCase.name, func(t *testing.T) {
   478  			repo := NewRepository()
   479  			repo.DefineSchema(repoSchema)
   480  
   481  			for path, value := range testCase.input {
   482  				repo.RegisterKey(types.NewKey(path), NewTestProv(value, DefaultWeight))
   483  			}
   484  
   485  			for lookupPath, expVal := range testCase.expected {
   486  				gotVal, gotOk := repo.Get(types.NewKey(lookupPath))
   487  				if !gotOk {
   488  					t.Fatalf("Expected lookup for key %q to find a value, none returned", lookupPath)
   489  				}
   490  				if !reflect.DeepEqual(gotVal, expVal) {
   491  					t.Fatalf("Unexpected value returned by lookup for key %q: got: %#v, want: %#v", lookupPath, gotVal, expVal)
   492  				}
   493  			}
   494  		})
   495  	}
   496  }
   497  
   498  func Test_DefineSchema_Struct(t *testing.T) {
   499  	repoSchema := cast.Schema(map[string]cast.Schema{
   500  		"system": map[string]cast.Schema{
   501  			"__self__": &systemcfgmapper{},
   502  			"maxprocs": cast.ToInt,
   503  			"admin": map[string]cast.Schema{
   504  				"__self__": &admincfgmapper{},
   505  				"enabled":  cast.ToBool,
   506  			},
   507  		},
   508  	})
   509  
   510  	tests := []struct {
   511  		name     string
   512  		input    map[string]types.Value
   513  		expected map[string]types.Value
   514  	}{
   515  		{
   516  			name: "No casting",
   517  			input: map[string]types.Value{
   518  				"system.maxprocs":      4,
   519  				"system.admin.enabled": true,
   520  			},
   521  			expected: map[string]types.Value{
   522  				"system.maxprocs":      4,
   523  				"system.admin.enabled": true,
   524  				"system.admin":         &admincfg{enabled: true},
   525  				"system":               &systemcfg{admincfg: &admincfg{enabled: true}, maxproc: 4},
   526  			},
   527  		},
   528  		{
   529  			name: "Casting from all-strings",
   530  			input: map[string]types.Value{
   531  				"system.maxprocs":      "4",
   532  				"system.admin.enabled": "true",
   533  			},
   534  			expected: map[string]types.Value{
   535  				"system.maxprocs":      4,
   536  				"system.admin.enabled": true,
   537  				"system.admin":         &admincfg{enabled: true},
   538  				"system":               &systemcfg{admincfg: &admincfg{enabled: true}, maxproc: 4},
   539  			},
   540  		},
   541  		{
   542  			name: "Casting from ptrs",
   543  			input: map[string]types.Value{
   544  				"system.maxprocs":      intptr(4),
   545  				"system.admin.enabled": boolptr(true),
   546  			},
   547  			expected: map[string]types.Value{
   548  				"system.maxprocs":      4,
   549  				"system.admin.enabled": true,
   550  				"system.admin":         &admincfg{enabled: true},
   551  				"system":               &systemcfg{admincfg: &admincfg{enabled: true}, maxproc: 4},
   552  			},
   553  		},
   554  	}
   555  
   556  	t.Parallel()
   557  
   558  	for _, testCase := range tests {
   559  		t.Run(testCase.name, func(t *testing.T) {
   560  			repo := NewRepository()
   561  			repo.DefineSchema(repoSchema)
   562  
   563  			for path, value := range testCase.input {
   564  				repo.RegisterKey(types.NewKey(path), NewTestProv(value, DefaultWeight))
   565  			}
   566  
   567  			for lookupPath, expVal := range testCase.expected {
   568  				gotVal, gotOk := repo.Get(types.NewKey(lookupPath))
   569  				if !gotOk {
   570  					t.Fatalf("Expected lookup for key %q to find a value, none returned", lookupPath)
   571  				}
   572  				if !reflect.DeepEqual(gotVal, expVal) {
   573  					t.Fatalf("Unexpected value returned by lookup for key %q: got: %#v, want: %#v", lookupPath, gotVal, expVal)
   574  				}
   575  			}
   576  		})
   577  	}
   578  }
   579  
   580  func TestExplain(t *testing.T) {
   581  	repo := NewRepository()
   582  	prov1 := NewTestProv("foo", 10)
   583  	prov2 := NewTestProv("bar", 20)
   584  
   585  	repo.RegisterKey(types.NewKey("foo.bar.1"), prov1)
   586  	repo.RegisterKey(types.NewKey("foo.baz.2"), prov2)
   587  	repo.RegisterKey(types.NewKey("foo.moo.3"), prov1)
   588  	repo.RegisterKey(types.NewKey("foo.moo.3"), prov2)
   589  
   590  	got := repo.Explain()
   591  
   592  	want := map[string]interface{}{
   593  		"foo": map[string]interface{}{
   594  			"bar": map[string]interface{}{
   595  				"1": map[string]interface{}{
   596  					"__value__": []map[string]interface{}{
   597  						{"provider_name": "test", "provider_weight": 10, "value": "foo"},
   598  					},
   599  				},
   600  			},
   601  			"baz": map[string]interface{}{
   602  				"2": map[string]interface{}{
   603  					"__value__": []map[string]interface{}{
   604  						{"provider_name": "test", "provider_weight": 20, "value": "bar"},
   605  					},
   606  				},
   607  			},
   608  			"moo": map[string]interface{}{
   609  				"3": map[string]interface{}{
   610  					"__value__": []map[string]interface{}{
   611  						{"provider_name": "test", "provider_weight": 20, "value": "bar"},
   612  						{"provider_name": "test", "provider_weight": 10, "value": "foo"},
   613  					},
   614  				},
   615  			},
   616  		},
   617  	}
   618  
   619  	if !reflect.DeepEqual(want, got) {
   620  		t.Fatalf("repo.Explain() = %#v, want: %#v", got, want)
   621  	}
   622  }