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 }