mirror of
https://github.com/rclone/rclone.git
synced 2026-06-30 02:45:04 -04:00
The mega backend keeps the whole account as an in-memory tree which go-mega reconciles asynchronously from the server's event stream. After an upload, delete or move, the optimistic local update could be undone moments later when go-mega replayed the corresponding server event, re-adding a node to its parent. In a long-running process such as mount or serve this showed up as a deleted file lingering in directory listings (a "ghost") until the next event poll, or reappearing after a move. Wait for the server to confirm uploads, deletes and moves so the in-memory tree is settled before returning, matching what Rmdir and Purge already do. The move wait was also previously started after the server-side move so only the rename was covered.
64 lines
2.0 KiB
Go
64 lines
2.0 KiB
Go
package mega
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs/object"
|
|
"github.com/rclone/rclone/fstest/fstests"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// InternalTestGhostAfterRemove checks that a file removed in a long-running
|
|
// process disappears from listings straight away rather than lingering as a
|
|
// ghost until go-mega's next event poll reconciles the in-memory tree.
|
|
//
|
|
// The ghost is a race between the upload's server event being replayed and
|
|
// the delete, so it puts, removes and lists in a tight loop to make it show
|
|
// up reliably.
|
|
func (f *Fs) InternalTestGhostAfterRemove(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Exercise the default soft-delete path - the hard_delete=true path
|
|
// hits a separate bug in go-mega's Delete which leaves the node in its
|
|
// parent's children regardless of what rclone does.
|
|
if f.opt.HardDelete {
|
|
oldHardDelete := f.opt.HardDelete
|
|
f.opt.HardDelete = false
|
|
defer func() { f.opt.HardDelete = oldHardDelete }()
|
|
}
|
|
|
|
dir := "ghost-test"
|
|
require.NoError(t, f.Mkdir(ctx, dir))
|
|
defer func() { _ = f.Rmdir(ctx, dir) }()
|
|
|
|
contents := []byte("ghost test contents")
|
|
for i := range 10 {
|
|
remote := dir + "/test.txt"
|
|
src := object.NewStaticObjectInfo(remote, time.Now(), int64(len(contents)), true, nil, nil)
|
|
obj, err := f.Put(ctx, bytes.NewReader(contents), src)
|
|
require.NoError(t, err)
|
|
require.NoError(t, obj.Remove(ctx))
|
|
|
|
// The file must be gone from the listing straight away - if the
|
|
// in-memory tree isn't settled it lingers as a ghost.
|
|
entries, err := f.List(ctx, dir)
|
|
require.NoError(t, err)
|
|
names := make([]string, 0, len(entries))
|
|
for _, entry := range entries {
|
|
names = append(names, entry.Remote())
|
|
}
|
|
assert.NotContains(t, names, remote, "file should be gone immediately after remove (iteration %d)", i)
|
|
}
|
|
}
|
|
|
|
// InternalTest dispatches the backend specific internal tests
|
|
func (f *Fs) InternalTest(t *testing.T) {
|
|
t.Run("GhostAfterRemove", f.InternalTestGhostAfterRemove)
|
|
}
|
|
|
|
var _ fstests.InternalTester = (*Fs)(nil)
|