build / windows (push) Canceled after 0s
build / other_os (push) Canceled after 0s
build / mac_amd64 (push) Canceled after 0s
build / mac_arm64 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/386 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/amd64 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/arm/v6 (push) Canceled after 0s
build / linux (push) Canceled after 0s
build / go1.26 (push) Canceled after 0s
build / linux_386 (push) Canceled after 0s
build / lint (push) Canceled after 0s
build / android-all (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/arm/v7 (push) Canceled after 0s
Build & Push Docker Images / Build Docker Image for linux/arm64 (push) Canceled after 0s
Build & Push Docker Images / Merge & Push Final Docker Image (push) Canceled after 0s
--max-delete-files and --max-delete-size are independent of the percent cap. --session-name shares listings/locks across OS path strings.
160 lines
5.2 KiB
Go
160 lines
5.2 KiB
Go
package bisync
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs/hash"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func testListing() *fileList {
|
|
ls := newFileList()
|
|
ls.hash = hash.MD5
|
|
t0 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
ls.put("keep.txt", 10, t0, "aaa", "", "-")
|
|
ls.put("gone.txt", 20, t0, "bbb", "", "-")
|
|
ls.put("dir/a.txt", 30, t0, "ccc", "", "-")
|
|
ls.put("dir/b.txt", 40, t0, "ddd", "", "-")
|
|
ls.put("dir", 0, t0, "", "", "d")
|
|
return ls
|
|
}
|
|
|
|
func TestApplyListingDeltas_UpsertAndDelete(t *testing.T) {
|
|
ls := testListing()
|
|
t1 := time.Date(2024, 2, 1, 0, 0, 0, 0, time.UTC)
|
|
err := applyListingDeltas(ls, []listingDelta{
|
|
{Remote: "keep.txt", Size: 11, ModTime: t1, Hash: "zzz", Flags: "-"},
|
|
{Remote: "new.txt", Size: 5, ModTime: t1, Hash: "eee", Flags: "-"},
|
|
{Remote: "gone.txt", Deleted: true},
|
|
}, deltaApplyOpts{MaxDelete: 50})
|
|
require.NoError(t, err)
|
|
assert.True(t, ls.has("keep.txt"))
|
|
assert.Equal(t, int64(11), ls.getSize("keep.txt"))
|
|
assert.Equal(t, "zzz", ls.getHash("keep.txt"))
|
|
assert.True(t, ls.has("new.txt"))
|
|
assert.False(t, ls.has("gone.txt"))
|
|
assert.True(t, ls.has("dir/a.txt"))
|
|
}
|
|
|
|
func TestApplyListingDeltas_FolderDeleteRemovesChildren(t *testing.T) {
|
|
ls := testListing()
|
|
err := applyListingDeltas(ls, []listingDelta{
|
|
{Remote: "dir", Deleted: true, Flags: "d"},
|
|
}, deltaApplyOpts{MaxDelete: 90})
|
|
require.NoError(t, err)
|
|
assert.False(t, ls.has("dir"))
|
|
assert.False(t, ls.has("dir/a.txt"))
|
|
assert.False(t, ls.has("dir/b.txt"))
|
|
assert.True(t, ls.has("keep.txt"))
|
|
}
|
|
|
|
func TestApplyListingDeltas_MaxDeleteAbortsWithoutMutation(t *testing.T) {
|
|
ls := testListing()
|
|
orig := len(ls.list)
|
|
err := applyListingDeltas(ls, []listingDelta{
|
|
{Remote: "keep.txt", Deleted: true},
|
|
{Remote: "gone.txt", Deleted: true},
|
|
{Remote: "dir/a.txt", Deleted: true},
|
|
}, deltaApplyOpts{MaxDelete: 50})
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, errDeltaTooManyDeletes))
|
|
assert.Equal(t, orig, len(ls.list))
|
|
assert.True(t, ls.has("keep.txt"))
|
|
assert.True(t, ls.has("gone.txt"))
|
|
}
|
|
|
|
func TestApplyListingDeltas_ForceBypassesMaxDelete(t *testing.T) {
|
|
ls := testListing()
|
|
err := applyListingDeltas(ls, []listingDelta{
|
|
{Remote: "keep.txt", Deleted: true},
|
|
{Remote: "gone.txt", Deleted: true},
|
|
{Remote: "dir/a.txt", Deleted: true},
|
|
}, deltaApplyOpts{MaxDelete: 50, Force: true})
|
|
require.NoError(t, err)
|
|
assert.False(t, ls.has("keep.txt"))
|
|
}
|
|
|
|
func TestApplyListingDeltas_ResetLeavesListingUntouched(t *testing.T) {
|
|
ls := testListing()
|
|
orig := len(ls.list)
|
|
err := applyListingDeltas(ls, []listingDelta{
|
|
{Remote: "keep.txt", Deleted: true},
|
|
}, deltaApplyOpts{Reset: true, MaxDelete: 50})
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, errDeltaCursorReset))
|
|
assert.Equal(t, orig, len(ls.list))
|
|
assert.True(t, ls.has("keep.txt"))
|
|
}
|
|
|
|
func TestAtomicCursorAndListing(t *testing.T) {
|
|
dir := t.TempDir()
|
|
listing := filepath.Join(dir, "path2.lst")
|
|
ls := testListing()
|
|
require.NoError(t, saveListingAndCursor(listing, "CUR1", "/Dropbox", ls))
|
|
got, root, err := loadCursor(listing)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "CUR1", got)
|
|
assert.Equal(t, "/Dropbox", root)
|
|
ls2, err := loadListingFile(listing)
|
|
require.NoError(t, err)
|
|
assert.True(t, ls2.has("keep.txt"))
|
|
|
|
require.NoError(t, saveListingAndCursor(listing, "CUR2", "/Dropbox", ls))
|
|
got, _, err = loadCursor(listing)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "CUR2", got)
|
|
}
|
|
|
|
func TestLoadCursor_MissingIsEmpty(t *testing.T) {
|
|
dir := t.TempDir()
|
|
listing := filepath.Join(dir, "nope.lst")
|
|
got, root, err := loadCursor(listing)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, got)
|
|
assert.Empty(t, root)
|
|
_, err = os.Stat(cursorPath(listing))
|
|
assert.True(t, os.IsNotExist(err))
|
|
}
|
|
|
|
func TestApplyListingDeltas_MaxDeleteFilesAborts(t *testing.T) {
|
|
ls := testListing()
|
|
orig := len(ls.list)
|
|
err := applyListingDeltas(ls, []listingDelta{
|
|
{Remote: "gone.txt", Deleted: true},
|
|
{Remote: "keep.txt", Deleted: true},
|
|
}, deltaApplyOpts{MaxDelete: 100, MaxDeleteFiles: 1})
|
|
require.ErrorIs(t, err, errDeltaTooManyDeletes)
|
|
assert.Equal(t, orig, len(ls.list))
|
|
}
|
|
|
|
func TestGuardFileCount_MaxDeleteFiles(t *testing.T) {
|
|
require.ErrorIs(t, guardFileCount(100, 89, deltaApplyOpts{MaxDelete: 50, MaxDeleteFiles: 10}), errDeltaListingCollapsed)
|
|
require.NoError(t, guardFileCount(100, 95, deltaApplyOpts{MaxDelete: 50, MaxDeleteFiles: 10}))
|
|
}
|
|
|
|
func TestDeleteBudgetExceeded(t *testing.T) {
|
|
opt := &Options{MaxDelete: 50, MaxDeleteFiles: 10, MaxDeleteSize: 100}
|
|
hit, _ := deleteBudgetExceeded(opt, 1000, 11, 0)
|
|
require.True(t, hit)
|
|
hit, _ = deleteBudgetExceeded(opt, 1000, 1, 200)
|
|
require.True(t, hit)
|
|
hit, _ = deleteBudgetExceeded(&Options{MaxDelete: 1, MaxDeleteFiles: 100}, 1000, 20, 0)
|
|
require.True(t, hit)
|
|
hit, _ = deleteBudgetExceeded(&Options{MaxDelete: 50, MaxDeleteFiles: 10, Force: true}, 1000, 500, 1e12)
|
|
require.False(t, hit)
|
|
}
|
|
|
|
func TestGuardFileCount(t *testing.T) {
|
|
opt := deltaApplyOpts{MaxDelete: 50}
|
|
require.NoError(t, guardFileCount(0, 0, opt))
|
|
require.NoError(t, guardFileCount(100, 80, opt))
|
|
require.ErrorIs(t, guardFileCount(100, 0, opt), errDeltaListingEmptied)
|
|
require.ErrorIs(t, guardFileCount(100, 40, opt), errDeltaListingCollapsed)
|
|
require.NoError(t, guardFileCount(100, 0, deltaApplyOpts{MaxDelete: 50, Force: true}))
|
|
}
|