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 }