123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- // Copyright (C) 2025 The Syncthing Authors.
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- package sqlite
- import (
- "slices"
- "testing"
- "github.com/syncthing/syncthing/lib/config"
- "github.com/syncthing/syncthing/lib/protocol"
- )
- func TestNeed(t *testing.T) {
- t.Helper()
- db, err := OpenTemp()
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Some local files
- var v protocol.Vector
- baseV := v.Update(1)
- newerV := baseV.Update(42)
- files := []protocol.FileInfo{
- genFile("test1", 1, 0), // remote need
- genFile("test2", 2, 0), // local need
- genFile("test3", 3, 0), // global
- }
- files[0].Version = baseV
- files[1].Version = baseV
- files[2].Version = newerV
- err = db.Update(folderID, protocol.LocalDeviceID, files)
- if err != nil {
- t.Fatal(err)
- }
- // Some remote files
- remote := []protocol.FileInfo{
- genFile("test2", 2, 100), // global
- genFile("test3", 3, 101), // remote need
- genFile("test4", 4, 102), // local need
- }
- remote[0].Version = newerV
- remote[1].Version = baseV
- remote[2].Version = newerV
- err = db.Update(folderID, protocol.DeviceID{42}, remote)
- if err != nil {
- t.Fatal(err)
- }
- // A couple are needed locally
- localNeed := fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)))
- if !slices.Equal(localNeed, []string{"test2", "test4"}) {
- t.Log(localNeed)
- t.Fatal("bad local need")
- }
- // Another couple are needed remotely
- remoteNeed := fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0)))
- if !slices.Equal(remoteNeed, []string{"test1", "test3"}) {
- t.Log(remoteNeed)
- t.Fatal("bad remote need")
- }
- }
- func TestDropRecalcsGlobal(t *testing.T) {
- // When we drop a device we may get a new global
- t.Parallel()
- t.Run("DropAllFiles", func(t *testing.T) {
- t.Parallel()
- testDropWithDropper(t, func(t *testing.T, db *DB) {
- t.Helper()
- if err := db.DropAllFiles(folderID, protocol.DeviceID{42}); err != nil {
- t.Fatal(err)
- }
- })
- })
- t.Run("DropDevice", func(t *testing.T) {
- t.Parallel()
- testDropWithDropper(t, func(t *testing.T, db *DB) {
- t.Helper()
- if err := db.DropDevice(protocol.DeviceID{42}); err != nil {
- t.Fatal(err)
- }
- })
- })
- t.Run("DropFilesNamed", func(t *testing.T) {
- t.Parallel()
- testDropWithDropper(t, func(t *testing.T, db *DB) {
- t.Helper()
- if err := db.DropFilesNamed(folderID, protocol.DeviceID{42}, []string{"test1", "test42"}); err != nil {
- t.Fatal(err)
- }
- })
- })
- }
- func testDropWithDropper(t *testing.T, dropper func(t *testing.T, db *DB)) {
- t.Helper()
- db, err := OpenTemp()
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Some local files
- err = db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{
- genFile("test1", 1, 0),
- genFile("test2", 2, 0),
- })
- if err != nil {
- t.Fatal(err)
- }
- // Some remote files
- remote := []protocol.FileInfo{
- genFile("test1", 3, 0),
- }
- remote[0].Version = remote[0].Version.Update(42)
- err = db.Update(folderID, protocol.DeviceID{42}, remote)
- if err != nil {
- t.Fatal(err)
- }
- // Remote test1 wins as the global, verify.
- count, err := db.CountGlobal(folderID)
- if err != nil {
- t.Fatal(err)
- }
- if count.Bytes != (2+3)*128<<10 {
- t.Log(count)
- t.Fatal("bad global size to begin with")
- }
- if g, ok, err := db.GetGlobalFile(folderID, "test1"); err != nil || !ok {
- t.Fatal("missing global to begin with")
- } else if g.Size != 3*128<<10 {
- t.Fatal("remote test1 should be the global")
- }
- // Now remove that remote device
- dropper(t, db)
- // Our test1 should now be the global
- count, err = db.CountGlobal(folderID)
- if err != nil {
- t.Fatal(err)
- }
- if count.Bytes != (1+2)*128<<10 {
- t.Log(count)
- t.Fatal("bad global size after drop")
- }
- if g, ok, err := db.GetGlobalFile(folderID, "test1"); err != nil || !ok {
- t.Fatal("missing global after drop")
- } else if g.Size != 1*128<<10 {
- t.Fatal("local test1 should be the global")
- }
- }
- func TestNeedDeleted(t *testing.T) {
- t.Parallel()
- db, err := OpenTemp()
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Some local files
- err = db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{
- genFile("test1", 1, 0),
- genFile("test2", 2, 0),
- })
- if err != nil {
- t.Fatal(err)
- }
- // A remote deleted file
- remote := []protocol.FileInfo{
- genFile("test1", 1, 101),
- }
- remote[0].SetDeleted(42)
- err = db.Update(folderID, protocol.DeviceID{42}, remote)
- if err != nil {
- t.Fatal(err)
- }
- // We need the one deleted file
- s, err := db.CountNeed(folderID, protocol.LocalDeviceID)
- if err != nil {
- t.Fatal(err)
- }
- if s.Bytes != 0 || s.Deleted != 1 {
- t.Log(s)
- t.Error("bad need")
- }
- }
- func TestDontNeedIgnored(t *testing.T) {
- t.Parallel()
- db, err := OpenTemp()
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // A remote file
- files := []protocol.FileInfo{
- genFile("test1", 1, 103),
- }
- err = db.Update(folderID, protocol.DeviceID{42}, files)
- if err != nil {
- t.Fatal(err)
- }
- // Which we've ignored locally
- files[0].SetIgnored()
- err = db.Update(folderID, protocol.LocalDeviceID, files)
- if err != nil {
- t.Fatal(err)
- }
- // We don't need it
- s, err := db.CountNeed(folderID, protocol.LocalDeviceID)
- if err != nil {
- t.Fatal(err)
- }
- if s.Bytes != 0 || s.Files != 0 {
- t.Log(s)
- t.Error("bad need")
- }
- // It shouldn't show up in the need list
- names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0))
- if len(names) != 0 {
- t.Log(names)
- t.Error("need no files")
- }
- }
- func TestRemoveDontNeedLocalIgnored(t *testing.T) {
- t.Parallel()
- db, err := OpenTemp()
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // A local ignored file
- file := genFile("test1", 1, 103)
- file.SetIgnored()
- files := []protocol.FileInfo{file}
- err = db.Update(folderID, protocol.LocalDeviceID, files)
- if err != nil {
- t.Fatal(err)
- }
- // Which the remote doesn't have (no update)
- // They don't need it
- s, err := db.CountNeed(folderID, protocol.DeviceID{42})
- if err != nil {
- t.Fatal(err)
- }
- if s.Bytes != 0 || s.Files != 0 {
- t.Log(s)
- t.Error("bad need")
- }
- // It shouldn't show up in their need list
- names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0))
- if len(names) != 0 {
- t.Log(names)
- t.Error("need no files")
- }
- }
- func TestLocalDontNeedDeletedMissing(t *testing.T) {
- t.Parallel()
- db, err := OpenTemp()
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // A remote deleted file
- file := genFile("test1", 1, 103)
- file.SetDeleted(42)
- files := []protocol.FileInfo{file}
- err = db.Update(folderID, protocol.DeviceID{42}, files)
- if err != nil {
- t.Fatal(err)
- }
- // Which we don't have (no local update)
- // We don't need it
- s, err := db.CountNeed(folderID, protocol.LocalDeviceID)
- if err != nil {
- t.Fatal(err)
- }
- if s.Bytes != 0 || s.Files != 0 || s.Deleted != 0 {
- t.Log(s)
- t.Error("bad need")
- }
- // It shouldn't show up in the need list
- names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0))
- if len(names) != 0 {
- t.Log(names)
- t.Error("need no files")
- }
- }
- func TestRemoteDontNeedDeletedMissing(t *testing.T) {
- t.Parallel()
- db, err := OpenTemp()
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // A local deleted file
- file := genFile("test1", 1, 103)
- file.SetDeleted(42)
- files := []protocol.FileInfo{file}
- err = db.Update(folderID, protocol.LocalDeviceID, files)
- if err != nil {
- t.Fatal(err)
- }
- // Which the remote doesn't have (no local update)
- // They don't need it
- s, err := db.CountNeed(folderID, protocol.DeviceID{42})
- if err != nil {
- t.Fatal(err)
- }
- if s.Bytes != 0 || s.Files != 0 || s.Deleted != 0 {
- t.Log(s)
- t.Error("bad need")
- }
- // It shouldn't show up in their need list
- names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0))
- if len(names) != 0 {
- t.Log(names)
- t.Error("need no files")
- }
- }
- func TestNeedRemoteSymlinkAndDir(t *testing.T) {
- t.Parallel()
- db, err := OpenTemp()
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Two remote "specials", a symlink and a directory
- var v protocol.Vector
- v.Update(1)
- files := []protocol.FileInfo{
- {Name: "sym", Type: protocol.FileInfoTypeSymlink, Sequence: 100, Version: v, Blocks: genBlocks("symlink", 0, 1)},
- {Name: "dir", Type: protocol.FileInfoTypeDirectory, Sequence: 101, Version: v},
- }
- err = db.Update(folderID, protocol.DeviceID{42}, files)
- if err != nil {
- t.Fatal(err)
- }
- // We need them
- s, err := db.CountNeed(folderID, protocol.LocalDeviceID)
- if err != nil {
- t.Fatal(err)
- }
- if s.Directories != 1 || s.Symlinks != 1 {
- t.Log(s)
- t.Error("bad need")
- }
- // They should be in the need list
- names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0))
- if len(names) != 2 {
- t.Log(names)
- t.Error("bad need")
- }
- }
- func TestNeedPagination(t *testing.T) {
- t.Parallel()
- db, err := OpenTemp()
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Several remote files
- var v protocol.Vector
- v.Update(1)
- files := []protocol.FileInfo{
- genFile("test0", 1, 100),
- genFile("test1", 1, 101),
- genFile("test2", 1, 102),
- genFile("test3", 1, 103),
- genFile("test4", 1, 104),
- genFile("test5", 1, 105),
- genFile("test6", 1, 106),
- genFile("test7", 1, 107),
- genFile("test8", 1, 108),
- genFile("test9", 1, 109),
- }
- err = db.Update(folderID, protocol.DeviceID{42}, files)
- if err != nil {
- t.Fatal(err)
- }
- // We should get the first two
- names := fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 2, 0)))
- if !slices.Equal(names, []string{"test0", "test1"}) {
- t.Log(names)
- t.Error("bad need")
- }
- // We should get the next three
- names = fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 3, 2)))
- if !slices.Equal(names, []string{"test2", "test3", "test4"}) {
- t.Log(names)
- t.Error("bad need")
- }
- // We should get the last five
- names = fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 5, 5)))
- if !slices.Equal(names, []string{"test5", "test6", "test7", "test8", "test9"}) {
- t.Log(names)
- t.Error("bad need")
- }
- }
|