mirror of
https://github.com/rclone/rclone
synced 2024-11-27 05:23:40 +01:00
e43b5ce5e5
This is possible now that we no longer support go1.12 and brings rclone into line with standard practices in the Go world. This also removes errors.New and errors.Errorf from lib/errors and prefers the stdlib errors package over lib/errors.
383 lines
9.6 KiB
Go
383 lines
9.6 KiB
Go
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"
|
|
)
|
|
|
|
func TestFeaturesDisable(t *testing.T) {
|
|
ft := new(Features)
|
|
ft.Copy = func(ctx context.Context, src Object, remote string) (Object, error) {
|
|
return nil, nil
|
|
}
|
|
ft.CaseInsensitive = true
|
|
|
|
assert.NotNil(t, ft.Copy)
|
|
assert.Nil(t, ft.Purge)
|
|
ft.Disable("copy")
|
|
assert.Nil(t, ft.Copy)
|
|
assert.Nil(t, ft.Purge)
|
|
|
|
assert.True(t, ft.CaseInsensitive)
|
|
assert.False(t, ft.DuplicateFiles)
|
|
ft.Disable("caseinsensitive")
|
|
assert.False(t, ft.CaseInsensitive)
|
|
assert.False(t, ft.DuplicateFiles)
|
|
}
|
|
|
|
func TestFeaturesList(t *testing.T) {
|
|
ft := new(Features)
|
|
names := strings.Join(ft.List(), ",")
|
|
assert.True(t, strings.Contains(names, ",Copy,"))
|
|
}
|
|
|
|
func TestFeaturesEnabled(t *testing.T) {
|
|
ft := new(Features)
|
|
ft.CaseInsensitive = true
|
|
ft.Purge = func(ctx context.Context, dir string) error { return nil }
|
|
enabled := ft.Enabled()
|
|
|
|
flag, ok := enabled["CaseInsensitive"]
|
|
assert.Equal(t, true, ok)
|
|
assert.Equal(t, true, flag, enabled)
|
|
|
|
flag, ok = enabled["Purge"]
|
|
assert.Equal(t, true, ok)
|
|
assert.Equal(t, true, flag, enabled)
|
|
|
|
flag, ok = enabled["DuplicateFiles"]
|
|
assert.Equal(t, true, ok)
|
|
assert.Equal(t, false, flag, enabled)
|
|
|
|
flag, ok = enabled["Copy"]
|
|
assert.Equal(t, true, ok)
|
|
assert.Equal(t, false, flag, enabled)
|
|
|
|
assert.Equal(t, len(ft.List()), len(enabled))
|
|
}
|
|
|
|
func TestFeaturesDisableList(t *testing.T) {
|
|
ft := new(Features)
|
|
ft.Copy = func(ctx context.Context, src Object, remote string) (Object, error) {
|
|
return nil, nil
|
|
}
|
|
ft.CaseInsensitive = true
|
|
|
|
assert.NotNil(t, ft.Copy)
|
|
assert.Nil(t, ft.Purge)
|
|
assert.True(t, ft.CaseInsensitive)
|
|
assert.False(t, ft.DuplicateFiles)
|
|
|
|
ft.DisableList([]string{"copy", "caseinsensitive"})
|
|
|
|
assert.Nil(t, ft.Copy)
|
|
assert.Nil(t, ft.Purge)
|
|
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",
|
|
"Help": "",
|
|
"Provider": "",
|
|
"Default": false,
|
|
"Value": true,
|
|
"ShortOpt": "",
|
|
"Hide": 0,
|
|
"Required": false,
|
|
"IsPassword": false,
|
|
"NoPrefix": false,
|
|
"Advanced": true,
|
|
"Exclusive": 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 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]))
|
|
}
|
|
}()
|
|
|
|
fsInfo := &RegInfo{
|
|
Name: "local",
|
|
Prefix: "local",
|
|
Options: testOptions,
|
|
}
|
|
|
|
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{fsInfo}
|
|
|
|
// A configmap.Getter to read either the default value or the set
|
|
// value from the RegInfo.Options
|
|
regInfoValuesGetterFalse := ®InfoValues{
|
|
fsInfo: fsInfo,
|
|
useDefault: false,
|
|
}
|
|
regInfoValuesGetterTrue := ®InfoValues{
|
|
fsInfo: fsInfo,
|
|
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)
|
|
}
|
|
|
|
}
|