1package flags 2 3import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "reflect" 9 "strings" 10 "testing" 11) 12 13func TestWriteIni(t *testing.T) { 14 oldEnv := EnvSnapshot() 15 defer oldEnv.Restore() 16 os.Setenv("ENV_DEFAULT", "env-def") 17 18 var tests = []struct { 19 args []string 20 options IniOptions 21 expected string 22 }{ 23 { 24 []string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"}, 25 IniDefault, 26 `[Application Options] 27; Show verbose debug information 28verbose = true 29verbose = true 30 31; Test env-default1 value 32EnvDefault1 = env-def 33 34; Test env-default2 value 35EnvDefault2 = env-def 36 37[Other Options] 38; A map from string to int 39int-map = a:2 40int-map = b:3 41 42`, 43 }, 44 { 45 []string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"}, 46 IniDefault | IniIncludeDefaults, 47 `[Application Options] 48; Show verbose debug information 49verbose = true 50verbose = true 51 52; A slice of pointers to string 53; PtrSlice = 54 55EmptyDescription = false 56 57; Test default value 58Default = "Some\nvalue" 59 60; Test default array value 61DefaultArray = Some value 62DefaultArray = "Other\tvalue" 63 64; Testdefault map value 65DefaultMap = another:value 66DefaultMap = some:value 67 68; Test env-default1 value 69EnvDefault1 = env-def 70 71; Test env-default2 value 72EnvDefault2 = env-def 73 74; Option with named argument 75OptionWithArgName = 76 77; Option with choices 78OptionWithChoices = 79 80; Option only available in ini 81only-ini = 82 83[Other Options] 84; A slice of strings 85StringSlice = some 86StringSlice = value 87 88; A map from string to int 89int-map = a:2 90int-map = b:3 91 92[Subgroup] 93; This is a subgroup option 94Opt = 95 96; Not hidden inside group 97NotHiddenInsideGroup = 98 99[Subsubgroup] 100; This is a subsubgroup option 101Opt = 102 103[command] 104; Use for extra verbosity 105; ExtraVerbose = 106 107`, 108 }, 109 { 110 []string{"filename", "0", "3.14", "command"}, 111 IniDefault | IniIncludeDefaults | IniCommentDefaults, 112 `[Application Options] 113; Show verbose debug information 114; verbose = 115 116; A slice of pointers to string 117; PtrSlice = 118 119; EmptyDescription = false 120 121; Test default value 122; Default = "Some\nvalue" 123 124; Test default array value 125; DefaultArray = Some value 126; DefaultArray = "Other\tvalue" 127 128; Testdefault map value 129; DefaultMap = another:value 130; DefaultMap = some:value 131 132; Test env-default1 value 133EnvDefault1 = env-def 134 135; Test env-default2 value 136EnvDefault2 = env-def 137 138; Option with named argument 139; OptionWithArgName = 140 141; Option with choices 142; OptionWithChoices = 143 144; Option only available in ini 145; only-ini = 146 147[Other Options] 148; A slice of strings 149; StringSlice = some 150; StringSlice = value 151 152; A map from string to int 153; int-map = a:1 154 155[Subgroup] 156; This is a subgroup option 157; Opt = 158 159; Not hidden inside group 160; NotHiddenInsideGroup = 161 162[Subsubgroup] 163; This is a subsubgroup option 164; Opt = 165 166[command] 167; Use for extra verbosity 168; ExtraVerbose = 169 170`, 171 }, 172 { 173 []string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "3.14", "command"}, 174 IniDefault | IniIncludeDefaults | IniCommentDefaults, 175 `[Application Options] 176; Show verbose debug information 177; verbose = 178 179; A slice of pointers to string 180; PtrSlice = 181 182; EmptyDescription = false 183 184; Test default value 185Default = New value 186 187; Test default array value 188DefaultArray = New value 189 190; Testdefault map value 191DefaultMap = new:value 192 193; Test env-default1 value 194EnvDefault1 = env-def 195 196; Test env-default2 value 197EnvDefault2 = env-def 198 199; Option with named argument 200; OptionWithArgName = 201 202; Option with choices 203; OptionWithChoices = 204 205; Option only available in ini 206; only-ini = 207 208[Other Options] 209; A slice of strings 210; StringSlice = some 211; StringSlice = value 212 213; A map from string to int 214; int-map = a:1 215 216[Subgroup] 217; This is a subgroup option 218; Opt = 219 220; Not hidden inside group 221; NotHiddenInsideGroup = 222 223[Subsubgroup] 224; This is a subsubgroup option 225; Opt = 226 227[command] 228; Use for extra verbosity 229; ExtraVerbose = 230 231`, 232 }, 233 } 234 235 for _, test := range tests { 236 var opts helpOptions 237 238 p := NewNamedParser("TestIni", Default) 239 p.AddGroup("Application Options", "The application options", &opts) 240 241 _, err := p.ParseArgs(test.args) 242 243 if err != nil { 244 t.Fatalf("Unexpected error: %v", err) 245 } 246 247 inip := NewIniParser(p) 248 249 var b bytes.Buffer 250 inip.Write(&b, test.options) 251 252 got := b.String() 253 expected := test.expected 254 255 msg := fmt.Sprintf("with arguments %+v and ini options %b", test.args, test.options) 256 assertDiff(t, got, expected, msg) 257 } 258} 259 260func TestReadIni_flagEquivalent(t *testing.T) { 261 type options struct { 262 Opt1 bool `long:"opt1"` 263 264 Group1 struct { 265 Opt2 bool `long:"opt2"` 266 } `group:"group1"` 267 268 Group2 struct { 269 Opt3 bool `long:"opt3"` 270 } `group:"group2" namespace:"ns1"` 271 272 Cmd1 struct { 273 Opt4 bool `long:"opt4"` 274 Opt5 bool `long:"foo.opt5"` 275 276 Group1 struct { 277 Opt6 bool `long:"opt6"` 278 Opt7 bool `long:"foo.opt7"` 279 } `group:"group1"` 280 281 Group2 struct { 282 Opt8 bool `long:"opt8"` 283 } `group:"group2" namespace:"ns1"` 284 } `command:"cmd1"` 285 } 286 287 a := ` 288opt1=true 289 290[group1] 291opt2=true 292 293[group2] 294ns1.opt3=true 295 296[cmd1] 297opt4=true 298foo.opt5=true 299 300[cmd1.group1] 301opt6=true 302foo.opt7=true 303 304[cmd1.group2] 305ns1.opt8=true 306` 307 b := ` 308opt1=true 309opt2=true 310ns1.opt3=true 311 312[cmd1] 313opt4=true 314foo.opt5=true 315opt6=true 316foo.opt7=true 317ns1.opt8=true 318` 319 320 parse := func(readIni string) (opts options, writeIni string) { 321 p := NewNamedParser("TestIni", Default) 322 p.AddGroup("Application Options", "The application options", &opts) 323 324 inip := NewIniParser(p) 325 err := inip.Parse(strings.NewReader(readIni)) 326 327 if err != nil { 328 t.Fatalf("Unexpected error: %s\n\nFile:\n%s", err, readIni) 329 } 330 331 var b bytes.Buffer 332 inip.Write(&b, Default) 333 334 return opts, b.String() 335 } 336 337 aOpt, aIni := parse(a) 338 bOpt, bIni := parse(b) 339 340 assertDiff(t, aIni, bIni, "") 341 if !reflect.DeepEqual(aOpt, bOpt) { 342 t.Errorf("not equal") 343 } 344} 345 346func TestReadIni(t *testing.T) { 347 var opts helpOptions 348 349 p := NewNamedParser("TestIni", Default) 350 p.AddGroup("Application Options", "The application options", &opts) 351 352 inip := NewIniParser(p) 353 354 inic := ` 355; Show verbose debug information 356verbose = true 357verbose = true 358 359DefaultMap = another:"value\n1" 360DefaultMap = some:value 2 361 362[Application Options] 363; A slice of pointers to string 364; PtrSlice = 365 366; Test default value 367Default = "New\nvalue" 368 369; Test env-default1 value 370EnvDefault1 = New value 371 372[Other Options] 373# A slice of strings 374StringSlice = "some\nvalue" 375StringSlice = another value 376 377; A map from string to int 378int-map = a:2 379int-map = b:3 380 381` 382 383 b := strings.NewReader(inic) 384 err := inip.Parse(b) 385 386 if err != nil { 387 t.Fatalf("Unexpected error: %s", err) 388 } 389 390 assertBoolArray(t, opts.Verbose, []bool{true, true}) 391 392 if v := map[string]string{"another": "value\n1", "some": "value 2"}; !reflect.DeepEqual(opts.DefaultMap, v) { 393 t.Fatalf("Expected %#v for DefaultMap but got %#v", v, opts.DefaultMap) 394 } 395 396 assertString(t, opts.Default, "New\nvalue") 397 398 assertString(t, opts.EnvDefault1, "New value") 399 400 assertStringArray(t, opts.Other.StringSlice, []string{"some\nvalue", "another value"}) 401 402 if v, ok := opts.Other.IntMap["a"]; !ok { 403 t.Errorf("Expected \"a\" in Other.IntMap") 404 } else if v != 2 { 405 t.Errorf("Expected Other.IntMap[\"a\"] = 2, but got %v", v) 406 } 407 408 if v, ok := opts.Other.IntMap["b"]; !ok { 409 t.Errorf("Expected \"b\" in Other.IntMap") 410 } else if v != 3 { 411 t.Errorf("Expected Other.IntMap[\"b\"] = 3, but got %v", v) 412 } 413} 414 415func TestReadAndWriteIni(t *testing.T) { 416 var tests = []struct { 417 options IniOptions 418 read string 419 write string 420 }{ 421 { 422 IniIncludeComments, 423 `[Application Options] 424; Show verbose debug information 425verbose = true 426verbose = true 427 428; Test default value 429Default = "quote me" 430 431; Test default array value 432DefaultArray = 1 433DefaultArray = "2" 434DefaultArray = 3 435 436; Testdefault map value 437; DefaultMap = 438 439; Test env-default1 value 440EnvDefault1 = env-def 441 442; Test env-default2 value 443EnvDefault2 = env-def 444 445[Other Options] 446; A slice of strings 447; StringSlice = 448 449; A map from string to int 450int-map = a:2 451int-map = b:"3" 452 453`, 454 `[Application Options] 455; Show verbose debug information 456verbose = true 457verbose = true 458 459; Test default value 460Default = "quote me" 461 462; Test default array value 463DefaultArray = 1 464DefaultArray = 2 465DefaultArray = 3 466 467; Testdefault map value 468; DefaultMap = 469 470; Test env-default1 value 471EnvDefault1 = env-def 472 473; Test env-default2 value 474EnvDefault2 = env-def 475 476[Other Options] 477; A slice of strings 478; StringSlice = 479 480; A map from string to int 481int-map = a:2 482int-map = b:3 483 484`, 485 }, 486 { 487 IniIncludeComments, 488 `[Application Options] 489; Show verbose debug information 490verbose = true 491verbose = true 492 493; Test default value 494Default = "quote me" 495 496; Test default array value 497DefaultArray = "1" 498DefaultArray = "2" 499DefaultArray = "3" 500 501; Testdefault map value 502; DefaultMap = 503 504; Test env-default1 value 505EnvDefault1 = env-def 506 507; Test env-default2 value 508EnvDefault2 = env-def 509 510[Other Options] 511; A slice of strings 512; StringSlice = 513 514; A map from string to int 515int-map = a:"2" 516int-map = b:"3" 517 518`, 519 `[Application Options] 520; Show verbose debug information 521verbose = true 522verbose = true 523 524; Test default value 525Default = "quote me" 526 527; Test default array value 528DefaultArray = "1" 529DefaultArray = "2" 530DefaultArray = "3" 531 532; Testdefault map value 533; DefaultMap = 534 535; Test env-default1 value 536EnvDefault1 = env-def 537 538; Test env-default2 value 539EnvDefault2 = env-def 540 541[Other Options] 542; A slice of strings 543; StringSlice = 544 545; A map from string to int 546int-map = a:"2" 547int-map = b:"3" 548 549`, 550 }, 551 } 552 553 for _, test := range tests { 554 var opts helpOptions 555 556 p := NewNamedParser("TestIni", Default) 557 p.AddGroup("Application Options", "The application options", &opts) 558 559 inip := NewIniParser(p) 560 561 read := strings.NewReader(test.read) 562 err := inip.Parse(read) 563 if err != nil { 564 t.Fatalf("Unexpected error: %s", err) 565 } 566 567 var write bytes.Buffer 568 inip.Write(&write, test.options) 569 570 got := write.String() 571 572 msg := fmt.Sprintf("with ini options %b", test.options) 573 assertDiff(t, got, test.write, msg) 574 } 575} 576 577func TestReadIniWrongQuoting(t *testing.T) { 578 var tests = []struct { 579 iniFile string 580 lineNumber uint 581 }{ 582 { 583 iniFile: `Default = "New\nvalue`, 584 lineNumber: 1, 585 }, 586 { 587 iniFile: `StringSlice = "New\nvalue`, 588 lineNumber: 1, 589 }, 590 { 591 iniFile: `StringSlice = "New\nvalue" 592 StringSlice = "Second\nvalue`, 593 lineNumber: 2, 594 }, 595 { 596 iniFile: `DefaultMap = some:"value`, 597 lineNumber: 1, 598 }, 599 { 600 iniFile: `DefaultMap = some:value 601 DefaultMap = another:"value`, 602 lineNumber: 2, 603 }, 604 } 605 606 for _, test := range tests { 607 var opts helpOptions 608 609 p := NewNamedParser("TestIni", Default) 610 p.AddGroup("Application Options", "The application options", &opts) 611 612 inip := NewIniParser(p) 613 614 inic := test.iniFile 615 616 b := strings.NewReader(inic) 617 err := inip.Parse(b) 618 619 if err == nil { 620 t.Fatalf("Expect error") 621 } 622 623 iniError := err.(*IniError) 624 625 if iniError.LineNumber != test.lineNumber { 626 t.Fatalf("Expect error on line %d", test.lineNumber) 627 } 628 } 629} 630 631func TestIniCommands(t *testing.T) { 632 var opts struct { 633 Value string `short:"v" long:"value"` 634 635 Add struct { 636 Name int `short:"n" long:"name" ini-name:"AliasName"` 637 638 Other struct { 639 O string `short:"o" long:"other"` 640 } `group:"Other Options"` 641 } `command:"add"` 642 } 643 644 p := NewNamedParser("TestIni", Default) 645 p.AddGroup("Application Options", "The application options", &opts) 646 647 inip := NewIniParser(p) 648 649 inic := `[Application Options] 650value = some value 651 652[add] 653AliasName = 5 654 655[add.Other Options] 656other = subgroup 657 658` 659 660 b := strings.NewReader(inic) 661 err := inip.Parse(b) 662 663 if err != nil { 664 t.Fatalf("Unexpected error: %s", err) 665 } 666 667 assertString(t, opts.Value, "some value") 668 669 if opts.Add.Name != 5 { 670 t.Errorf("Expected opts.Add.Name to be 5, but got %v", opts.Add.Name) 671 } 672 673 assertString(t, opts.Add.Other.O, "subgroup") 674 675 // Test writing it back 676 buf := &bytes.Buffer{} 677 678 inip.Write(buf, IniDefault) 679 680 assertDiff(t, buf.String(), inic, "ini contents") 681} 682 683func TestIniNoIni(t *testing.T) { 684 var opts struct { 685 NoValue string `short:"n" long:"novalue" no-ini:"yes"` 686 Value string `short:"v" long:"value"` 687 } 688 689 p := NewNamedParser("TestIni", Default) 690 p.AddGroup("Application Options", "The application options", &opts) 691 692 inip := NewIniParser(p) 693 694 // read INI 695 inic := `[Application Options] 696novalue = some value 697value = some other value 698` 699 700 b := strings.NewReader(inic) 701 err := inip.Parse(b) 702 703 if err == nil { 704 t.Fatalf("Expected error") 705 } 706 707 iniError := err.(*IniError) 708 709 if v := uint(2); iniError.LineNumber != v { 710 t.Errorf("Expected opts.Add.Name to be %d, but got %d", v, iniError.LineNumber) 711 } 712 713 if v := "unknown option: novalue"; iniError.Message != v { 714 t.Errorf("Expected opts.Add.Name to be %s, but got %s", v, iniError.Message) 715 } 716 717 // write INI 718 opts.NoValue = "some value" 719 opts.Value = "some other value" 720 721 file, err := ioutil.TempFile("", "") 722 if err != nil { 723 t.Fatalf("Cannot create temporary file: %s", err) 724 } 725 defer os.Remove(file.Name()) 726 727 err = inip.WriteFile(file.Name(), IniIncludeDefaults) 728 if err != nil { 729 t.Fatalf("Could not write ini file: %s", err) 730 } 731 732 found, err := ioutil.ReadFile(file.Name()) 733 if err != nil { 734 t.Fatalf("Could not read written ini file: %s", err) 735 } 736 737 expected := "[Application Options]\nValue = some other value\n\n" 738 739 assertDiff(t, string(found), expected, "ini content") 740} 741 742func TestIniParse(t *testing.T) { 743 file, err := ioutil.TempFile("", "") 744 if err != nil { 745 t.Fatalf("Cannot create temporary file: %s", err) 746 } 747 defer os.Remove(file.Name()) 748 749 _, err = file.WriteString("value = 123") 750 if err != nil { 751 t.Fatalf("Cannot write to temporary file: %s", err) 752 } 753 754 file.Close() 755 756 var opts struct { 757 Value int `long:"value"` 758 } 759 760 err = IniParse(file.Name(), &opts) 761 if err != nil { 762 t.Fatalf("Could not parse ini: %s", err) 763 } 764 765 if opts.Value != 123 { 766 t.Fatalf("Expected Value to be \"123\" but was \"%d\"", opts.Value) 767 } 768} 769 770func TestIniCliOverrides(t *testing.T) { 771 file, err := ioutil.TempFile("", "") 772 773 if err != nil { 774 t.Fatalf("Cannot create temporary file: %s", err) 775 } 776 777 defer os.Remove(file.Name()) 778 779 _, err = file.WriteString("values = 123\n") 780 _, err = file.WriteString("values = 456\n") 781 782 if err != nil { 783 t.Fatalf("Cannot write to temporary file: %s", err) 784 } 785 786 file.Close() 787 788 var opts struct { 789 Values []int `long:"values"` 790 } 791 792 p := NewParser(&opts, Default) 793 err = NewIniParser(p).ParseFile(file.Name()) 794 795 if err != nil { 796 t.Fatalf("Could not parse ini: %s", err) 797 } 798 799 _, err = p.ParseArgs([]string{"--values", "111", "--values", "222"}) 800 801 if err != nil { 802 t.Fatalf("Failed to parse arguments: %s", err) 803 } 804 805 if len(opts.Values) != 2 { 806 t.Fatalf("Expected Values to contain two elements, but got %d", len(opts.Values)) 807 } 808 809 if opts.Values[0] != 111 { 810 t.Fatalf("Expected Values[0] to be 111, but got '%d'", opts.Values[0]) 811 } 812 813 if opts.Values[1] != 222 { 814 t.Fatalf("Expected Values[1] to be 222, but got '%d'", opts.Values[1]) 815 } 816} 817 818func TestIniOverrides(t *testing.T) { 819 file, err := ioutil.TempFile("", "") 820 821 if err != nil { 822 t.Fatalf("Cannot create temporary file: %s", err) 823 } 824 825 defer os.Remove(file.Name()) 826 827 _, err = file.WriteString("value-with-default = \"ini-value\"\n") 828 _, err = file.WriteString("value-with-default-override-cli = \"ini-value\"\n") 829 830 if err != nil { 831 t.Fatalf("Cannot write to temporary file: %s", err) 832 } 833 834 file.Close() 835 836 var opts struct { 837 ValueWithDefault string `long:"value-with-default" default:"value"` 838 ValueWithDefaultOverrideCli string `long:"value-with-default-override-cli" default:"value"` 839 } 840 841 p := NewParser(&opts, Default) 842 err = NewIniParser(p).ParseFile(file.Name()) 843 844 if err != nil { 845 t.Fatalf("Could not parse ini: %s", err) 846 } 847 848 _, err = p.ParseArgs([]string{"--value-with-default-override-cli", "cli-value"}) 849 850 if err != nil { 851 t.Fatalf("Failed to parse arguments: %s", err) 852 } 853 854 assertString(t, opts.ValueWithDefault, "ini-value") 855 assertString(t, opts.ValueWithDefaultOverrideCli, "cli-value") 856} 857 858func TestIniRequired(t *testing.T) { 859 var opts struct { 860 Required string `short:"r" required:"yes" description:"required"` 861 Config func(s string) error `long:"config" default:"no-ini-file" no-ini:"true"` 862 } 863 864 p := NewParser(&opts, Default) 865 866 opts.Config = func(s string) error { 867 inip := NewIniParser(p) 868 inip.ParseAsDefaults = true 869 return inip.Parse(strings.NewReader("Required = ini-value\n")) 870 } 871 872 _, err := p.ParseArgs([]string{"-r", "cli-value"}) 873 874 if err != nil { 875 t.Fatalf("Failed to parse arguments: %s", err) 876 } 877 878 assertString(t, opts.Required, "cli-value") 879} 880 881func TestWriteFile(t *testing.T) { 882 file, err := ioutil.TempFile("", "") 883 if err != nil { 884 t.Fatalf("Cannot create temporary file: %s", err) 885 } 886 defer os.Remove(file.Name()) 887 888 var opts struct { 889 Value int `long:"value"` 890 } 891 892 opts.Value = 123 893 894 p := NewParser(&opts, Default) 895 ini := NewIniParser(p) 896 897 err = ini.WriteFile(file.Name(), IniIncludeDefaults) 898 if err != nil { 899 t.Fatalf("Could not write ini file: %s", err) 900 } 901 902 found, err := ioutil.ReadFile(file.Name()) 903 if err != nil { 904 t.Fatalf("Could not read written ini file: %s", err) 905 } 906 907 expected := "[Application Options]\nValue = 123\n\n" 908 909 assertDiff(t, string(found), expected, "ini content") 910} 911 912func TestOverwriteRequiredOptions(t *testing.T) { 913 var tests = []struct { 914 args []string 915 expected []string 916 }{ 917 { 918 args: []string{"--value", "from CLI"}, 919 expected: []string{ 920 "from CLI", 921 "from default", 922 }, 923 }, 924 { 925 args: []string{"--value", "from CLI", "--default", "from CLI"}, 926 expected: []string{ 927 "from CLI", 928 "from CLI", 929 }, 930 }, 931 { 932 args: []string{"--config", "no file name"}, 933 expected: []string{ 934 "from INI", 935 "from INI", 936 }, 937 }, 938 { 939 args: []string{"--value", "from CLI before", "--default", "from CLI before", "--config", "no file name"}, 940 expected: []string{ 941 "from INI", 942 "from INI", 943 }, 944 }, 945 { 946 args: []string{"--value", "from CLI before", "--default", "from CLI before", "--config", "no file name", "--value", "from CLI after", "--default", "from CLI after"}, 947 expected: []string{ 948 "from CLI after", 949 "from CLI after", 950 }, 951 }, 952 } 953 954 for _, test := range tests { 955 var opts struct { 956 Config func(s string) error `long:"config" no-ini:"true"` 957 Value string `long:"value" required:"true"` 958 Default string `long:"default" required:"true" default:"from default"` 959 } 960 961 p := NewParser(&opts, Default) 962 963 opts.Config = func(s string) error { 964 ini := NewIniParser(p) 965 966 return ini.Parse(bytes.NewBufferString("value = from INI\ndefault = from INI")) 967 } 968 969 _, err := p.ParseArgs(test.args) 970 if err != nil { 971 t.Fatalf("Unexpected error %s with args %+v", err, test.args) 972 } 973 974 if opts.Value != test.expected[0] { 975 t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected[0], opts.Value, test.args) 976 } 977 978 if opts.Default != test.expected[1] { 979 t.Fatalf("Expected Default to be \"%s\" but was \"%s\" with args %+v", test.expected[1], opts.Default, test.args) 980 } 981 } 982} 983 984func TestIniOverwriteOptions(t *testing.T) { 985 var tests = []struct { 986 args []string 987 expected string 988 toggled bool 989 }{ 990 { 991 args: []string{}, 992 expected: "from default", 993 }, 994 { 995 args: []string{"--value", "from CLI"}, 996 expected: "from CLI", 997 }, 998 { 999 args: []string{"--config", "no file name"}, 1000 expected: "from INI", 1001 toggled: true, 1002 }, 1003 { 1004 args: []string{"--value", "from CLI before", "--config", "no file name"}, 1005 expected: "from CLI before", 1006 toggled: true, 1007 }, 1008 { 1009 args: []string{"--config", "no file name", "--value", "from CLI after"}, 1010 expected: "from CLI after", 1011 toggled: true, 1012 }, 1013 { 1014 args: []string{"--toggle"}, 1015 toggled: true, 1016 expected: "from default", 1017 }, 1018 } 1019 1020 for _, test := range tests { 1021 var opts struct { 1022 Config string `long:"config" no-ini:"true"` 1023 Value string `long:"value" default:"from default"` 1024 Toggle bool `long:"toggle"` 1025 } 1026 1027 p := NewParser(&opts, Default) 1028 1029 _, err := p.ParseArgs(test.args) 1030 if err != nil { 1031 t.Fatalf("Unexpected error %s with args %+v", err, test.args) 1032 } 1033 1034 if opts.Config != "" { 1035 inip := NewIniParser(p) 1036 inip.ParseAsDefaults = true 1037 1038 err = inip.Parse(bytes.NewBufferString("value = from INI\ntoggle = true")) 1039 if err != nil { 1040 t.Fatalf("Unexpected error %s with args %+v", err, test.args) 1041 } 1042 } 1043 1044 if opts.Value != test.expected { 1045 t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected, opts.Value, test.args) 1046 } 1047 1048 if opts.Toggle != test.toggled { 1049 t.Fatalf("Expected Toggle to be \"%v\" but was \"%v\" with args %+v", test.toggled, opts.Toggle, test.args) 1050 } 1051 1052 } 1053} 1054