github.com/ardanlabs/conf/v2@v2.2.0/conf_test.go (about) 1 package conf_test 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/ardanlabs/conf/v2" 11 "github.com/ardanlabs/conf/v2/yaml" 12 "github.com/google/go-cmp/cmp" 13 ) 14 15 const ( 16 success = "\u2713" 17 failed = "\u2717" 18 ) 19 20 // ============================================================================= 21 22 // CustomValue provides support for testing a custom value. 23 type CustomValue struct { 24 something string 25 } 26 27 // Set implements the Setter interface 28 func (c *CustomValue) Set(data string) error { 29 *c = CustomValue{something: fmt.Sprintf("@%s@", data)} 30 return nil 31 } 32 33 // String implements the Stringer interface 34 func (c CustomValue) String() string { 35 return c.something 36 } 37 38 // Equal implements the Equal "interface" for go-cmp 39 func (c CustomValue) Equal(o CustomValue) bool { 40 return c.something == o.something 41 } 42 43 // ============================================================================= 44 45 type ip struct { 46 Name string `conf:"default:localhost,env:IP_NAME_VAR"` 47 IP string `conf:"default:127.0.0.0"` 48 Endpoints []string `conf:"default:127.0.0.1:200;127.0.0.1:829"` 49 } 50 type Embed struct { 51 Name string `conf:"default:bill"` 52 Duration time.Duration `conf:"default:1s,flag:e-dur,short:d"` 53 } 54 type config struct { 55 AnInt int `conf:"default:9"` 56 AString string `conf:"default:B,short:s"` 57 Bool bool 58 Skip string `conf:"-"` 59 IP ip 60 DebugHost string `conf:"default:http://user:password@0.0.0.0:4000,mask"` 61 Password string `conf:"default:password,mask"` 62 Custom CustomValue `conf:"default:hello"` 63 Embed 64 } 65 66 // ============================================================================= 67 func TestRequired(t *testing.T) { 68 t.Logf("\tTest: %d\tWhen required values are missing.", 1) 69 { 70 f := func(t *testing.T) { 71 os.Args = []string{"conf.test"} 72 var cfg struct { 73 TestInt int `conf:"required, default:1"` 74 TestString string 75 TestBool bool 76 } 77 _, err := conf.Parse("TEST", &cfg) 78 if err == nil { 79 t.Fatalf("\t%s\tShould fail for missing required value.", failed) 80 } 81 t.Logf("\t%s\tShould fail for missing required value : %s", success, err) 82 } 83 t.Run("required-missing-value", f) 84 } 85 86 t.Logf("\tTest: %d\tWhen struct has no fields.", 2) 87 { 88 f := func(t *testing.T) { 89 os.Args = []string{"conf.test"} 90 var cfg struct { 91 testInt int `conf:"required, default:1"` 92 testString string 93 testBool bool 94 } 95 _, err := conf.Parse("TEST", &cfg) 96 if err == nil { 97 t.Fatalf("\t%s\tShould fail for struct with no exported fields.", failed) 98 } 99 t.Logf("\t%s\tShould fail for struct with no exported fields : %s", success, err) 100 } 101 t.Run("struct-missing-fields", f) 102 } 103 104 t.Logf("\tTest: %d\tWhen required values exist and are passed on args.", 3) 105 { 106 f := func(t *testing.T) { 107 os.Args = []string{"conf.test", "--test-int", "1"} 108 109 var cfg struct { 110 TestInt int `conf:"required, default:1"` 111 TestString string 112 TestBool bool 113 } 114 _, err := conf.Parse("TEST", &cfg) 115 if err != nil { 116 t.Fatalf("\t%s\tShould have parsed the required field on args : %s", failed, err) 117 } 118 t.Logf("\t%s\tShould have parsed the required field on args.", success) 119 } 120 t.Run("required-existing-fields-args", f) 121 } 122 123 t.Logf("\tTest: %d\tWhen required values exist and are passed on env.", 4) 124 { 125 f := func(t *testing.T) { 126 os.Args = []string{"conf.test"} 127 os.Setenv("TEST_TEST_INT", "1") 128 129 var cfg struct { 130 TestInt int `conf:"required, default:1"` 131 TestString string 132 TestBool bool 133 } 134 _, err := conf.Parse("TEST", &cfg) 135 if err != nil { 136 t.Fatalf("\t%s\tShould have parsed the required field on Env : %s", failed, err) 137 } 138 t.Logf("\t%s\tShould have parsed the required field on Env.", success) 139 } 140 t.Run("required-existing-fields-args", f) 141 } 142 } 143 144 func TestParse(t *testing.T) { 145 tests := []struct { 146 name string 147 envs map[string]string 148 args []string 149 want config 150 }{ 151 { 152 "default", 153 nil, 154 nil, 155 config{9, "B", false, "", ip{"localhost", "127.0.0.0", []string{"127.0.0.1:200", "127.0.0.1:829"}}, "http://user:password@0.0.0.0:4000", "password", CustomValue{something: "@hello@"}, Embed{"bill", time.Second}}, 156 }, 157 { 158 "env", 159 map[string]string{"TEST_AN_INT": "1", "TEST_A_STRING": "s", "TEST_BOOL": "TRUE", "TEST_SKIP": "SKIP", "TEST_IP_NAME_VAR": "local", "TEST_DEBUG_HOST": "http://bill:gopher@0.0.0.0:4000", "TEST_PASSWORD": "gopher", "TEST_NAME": "andy", "TEST_DURATION": "1m"}, 160 nil, 161 config{1, "s", true, "", ip{"local", "127.0.0.0", []string{"127.0.0.1:200", "127.0.0.1:829"}}, "http://bill:gopher@0.0.0.0:4000", "gopher", CustomValue{something: "@hello@"}, Embed{"andy", time.Minute}}, 162 }, 163 { 164 "flag", 165 nil, 166 []string{"conf.test", "--an-int", "1", "-s", "s", "--bool", "--skip", "skip", "--ip-name", "local", "--debug-host", "http://bill:gopher@0.0.0.0:4000", "--password", "gopher", "--name", "andy", "--e-dur", "1m"}, 167 config{1, "s", true, "", ip{"local", "127.0.0.0", []string{"127.0.0.1:200", "127.0.0.1:829"}}, "http://bill:gopher@0.0.0.0:4000", "gopher", CustomValue{something: "@hello@"}, Embed{"andy", time.Minute}}, 168 }, 169 { 170 "multi", 171 map[string]string{"TEST_A_STRING": "s", "TEST_BOOL": "TRUE", "TEST_IP_NAME_VAR": "local", "TEST_DEBUG_HOST": "http://bill:gopher@0.0.0.0:4000", "TEST_PASSWORD": "gopher", "TEST_NAME": "andy", "TEST_DURATION": "1m"}, 172 []string{"conf.test", "--an-int", "2", "--bool", "--skip", "skip", "--name", "jack", "-d", "1ms"}, 173 config{2, "s", true, "", ip{"local", "127.0.0.0", []string{"127.0.0.1:200", "127.0.0.1:829"}}, "http://bill:gopher@0.0.0.0:4000", "gopher", CustomValue{something: "@hello@"}, Embed{"jack", time.Millisecond}}, 174 }, 175 } 176 177 t.Log("Given the need to parse basic configuration.") 178 { 179 for i, tt := range tests { 180 t.Logf("\tTest: %d\tWhen checking with arguments %v", i, tt.args) 181 { 182 os.Clearenv() 183 for k, v := range tt.envs { 184 os.Setenv(k, v) 185 } 186 187 f := func(t *testing.T) { 188 os.Args = tt.args 189 190 var cfg config 191 if _, err := conf.Parse("TEST", &cfg); err != nil { 192 t.Fatalf("\t%s\tShould be able to Parse arguments : %s.", failed, err) 193 } 194 t.Logf("\t%s\tShould be able to Parse arguments.", success) 195 196 if diff := cmp.Diff(tt.want, cfg); diff != "" { 197 t.Fatalf("\t%s\tShould have properly initialized struct value\n%s", failed, diff) 198 } 199 t.Logf("\t%s\tShould have properly initialized struct value.", success) 200 } 201 202 t.Run(tt.name, f) 203 } 204 } 205 } 206 } 207 208 func TestParseEmptyNamespace(t *testing.T) { 209 tests := []struct { 210 name string 211 envs map[string]string 212 args []string 213 want config 214 }{ 215 { 216 "env", 217 map[string]string{"AN_INT": "1", "A_STRING": "s", "BOOL": "TRUE", "SKIP": "SKIP", "IP_NAME_VAR": "local", "DEBUG_HOST": "http://bill:gopher@0.0.0.0:4000", "PASSWORD": "gopher", "NAME": "andy", "DURATION": "1m"}, 218 nil, 219 config{1, "s", true, "", ip{"local", "127.0.0.0", []string{"127.0.0.1:200", "127.0.0.1:829"}}, "http://bill:gopher@0.0.0.0:4000", "gopher", CustomValue{something: "@hello@"}, Embed{"andy", time.Minute}}, 220 }, 221 } 222 223 t.Log("Given the need to parse basic configuration.") 224 { 225 for i, tt := range tests { 226 t.Logf("\tTest: %d\tWhen checking with arguments %v", i, tt.args) 227 { 228 os.Clearenv() 229 for k, v := range tt.envs { 230 os.Setenv(k, v) 231 } 232 233 f := func(t *testing.T) { 234 os.Args = tt.args 235 236 var cfg config 237 if _, err := conf.Parse("", &cfg); err != nil { 238 t.Fatalf("\t%s\tShould be able to Parse arguments : %s.", failed, err) 239 } 240 t.Logf("\t%s\tShould be able to Parse arguments.", success) 241 242 if diff := cmp.Diff(tt.want, cfg); diff != "" { 243 t.Fatalf("\t%s\tShould have properly initialized struct value\n%s", failed, diff) 244 } 245 t.Logf("\t%s\tShould have properly initialized struct value.", success) 246 } 247 248 t.Run(tt.name, f) 249 } 250 } 251 } 252 } 253 254 func TestParse_Args(t *testing.T) { 255 t.Log("Given the need to capture remaining command line arguments after flags.") 256 { 257 type configArgs struct { 258 Port int 259 Args conf.Args 260 } 261 262 want := configArgs{ 263 Port: 9000, 264 Args: conf.Args{"migrate", "seed"}, 265 } 266 267 os.Args = []string{"conf.test", "--port", "9000", "migrate", "seed"} 268 269 var cfg configArgs 270 if _, err := conf.Parse("TEST", &cfg); err != nil { 271 t.Fatalf("\t%s\tShould be able to Parse arguments : %s.", failed, err) 272 } 273 t.Logf("\t%s\tShould be able to Parse arguments.", success) 274 275 if diff := cmp.Diff(want, cfg); diff != "" { 276 t.Fatalf("\t%s\tShould have properly initialized struct value\n%s", failed, diff) 277 } 278 t.Logf("\t%s\tShould have properly initialized struct value.", success) 279 } 280 } 281 282 func TestErrors(t *testing.T) { 283 t.Log("Given the need to validate errors that can occur with Parse.") 284 { 285 t.Logf("\tTest: %d\tWhen passing bad values to Parse.", 0) 286 { 287 f := func(t *testing.T) { 288 os.Args = []string{"conf.test"} 289 290 var cfg struct { 291 TestInt int 292 TestString string 293 TestBool bool 294 } 295 _, err := conf.Parse("TEST", cfg) 296 if err == nil { 297 t.Fatalf("\t%s\tShould NOT be able to accept a value by value.", failed) 298 } 299 t.Logf("\t%s\tShould NOT be able to accept a value by value : %s", success, err) 300 } 301 t.Run("not-by-ref", f) 302 303 f = func(t *testing.T) { 304 os.Args = []string{"conf.test"} 305 306 var cfg []string 307 _, err := conf.Parse("TEST", &cfg) 308 if err == nil { 309 t.Fatalf("\t%s\tShould NOT be able to pass anything but a struct value.", failed) 310 } 311 t.Logf("\t%s\tShould NOT be able to pass anything but a struct value : %s", success, err) 312 } 313 t.Run("not-struct-value", f) 314 } 315 316 t.Logf("\tTest: %d\tWhen bad tags to Parse.", 1) 317 { 318 f := func(t *testing.T) { 319 os.Args = []string{"conf.test"} 320 321 var cfg struct { 322 TestInt int `conf:"default:"` 323 TestString string 324 TestBool bool 325 } 326 _, err := conf.Parse("TEST", &cfg) 327 if err == nil { 328 t.Fatalf("\t%s\tShould NOT be able to accept tag missing value.", failed) 329 } 330 t.Logf("\t%s\tShould NOT be able to accept tag missing value : %s", success, err) 331 } 332 t.Run("tag-missing-value", f) 333 334 f = func(t *testing.T) { 335 os.Args = []string{"conf.test"} 336 337 var cfg struct { 338 TestInt int `conf:"short:ab"` 339 TestString string 340 TestBool bool 341 } 342 _, err := conf.Parse("TEST", &cfg) 343 if err == nil { 344 t.Fatalf("\t%s\tShould NOT be able to accept invalid short tag.", failed) 345 } 346 t.Logf("\t%s\tShould NOT be able to accept invalid short tag : %s", success, err) 347 } 348 t.Run("tag-bad-short", f) 349 } 350 } 351 } 352 353 var withNamespace = `Usage: conf.test [options] [arguments] 354 355 OPTIONS 356 --an-int/$TEST_AN_INT <int> (default: 9) 357 --a-string/-s/$TEST_A_STRING <string> (default: B) 358 --bool/$TEST_BOOL <bool> 359 --ip-name/$TEST_IP_NAME_VAR <string> (default: localhost) 360 --ip-ip/$TEST_IP_IP <string> (default: 127.0.0.0) 361 --ip-endpoints/$TEST_IP_ENDPOINTS <string>,[string...] (default: 127.0.0.1:200;127.0.0.1:829) 362 --debug-host/$TEST_DEBUG_HOST <string> (default: http://user:password@0.0.0.0:4000) 363 --password/$TEST_PASSWORD <string> (default: password) 364 --custom/$TEST_CUSTOM <value> (default: hello) 365 --name/$TEST_NAME <string> (default: bill) 366 --e-dur/-d/$TEST_DURATION <duration> (default: 1s) 367 --help/-h 368 display this help message` 369 370 var emptyNamespace = `Usage: conf.test [options] [arguments] 371 372 OPTIONS 373 --an-int/$AN_INT <int> (default: 9) 374 --a-string/-s/$A_STRING <string> (default: B) 375 --bool/$BOOL <bool> 376 --ip-name/$IP_NAME_VAR <string> (default: localhost) 377 --ip-ip/$IP_IP <string> (default: 127.0.0.0) 378 --ip-endpoints/$IP_ENDPOINTS <string>,[string...] (default: 127.0.0.1:200;127.0.0.1:829) 379 --debug-host/$DEBUG_HOST <string> (default: http://user:password@0.0.0.0:4000) 380 --password/$PASSWORD <string> (default: password) 381 --custom/$CUSTOM <value> (default: hello) 382 --name/$NAME <string> (default: bill) 383 --e-dur/-d/$DURATION <duration> (default: 1s) 384 --help/-h 385 display this help message` 386 387 var withNamespaceOptions = `Usage: conf.test [options] [arguments] 388 389 OPTIONS 390 --port/$TEST_PORT <int> 391 --help/-h 392 display this help message` 393 394 var emptyNamespaceOptions = `Usage: conf.test [options] [arguments] 395 396 OPTIONS 397 --port/$PORT <int> 398 --help/-h 399 display this help message` 400 401 func TestUsage(t *testing.T) { 402 tests := []struct { 403 name string 404 namespace string 405 envs map[string]string 406 want string 407 options string 408 }{ 409 { 410 name: "with-namespace", 411 namespace: "TEST", 412 envs: map[string]string{"TEST_AN_INT": "1", "TEST_A_STRING": "s", "TEST_BOOL": "TRUE", "TEST_SKIP": "SKIP", "TEST_IP_NAME_VAR": "local", "TEST_NAME": "andy", "TEST_DURATION": "1m"}, 413 want: withNamespace, 414 options: withNamespaceOptions, 415 }, 416 { 417 name: "empty-namespace", 418 namespace: "", 419 envs: map[string]string{"AN_INT": "1", "A_STRING": "s", "BOOL": "TRUE", "SKIP": "SKIP", "IP_NAME_VAR": "local", "NAME": "andy", "DURATION": "1m"}, 420 want: emptyNamespace, 421 options: emptyNamespaceOptions, 422 }, 423 } 424 425 t.Log("Given the need validate usage output.") 426 { 427 for testID, tt := range tests { 428 f := func(t *testing.T) { 429 t.Logf("\tTest: %d\tWhen testing %s", testID, tt.name) 430 { 431 os.Clearenv() 432 for k, v := range tt.envs { 433 os.Setenv(k, v) 434 } 435 436 os.Args = []string{"conf.test"} 437 438 var cfg config 439 if _, err := conf.Parse(tt.namespace, &cfg); err != nil { 440 fmt.Print(err) 441 return 442 } 443 444 got, err := conf.UsageInfo(tt.namespace, &cfg) 445 if err != nil { 446 fmt.Print(err) 447 return 448 } 449 450 got = strings.TrimRight(got, " \n") 451 t.Log(got) 452 gotS := strings.Split(got, "\n") 453 wantS := strings.Split(tt.want, "\n") 454 if diff := cmp.Diff(gotS, wantS); diff != "" { 455 t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed) 456 t.Log(diff) 457 } 458 t.Logf("\t%s\tShould match byte for byte the output.", success) 459 } 460 461 t.Logf("\tTest: %d\tWhen using a struct with arguments.", 1) 462 { 463 var cfg struct { 464 Port int 465 Args conf.Args 466 } 467 468 got, err := conf.UsageInfo(tt.namespace, &cfg) 469 if err != nil { 470 fmt.Print(err) 471 return 472 } 473 474 got = strings.TrimRight(got, " \n") 475 gotS := strings.Split(got, "\n") 476 wantS := strings.Split(tt.options, "\n") 477 if diff := cmp.Diff(gotS, wantS); diff != "" { 478 t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed) 479 t.Log(diff) 480 } 481 t.Logf("\t%s\tShould match byte for byte the output.", success) 482 } 483 } 484 485 t.Run(tt.name, f) 486 } 487 } 488 } 489 490 func ExampleString() { 491 tt := struct { 492 name string 493 envs map[string]string 494 }{ 495 name: "one-example", 496 envs: map[string]string{"TEST_AN_INT": "1", "TEST_S": "s", "TEST_BOOL": "TRUE", "TEST_SKIP": "SKIP", "TEST_IP_NAME": "local", "TEST_NAME": "andy", "TEST_DURATION": "1m"}, 497 } 498 499 os.Clearenv() 500 for k, v := range tt.envs { 501 os.Setenv(k, v) 502 } 503 504 os.Args = []string{"conf.test"} 505 506 var cfg config 507 if _, err := conf.Parse("TEST", &cfg); err != nil { 508 fmt.Print(err) 509 return 510 } 511 512 out, err := conf.String(&cfg) 513 if err != nil { 514 fmt.Print(err) 515 return 516 } 517 518 fmt.Print(out) 519 520 // Output: 521 // --an-int=1 522 // --a-string/-s=B 523 // --bool=true 524 // --ip-name=localhost 525 // --ip-ip=127.0.0.0 526 // --ip-endpoints=[127.0.0.1:200 127.0.0.1:829] 527 // --debug-host=http://xxxxxx:xxxxxx@0.0.0.0:4000 528 // --password=xxxxxx 529 // --custom=@hello@ 530 // --name=andy 531 // --e-dur/-d=1m0s 532 } 533 534 type ConfExplicit struct { 535 Version conf.Version 536 Address string 537 } 538 539 type ConfImplicit struct { 540 conf.Version 541 Address string 542 } 543 544 func TestVersionExplicit(t *testing.T) { 545 tests := []struct { 546 name string 547 config ConfExplicit 548 args []string 549 want string 550 wantErr bool 551 }{ 552 { 553 name: "version", 554 args: []string{"--version"}, 555 config: ConfExplicit{ 556 Version: conf.Version{ 557 Build: "v1.0.0", 558 }, 559 }, 560 wantErr: false, 561 want: "Version: v1.0.0", 562 }, 563 { 564 name: "vershort", 565 args: []string{"conf.test", "-v"}, 566 config: ConfExplicit{ 567 Version: conf.Version{ 568 Build: "v1.0.0", 569 }, 570 }, 571 wantErr: false, 572 want: "Version: v1.0.0", 573 }, 574 { 575 name: "verdes", 576 args: []string{"conf.test", "-version"}, 577 config: ConfExplicit{ 578 Version: conf.Version{ 579 Build: "v1.0.0", 580 Desc: "Service Description", 581 }, 582 }, 583 wantErr: false, 584 want: "Version: v1.0.0\nService Description", 585 }, 586 { 587 name: "verdesshort", 588 args: []string{"conf.test", "-v"}, 589 config: ConfExplicit{ 590 Version: conf.Version{ 591 Build: "v1.0.0", 592 Desc: "Service Description", 593 }, 594 }, 595 wantErr: false, 596 want: "Version: v1.0.0\nService Description", 597 }, 598 { 599 name: "desshort", 600 args: []string{"conf.test", "-v"}, 601 config: ConfExplicit{ 602 Version: conf.Version{ 603 Desc: "Service Description", 604 }, 605 }, 606 wantErr: false, 607 want: "Service Description", 608 }, 609 { 610 name: "none", 611 args: []string{"conf.test", "-v"}, 612 config: ConfExplicit{}, 613 want: "", 614 wantErr: false, 615 }, 616 } 617 618 t.Log("Given the need validate version output.") 619 { 620 for i, tt := range tests { 621 t.Logf("\tTest: %d\tWhen using an explict struct.", i) 622 { 623 f := func(t *testing.T) { 624 os.Args = tt.args 625 if help, err := conf.Parse("APP", &tt.config); err != nil { 626 if err == conf.ErrHelpWanted { 627 if diff := cmp.Diff(tt.want, help); diff != "" { 628 t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed) 629 t.Log(diff) 630 } 631 t.Logf("\t%s\tShould match byte for byte the output.", success) 632 } 633 } 634 } 635 636 t.Run(tt.name, f) 637 } 638 } 639 } 640 } 641 642 func TestVersionImplicit(t *testing.T) { 643 tests := []struct { 644 name string 645 config ConfImplicit 646 args []string 647 want string 648 wantErr bool 649 }{ 650 { 651 name: "only version", 652 args: []string{"conf.test", "--version"}, 653 config: ConfImplicit{ 654 Version: conf.Version{ 655 Build: "v1.0.0", 656 }, 657 }, 658 wantErr: false, 659 want: "Version: v1.0.0", 660 }, 661 { 662 name: "only version shortcut", 663 args: []string{"conf.test", "-v"}, 664 config: ConfImplicit{ 665 Version: conf.Version{ 666 Build: "v1.0.0", 667 }, 668 }, 669 wantErr: false, 670 want: "Version: v1.0.0", 671 }, 672 { 673 name: "version and description", 674 args: []string{"conf.test", "-version"}, 675 config: ConfImplicit{ 676 Version: conf.Version{ 677 Build: "v1.0.0", 678 Desc: "Service Description", 679 }, 680 }, 681 wantErr: false, 682 want: "Version: v1.0.0\nService Description", 683 }, 684 { 685 name: "version and description shortcut", 686 args: []string{"conf.test", "-v"}, 687 config: ConfImplicit{ 688 Version: conf.Version{ 689 Build: "v1.0.0", 690 Desc: "Service Description", 691 }, 692 }, 693 wantErr: false, 694 want: "Version: v1.0.0\nService Description", 695 }, 696 { 697 name: "only description shortcut", 698 args: []string{"conf.test", "-v"}, 699 config: ConfImplicit{ 700 Version: conf.Version{ 701 Desc: "Service Description", 702 }, 703 }, 704 wantErr: false, 705 want: "Service Description", 706 }, 707 { 708 name: "no version", 709 args: []string{"conf.test", "-v"}, 710 config: ConfImplicit{}, 711 want: "", 712 wantErr: false, 713 }, 714 } 715 716 t.Log("Given the need validate version output.") 717 { 718 for i, tt := range tests { 719 t.Logf("\tTest: %d\tWhen using an emplicit struct with.", i) 720 { 721 f := func(t *testing.T) { 722 os.Args = tt.args 723 if help, err := conf.Parse("APP", &tt.config); err != nil { 724 if err == conf.ErrHelpWanted { 725 if diff := cmp.Diff(tt.want, help); diff != "" { 726 t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed) 727 t.Log(diff) 728 } 729 t.Logf("\t%s\tShould match byte for byte the output.", success) 730 } 731 } 732 } 733 734 t.Run(tt.name, f) 735 } 736 } 737 } 738 } 739 740 // ============================================================================= 741 742 var yamlData = ` 743 a: Easy! 744 b: 745 c: 2 746 d: [3, 4] 747 ` 748 749 type internal struct { 750 RenamedC int `yaml:"c"` 751 D []int `yaml:",flow"` 752 } 753 type yamlConfig struct { 754 A string 755 B internal 756 E string `conf:"default:postgres"` 757 } 758 759 func TestYAML(t *testing.T) { 760 tests := []struct { 761 name string 762 yaml []byte 763 envs map[string]string 764 args []string 765 want yamlConfig 766 }{ 767 { 768 "default", 769 []byte(yamlData), 770 nil, 771 nil, 772 yamlConfig{A: "Easy!", B: internal{RenamedC: 2, D: []int{3, 4}}, E: "postgres"}, 773 }, 774 { 775 "env", 776 []byte(yamlData), 777 map[string]string{"TEST_A": "EnvEasy!"}, 778 nil, 779 yamlConfig{A: "EnvEasy!", B: internal{RenamedC: 2, D: []int{3, 4}}, E: "postgres"}, 780 }, 781 { 782 "flag", 783 []byte(yamlData), 784 nil, 785 []string{"conf.test", "--a", "FlagEasy!"}, 786 yamlConfig{A: "FlagEasy!", B: internal{RenamedC: 2, D: []int{3, 4}}, E: "postgres"}, 787 }, 788 } 789 790 t.Log("Given the need to parse basic yaml configuration.") 791 { 792 for i, tt := range tests { 793 t.Logf("\tTest: %d\tWhen checking with arguments %v", i, tt.args) 794 { 795 os.Clearenv() 796 for k, v := range tt.envs { 797 os.Setenv(k, v) 798 } 799 800 f := func(t *testing.T) { 801 os.Args = tt.args 802 803 var cfg yamlConfig 804 if _, err := conf.Parse("TEST", &cfg, yaml.WithData(tt.yaml)); err != nil { 805 t.Fatalf("\t%s\tShould be able to Parse arguments : %s.", failed, err) 806 } 807 t.Logf("\t%s\tShould be able to Parse arguments.", success) 808 809 if diff := cmp.Diff(tt.want, cfg); diff != "" { 810 t.Fatalf("\t%s\tShould have properly initialized struct value\n%s", failed, diff) 811 } 812 t.Logf("\t%s\tShould have properly initialized struct value.", success) 813 } 814 815 t.Run(tt.name, f) 816 } 817 } 818 } 819 }