blob: ad4852e8a24cd7069cd342b1850c3397bc1f4ad2 [file] [log] [blame]
package flags
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"
)
func TestWriteIni(t *testing.T) {
oldEnv := EnvSnapshot()
defer oldEnv.Restore()
os.Setenv("ENV_DEFAULT", "env-def")
var tests = []struct {
args []string
options IniOptions
expected string
}{
{
[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
IniDefault,
`[Application Options]
; Show verbose debug information
verbose = true
verbose = true
; Test env-default1 value
EnvDefault1 = env-def
; Test env-default2 value
EnvDefault2 = env-def
[Other Options]
; A map from string to int
int-map = a:2
int-map = b:3
`,
},
{
[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
IniDefault | IniIncludeDefaults,
`[Application Options]
; Show verbose debug information
verbose = true
verbose = true
; A slice of pointers to string
; PtrSlice =
EmptyDescription = false
; Test default value
Default = "Some\nvalue"
; Test default array value
DefaultArray = Some value
DefaultArray = "Other\tvalue"
; Testdefault map value
DefaultMap = another:value
DefaultMap = some:value
; Test env-default1 value
EnvDefault1 = env-def
; Test env-default2 value
EnvDefault2 = env-def
; Option with named argument
OptionWithArgName =
; Option with choices
OptionWithChoices =
; Option only available in ini
only-ini =
[Other Options]
; A slice of strings
StringSlice = some
StringSlice = value
; A map from string to int
int-map = a:2
int-map = b:3
[Subgroup]
; This is a subgroup option
Opt =
; Not hidden inside group
NotHiddenInsideGroup =
[Subsubgroup]
; This is a subsubgroup option
Opt =
[command]
; Use for extra verbosity
; ExtraVerbose =
`,
},
{
[]string{"filename", "0", "3.14", "command"},
IniDefault | IniIncludeDefaults | IniCommentDefaults,
`[Application Options]
; Show verbose debug information
; verbose =
; A slice of pointers to string
; PtrSlice =
; EmptyDescription = false
; Test default value
; Default = "Some\nvalue"
; Test default array value
; DefaultArray = Some value
; DefaultArray = "Other\tvalue"
; Testdefault map value
; DefaultMap = another:value
; DefaultMap = some:value
; Test env-default1 value
EnvDefault1 = env-def
; Test env-default2 value
EnvDefault2 = env-def
; Option with named argument
; OptionWithArgName =
; Option with choices
; OptionWithChoices =
; Option only available in ini
; only-ini =
[Other Options]
; A slice of strings
; StringSlice = some
; StringSlice = value
; A map from string to int
; int-map = a:1
[Subgroup]
; This is a subgroup option
; Opt =
; Not hidden inside group
; NotHiddenInsideGroup =
[Subsubgroup]
; This is a subsubgroup option
; Opt =
[command]
; Use for extra verbosity
; ExtraVerbose =
`,
},
{
[]string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "3.14", "command"},
IniDefault | IniIncludeDefaults | IniCommentDefaults,
`[Application Options]
; Show verbose debug information
; verbose =
; A slice of pointers to string
; PtrSlice =
; EmptyDescription = false
; Test default value
Default = New value
; Test default array value
DefaultArray = New value
; Testdefault map value
DefaultMap = new:value
; Test env-default1 value
EnvDefault1 = env-def
; Test env-default2 value
EnvDefault2 = env-def
; Option with named argument
; OptionWithArgName =
; Option with choices
; OptionWithChoices =
; Option only available in ini
; only-ini =
[Other Options]
; A slice of strings
; StringSlice = some
; StringSlice = value
; A map from string to int
; int-map = a:1
[Subgroup]
; This is a subgroup option
; Opt =
; Not hidden inside group
; NotHiddenInsideGroup =
[Subsubgroup]
; This is a subsubgroup option
; Opt =
[command]
; Use for extra verbosity
; ExtraVerbose =
`,
},
}
for _, test := range tests {
var opts helpOptions
p := NewNamedParser("TestIni", Default)
p.AddGroup("Application Options", "The application options", &opts)
_, err := p.ParseArgs(test.args)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
inip := NewIniParser(p)
var b bytes.Buffer
inip.Write(&b, test.options)
got := b.String()
expected := test.expected
msg := fmt.Sprintf("with arguments %+v and ini options %b", test.args, test.options)
assertDiff(t, got, expected, msg)
}
}
func TestReadIni_flagEquivalent(t *testing.T) {
type options struct {
Opt1 bool `long:"opt1"`
Group1 struct {
Opt2 bool `long:"opt2"`
} `group:"group1"`
Group2 struct {
Opt3 bool `long:"opt3"`
} `group:"group2" namespace:"ns1"`
Cmd1 struct {
Opt4 bool `long:"opt4"`
Opt5 bool `long:"foo.opt5"`
Group1 struct {
Opt6 bool `long:"opt6"`
Opt7 bool `long:"foo.opt7"`
} `group:"group1"`
Group2 struct {
Opt8 bool `long:"opt8"`
} `group:"group2" namespace:"ns1"`
} `command:"cmd1"`
}
a := `
opt1=true
[group1]
opt2=true
[group2]
ns1.opt3=true
[cmd1]
opt4=true
foo.opt5=true
[cmd1.group1]
opt6=true
foo.opt7=true
[cmd1.group2]
ns1.opt8=true
`
b := `
opt1=true
opt2=true
ns1.opt3=true
[cmd1]
opt4=true
foo.opt5=true
opt6=true
foo.opt7=true
ns1.opt8=true
`
parse := func(readIni string) (opts options, writeIni string) {
p := NewNamedParser("TestIni", Default)
p.AddGroup("Application Options", "The application options", &opts)
inip := NewIniParser(p)
err := inip.Parse(strings.NewReader(readIni))
if err != nil {
t.Fatalf("Unexpected error: %s\n\nFile:\n%s", err, readIni)
}
var b bytes.Buffer
inip.Write(&b, Default)
return opts, b.String()
}
aOpt, aIni := parse(a)
bOpt, bIni := parse(b)
assertDiff(t, aIni, bIni, "")
if !reflect.DeepEqual(aOpt, bOpt) {
t.Errorf("not equal")
}
}
func TestReadIni(t *testing.T) {
var opts helpOptions
p := NewNamedParser("TestIni", Default)
p.AddGroup("Application Options", "The application options", &opts)
inip := NewIniParser(p)
inic := `
; Show verbose debug information
verbose = true
verbose = true
DefaultMap = another:"value\n1"
DefaultMap = some:value 2
[Application Options]
; A slice of pointers to string
; PtrSlice =
; Test default value
Default = "New\nvalue"
; Test env-default1 value
EnvDefault1 = New value
[Other Options]
# A slice of strings
StringSlice = "some\nvalue"
StringSlice = another value
; A map from string to int
int-map = a:2
int-map = b:3
`
b := strings.NewReader(inic)
err := inip.Parse(b)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
assertBoolArray(t, opts.Verbose, []bool{true, true})
if v := map[string]string{"another": "value\n1", "some": "value 2"}; !reflect.DeepEqual(opts.DefaultMap, v) {
t.Fatalf("Expected %#v for DefaultMap but got %#v", v, opts.DefaultMap)
}
assertString(t, opts.Default, "New\nvalue")
assertString(t, opts.EnvDefault1, "New value")
assertStringArray(t, opts.Other.StringSlice, []string{"some\nvalue", "another value"})
if v, ok := opts.Other.IntMap["a"]; !ok {
t.Errorf("Expected \"a\" in Other.IntMap")
} else if v != 2 {
t.Errorf("Expected Other.IntMap[\"a\"] = 2, but got %v", v)
}
if v, ok := opts.Other.IntMap["b"]; !ok {
t.Errorf("Expected \"b\" in Other.IntMap")
} else if v != 3 {
t.Errorf("Expected Other.IntMap[\"b\"] = 3, but got %v", v)
}
}
func TestReadAndWriteIni(t *testing.T) {
var tests = []struct {
options IniOptions
read string
write string
}{
{
IniIncludeComments,
`[Application Options]
; Show verbose debug information
verbose = true
verbose = true
; Test default value
Default = "quote me"
; Test default array value
DefaultArray = 1
DefaultArray = "2"
DefaultArray = 3
; Testdefault map value
; DefaultMap =
; Test env-default1 value
EnvDefault1 = env-def
; Test env-default2 value
EnvDefault2 = env-def
[Other Options]
; A slice of strings
; StringSlice =
; A map from string to int
int-map = a:2
int-map = b:"3"
`,
`[Application Options]
; Show verbose debug information
verbose = true
verbose = true
; Test default value
Default = "quote me"
; Test default array value
DefaultArray = 1
DefaultArray = 2
DefaultArray = 3
; Testdefault map value
; DefaultMap =
; Test env-default1 value
EnvDefault1 = env-def
; Test env-default2 value
EnvDefault2 = env-def
[Other Options]
; A slice of strings
; StringSlice =
; A map from string to int
int-map = a:2
int-map = b:3
`,
},
{
IniIncludeComments,
`[Application Options]
; Show verbose debug information
verbose = true
verbose = true
; Test default value
Default = "quote me"
; Test default array value
DefaultArray = "1"
DefaultArray = "2"
DefaultArray = "3"
; Testdefault map value
; DefaultMap =
; Test env-default1 value
EnvDefault1 = env-def
; Test env-default2 value
EnvDefault2 = env-def
[Other Options]
; A slice of strings
; StringSlice =
; A map from string to int
int-map = a:"2"
int-map = b:"3"
`,
`[Application Options]
; Show verbose debug information
verbose = true
verbose = true
; Test default value
Default = "quote me"
; Test default array value
DefaultArray = "1"
DefaultArray = "2"
DefaultArray = "3"
; Testdefault map value
; DefaultMap =
; Test env-default1 value
EnvDefault1 = env-def
; Test env-default2 value
EnvDefault2 = env-def
[Other Options]
; A slice of strings
; StringSlice =
; A map from string to int
int-map = a:"2"
int-map = b:"3"
`,
},
}
for _, test := range tests {
var opts helpOptions
p := NewNamedParser("TestIni", Default)
p.AddGroup("Application Options", "The application options", &opts)
inip := NewIniParser(p)
read := strings.NewReader(test.read)
err := inip.Parse(read)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
var write bytes.Buffer
inip.Write(&write, test.options)
got := write.String()
msg := fmt.Sprintf("with ini options %b", test.options)
assertDiff(t, got, test.write, msg)
}
}
func TestReadIniWrongQuoting(t *testing.T) {
var tests = []struct {
iniFile string
lineNumber uint
}{
{
iniFile: `Default = "New\nvalue`,
lineNumber: 1,
},
{
iniFile: `StringSlice = "New\nvalue`,
lineNumber: 1,
},
{
iniFile: `StringSlice = "New\nvalue"
StringSlice = "Second\nvalue`,
lineNumber: 2,
},
{
iniFile: `DefaultMap = some:"value`,
lineNumber: 1,
},
{
iniFile: `DefaultMap = some:value
DefaultMap = another:"value`,
lineNumber: 2,
},
}
for _, test := range tests {
var opts helpOptions
p := NewNamedParser("TestIni", Default)
p.AddGroup("Application Options", "The application options", &opts)
inip := NewIniParser(p)
inic := test.iniFile
b := strings.NewReader(inic)
err := inip.Parse(b)
if err == nil {
t.Fatalf("Expect error")
}
iniError := err.(*IniError)
if iniError.LineNumber != test.lineNumber {
t.Fatalf("Expect error on line %d", test.lineNumber)
}
}
}
func TestIniCommands(t *testing.T) {
var opts struct {
Value string `short:"v" long:"value"`
Add struct {
Name int `short:"n" long:"name" ini-name:"AliasName"`
Other struct {
O string `short:"o" long:"other"`
} `group:"Other Options"`
} `command:"add"`
}
p := NewNamedParser("TestIni", Default)
p.AddGroup("Application Options", "The application options", &opts)
inip := NewIniParser(p)
inic := `[Application Options]
value = some value
[add]
AliasName = 5
[add.Other Options]
other = subgroup
`
b := strings.NewReader(inic)
err := inip.Parse(b)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
assertString(t, opts.Value, "some value")
if opts.Add.Name != 5 {
t.Errorf("Expected opts.Add.Name to be 5, but got %v", opts.Add.Name)
}
assertString(t, opts.Add.Other.O, "subgroup")
// Test writing it back
buf := &bytes.Buffer{}
inip.Write(buf, IniDefault)
assertDiff(t, buf.String(), inic, "ini contents")
}
func TestIniNoIni(t *testing.T) {
var opts struct {
NoValue string `short:"n" long:"novalue" no-ini:"yes"`
Value string `short:"v" long:"value"`
}
p := NewNamedParser("TestIni", Default)
p.AddGroup("Application Options", "The application options", &opts)
inip := NewIniParser(p)
// read INI
inic := `[Application Options]
novalue = some value
value = some other value
`
b := strings.NewReader(inic)
err := inip.Parse(b)
if err == nil {
t.Fatalf("Expected error")
}
iniError := err.(*IniError)
if v := uint(2); iniError.LineNumber != v {
t.Errorf("Expected opts.Add.Name to be %d, but got %d", v, iniError.LineNumber)
}
if v := "unknown option: novalue"; iniError.Message != v {
t.Errorf("Expected opts.Add.Name to be %s, but got %s", v, iniError.Message)
}
// write INI
opts.NoValue = "some value"
opts.Value = "some other value"
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("Cannot create temporary file: %s", err)
}
defer os.Remove(file.Name())
err = inip.WriteFile(file.Name(), IniIncludeDefaults)
if err != nil {
t.Fatalf("Could not write ini file: %s", err)
}
found, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatalf("Could not read written ini file: %s", err)
}
expected := "[Application Options]\nValue = some other value\n\n"
assertDiff(t, string(found), expected, "ini content")
}
func TestIniParse(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("Cannot create temporary file: %s", err)
}
defer os.Remove(file.Name())
_, err = file.WriteString("value = 123")
if err != nil {
t.Fatalf("Cannot write to temporary file: %s", err)
}
file.Close()
var opts struct {
Value int `long:"value"`
}
err = IniParse(file.Name(), &opts)
if err != nil {
t.Fatalf("Could not parse ini: %s", err)
}
if opts.Value != 123 {
t.Fatalf("Expected Value to be \"123\" but was \"%d\"", opts.Value)
}
}
func TestIniCliOverrides(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("Cannot create temporary file: %s", err)
}
defer os.Remove(file.Name())
_, err = file.WriteString("values = 123\n")
_, err = file.WriteString("values = 456\n")
if err != nil {
t.Fatalf("Cannot write to temporary file: %s", err)
}
file.Close()
var opts struct {
Values []int `long:"values"`
}
p := NewParser(&opts, Default)
err = NewIniParser(p).ParseFile(file.Name())
if err != nil {
t.Fatalf("Could not parse ini: %s", err)
}
_, err = p.ParseArgs([]string{"--values", "111", "--values", "222"})
if err != nil {
t.Fatalf("Failed to parse arguments: %s", err)
}
if len(opts.Values) != 2 {
t.Fatalf("Expected Values to contain two elements, but got %d", len(opts.Values))
}
if opts.Values[0] != 111 {
t.Fatalf("Expected Values[0] to be 111, but got '%d'", opts.Values[0])
}
if opts.Values[1] != 222 {
t.Fatalf("Expected Values[1] to be 222, but got '%d'", opts.Values[1])
}
}
func TestIniOverrides(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("Cannot create temporary file: %s", err)
}
defer os.Remove(file.Name())
_, err = file.WriteString("value-with-default = \"ini-value\"\n")
_, err = file.WriteString("value-with-default-override-cli = \"ini-value\"\n")
if err != nil {
t.Fatalf("Cannot write to temporary file: %s", err)
}
file.Close()
var opts struct {
ValueWithDefault string `long:"value-with-default" default:"value"`
ValueWithDefaultOverrideCli string `long:"value-with-default-override-cli" default:"value"`
}
p := NewParser(&opts, Default)
err = NewIniParser(p).ParseFile(file.Name())
if err != nil {
t.Fatalf("Could not parse ini: %s", err)
}
_, err = p.ParseArgs([]string{"--value-with-default-override-cli", "cli-value"})
if err != nil {
t.Fatalf("Failed to parse arguments: %s", err)
}
assertString(t, opts.ValueWithDefault, "ini-value")
assertString(t, opts.ValueWithDefaultOverrideCli, "cli-value")
}
func TestIniRequired(t *testing.T) {
var opts struct {
Required string `short:"r" required:"yes" description:"required"`
Config func(s string) error `long:"config" default:"no-ini-file" no-ini:"true"`
}
p := NewParser(&opts, Default)
opts.Config = func(s string) error {
inip := NewIniParser(p)
inip.ParseAsDefaults = true
return inip.Parse(strings.NewReader("Required = ini-value\n"))
}
_, err := p.ParseArgs([]string{"-r", "cli-value"})
if err != nil {
t.Fatalf("Failed to parse arguments: %s", err)
}
assertString(t, opts.Required, "cli-value")
}
func TestWriteFile(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("Cannot create temporary file: %s", err)
}
defer os.Remove(file.Name())
var opts struct {
Value int `long:"value"`
}
opts.Value = 123
p := NewParser(&opts, Default)
ini := NewIniParser(p)
err = ini.WriteFile(file.Name(), IniIncludeDefaults)
if err != nil {
t.Fatalf("Could not write ini file: %s", err)
}
found, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatalf("Could not read written ini file: %s", err)
}
expected := "[Application Options]\nValue = 123\n\n"
assertDiff(t, string(found), expected, "ini content")
}
func TestOverwriteRequiredOptions(t *testing.T) {
var tests = []struct {
args []string
expected []string
}{
{
args: []string{"--value", "from CLI"},
expected: []string{
"from CLI",
"from default",
},
},
{
args: []string{"--value", "from CLI", "--default", "from CLI"},
expected: []string{
"from CLI",
"from CLI",
},
},
{
args: []string{"--config", "no file name"},
expected: []string{
"from INI",
"from INI",
},
},
{
args: []string{"--value", "from CLI before", "--default", "from CLI before", "--config", "no file name"},
expected: []string{
"from INI",
"from INI",
},
},
{
args: []string{"--value", "from CLI before", "--default", "from CLI before", "--config", "no file name", "--value", "from CLI after", "--default", "from CLI after"},
expected: []string{
"from CLI after",
"from CLI after",
},
},
}
for _, test := range tests {
var opts struct {
Config func(s string) error `long:"config" no-ini:"true"`
Value string `long:"value" required:"true"`
Default string `long:"default" required:"true" default:"from default"`
}
p := NewParser(&opts, Default)
opts.Config = func(s string) error {
ini := NewIniParser(p)
return ini.Parse(bytes.NewBufferString("value = from INI\ndefault = from INI"))
}
_, err := p.ParseArgs(test.args)
if err != nil {
t.Fatalf("Unexpected error %s with args %+v", err, test.args)
}
if opts.Value != test.expected[0] {
t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected[0], opts.Value, test.args)
}
if opts.Default != test.expected[1] {
t.Fatalf("Expected Default to be \"%s\" but was \"%s\" with args %+v", test.expected[1], opts.Default, test.args)
}
}
}
func TestIniOverwriteOptions(t *testing.T) {
var tests = []struct {
args []string
expected string
toggled bool
}{
{
args: []string{},
expected: "from default",
},
{
args: []string{"--value", "from CLI"},
expected: "from CLI",
},
{
args: []string{"--config", "no file name"},
expected: "from INI",
toggled: true,
},
{
args: []string{"--value", "from CLI before", "--config", "no file name"},
expected: "from CLI before",
toggled: true,
},
{
args: []string{"--config", "no file name", "--value", "from CLI after"},
expected: "from CLI after",
toggled: true,
},
{
args: []string{"--toggle"},
toggled: true,
expected: "from default",
},
}
for _, test := range tests {
var opts struct {
Config string `long:"config" no-ini:"true"`
Value string `long:"value" default:"from default"`
Toggle bool `long:"toggle"`
}
p := NewParser(&opts, Default)
_, err := p.ParseArgs(test.args)
if err != nil {
t.Fatalf("Unexpected error %s with args %+v", err, test.args)
}
if opts.Config != "" {
inip := NewIniParser(p)
inip.ParseAsDefaults = true
err = inip.Parse(bytes.NewBufferString("value = from INI\ntoggle = true"))
if err != nil {
t.Fatalf("Unexpected error %s with args %+v", err, test.args)
}
}
if opts.Value != test.expected {
t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected, opts.Value, test.args)
}
if opts.Toggle != test.toggled {
t.Fatalf("Expected Toggle to be \"%v\" but was \"%v\" with args %+v", test.toggled, opts.Toggle, test.args)
}
}
}