chore: switch database engine to sqlite (fixes #9954) (#9965)

Switch the database from LevelDB to SQLite, for greater stability and
simpler code.

Co-authored-by: Tommy van der Vorst <tommy@pixelspark.nl>
Co-authored-by: bt90 <btom1990@googlemail.com>
This commit is contained in:
Jakob Borg
2025-03-29 13:50:08 +01:00
committed by GitHub
co-authored by Tommy van der Vorst bt90
parent b1c8f88a44
commit 025905fcdf
146 changed files with 8315 additions and 11984 deletions
+132 -218
View File
@@ -13,6 +13,7 @@ import (
"errors"
"fmt"
"io"
"iter"
mrand "math/rand"
"os"
"path/filepath"
@@ -24,10 +25,10 @@ import (
"testing"
"time"
"github.com/syncthing/syncthing/internal/db"
"github.com/syncthing/syncthing/internal/itererr"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
@@ -77,7 +78,7 @@ func addFolderDevicesToClusterConfig(cc *protocol.ClusterConfig, remote protocol
func TestRequest(t *testing.T) {
wrapper, fcfg, cancel := newDefaultCfgWrapper()
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
defer cancel()
m := setupModel(t, wrapper)
defer cleanupModel(m)
@@ -165,7 +166,7 @@ func BenchmarkIndex_100(b *testing.B) {
func benchmarkIndex(b *testing.B, nfiles int) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(b)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
files := genFiles(nfiles)
must(b, m.Index(device1Conn, &protocol.Index{Folder: fcfg.ID, Files: files}))
@@ -192,7 +193,7 @@ func BenchmarkIndexUpdate_10000_1(b *testing.B) {
func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(b)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
files := genFiles(nfiles)
ufiles := genFiles(nufiles)
@@ -235,7 +236,7 @@ func BenchmarkRequestOut(b *testing.B) {
func BenchmarkRequestInSingleFile(b *testing.B) {
w, cancel := newConfigWrapper(defaultCfg)
defer cancel()
ffs := w.FolderList()[0].Filesystem(nil)
ffs := w.FolderList()[0].Filesystem()
m := setupModel(b, w)
defer cleanupModel(m)
@@ -1195,7 +1196,7 @@ func TestAutoAcceptPrefersLabel(t *testing.T) {
func TestAutoAcceptFallsBackToID(t *testing.T) {
// Prefers label, falls back to ID.
m, cancel := newState(t, defaultAutoAcceptCfg)
ffs := defaultFolderConfig.Filesystem(nil)
ffs := defaultFolderConfig.Filesystem()
id := srand.String(8)
label := srand.String(8)
if err := ffs.MkdirAll(label, 0o777); err != nil {
@@ -1488,7 +1489,7 @@ func changeIgnores(t *testing.T, m *testModel, expected []string) {
func TestIgnores(t *testing.T) {
w, cancel := newConfigWrapper(defaultCfg)
defer cancel()
ffs := w.FolderList()[0].Filesystem(nil)
ffs := w.FolderList()[0].Filesystem()
m := setupModel(t, w)
defer cleanupModel(m)
@@ -1523,7 +1524,7 @@ func TestIgnores(t *testing.T) {
ID: "fresh", Path: "XXX",
FilesystemType: config.FilesystemTypeFake,
}
ignores := ignore.New(fcfg.Filesystem(nil), ignore.WithCache(m.cfg.Options().CacheIgnoredFiles))
ignores := ignore.New(fcfg.Filesystem(), ignore.WithCache(m.cfg.Options().CacheIgnoredFiles))
m.mut.Lock()
m.folderCfgs[fcfg.ID] = fcfg
m.folderIgnores[fcfg.ID] = ignores
@@ -1555,7 +1556,7 @@ func TestIgnores(t *testing.T) {
func TestEmptyIgnores(t *testing.T) {
w, cancel := newConfigWrapper(defaultCfg)
defer cancel()
ffs := w.FolderList()[0].Filesystem(nil)
ffs := w.FolderList()[0].Filesystem()
m := setupModel(t, w)
defer cleanupModel(m)
@@ -1628,12 +1629,11 @@ func TestROScanRecovery(t *testing.T) {
defer cancel()
m := newModel(t, cfg, myID, nil)
set := newFileSet(t, "default", m.db)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
m.sdb.Update("default", protocol.LocalDeviceID, []protocol.FileInfo{
{Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}},
})
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
// Remove marker to generate an error
ffs.Remove(fcfg.MarkerName)
@@ -1675,12 +1675,11 @@ func TestRWScanRecovery(t *testing.T) {
defer cancel()
m := newModel(t, cfg, myID, nil)
set := newFileSet(t, "default", m.db)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
m.sdb.Update("default", protocol.LocalDeviceID, []protocol.FileInfo{
{Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}},
})
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
// Generate error
if err := ffs.Remove(config.DefaultMarkerName); err != nil {
@@ -1706,8 +1705,9 @@ func TestRWScanRecovery(t *testing.T) {
func TestGlobalDirectoryTree(t *testing.T) {
m, conn, fcfg, wCancel := setupModelWithConnection(t)
defer wCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
var seq int64
b := func(isfile bool, path ...string) protocol.FileInfo {
typ := protocol.FileInfoTypeDirectory
var blocks []protocol.BlockInfo
@@ -1716,12 +1716,14 @@ func TestGlobalDirectoryTree(t *testing.T) {
typ = protocol.FileInfoTypeFile
blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
}
seq++
return protocol.FileInfo{
Name: filepath.Join(path...),
Type: typ,
ModifiedS: 0x666,
Blocks: blocks,
Size: 0xa,
Sequence: seq,
}
}
f := func(name string) *TreeEntry {
@@ -1813,13 +1815,13 @@ func TestGlobalDirectoryTree(t *testing.T) {
result, _ := m.GlobalDirectoryTree("default", "", -1, false)
if mm(result) != mm(expectedResult) {
t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(expectedResult))
t.Fatalf("Does not match:\n%s\n============\n%s", mm(result), mm(expectedResult))
}
result, _ = m.GlobalDirectoryTree("default", "another", -1, false)
if mm(result) != mm(findByName(expectedResult, "another").Children) {
t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(findByName(expectedResult, "another").Children))
t.Fatalf("Does not match:\n%s\n============\n%s", mm(result), mm(findByName(expectedResult, "another").Children))
}
result, _ = m.GlobalDirectoryTree("default", "", 0, false)
@@ -1831,7 +1833,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n============\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "", 1, false)
@@ -1852,7 +1854,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "", -1, true)
@@ -1882,7 +1884,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "", 1, true)
@@ -1901,7 +1903,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "another", 0, false)
@@ -1911,7 +1913,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "some/directory", 0, false)
@@ -1920,7 +1922,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "some/directory", 1, false)
@@ -1931,7 +1933,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "some/directory", 2, false)
@@ -1944,7 +1946,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result, _ = m.GlobalDirectoryTree("default", "another", -1, true)
@@ -1957,7 +1959,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
// No prefix matching!
@@ -1965,7 +1967,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
currentResult = []*TreeEntry{}
if mm(result) != mm(currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
}
@@ -2010,7 +2012,7 @@ func BenchmarkTree_100_10(b *testing.B) {
func benchmarkTree(b *testing.B, n1, n2 int) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(b)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
m.ScanFolder(fcfg.ID)
files := genDeepFiles(n1, n2)
@@ -2027,7 +2029,7 @@ func benchmarkTree(b *testing.B, n1, n2 int) {
func TestIssue3028(t *testing.T) {
w, cancel := newConfigWrapper(defaultCfg)
defer cancel()
ffs := w.FolderList()[0].Filesystem(nil)
ffs := w.FolderList()[0].Filesystem()
m := setupModel(t, w)
defer cleanupModel(m)
@@ -2039,8 +2041,8 @@ func TestIssue3028(t *testing.T) {
// Scan, and get a count of how many files are there now
m.ScanFolderSubdirs("default", []string{"testrm", "testrm2"})
locorigfiles := localSize(t, m, "default").Files
globorigfiles := globalSize(t, m, "default").Files
locorigfiles := mustV(m.LocalSize("default", protocol.LocalDeviceID)).Files
globorigfiles := mustV(m.GlobalSize("default")).Files
// Delete
@@ -2051,8 +2053,8 @@ func TestIssue3028(t *testing.T) {
// deleted files increases by two
m.ScanFolderSubdirs("default", []string{"testrm", "testrm2"})
loc := localSize(t, m, "default")
glob := globalSize(t, m, "default")
loc := mustV(m.LocalSize("default", protocol.LocalDeviceID))
glob := mustV(m.GlobalSize("default"))
if loc.Files != locorigfiles-2 {
t.Errorf("Incorrect local accounting; got %d current files, expected %d", loc.Files, locorigfiles-2)
@@ -2127,24 +2129,22 @@ func TestIssue4357(t *testing.T) {
func TestIndexesForUnknownDevicesDropped(t *testing.T) {
m := newModel(t, defaultCfgWrapper, myID, nil)
files := newFileSet(t, "default", m.db)
files.Drop(device1)
files.Update(device1, genFiles(1))
files.Drop(device2)
files.Update(device2, genFiles(1))
m.sdb.DropAllFiles("default", device1)
m.sdb.Update("default", device1, genFiles(1))
m.sdb.DropAllFiles("default", device2)
m.sdb.Update("default", device2, genFiles(1))
if len(files.ListDevices()) != 2 {
if devs, err := m.sdb.ListDevicesForFolder("default"); err != nil || len(devs) != 2 {
t.Log(devs, err)
t.Error("expected two devices")
}
m.newFolder(defaultFolderConfig, false)
defer cleanupModel(m)
// Remote sequence is cached, hence need to recreated.
files = newFileSet(t, "default", m.db)
if l := len(files.ListDevices()); l != 1 {
t.Errorf("Expected one device got %v", l)
if devs, err := m.sdb.ListDevicesForFolder("default"); err != nil || len(devs) != 1 {
t.Log(devs, err)
t.Error("expected one device")
}
}
@@ -2270,7 +2270,7 @@ func TestIssue3829(t *testing.T) {
func TestIssue4573(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
testFs := fcfg.Filesystem(nil)
testFs := fcfg.Filesystem()
defer os.RemoveAll(testFs.URI())
must(t, testFs.MkdirAll("inaccessible", 0o755))
@@ -2300,7 +2300,7 @@ func TestIssue4573(t *testing.T) {
func TestInternalScan(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
testFs := fcfg.Filesystem(nil)
testFs := fcfg.Filesystem()
defer os.RemoveAll(testFs.URI())
testCases := map[string]func(protocol.FileInfo) bool{
@@ -2372,12 +2372,11 @@ func TestCustomMarkerName(t *testing.T) {
})
defer cancel()
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
m := newModel(t, cfg, myID, nil)
set := newFileSet(t, "default", m.db)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
m.sdb.Update("default", protocol.LocalDeviceID, []protocol.FileInfo{
{Name: "dummyfile"},
})
@@ -2401,7 +2400,7 @@ func TestCustomMarkerName(t *testing.T) {
func TestRemoveDirWithContent(t *testing.T) {
m, conn, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
tfs.MkdirAll("dirwith", 0o755)
@@ -2463,7 +2462,7 @@ func TestIssue4475(t *testing.T) {
m, conn, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModel(m)
testFs := fcfg.Filesystem(nil)
testFs := fcfg.Filesystem()
// Scenario: Dir is deleted locally and before syncing/index exchange
// happens, a file is create in that dir on the remote.
@@ -2525,7 +2524,7 @@ func TestVersionRestore(t *testing.T) {
fcfg := newFolderConfiguration(defaultCfgWrapper, "default", "default", config.FilesystemTypeFake, srand.String(32))
fcfg.Versioning.Type = "simple"
fcfg.FSWatcherEnabled = false
filesystem := fcfg.Filesystem(nil)
filesystem := fcfg.Filesystem()
rawConfig := config.Configuration{
Version: config.CurrentVersion,
@@ -2759,7 +2758,7 @@ func TestIssue4094(t *testing.T) {
t.Fatalf("failed setting ignores: %v", err)
}
if _, err := fcfg.Filesystem(nil).Lstat(".stignore"); err != nil {
if _, err := fcfg.Filesystem().Lstat(".stignore"); err != nil {
t.Fatalf("failed stating .stignore: %v", err)
}
}
@@ -2788,7 +2787,7 @@ func TestIssue4903(t *testing.T) {
t.Fatalf("expected path missing error, got: %v, debug: %s", err, fcfg.CheckPath())
}
if _, err := fcfg.Filesystem(nil).Lstat("."); !fs.IsNotExist(err) {
if _, err := fcfg.Filesystem().Lstat("."); !fs.IsNotExist(err) {
t.Fatalf("Expected missing path error, got: %v", err)
}
}
@@ -2798,7 +2797,7 @@ func TestIssue5002(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
fd, err := ffs.Create("foo")
must(t, err)
@@ -2827,7 +2826,7 @@ func TestIssue5002(t *testing.T) {
func TestParentOfUnignored(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
must(t, ffs.Mkdir("bar", 0o755))
must(t, ffs.Mkdir("baz", 0o755))
@@ -2906,7 +2905,7 @@ func TestFolderRestartZombies(t *testing.T) {
func TestRequestLimit(t *testing.T) {
wrapper, fcfg, cancel := newDefaultCfgWrapper()
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
file := "tmpfile"
fd, err := ffs.Create(file)
@@ -2966,7 +2965,7 @@ func TestConnCloseOnRestart(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
m := setupModel(t, w)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
br := &testutil.BlockingRW{}
nw := &testutil.NoopRW{}
@@ -3009,7 +3008,7 @@ func TestModTimeWindow(t *testing.T) {
defer wCancel()
tfs := modtimeTruncatingFS{
trunc: 0,
Filesystem: fcfg.Filesystem(nil),
Filesystem: fcfg.Filesystem(),
}
// fcfg.RawModTimeWindowS = 2
setFolder(t, w, fcfg)
@@ -3069,7 +3068,7 @@ func TestModTimeWindow(t *testing.T) {
func TestDevicePause(t *testing.T) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
sub := m.evLogger.Subscribe(events.DevicePaused)
defer sub.Unsubscribe()
@@ -3099,7 +3098,7 @@ func TestDevicePause(t *testing.T) {
func TestDeviceWasSeen(t *testing.T) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
m.deviceWasSeen(device1)
@@ -3194,7 +3193,7 @@ func TestRenameSequenceOrder(t *testing.T) {
numFiles := 20
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
for i := 0; i < numFiles; i++ {
v := fmt.Sprintf("%d", i)
writeFile(t, ffs, v, []byte(v))
@@ -3202,14 +3201,7 @@ func TestRenameSequenceOrder(t *testing.T) {
m.ScanFolders()
count := 0
snap := dbSnapshot(t, m, "default")
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool {
count++
return true
})
snap.Release()
count := countIterator[protocol.FileInfo](t)(m.LocalFiles("default", protocol.LocalDeviceID))
if count != numFiles {
t.Errorf("Unexpected count: %d != %d", count, numFiles)
}
@@ -3229,14 +3221,11 @@ func TestRenameSequenceOrder(t *testing.T) {
// Scan
m.ScanFolders()
// Verify sequence of a appearing is followed by c disappearing.
snap = dbSnapshot(t, m, "default")
defer snap.Release()
var firstExpectedSequence int64
var secondExpectedSequence int64
failed := false
snap.WithHaveSequence(0, func(i protocol.FileInfo) bool {
it, errFn := m.LocalFilesSequenced("default", protocol.LocalDeviceID, 0)
for i := range it {
t.Log(i)
if i.FileName() == "17" {
firstExpectedSequence = i.SequenceNo() + 1
@@ -3250,8 +3239,10 @@ func TestRenameSequenceOrder(t *testing.T) {
if i.FileName() == "16" {
failed = i.SequenceNo() != secondExpectedSequence || failed
}
return true
})
}
if err := errFn(); err != nil {
t.Fatal(err)
}
if failed {
t.Fail()
}
@@ -3263,19 +3254,12 @@ func TestRenameSameFile(t *testing.T) {
m := setupModel(t, wcfg)
defer cleanupModel(m)
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
writeFile(t, ffs, "file", []byte("file"))
m.ScanFolders()
count := 0
snap := dbSnapshot(t, m, "default")
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool {
count++
return true
})
snap.Release()
count := countIterator[protocol.FileInfo](t)(m.LocalFiles("default", protocol.LocalDeviceID))
if count != 1 {
t.Errorf("Unexpected count: %d != %d", count, 1)
}
@@ -3288,12 +3272,10 @@ func TestRenameSameFile(t *testing.T) {
m.ScanFolders()
snap = dbSnapshot(t, m, "default")
defer snap.Release()
prevSeq := int64(0)
seen := false
snap.WithHaveSequence(0, func(i protocol.FileInfo) bool {
it, errFn := m.LocalFilesSequenced("default", protocol.LocalDeviceID, 0)
for i := range it {
if i.SequenceNo() <= prevSeq {
t.Fatalf("non-increasing sequences: %d <= %d", i.SequenceNo(), prevSeq)
}
@@ -3304,84 +3286,9 @@ func TestRenameSameFile(t *testing.T) {
seen = true
}
prevSeq = i.SequenceNo()
return true
})
}
func TestRenameEmptyFile(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
m := setupModel(t, wcfg)
defer cleanupModel(m)
ffs := fcfg.Filesystem(nil)
writeFile(t, ffs, "file", []byte("data"))
writeFile(t, ffs, "empty", nil)
m.ScanFolders()
snap := dbSnapshot(t, m, "default")
defer snap.Release()
empty, eok := snap.Get(protocol.LocalDeviceID, "empty")
if !eok {
t.Fatal("failed to find empty file")
}
file, fok := snap.Get(protocol.LocalDeviceID, "file")
if !fok {
t.Fatal("failed to find non-empty file")
}
count := 0
snap.WithBlocksHash(empty.BlocksHash, func(_ protocol.FileInfo) bool {
count++
return true
})
if count != 0 {
t.Fatalf("Found %d entries for empty file, expected 0", count)
}
count = 0
snap.WithBlocksHash(file.BlocksHash, func(_ protocol.FileInfo) bool {
count++
return true
})
if count != 1 {
t.Fatalf("Found %d entries for non-empty file, expected 1", count)
}
must(t, ffs.Rename("file", "new-file"))
must(t, ffs.Rename("empty", "new-empty"))
// Scan
m.ScanFolders()
snap = dbSnapshot(t, m, "default")
defer snap.Release()
count = 0
snap.WithBlocksHash(empty.BlocksHash, func(_ protocol.FileInfo) bool {
count++
return true
})
if count != 0 {
t.Fatalf("Found %d entries for empty file, expected 0", count)
}
count = 0
snap.WithBlocksHash(file.BlocksHash, func(i protocol.FileInfo) bool {
count++
if i.FileName() != "new-file" {
t.Fatalf("unexpected file name %s, expected new-file", i.FileName())
}
return true
})
if count != 1 {
t.Fatalf("Found %d entries for non-empty file, expected 1", count)
if err := errFn(); err != nil {
t.Fatal(err)
}
}
@@ -3391,7 +3298,7 @@ func TestBlockListMap(t *testing.T) {
m := setupModel(t, wcfg)
defer cleanupModel(m)
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
writeFile(t, ffs, "one", []byte("content"))
writeFile(t, ffs, "two", []byte("content"))
writeFile(t, ffs, "three", []byte("content"))
@@ -3400,23 +3307,25 @@ func TestBlockListMap(t *testing.T) {
m.ScanFolders()
snap := dbSnapshot(t, m, "default")
defer snap.Release()
fi, ok := snap.Get(protocol.LocalDeviceID, "one")
fi, ok, err := m.model.CurrentFolderFile("default", "one")
if err != nil {
t.Fatal(err)
}
if !ok {
t.Error("failed to find existing file")
}
var paths []string
snap.WithBlocksHash(fi.BlocksHash, func(fi protocol.FileInfo) bool {
paths = append(paths, fi.FileName())
return true
})
snap.Release()
for fi, err := range itererr.Zip(m.model.AllForBlocksHash(fcfg.ID, fi.BlocksHash)) {
if err != nil {
t.Fatal(err)
}
paths = append(paths, fi.Name)
}
expected := []string{"one", "two", "three", "four", "five"}
if !equalStringsInAnyOrder(paths, expected) {
t.Errorf("expected %q got %q", expected, paths)
t.Fatalf("expected %q got %q", expected, paths)
}
// Fudge the files around
@@ -3437,19 +3346,18 @@ func TestBlockListMap(t *testing.T) {
m.ScanFolders()
// Check we're left with 2 of the 5
snap = dbSnapshot(t, m, "default")
defer snap.Release()
paths = paths[:0]
snap.WithBlocksHash(fi.BlocksHash, func(fi protocol.FileInfo) bool {
paths = append(paths, fi.FileName())
return true
})
snap.Release()
for fi, err := range itererr.Zip(m.model.AllForBlocksHash(fcfg.ID, fi.BlocksHash)) {
if err != nil {
t.Fatal(err)
}
paths = append(paths, fi.Name)
}
expected = []string{"new-three", "five"}
if !equalStringsInAnyOrder(paths, expected) {
t.Errorf("expected %q got %q", expected, paths)
t.Fatalf("expected %q got %q", expected, paths)
}
}
@@ -3459,16 +3367,17 @@ func TestScanRenameCaseOnly(t *testing.T) {
m := setupModel(t, wcfg)
defer cleanupModel(m)
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
name := "foo"
writeFile(t, ffs, name, []byte("contents"))
m.ScanFolders()
snap := dbSnapshot(t, m, fcfg.ID)
defer snap.Release()
found := false
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool {
for i, err := range itererr.Zip(m.LocalFiles(fcfg.ID, protocol.LocalDeviceID)) {
if err != nil {
t.Fatal(err)
}
if found {
t.Fatal("got more than one file")
}
@@ -3476,21 +3385,20 @@ func TestScanRenameCaseOnly(t *testing.T) {
t.Fatalf("got file %v, expected %v", i.FileName(), name)
}
found = true
return true
})
snap.Release()
}
upper := strings.ToUpper(name)
must(t, ffs.Rename(name, upper))
m.ScanFolders()
snap = dbSnapshot(t, m, fcfg.ID)
defer snap.Release()
found = false
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool {
for i, err := range itererr.Zip(m.LocalFiles(fcfg.ID, protocol.LocalDeviceID)) {
if err != nil {
t.Fatal(err)
}
if i.FileName() == name {
if i.IsDeleted() {
return true
continue
}
t.Fatal("renamed file not deleted")
}
@@ -3501,8 +3409,7 @@ func TestScanRenameCaseOnly(t *testing.T) {
t.Fatal("got more than the expected files")
}
found = true
return true
})
}
}
func TestClusterConfigOnFolderAdd(t *testing.T) {
@@ -3577,7 +3484,7 @@ func TestAddFolderCompletion(t *testing.T) {
func TestScanDeletedROChangedOnSR(t *testing.T) {
m, conn, fcfg, wCancel := setupModelWithConnection(t)
ffs := fcfg.Filesystem(nil)
ffs := fcfg.Filesystem()
defer wCancel()
defer cleanupModelAndRemoveDir(m, ffs.URI())
fcfg.Type = config.FolderTypeReceiveOnly
@@ -3599,7 +3506,7 @@ func TestScanDeletedROChangedOnSR(t *testing.T) {
must(t, ffs.Remove(name))
m.ScanFolders()
if receiveOnlyChangedSize(t, m, fcfg.ID).Deleted != 1 {
if mustV(m.ReceiveOnlySize(fcfg.ID)).Deleted != 1 {
t.Fatal("expected one receive only changed deleted item")
}
@@ -3607,10 +3514,10 @@ func TestScanDeletedROChangedOnSR(t *testing.T) {
setFolder(t, m.cfg, fcfg)
m.ScanFolders()
if receiveOnlyChangedSize(t, m, fcfg.ID).Deleted != 0 {
if mustV(m.ReceiveOnlySize(fcfg.ID)).Deleted != 0 {
t.Fatal("expected no receive only changed deleted item")
}
if localSize(t, m, fcfg.ID).Deleted != 1 {
if mustV(m.LocalSize(fcfg.ID, protocol.LocalDeviceID)).Deleted != 1 {
t.Fatal("expected one local deleted item")
}
}
@@ -3682,7 +3589,7 @@ func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSec
func TestIssue6961(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
waiter, err := wcfg.Modify(func(cfg *config.Configuration) {
cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2"))
fcfg.Type = config.FolderTypeReceiveOnly
@@ -3693,11 +3600,6 @@ func TestIssue6961(t *testing.T) {
waiter.Wait()
// Always recalc/repair when opening a fileset.
m := newModel(t, wcfg, myID, nil)
m.db.Close()
m.db, err = db.NewLowlevel(backend.OpenMemory(), m.evLogger, db.WithRecheckInterval(time.Millisecond))
if err != nil {
t.Fatal(err)
}
m.ServeBackground()
defer cleanupModelAndRemoveDir(m, tfs.URI())
conn1 := addFakeConn(m, device1, fcfg.ID)
@@ -3752,11 +3654,9 @@ func TestIssue6961(t *testing.T) {
func TestCompletionEmptyGlobal(t *testing.T) {
m, conn, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
files := []protocol.FileInfo{{Name: "foo", Version: protocol.Vector{}.Update(myID.Short()), Sequence: 1}}
m.mut.Lock()
m.folderFiles[fcfg.ID].Update(protocol.LocalDeviceID, files)
m.mut.Unlock()
m.sdb.Update(fcfg.ID, protocol.LocalDeviceID, files)
files[0].Deleted = true
files[0].Version = files[0].Version.Update(device1.Short())
must(t, m.IndexUpdate(conn, &protocol.IndexUpdate{Folder: fcfg.ID, Files: files}))
@@ -3953,7 +3853,7 @@ func TestCCFolderNotRunning(t *testing.T) {
// Create the folder, but don't start it.
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
m := newModel(t, w, myID, nil)
defer cleanupModelAndRemoveDir(m, tfs.URI())
@@ -3990,7 +3890,7 @@ func TestPendingFolder(t *testing.T) {
Time: time.Now().Truncate(time.Second),
Label: pfolder,
}
if err := m.db.AddOrUpdatePendingFolder(pfolder, of, device2); err != nil {
if err := m.observed.AddOrUpdatePendingFolder(pfolder, of, device2); err != nil {
t.Fatal(err)
}
deviceFolders, err := m.PendingFolders(protocol.EmptyDeviceID)
@@ -4009,7 +3909,7 @@ func TestPendingFolder(t *testing.T) {
t.Fatal(err)
}
setDevice(t, w, config.DeviceConfiguration{DeviceID: device3})
if err := m.db.AddOrUpdatePendingFolder(pfolder, of, device3); err != nil {
if err := m.observed.AddOrUpdatePendingFolder(pfolder, of, device3); err != nil {
t.Fatal(err)
}
deviceFolders, err = m.PendingFolders(device2)
@@ -4060,7 +3960,7 @@ func TestDeletedNotLocallyChangedReceiveEncrypted(t *testing.T) {
func deletedNotLocallyChanged(t *testing.T, ft config.FolderType) {
w, fcfg, wCancel := newDefaultCfgWrapper()
tfs := fcfg.Filesystem(nil)
tfs := fcfg.Filesystem()
fcfg.Type = ft
setFolder(t, w, fcfg)
defer wCancel()
@@ -4141,3 +4041,17 @@ type modtimeTruncatingFileInfo struct {
func (fi modtimeTruncatingFileInfo) ModTime() time.Time {
return fi.FileInfo.ModTime().Truncate(fi.trunc)
}
func countIterator[T any](t *testing.T) func(it iter.Seq[T], errFn func() error) int {
return func(it iter.Seq[T], errFn func() error) int {
t.Helper()
count := 0
for range it {
count++
}
if err := errFn(); err != nil {
t.Fatal(err)
}
return count
}
}