| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276 |
- // 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 (
- "context"
- "crypto/sha256"
- "encoding/binary"
- "errors"
- "iter"
- "os"
- "path"
- "path/filepath"
- "sync"
- "testing"
- "time"
- "github.com/syncthing/syncthing/internal/db"
- "github.com/syncthing/syncthing/internal/itererr"
- "github.com/syncthing/syncthing/internal/timeutil"
- "github.com/syncthing/syncthing/lib/build"
- "github.com/syncthing/syncthing/lib/config"
- "github.com/syncthing/syncthing/lib/protocol"
- )
- const (
- folderID = "test"
- blockSize = 128 << 10
- dirSize = 128
- )
- func TestBasics(t *testing.T) {
- t.Parallel()
- sdb, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := sdb.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Some local files
- local := []protocol.FileInfo{
- genFile("test1", 1, 0),
- genDir("test2", 0),
- genFile("test2/a", 2, 0),
- genFile("test2/b", 3, 0),
- }
- err = sdb.Update(folderID, protocol.LocalDeviceID, local)
- if err != nil {
- t.Fatal(err)
- }
- // Some remote files
- remote := []protocol.FileInfo{
- genFile("test3", 3, 101),
- genFile("test4", 4, 102),
- genFile("test1", 5, 103),
- }
- // All newer than the local ones
- for i := range remote {
- remote[i].Version = remote[i].Version.Update(42)
- }
- err = sdb.Update(folderID, protocol.DeviceID{42}, remote)
- if err != nil {
- t.Fatal(err)
- }
- const (
- localSize = (1+2+3)*blockSize + dirSize
- remoteSize = (3 + 4 + 5) * blockSize
- globalSize = (2+3+3+4+5)*blockSize + dirSize
- needSizeLocal = remoteSize
- needSizeRemote = (2+3)*blockSize + dirSize
- )
- t.Run("SchemaVersion", func(t *testing.T) {
- ver, err := sdb.getAppliedSchemaVersion()
- if err != nil {
- t.Fatal(err)
- }
- if ver.SchemaVersion != currentSchemaVersion {
- t.Log(ver)
- t.Error("should be version 1")
- }
- if d := time.Since(ver.AppliedTime()); d > time.Minute || d < 0 {
- t.Log(ver)
- t.Error("suspicious applied tim")
- }
- })
- t.Run("Local", func(t *testing.T) {
- t.Parallel()
- fi, ok, err := sdb.GetDeviceFile(folderID, protocol.LocalDeviceID, "test2/a") // exists
- if err != nil {
- t.Fatal(err)
- }
- if !ok {
- t.Fatal("not found")
- }
- if fi.Name != filepath.FromSlash("test2/a") {
- t.Fatal("should have got test2/a")
- }
- if len(fi.Blocks) != 2 {
- t.Fatal("expected two blocks")
- }
- _, ok, err = sdb.GetDeviceFile(folderID, protocol.LocalDeviceID, "test3") // does not exist
- if err != nil {
- t.Fatal(err)
- }
- if ok {
- t.Fatal("should be not found")
- }
- })
- t.Run("Global", func(t *testing.T) {
- t.Parallel()
- fi, ok, err := sdb.GetGlobalFile(folderID, "test1")
- if err != nil {
- t.Fatal(err)
- }
- if !ok {
- t.Fatal("not found")
- }
- if fi.Size != 5*blockSize {
- t.Fatal("should be the remote file")
- }
- })
- t.Run("AllLocal", func(t *testing.T) {
- t.Parallel()
- have := mustCollect[protocol.FileInfo](t)(sdb.AllLocalFiles(folderID, protocol.LocalDeviceID))
- if len(have) != 4 {
- t.Log(have)
- t.Error("expected four files")
- }
- have = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFiles(folderID, protocol.DeviceID{42}))
- if len(have) != 3 {
- t.Log(have)
- t.Error("expected three files")
- }
- })
- t.Run("AllNeededNamesLocal", func(t *testing.T) {
- t.Parallel()
- need := fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)))
- if len(need) != 3 || need[0] != "test1" {
- t.Log(need)
- t.Error("expected three files, ordered alphabetically")
- }
- need = fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 1, 0)))
- if len(need) != 1 || need[0] != "test1" {
- t.Log(need)
- t.Error("expected one file, limited, ordered alphabetically")
- }
- need = fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderLargestFirst, 0, 0)))
- if len(need) != 3 || need[0] != "test1" { // largest
- t.Log(need)
- t.Error("expected three files, ordered largest to smallest")
- }
- need = fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderSmallestFirst, 0, 0)))
- if len(need) != 3 || need[0] != "test3" { // smallest
- t.Log(need)
- t.Error("expected three files, ordered smallest to largest")
- }
- need = fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderNewestFirst, 0, 0)))
- if len(need) != 3 || need[0] != "test1" { // newest
- t.Log(need)
- t.Error("expected three files, ordered newest to oldest")
- }
- need = fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderOldestFirst, 0, 0)))
- if len(need) != 3 || need[0] != "test3" { // oldest
- t.Log(need)
- t.Error("expected three files, ordered oldest to newest")
- }
- })
- t.Run("LocalSize", func(t *testing.T) {
- t.Parallel()
- // Local device
- c, err := sdb.CountLocal(folderID, protocol.LocalDeviceID)
- if err != nil {
- t.Fatal(err)
- }
- if c.Files != 3 {
- t.Log(c)
- t.Error("one file expected")
- }
- if c.Directories != 1 {
- t.Log(c)
- t.Error("one directory expected")
- }
- if c.Bytes != localSize {
- t.Log(c)
- t.Error("size unexpected")
- }
- // Other device
- c, err = sdb.CountLocal(folderID, protocol.DeviceID{42})
- if err != nil {
- t.Fatal(err)
- }
- if c.Files != 3 {
- t.Log(c)
- t.Error("three files expected")
- }
- if c.Directories != 0 {
- t.Log(c)
- t.Error("no directories expected")
- }
- if c.Bytes != remoteSize {
- t.Log(c)
- t.Error("size unexpected")
- }
- })
- t.Run("GlobalSize", func(t *testing.T) {
- t.Parallel()
- c, err := sdb.CountGlobal(folderID)
- if err != nil {
- t.Fatal(err)
- }
- if c.Files != 5 {
- t.Log(c)
- t.Error("five files expected")
- }
- if c.Directories != 1 {
- t.Log(c)
- t.Error("one directory expected")
- }
- if c.Bytes != int64(globalSize) {
- t.Log(c)
- t.Error("size unexpected")
- }
- })
- t.Run("NeedSizeLocal", func(t *testing.T) {
- t.Parallel()
- c, err := sdb.CountNeed(folderID, protocol.LocalDeviceID)
- if err != nil {
- t.Fatal(err)
- }
- if c.Files != 3 {
- t.Log(c)
- t.Error("three files expected")
- }
- if c.Directories != 0 {
- t.Log(c)
- t.Error("no directories expected")
- }
- if c.Bytes != needSizeLocal {
- t.Log(c)
- t.Error("size unexpected")
- }
- })
- t.Run("NeedSizeRemote", func(t *testing.T) {
- t.Parallel()
- c, err := sdb.CountNeed(folderID, protocol.DeviceID{42})
- if err != nil {
- t.Fatal(err)
- }
- if c.Files != 2 {
- t.Log(c)
- t.Error("two files expected")
- }
- if c.Directories != 1 {
- t.Log(c)
- t.Error("one directory expected")
- }
- if c.Bytes != needSizeRemote {
- t.Log(c)
- t.Error("size unexpected")
- }
- })
- t.Run("Folders", func(t *testing.T) {
- t.Parallel()
- folders, err := sdb.ListFolders()
- if err != nil {
- t.Fatal(err)
- }
- if len(folders) != 1 || folders[0] != folderID {
- t.Log(folders)
- t.Error("expected one folder")
- }
- })
- t.Run("DevicesForFolder", func(t *testing.T) {
- t.Parallel()
- devs, err := sdb.ListDevicesForFolder("test")
- if err != nil {
- t.Fatal(err)
- }
- if len(devs) != 1 || devs[0] != (protocol.DeviceID{42}) {
- t.Log(devs)
- t.Error("expected one device")
- }
- })
- t.Run("Sequence", func(t *testing.T) {
- t.Parallel()
- iid, err := sdb.GetIndexID(folderID, protocol.LocalDeviceID)
- if err != nil {
- t.Fatal(err)
- }
- if iid == 0 {
- t.Log(iid)
- t.Fatal("expected index ID")
- }
- if seq, err := sdb.GetDeviceSequence(folderID, protocol.LocalDeviceID); err != nil {
- t.Fatal(err)
- } else if seq != 4 {
- t.Log(seq)
- t.Error("expected local sequence to match number of files inserted")
- }
- if seq, err := sdb.GetDeviceSequence(folderID, protocol.DeviceID{42}); err != nil {
- t.Fatal(err)
- } else if seq != 103 {
- t.Log(seq)
- t.Error("expected remote sequence to match highest sent")
- }
- // Non-existent should be zero and no error
- if seq, err := sdb.GetDeviceSequence("trolol", protocol.LocalDeviceID); err != nil {
- t.Fatal(err)
- } else if seq != 0 {
- t.Log(seq)
- t.Error("expected zero sequence")
- }
- if seq, err := sdb.GetDeviceSequence("trolol", protocol.DeviceID{42}); err != nil {
- t.Fatal(err)
- } else if seq != 0 {
- t.Log(seq)
- t.Error("expected zero sequence")
- }
- if seq, err := sdb.GetDeviceSequence(folderID, protocol.DeviceID{99}); err != nil {
- t.Fatal(err)
- } else if seq != 0 {
- t.Log(seq)
- t.Error("expected zero sequence")
- }
- })
- t.Run("AllGlobalPrefix", func(t *testing.T) {
- t.Parallel()
- vals := mustCollect[db.FileMetadata](t)(sdb.AllGlobalFilesPrefix(folderID, "test2"))
- // Vals should be test2, test2/a, test2/b
- if len(vals) != 3 {
- t.Log(vals)
- t.Error("expected three items")
- } else if vals[0].Name != "test2" {
- t.Error(vals)
- }
- // Empty prefix should be all the files
- vals = mustCollect[db.FileMetadata](t)(sdb.AllGlobalFilesPrefix(folderID, ""))
- if len(vals) != 6 {
- t.Log(vals)
- t.Error("expected six items")
- }
- })
- t.Run("AllLocalPrefix", func(t *testing.T) {
- t.Parallel()
- vals := mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "test2"))
- // Vals should be test2, test2/a, test2/b
- if len(vals) != 3 {
- t.Log(vals)
- t.Error("expected three items")
- } else if vals[0].Name != "test2" {
- t.Error(vals)
- }
- // Empty prefix should be all the files
- vals = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, ""))
- if len(vals) != 4 {
- t.Log(vals)
- t.Error("expected four items")
- }
- })
- t.Run("AllLocalSequenced", func(t *testing.T) {
- t.Parallel()
- vals := mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesBySequence(folderID, protocol.LocalDeviceID, 3, 0))
- // Vals should be test2/a, test2/b
- if len(vals) != 2 {
- t.Log(vals)
- t.Error("expected three items")
- } else if vals[0].Name != filepath.FromSlash("test2/a") || vals[0].Sequence != 3 {
- t.Error(vals)
- }
- })
- }
- func TestPrefixGlobbing(t *testing.T) {
- t.Parallel()
- sdb, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := sdb.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Some local files
- local := []protocol.FileInfo{
- genFile("test1", 1, 0),
- genDir("test2", 0),
- genFile("test2/a", 2, 0),
- genDir("test2/b", 0),
- genFile("test2/b/c", 3, 0),
- }
- err = sdb.Update(folderID, protocol.LocalDeviceID, local)
- if err != nil {
- t.Fatal(err)
- }
- vals := mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "test2"))
- // Vals should be test2, test2/a, test2/b, test2/b/c
- if len(vals) != 4 {
- t.Log(vals)
- t.Error("expected four items")
- } else if vals[0].Name != "test2" || vals[3].Name != filepath.FromSlash("test2/b/c") {
- t.Error(vals)
- }
- // Empty prefix should be all the files
- vals = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, ""))
- if len(vals) != 5 {
- t.Log(vals)
- t.Error("expected five items")
- }
- // Same as partial prefix
- vals = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "tes"))
- if len(vals) != 5 {
- t.Log(vals)
- t.Error("expected five items")
- }
- // Prefix should be case sensitive, so no match here
- vals = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "tEsT2"))
- if len(vals) != 0 {
- t.Log(vals)
- t.Error("expected no items")
- }
- // Subdir should match
- vals = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "test2/b"))
- if len(vals) != 2 {
- t.Log(vals)
- t.Error("expected two items")
- }
- }
- func TestPrefixGlobbingStar(t *testing.T) {
- t.Parallel()
- sdb, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := sdb.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Some local files
- local := []protocol.FileInfo{
- genFile("test1a", 1, 0),
- genFile("test*a", 2, 0),
- genFile("test2a", 3, 0),
- }
- err = sdb.Update(folderID, protocol.LocalDeviceID, local)
- if err != nil {
- t.Fatal(err)
- }
- vals := mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "test*a"))
- // Vals should be test*a
- if len(vals) != 1 {
- t.Log(vals)
- t.Error("expected one item")
- } else if vals[0].Name != "test*a" {
- t.Error(vals)
- }
- }
- func TestAvailability(t *testing.T) {
- db, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- const folderID = "test"
- // 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
- err = db.Update(folderID, protocol.DeviceID{42}, []protocol.FileInfo{
- genFile("test2", 2, 1),
- genFile("test3", 3, 2),
- })
- if err != nil {
- t.Fatal(err)
- }
- // Further remote files
- err = db.Update(folderID, protocol.DeviceID{45}, []protocol.FileInfo{
- genFile("test3", 3, 1),
- genFile("test4", 4, 2),
- })
- if err != nil {
- t.Fatal(err)
- }
- a, err := db.GetGlobalAvailability(folderID, "test1")
- if err != nil {
- t.Fatal(err)
- }
- if len(a) != 0 {
- t.Log(a)
- t.Error("expected no availability (only local)")
- }
- a, err = db.GetGlobalAvailability(folderID, "test2")
- if err != nil {
- t.Fatal(err)
- }
- if len(a) != 1 || a[0] != (protocol.DeviceID{42}) {
- t.Log(a)
- t.Error("expected one availability (only 42)")
- }
- a, err = db.GetGlobalAvailability(folderID, "test3")
- if err != nil {
- t.Fatal(err)
- }
- if len(a) != 2 || a[0] != (protocol.DeviceID{42}) || a[1] != (protocol.DeviceID{45}) {
- t.Log(a)
- t.Error("expected two availabilities (both remotes)")
- }
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- }
- func TestDropFilesNamed(t *testing.T) {
- db, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- const folderID = "test"
- // 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)
- }
- // Drop test1
- if err := db.DropFilesNamed(folderID, protocol.LocalDeviceID, []string{"test1"}); err != nil {
- t.Fatal(err)
- }
- // Check
- if _, ok, err := db.GetDeviceFile(folderID, protocol.LocalDeviceID, "test1"); err != nil || ok {
- t.Log(err, ok)
- t.Error("expected to not exist")
- }
- if c, err := db.CountLocal(folderID, protocol.LocalDeviceID); err != nil {
- t.Fatal(err)
- } else if c.Files != 1 {
- t.Log(c)
- t.Error("expected count to be one")
- }
- if _, ok, err := db.GetDeviceFile(folderID, protocol.LocalDeviceID, "test2"); err != nil || !ok {
- t.Log(err, ok)
- t.Error("expected to exist")
- }
- }
- func TestDropFolder(t *testing.T) {
- db, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Some local files
- // Folder A
- err = db.Update("a", protocol.LocalDeviceID, []protocol.FileInfo{
- genFile("test1", 1, 0),
- genFile("test2", 2, 0),
- })
- if err != nil {
- t.Fatal(err)
- }
- // Folder B
- err = db.Update("b", protocol.LocalDeviceID, []protocol.FileInfo{
- genFile("test1", 1, 0),
- genFile("test2", 2, 0),
- })
- if err != nil {
- t.Fatal(err)
- }
- // Drop A
- if err := db.DropFolder("a"); err != nil {
- t.Fatal(err)
- }
- // Check
- if _, ok, err := db.GetDeviceFile("a", protocol.LocalDeviceID, "test1"); err != nil || ok {
- t.Log(err, ok)
- t.Error("expected to not exist")
- }
- if c, err := db.CountLocal("a", protocol.LocalDeviceID); err != nil {
- t.Fatal(err)
- } else if c.Files != 0 {
- t.Log(c)
- t.Error("expected count to be zero")
- }
- if _, ok, err := db.GetDeviceFile("b", protocol.LocalDeviceID, "test1"); err != nil || !ok {
- t.Log(err, ok)
- t.Error("expected to exist")
- }
- if c, err := db.CountLocal("b", protocol.LocalDeviceID); err != nil {
- t.Fatal(err)
- } else if c.Files != 2 {
- t.Log(c)
- t.Error("expected count to be two")
- }
- }
- func TestDropDevice(t *testing.T) {
- db, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Some local files
- // Device 1
- err = db.Update("a", protocol.DeviceID{1}, []protocol.FileInfo{
- genFile("test1", 1, 1),
- genFile("test2", 2, 2),
- })
- if err != nil {
- t.Fatal(err)
- }
- // Device 2
- err = db.Update("a", protocol.DeviceID{2}, []protocol.FileInfo{
- genFile("test1", 1, 1),
- genFile("test2", 2, 2),
- })
- if err != nil {
- t.Fatal(err)
- }
- // Drop 1
- if err := db.DropDevice(protocol.DeviceID{1}); err != nil {
- t.Fatal(err)
- }
- // Check
- if _, ok, err := db.GetDeviceFile("a", protocol.DeviceID{1}, "test1"); err != nil || ok {
- t.Log(err, ok)
- t.Error("expected to not exist")
- }
- if c, err := db.CountLocal("a", protocol.DeviceID{1}); err != nil {
- t.Fatal(err)
- } else if c.Files != 0 {
- t.Log(c)
- t.Error("expected count to be zero")
- }
- if _, ok, err := db.GetDeviceFile("a", protocol.DeviceID{2}, "test1"); err != nil || !ok {
- t.Log(err, ok)
- t.Error("expected to exist")
- }
- if c, err := db.CountLocal("a", protocol.DeviceID{2}); err != nil {
- t.Fatal(err)
- } else if c.Files != 2 {
- t.Log(c)
- t.Error("expected count to be two")
- }
- // Drop something that doesn't exist
- if err := db.DropDevice(protocol.DeviceID{99}); err != nil {
- t.Fatal(err)
- }
- }
- func TestDropAllFiles(t *testing.T) {
- db, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Some local files
- // Device 1 folder A
- err = db.Update("a", protocol.DeviceID{1}, []protocol.FileInfo{
- genFile("test1", 1, 1),
- genFile("test2", 2, 2),
- })
- if err != nil {
- t.Fatal(err)
- }
- // Device 1 folder B
- err = db.Update("b", protocol.DeviceID{1}, []protocol.FileInfo{
- genFile("test1", 1, 1),
- genFile("test2", 2, 2),
- })
- if err != nil {
- t.Fatal(err)
- }
- // Drop folder A
- if err := db.DropAllFiles("a", protocol.DeviceID{1}); err != nil {
- t.Fatal(err)
- }
- // Check
- if _, ok, err := db.GetDeviceFile("a", protocol.DeviceID{1}, "test1"); err != nil || ok {
- t.Log(err, ok)
- t.Error("expected to not exist")
- }
- if c, err := db.CountLocal("a", protocol.DeviceID{1}); err != nil {
- t.Fatal(err)
- } else if c.Files != 0 {
- t.Log(c)
- t.Error("expected count to be zero")
- }
- if _, ok, err := db.GetDeviceFile("b", protocol.DeviceID{1}, "test1"); err != nil || !ok {
- t.Log(err, ok)
- t.Error("expected to exist")
- }
- if c, err := db.CountLocal("b", protocol.DeviceID{1}); err != nil {
- t.Fatal(err)
- } else if c.Files != 2 {
- t.Log(c)
- t.Error("expected count to be two")
- }
- // Drop things that don't exist
- if err := db.DropAllFiles("a", protocol.DeviceID{99}); err != nil {
- t.Fatal(err)
- }
- if err := db.DropAllFiles("trolol", protocol.DeviceID{1}); err != nil {
- t.Fatal(err)
- }
- if err := db.DropAllFiles("trolol", protocol.DeviceID{99}); err != nil {
- t.Fatal(err)
- }
- }
- func TestConcurrentUpdate(t *testing.T) {
- t.Parallel()
- db, err := Open(filepath.Join(t.TempDir(), "db"))
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- const folderID = "test"
- files := []protocol.FileInfo{
- genFile("test1", 1, 1),
- genFile("test2", 2, 2),
- genFile("test3", 3, 3),
- genFile("test4", 4, 4),
- }
- const n = 32
- res := make([]error, n)
- var wg sync.WaitGroup
- wg.Add(n)
- for i := range n {
- go func() {
- res[i] = db.Update(folderID, protocol.DeviceID{byte(i), byte(i), byte(i)}, files)
- wg.Done()
- }()
- }
- wg.Wait()
- for i, err := range res {
- if err != nil {
- t.Errorf("%d: %v", i, err)
- }
- }
- }
- func TestConcurrentUpdateSelect(t *testing.T) {
- t.Parallel()
- db, err := Open(filepath.Join(t.TempDir(), "db"))
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := db.Close(); err != nil {
- t.Fatal(err)
- }
- })
- const folderID = "test"
- // Some local files
- files := []protocol.FileInfo{
- genFile("test1", 1, 1),
- genFile("test2", 2, 2),
- genFile("test3", 3, 3),
- genFile("test4", 4, 4),
- }
- // Insert the files for a remote device
- if err := db.Update(folderID, protocol.DeviceID{42}, files); err != nil {
- t.Fatal()
- }
- // Iterate over handled files and insert them for the local device.
- // This is similar to a pattern we have in other places and should
- // work.
- handled := 0
- it, errFn := db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)
- for glob := range it {
- glob.Version = glob.Version.Update(1)
- if err := db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{glob}); err != nil {
- t.Fatal(err)
- }
- handled++
- }
- if err := errFn(); err != nil {
- t.Fatal(err)
- }
- if handled != len(files) {
- t.Log(handled)
- t.Error("should have handled all the files")
- }
- }
- func TestAllForBlocksHash(t *testing.T) {
- t.Parallel()
- sdb, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := sdb.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // test1 is unique, while test2 and test3 have the same blocks and hence
- // the same blocks hash
- files := []protocol.FileInfo{
- genFile("test1", 1, 1),
- genFile("test2", 2, 2),
- genFile("test3", 3, 3),
- }
- files[2].Blocks = files[1].Blocks
- if err := sdb.Update(folderID, protocol.LocalDeviceID, files); err != nil {
- t.Fatal(err)
- }
- // Check test1
- test1, ok, err := sdb.GetDeviceFile(folderID, protocol.LocalDeviceID, "test1")
- if err != nil || !ok {
- t.Fatal("expected to exist")
- }
- vals := mustCollect[db.FileMetadata](t)(sdb.AllLocalFilesWithBlocksHash(folderID, test1.BlocksHash))
- if len(vals) != 1 {
- t.Log(vals)
- t.Fatal("expected one file to match")
- }
- // Check test2 which also matches test3
- test2, ok, err := sdb.GetDeviceFile(folderID, protocol.LocalDeviceID, "test2")
- if err != nil || !ok {
- t.Fatal("expected to exist")
- }
- vals = mustCollect[db.FileMetadata](t)(sdb.AllLocalFilesWithBlocksHash(folderID, test2.BlocksHash))
- if len(vals) != 2 {
- t.Log(vals)
- t.Fatal("expected two files to match")
- }
- if vals[0].Name != "test2" {
- t.Log(vals[0])
- t.Error("expected test2")
- }
- if vals[1].Name != "test3" {
- t.Log(vals[1])
- t.Error("expected test3")
- }
- }
- func TestBlocklistGarbageCollection(t *testing.T) {
- t.Parallel()
- sdb, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := sdb.Close(); err != nil {
- t.Fatal(err)
- }
- })
- svc := sdb.Service(time.Hour).(*Service)
- // Add three files
- files := []protocol.FileInfo{
- genFile("test1", 1, 1),
- genFile("test2", 2, 2),
- genFile("test3", 3, 3),
- }
- if err := sdb.Update(folderID, protocol.LocalDeviceID, files); err != nil {
- t.Fatal(err)
- }
- // There should exist three blockslists and six blocks
- fdb, err := sdb.getFolderDB(folderID, false)
- if err != nil {
- t.Fatal(err)
- }
- var count int
- if err := fdb.sql.Get(&count, `SELECT count(*) FROM blocklists`); err != nil {
- t.Fatal(err)
- }
- if count != 3 {
- t.Log(count)
- t.Fatal("expected 3 blocklists")
- }
- if err := fdb.sql.Get(&count, `SELECT count(*) FROM blocks`); err != nil {
- t.Fatal(err)
- }
- if count != 6 {
- t.Log(count)
- t.Fatal("expected 6 blocks")
- }
- // Mark test3 as deleted, it's blocks and blocklist are now eligible for collection
- files = files[2:]
- files[0].SetDeleted(42)
- if err := sdb.Update(folderID, protocol.LocalDeviceID, files); err != nil {
- t.Fatal(err)
- }
- // Run garbage collection
- if err := svc.periodic(context.Background()); err != nil {
- t.Fatal(err)
- }
- // There should exist two blockslists and four blocks
- if err := fdb.sql.Get(&count, `SELECT count(*) FROM blocklists`); err != nil {
- t.Fatal(err)
- }
- if count != 2 {
- t.Log(count)
- t.Error("expected 2 blocklists")
- }
- if err := fdb.sql.Get(&count, `SELECT count(*) FROM blocks`); err != nil {
- t.Fatal(err)
- }
- if count != 3 {
- t.Log(count)
- t.Error("expected 3 blocks")
- }
- }
- func TestInsertLargeFile(t *testing.T) {
- t.Parallel()
- sdb, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := sdb.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // Add a large file (many blocks)
- files := []protocol.FileInfo{genFile("test1", 16000, 1)}
- if err := sdb.Update(folderID, protocol.LocalDeviceID, files); err != nil {
- t.Fatal(err)
- }
- // Verify all the blocks are here
- for i, block := range files[0].Blocks {
- bs, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, block.Hash))
- if err != nil {
- t.Fatal(err)
- }
- if len(bs) == 0 {
- t.Error("missing blocks for", i)
- }
- }
- }
- func TestErrorWrap(t *testing.T) {
- if wrap(nil, "foo") != nil {
- t.Fatal("nil should wrap to nil")
- }
- fooErr := errors.New("foo")
- if err := wrap(fooErr); err.Error() != "testerrorwrap: foo" {
- t.Fatalf("%q", err)
- }
- if err := wrap(fooErr, "bar", "baz"); err.Error() != "testerrorwrap (bar, baz): foo" {
- t.Fatalf("%q", err)
- }
- }
- func TestStrangeDeletedGlobalBug(t *testing.T) {
- // This exercises an edge case with serialisation and ordering of
- // version vectors. It does not need to make sense, it just needs to
- // pass.
- t.Parallel()
- sdb, err := Open(t.TempDir())
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := sdb.Close(); err != nil {
- t.Fatal(err)
- }
- })
- // One remote device announces the original version of the file
- file := genFile("test", 1, 1)
- file.Version = protocol.Vector{Counters: []protocol.Counter{{ID: 35494436325452, Value: 1742900373}}}
- t.Log("orig", file.Version)
- sdb.Update(folderID, protocol.DeviceID{42}, []protocol.FileInfo{file})
- // Another one announces a newer one that is deleted
- del := file
- del.SetDeleted(43)
- del.Version = protocol.Vector{Counters: []protocol.Counter{{ID: 55445057455644, Value: 1742918457}, {ID: 35494436325452, Value: 1742900373}}}
- t.Log("del", del.Version)
- sdb.Update(folderID, protocol.DeviceID{43}, []protocol.FileInfo{del})
- // We have an instance of the original file
- sdb.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{file})
- // Which one is the global? It should be the deleted one, clearly.
- g, _, err := sdb.GetGlobalFile(folderID, "test")
- if err != nil {
- t.Fatal(err)
- }
- if !g.Deleted {
- t.Log(g)
- t.Fatal("should be deleted")
- }
- }
- func TestOpenSpecialName(t *testing.T) {
- dir := t.TempDir()
- // Create a "base" dir that is in the way if the path becomes
- // incorrectly truncated in the next steps.
- base := path.Join(dir, "test")
- if err := os.Mkdir(base, 0o755); err != nil {
- t.Fatal(err)
- }
- // Should be able to open a path with a hash sign in it.
- p1 := base + "#foo"
- db, err := Open(p1)
- if err != nil {
- t.Fatal(err)
- }
- t.Log(db.path)
- db.Close()
- if !build.IsWindows {
- // Should be able to open a path with something that looks like
- // query params.
- p2 := base + "?foo=bar"
- db, err = Open(p2)
- if err != nil {
- t.Fatal(err)
- }
- t.Log(db.path)
- db.Close()
- }
- // Better not a have problem with a single ampersand either.
- p2 := base + "&foo"
- db, err = Open(p2)
- if err != nil {
- t.Fatal(err)
- }
- t.Log(db.path)
- db.Close()
- }
- func mustCollect[T any](t *testing.T) func(it iter.Seq[T], errFn func() error) []T {
- t.Helper()
- return func(it iter.Seq[T], errFn func() error) []T {
- t.Helper()
- vals, err := itererr.Collect(it, errFn)
- if err != nil {
- t.Fatal(err)
- }
- return vals
- }
- }
- func fiNames(fs []protocol.FileInfo) []string {
- names := make([]string, len(fs))
- for i, fi := range fs {
- names[i] = fi.Name
- }
- return names
- }
- func genDir(name string, seq int) protocol.FileInfo {
- return protocol.FileInfo{
- Name: name,
- Type: protocol.FileInfoTypeDirectory,
- ModifiedS: time.Now().Unix(),
- ModifiedBy: 1,
- Sequence: int64(seq),
- Version: protocol.Vector{}.Update(1),
- Permissions: 0o755,
- ModifiedNs: 12345678,
- }
- }
- func genFile(name string, numBlocks int, seq int) protocol.FileInfo {
- ts := timeutil.StrictlyMonotonicNanos()
- s := ts / 1e9
- ns := int32(ts % 1e9)
- return protocol.FileInfo{
- Name: name,
- Size: int64(numBlocks) * blockSize,
- ModifiedS: s,
- ModifiedBy: 1,
- Version: protocol.Vector{}.Update(1),
- Sequence: int64(seq),
- Blocks: genBlocks(name, 0, numBlocks),
- Permissions: 0o644,
- ModifiedNs: ns,
- RawBlockSize: blockSize,
- }
- }
- func genBlocks(name string, seed, count int) []protocol.BlockInfo {
- b := make([]protocol.BlockInfo, count)
- for i := range b {
- b[i].Hash = genBlockHash(name, seed, i)
- b[i].Size = blockSize
- b[i].Offset = (blockSize) * int64(i)
- }
- return b
- }
- func genBlockHash(name string, seed, index int) []byte {
- bs := sha256.Sum256([]byte(name))
- ebs := binary.LittleEndian.AppendUint64(nil, uint64(seed))
- for i := range ebs {
- bs[i] ^= ebs[i]
- }
- ebs = binary.LittleEndian.AppendUint64(nil, uint64(index))
- for i := range ebs {
- bs[i] ^= ebs[i]
- }
- return bs[:]
- }
|