12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145 |
- // 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"
- "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/config"
- "github.com/syncthing/syncthing/lib/protocol"
- )
- const (
- folderID = "test"
- blockSize = 128 << 10
- dirSize = 128
- )
- func TestBasics(t *testing.T) {
- t.Parallel()
- sdb, err := OpenTemp()
- 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.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 := OpenTemp()
- 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 := OpenTemp()
- 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 := OpenTemp()
- 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 := OpenTemp()
- 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 := OpenTemp()
- 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 := OpenTemp()
- 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 := OpenTemp()
- 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 := OpenTemp()
- 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 := OpenTemp()
- 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
- var count int
- if err := sdb.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 := sdb.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 := sdb.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 := sdb.sql.Get(&count, `SELECT count(*) FROM blocks`); err != nil {
- t.Fatal(err)
- }
- if count != 3 {
- t.Log(count)
- t.Error("expected 3 blocks")
- }
- }
- 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 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[:]
- }
|