mirror of
https://github.com/rclone/rclone.git
synced 2026-07-13 09:12:31 -04:00
The test set the short idle timeout before creating the test Fs, which made fs.NewFs fail to read the FTP welcome banner within 1s on slow CI hosts. Restore the long timeout while NewFs dials the control connection, then apply the short idle timeout before the upload so the data connection still exercises the close race that shut_timeout fixes.
124 lines
3.6 KiB
Go
124 lines
3.6 KiB
Go
package ftp
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/config/configmap"
|
|
"github.com/rclone/rclone/fs/object"
|
|
"github.com/rclone/rclone/fstest"
|
|
"github.com/rclone/rclone/fstest/fstests"
|
|
"github.com/rclone/rclone/lib/readers"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type settings map[string]any
|
|
|
|
func deriveFs(ctx context.Context, t *testing.T, f fs.Fs, opts settings) fs.Fs {
|
|
fsName := strings.Split(f.Name(), "{")[0] // strip off hash
|
|
configMap := configmap.Simple{}
|
|
for key, val := range opts {
|
|
configMap[key] = fmt.Sprintf("%v", val)
|
|
}
|
|
remote := fmt.Sprintf("%s,%s:%s", fsName, configMap.String(), f.Root())
|
|
fixFs, err := fs.NewFs(ctx, remote)
|
|
require.NoError(t, err)
|
|
return fixFs
|
|
}
|
|
|
|
// test that big file uploads do not cause network i/o timeout
|
|
func (f *Fs) testUploadTimeout(t *testing.T) {
|
|
const (
|
|
fileSize = 100000000 // 100 MiB
|
|
idleTimeout = 1 * time.Second // small because test server is local
|
|
maxTime = 10 * time.Second // prevent test hangup
|
|
)
|
|
|
|
if testing.Short() {
|
|
t.Skip("not running with -short")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
ci := fs.GetConfig(ctx)
|
|
saveLowLevelRetries := ci.LowLevelRetries
|
|
saveTimeout := ci.Timeout
|
|
defer func() {
|
|
ci.LowLevelRetries = saveLowLevelRetries
|
|
ci.Timeout = saveTimeout
|
|
}()
|
|
ci.LowLevelRetries = 1
|
|
|
|
upload := func(concurrency int, shutTimeout time.Duration) (obj fs.Object, err error) {
|
|
// Use the saved (long) timeout while NewFs dials the FTP control
|
|
// connection and reads the welcome banner. On slow CI servers this
|
|
// can take longer than idleTimeout, which would cause a spurious
|
|
// i/o timeout before the test can run. The connection's idle
|
|
// deadline is captured at dial time, so the data connection
|
|
// dialled later inside Put will still get idleTimeout - which is
|
|
// what the test is actually trying to exercise.
|
|
ci.Timeout = saveTimeout
|
|
fixFs := deriveFs(ctx, t, f, settings{
|
|
"concurrency": concurrency,
|
|
"shut_timeout": shutTimeout,
|
|
})
|
|
ci.Timeout = fs.Duration(idleTimeout)
|
|
|
|
// Make test object
|
|
fileTime := fstest.Time("2020-03-08T09:30:00.000000000Z")
|
|
meta := object.NewStaticObjectInfo("upload-timeout.test", fileTime, int64(fileSize), true, nil, nil)
|
|
data := readers.NewPatternReader(int64(fileSize))
|
|
|
|
// Run upload and ensure maximum time
|
|
done := make(chan bool)
|
|
deadline := time.After(maxTime)
|
|
go func() {
|
|
obj, err = fixFs.Put(ctx, data, meta)
|
|
done <- true
|
|
}()
|
|
select {
|
|
case <-done:
|
|
case <-deadline:
|
|
t.Fatalf("Upload got stuck for %v !", maxTime)
|
|
}
|
|
|
|
return obj, err
|
|
}
|
|
|
|
// non-zero shut_timeout should fix i/o errors
|
|
obj, err := upload(f.opt.Concurrency, time.Second)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, obj)
|
|
if obj != nil {
|
|
_ = obj.Remove(ctx)
|
|
}
|
|
}
|
|
|
|
// rclone must support precise time with ProFtpd and PureFtpd out of the box.
|
|
// The VsFtpd server does not support the MFMT command to set file time like
|
|
// other servers but by default supports the MDTM command in the non-standard
|
|
// two-argument form for the same purpose.
|
|
// See "mdtm_write" in https://security.appspot.com/vsftpd/vsftpd_conf.html
|
|
func (f *Fs) testTimePrecision(t *testing.T) {
|
|
name := f.Name()
|
|
if pos := strings.Index(name, "{"); pos != -1 {
|
|
name = name[:pos]
|
|
}
|
|
switch name {
|
|
case "TestFTPProftpd", "TestFTPPureftpd", "TestFTPVsftpd":
|
|
assert.LessOrEqual(t, f.Precision(), time.Second)
|
|
}
|
|
}
|
|
|
|
// InternalTest dispatches all internal tests
|
|
func (f *Fs) InternalTest(t *testing.T) {
|
|
t.Run("UploadTimeout", f.testUploadTimeout)
|
|
t.Run("TimePrecision", f.testTimePrecision)
|
|
}
|
|
|
|
var _ fstests.InternalTester = (*Fs)(nil)
|