package fs import ( "encoding/json" "fmt" "os" "testing" "github.com/rclone/rclone/fs/config/configmap" "github.com/spf13/pflag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Check it satisfies the interface var _ pflag.Value = (*Option)(nil) func TestOption(t *testing.T) { d := &Option{ Name: "potato", Value: SizeSuffix(17 << 20), } assert.Equal(t, "17Mi", d.String()) assert.Equal(t, "SizeSuffix", d.Type()) err := d.Set("18M") assert.NoError(t, err) assert.Equal(t, SizeSuffix(18<<20), d.Value) err = d.Set("sdfsdf") assert.Error(t, err) } // Test options var ( nouncOption = Option{ Name: "nounc", } copyLinksOption = Option{ Name: "copy_links", Default: false, NoPrefix: true, ShortOpt: "L", Advanced: true, } caseInsensitiveOption = Option{ Name: "case_insensitive", Default: false, Value: true, Advanced: true, } testOptions = Options{nouncOption, copyLinksOption, caseInsensitiveOption} ) func TestOptionsSetValues(t *testing.T) { assert.Nil(t, testOptions[0].Default) assert.Equal(t, false, testOptions[1].Default) assert.Equal(t, false, testOptions[2].Default) testOptions.setValues() assert.Equal(t, "", testOptions[0].Default) assert.Equal(t, false, testOptions[1].Default) assert.Equal(t, false, testOptions[2].Default) } func TestOptionsGet(t *testing.T) { opt := testOptions.Get("copy_links") assert.Equal(t, ©LinksOption, opt) opt = testOptions.Get("not_found") assert.Nil(t, opt) } func TestOptionsOveridden(t *testing.T) { m := configmap.New() m1 := configmap.Simple{ "nounc": "m1", "copy_links": "m1", } m.AddGetter(m1, configmap.PriorityNormal) m2 := configmap.Simple{ "nounc": "m2", "case_insensitive": "m2", } m.AddGetter(m2, configmap.PriorityConfig) m3 := configmap.Simple{ "nounc": "m3", } m.AddGetter(m3, configmap.PriorityDefault) got := testOptions.Overridden(m) assert.Equal(t, configmap.Simple{ "copy_links": "m1", "nounc": "m1", }, got) } func TestOptionsNonDefault(t *testing.T) { m := configmap.Simple{} got := testOptions.NonDefault(m) assert.Equal(t, configmap.Simple{}, got) m["case_insensitive"] = "false" got = testOptions.NonDefault(m) assert.Equal(t, configmap.Simple{}, got) m["case_insensitive"] = "true" got = testOptions.NonDefault(m) assert.Equal(t, configmap.Simple{"case_insensitive": "true"}, got) } func TestOptionMarshalJSON(t *testing.T) { out, err := json.MarshalIndent(&caseInsensitiveOption, "", "") assert.NoError(t, err) require.Equal(t, `{ "Name": "case_insensitive", "FieldName": "", "Help": "", "Default": false, "Value": true, "Hide": 0, "Required": false, "IsPassword": false, "NoPrefix": false, "Advanced": true, "Exclusive": false, "Sensitive": false, "DefaultStr": "false", "ValueStr": "true", "Type": "bool" }`, string(out)) } func TestOptionGetValue(t *testing.T) { assert.Equal(t, "", nouncOption.GetValue()) assert.Equal(t, false, copyLinksOption.GetValue()) assert.Equal(t, true, caseInsensitiveOption.GetValue()) } func TestOptionString(t *testing.T) { assert.Equal(t, "", nouncOption.String()) assert.Equal(t, "false", copyLinksOption.String()) assert.Equal(t, "true", caseInsensitiveOption.String()) } func TestOptionStringStringArray(t *testing.T) { opt := Option{ Name: "string_array", Default: []string(nil), } assert.Equal(t, "", opt.String()) opt.Default = []string{} assert.Equal(t, "", opt.String()) opt.Default = []string{"a", "b"} assert.Equal(t, "a,b", opt.String()) opt.Default = []string{"hello, world!", "goodbye, world!"} assert.Equal(t, `"hello, world!","goodbye, world!"`, opt.String()) } func TestOptionStringSizeSuffix(t *testing.T) { opt := Option{ Name: "size_suffix", Default: SizeSuffix(0), } assert.Equal(t, "0", opt.String()) opt.Default = SizeSuffix(-1) assert.Equal(t, "off", opt.String()) opt.Default = SizeSuffix(100) assert.Equal(t, "100B", opt.String()) opt.Default = SizeSuffix(1024) assert.Equal(t, "1Ki", opt.String()) } func TestOptionSet(t *testing.T) { o := caseInsensitiveOption assert.Equal(t, true, o.Value) err := o.Set("FALSE") assert.NoError(t, err) assert.Equal(t, false, o.Value) o = copyLinksOption assert.Equal(t, nil, o.Value) err = o.Set("True") assert.NoError(t, err) assert.Equal(t, true, o.Value) err = o.Set("INVALID") assert.Error(t, err) assert.Equal(t, true, o.Value) } func TestOptionType(t *testing.T) { assert.Equal(t, "string", nouncOption.Type()) assert.Equal(t, "bool", copyLinksOption.Type()) assert.Equal(t, "bool", caseInsensitiveOption.Type()) } func TestOptionFlagName(t *testing.T) { assert.Equal(t, "local-nounc", nouncOption.FlagName("local")) assert.Equal(t, "copy-links", copyLinksOption.FlagName("local")) assert.Equal(t, "local-case-insensitive", caseInsensitiveOption.FlagName("local")) } func TestOptionEnvVarName(t *testing.T) { assert.Equal(t, "RCLONE_LOCAL_NOUNC", nouncOption.EnvVarName("local")) assert.Equal(t, "RCLONE_LOCAL_COPY_LINKS", copyLinksOption.EnvVarName("local")) assert.Equal(t, "RCLONE_LOCAL_CASE_INSENSITIVE", caseInsensitiveOption.EnvVarName("local")) } func TestOptionGetters(t *testing.T) { // Set up env vars envVars := [][2]string{ {"RCLONE_CONFIG_LOCAL_POTATO_PIE", "yes"}, {"RCLONE_COPY_LINKS", "TRUE"}, {"RCLONE_LOCAL_NOUNC", "NOUNC"}, } for _, ev := range envVars { assert.NoError(t, os.Setenv(ev[0], ev[1])) } defer func() { for _, ev := range envVars { assert.NoError(t, os.Unsetenv(ev[0])) } }() oldConfigFileGet := ConfigFileGet ConfigFileGet = func(section, key string) (string, bool) { if section == "sausage" && key == "key1" { return "value1", true } return "", false } defer func() { ConfigFileGet = oldConfigFileGet }() // set up getters // A configmap.Getter to read from the environment RCLONE_CONFIG_backend_option_name configEnvVarsGetter := configEnvVars("local") // A configmap.Getter to read from the environment RCLONE_option_name optionEnvVarsGetter := optionEnvVars{"local", testOptions} // A configmap.Getter to read either the default value or the set // value from the RegInfo.Options regInfoValuesGetterFalse := ®InfoValues{ options: testOptions, useDefault: false, } regInfoValuesGetterTrue := ®InfoValues{ options: testOptions, useDefault: true, } // A configmap.Setter to read from the config file configFileGetter := getConfigFile("sausage") for i, test := range []struct { get configmap.Getter key string wantValue string wantOk bool }{ {configEnvVarsGetter, "not_found", "", false}, {configEnvVarsGetter, "potato_pie", "yes", true}, {optionEnvVarsGetter, "not_found", "", false}, {optionEnvVarsGetter, "copy_links", "TRUE", true}, {optionEnvVarsGetter, "nounc", "NOUNC", true}, {optionEnvVarsGetter, "case_insensitive", "", false}, {regInfoValuesGetterFalse, "not_found", "", false}, {regInfoValuesGetterFalse, "case_insensitive", "true", true}, {regInfoValuesGetterFalse, "copy_links", "", false}, {regInfoValuesGetterTrue, "not_found", "", false}, {regInfoValuesGetterTrue, "case_insensitive", "true", true}, {regInfoValuesGetterTrue, "copy_links", "false", true}, {configFileGetter, "not_found", "", false}, {configFileGetter, "key1", "value1", true}, } { what := fmt.Sprintf("%d: %+v: %q", i, test.get, test.key) gotValue, gotOk := test.get.Get(test.key) assert.Equal(t, test.wantValue, gotValue, what) assert.Equal(t, test.wantOk, gotOk, what) } } func TestOptionsNonDefaultRC(t *testing.T) { type cfg struct { X string `config:"x"` Y int `config:"y"` } c := &cfg{X: "a", Y: 6} opts := Options{ {Name: "x", Default: "a"}, // at default, should be omitted {Name: "y", Default: 5}, // non-default, should be included } got, err := opts.NonDefaultRC(c) require.NoError(t, err) require.Equal(t, map[string]any{"Y": 6}, got) } func TestOptionsNonDefaultRCMissingKey(t *testing.T) { type cfg struct { X string `config:"x"` } c := &cfg{X: "a"} // Options refers to a key not present in the struct -> expect error opts := Options{{Name: "missing", Default: ""}} _, err := opts.NonDefaultRC(c) assert.ErrorContains(t, err, "not found") }