diff --git a/fs/fs_test.go b/fs/fs_test.go index 45b16229c..ae28ecaa2 100644 --- a/fs/fs_test.go +++ b/fs/fs_test.go @@ -2,21 +2,9 @@ package fs import ( "context" - "encoding/json" - "errors" - "fmt" - "os" "strings" - "sync" "testing" - "time" - "github.com/spf13/pflag" - "github.com/stretchr/testify/require" - - "github.com/rclone/rclone/fs/config/configmap" - "github.com/rclone/rclone/fs/fserrors" - "github.com/rclone/rclone/lib/pacer" "github.com/stretchr/testify/assert" ) @@ -90,315 +78,3 @@ func TestFeaturesDisableList(t *testing.T) { assert.False(t, ft.CaseInsensitive) assert.False(t, ft.DuplicateFiles) } - -// 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) -} - -var errFoo = errors.New("foo") - -type dummyPaced struct { - retry bool - called int - wait *sync.Cond -} - -func (dp *dummyPaced) fn() (bool, error) { - if dp.wait != nil { - dp.wait.L.Lock() - dp.wait.Wait() - dp.wait.L.Unlock() - } - dp.called++ - return dp.retry, errFoo -} - -func TestPacerCall(t *testing.T) { - ctx := context.Background() - config := GetConfig(ctx) - expectedCalled := config.LowLevelRetries - if expectedCalled == 0 { - ctx, config = AddConfig(ctx) - expectedCalled = 20 - config.LowLevelRetries = expectedCalled - } - p := NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(1*time.Millisecond), pacer.MaxSleep(2*time.Millisecond))) - - dp := &dummyPaced{retry: true} - err := p.Call(dp.fn) - require.Equal(t, expectedCalled, dp.called) - require.Implements(t, (*fserrors.Retrier)(nil), err) -} - -func TestPacerCallNoRetry(t *testing.T) { - p := NewPacer(context.Background(), pacer.NewDefault(pacer.MinSleep(1*time.Millisecond), pacer.MaxSleep(2*time.Millisecond))) - - dp := &dummyPaced{retry: true} - err := p.CallNoRetry(dp.fn) - require.Equal(t, 1, dp.called) - require.Implements(t, (*fserrors.Retrier)(nil), 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) - } - -} diff --git a/fs/pacer_test.go b/fs/pacer_test.go new file mode 100644 index 000000000..77507327a --- /dev/null +++ b/fs/pacer_test.go @@ -0,0 +1,57 @@ +package fs + +import ( + "context" + "errors" + "sync" + "testing" + "time" + + "github.com/rclone/rclone/fs/fserrors" + "github.com/rclone/rclone/lib/pacer" + "github.com/stretchr/testify/require" +) + +var errFoo = errors.New("foo") + +type dummyPaced struct { + retry bool + called int + wait *sync.Cond +} + +func (dp *dummyPaced) fn() (bool, error) { + if dp.wait != nil { + dp.wait.L.Lock() + dp.wait.Wait() + dp.wait.L.Unlock() + } + dp.called++ + return dp.retry, errFoo +} + +func TestPacerCall(t *testing.T) { + ctx := context.Background() + config := GetConfig(ctx) + expectedCalled := config.LowLevelRetries + if expectedCalled == 0 { + ctx, config = AddConfig(ctx) + expectedCalled = 20 + config.LowLevelRetries = expectedCalled + } + p := NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(1*time.Millisecond), pacer.MaxSleep(2*time.Millisecond))) + + dp := &dummyPaced{retry: true} + err := p.Call(dp.fn) + require.Equal(t, expectedCalled, dp.called) + require.Implements(t, (*fserrors.Retrier)(nil), err) +} + +func TestPacerCallNoRetry(t *testing.T) { + p := NewPacer(context.Background(), pacer.NewDefault(pacer.MinSleep(1*time.Millisecond), pacer.MaxSleep(2*time.Millisecond))) + + dp := &dummyPaced{retry: true} + err := p.CallNoRetry(dp.fn) + require.Equal(t, 1, dp.called) + require.Implements(t, (*fserrors.Retrier)(nil), err) +} diff --git a/fs/registry_test.go b/fs/registry_test.go new file mode 100644 index 000000000..0ecaa5c50 --- /dev/null +++ b/fs/registry_test.go @@ -0,0 +1,281 @@ +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) + } + +}