| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320 |
- // 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) {
- tx, err := sdb.sql.Beginx()
- if err != nil {
- t.Fatal(err)
- }
- defer tx.Rollback()
- ver, err := sdb.getAppliedSchemaVersion(tx)
- 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
- for i := range n {
- wg.Go(func() {
- res[i] = db.Update(folderID, protocol.DeviceID{byte(i), byte(i), byte(i)}, files)
- })
- }
- 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 TestBlocksInserted(t *testing.T) {
- // Verifies that blocks are inserted after syncing a file
- 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 remote file
- files := []protocol.FileInfo{genFile("test1", 100, 1)}
- if err := sdb.Update(folderID, protocol.DeviceID{42}, files); err != nil {
- t.Fatal(err)
- }
- // Add the same file locally
- 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 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[:]
- }
|