testutils_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. // Copyright (C) 2016 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package model
  7. import (
  8. "context"
  9. "os"
  10. "testing"
  11. "time"
  12. "github.com/syncthing/syncthing/lib/config"
  13. "github.com/syncthing/syncthing/lib/db"
  14. "github.com/syncthing/syncthing/lib/db/backend"
  15. "github.com/syncthing/syncthing/lib/events"
  16. "github.com/syncthing/syncthing/lib/fs"
  17. "github.com/syncthing/syncthing/lib/ignore"
  18. "github.com/syncthing/syncthing/lib/protocol"
  19. "github.com/syncthing/syncthing/lib/protocol/mocks"
  20. "github.com/syncthing/syncthing/lib/rand"
  21. )
  22. var (
  23. myID, device1, device2 protocol.DeviceID
  24. defaultCfgWrapper config.Wrapper
  25. defaultCfgWrapperCancel context.CancelFunc
  26. defaultFolderConfig config.FolderConfiguration
  27. defaultCfg config.Configuration
  28. defaultAutoAcceptCfg config.Configuration
  29. device1Conn = &mocks.Connection{}
  30. device2Conn = &mocks.Connection{}
  31. )
  32. func init() {
  33. myID, _ = protocol.DeviceIDFromString("ZNWFSWE-RWRV2BD-45BLMCV-LTDE2UR-4LJDW6J-R5BPWEB-TXD27XJ-IZF5RA4")
  34. device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
  35. device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
  36. device1Conn.DeviceIDReturns(device1)
  37. device1Conn.ConnectionIDReturns(rand.String(16))
  38. device2Conn.DeviceIDReturns(device2)
  39. device2Conn.ConnectionIDReturns(rand.String(16))
  40. cfg := config.New(myID)
  41. cfg.Options.MinHomeDiskFree.Value = 0 // avoids unnecessary free space checks
  42. defaultCfgWrapper, defaultCfgWrapperCancel = newConfigWrapper(cfg)
  43. defaultFolderConfig = newFolderConfig()
  44. waiter, _ := defaultCfgWrapper.Modify(func(cfg *config.Configuration) {
  45. cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device1, "device1"))
  46. cfg.SetFolder(defaultFolderConfig)
  47. cfg.Options.KeepTemporariesH = 1
  48. })
  49. waiter.Wait()
  50. defaultCfg = defaultCfgWrapper.RawCopy()
  51. defaultAutoAcceptCfg = config.Configuration{
  52. Version: config.CurrentVersion,
  53. Devices: []config.DeviceConfiguration{
  54. {
  55. DeviceID: myID, // self
  56. },
  57. {
  58. DeviceID: device1,
  59. AutoAcceptFolders: true,
  60. },
  61. {
  62. DeviceID: device2,
  63. AutoAcceptFolders: true,
  64. },
  65. },
  66. Defaults: config.Defaults{
  67. Folder: config.FolderConfiguration{
  68. FilesystemType: fs.FilesystemTypeFake,
  69. Path: rand.String(32),
  70. },
  71. },
  72. Options: config.OptionsConfiguration{
  73. MinHomeDiskFree: config.Size{}, // avoids unnecessary free space checks
  74. },
  75. }
  76. }
  77. func newConfigWrapper(cfg config.Configuration) (config.Wrapper, context.CancelFunc) {
  78. wrapper := config.Wrap("", cfg, myID, events.NoopLogger)
  79. ctx, cancel := context.WithCancel(context.Background())
  80. go wrapper.Serve(ctx)
  81. return wrapper, cancel
  82. }
  83. func newDefaultCfgWrapper() (config.Wrapper, config.FolderConfiguration, context.CancelFunc) {
  84. w, cancel := newConfigWrapper(defaultCfgWrapper.RawCopy())
  85. fcfg := newFolderConfig()
  86. _, _ = w.Modify(func(cfg *config.Configuration) {
  87. cfg.SetFolder(fcfg)
  88. })
  89. return w, fcfg, cancel
  90. }
  91. func newFolderConfig() config.FolderConfiguration {
  92. cfg := newFolderConfiguration(defaultCfgWrapper, "default", "default", fs.FilesystemTypeFake, rand.String(32)+"?content=true")
  93. cfg.FSWatcherEnabled = false
  94. cfg.Devices = append(cfg.Devices, config.FolderDeviceConfiguration{DeviceID: device1})
  95. return cfg
  96. }
  97. func setupModelWithConnection(t testing.TB) (*testModel, *fakeConnection, config.FolderConfiguration, context.CancelFunc) {
  98. t.Helper()
  99. w, fcfg, cancel := newDefaultCfgWrapper()
  100. m, fc := setupModelWithConnectionFromWrapper(t, w)
  101. return m, fc, fcfg, cancel
  102. }
  103. func setupModelWithConnectionFromWrapper(t testing.TB, w config.Wrapper) (*testModel, *fakeConnection) {
  104. t.Helper()
  105. m := setupModel(t, w)
  106. fc := addFakeConn(m, device1, "default")
  107. fc.folder = "default"
  108. _ = m.ScanFolder("default")
  109. return m, fc
  110. }
  111. func setupModel(t testing.TB, w config.Wrapper) *testModel {
  112. t.Helper()
  113. m := newModel(t, w, myID, nil)
  114. m.ServeBackground()
  115. <-m.started
  116. m.ScanFolders()
  117. return m
  118. }
  119. type testModel struct {
  120. *model
  121. t testing.TB
  122. cancel context.CancelFunc
  123. evCancel context.CancelFunc
  124. stopped chan struct{}
  125. }
  126. func newModel(t testing.TB, cfg config.Wrapper, id protocol.DeviceID, protectedFiles []string) *testModel {
  127. t.Helper()
  128. evLogger := events.NewLogger()
  129. ldb, err := db.NewLowlevel(backend.OpenMemory(), evLogger)
  130. if err != nil {
  131. t.Fatal(err)
  132. }
  133. m := NewModel(cfg, id, ldb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model)
  134. ctx, cancel := context.WithCancel(context.Background())
  135. go evLogger.Serve(ctx)
  136. return &testModel{
  137. model: m,
  138. evCancel: cancel,
  139. stopped: make(chan struct{}),
  140. t: t,
  141. }
  142. }
  143. func (m *testModel) ServeBackground() {
  144. ctx, cancel := context.WithCancel(context.Background())
  145. m.cancel = cancel
  146. go func() {
  147. m.model.Serve(ctx)
  148. close(m.stopped)
  149. }()
  150. <-m.started
  151. }
  152. func (m *testModel) testAvailability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
  153. av, err := m.model.Availability(folder, file, block)
  154. must(m.t, err)
  155. return av
  156. }
  157. func (m *testModel) testCurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
  158. f, ok, err := m.model.CurrentFolderFile(folder, file)
  159. must(m.t, err)
  160. return f, ok
  161. }
  162. func (m *testModel) testCompletion(device protocol.DeviceID, folder string) FolderCompletion {
  163. comp, err := m.Completion(device, folder)
  164. must(m.t, err)
  165. return comp
  166. }
  167. func cleanupModel(m *testModel) {
  168. if m.cancel != nil {
  169. m.cancel()
  170. <-m.stopped
  171. }
  172. m.evCancel()
  173. m.db.Close()
  174. os.Remove(m.cfg.ConfigPath())
  175. }
  176. func cleanupModelAndRemoveDir(m *testModel, dir string) {
  177. cleanupModel(m)
  178. os.RemoveAll(dir)
  179. }
  180. type alwaysChangedKey struct {
  181. fs fs.Filesystem
  182. name string
  183. }
  184. // alwaysChanges is an ignore.ChangeDetector that always returns true on Changed()
  185. type alwaysChanged struct {
  186. seen map[alwaysChangedKey]struct{}
  187. }
  188. func newAlwaysChanged() *alwaysChanged {
  189. return &alwaysChanged{
  190. seen: make(map[alwaysChangedKey]struct{}),
  191. }
  192. }
  193. func (c *alwaysChanged) Remember(fs fs.Filesystem, name string, _ time.Time) {
  194. c.seen[alwaysChangedKey{fs, name}] = struct{}{}
  195. }
  196. func (c *alwaysChanged) Reset() {
  197. c.seen = make(map[alwaysChangedKey]struct{})
  198. }
  199. func (c *alwaysChanged) Seen(fs fs.Filesystem, name string) bool {
  200. _, ok := c.seen[alwaysChangedKey{fs, name}]
  201. return ok
  202. }
  203. func (*alwaysChanged) Changed() bool {
  204. return true
  205. }
  206. func localSize(t *testing.T, m Model, folder string) db.Counts {
  207. t.Helper()
  208. snap := dbSnapshot(t, m, folder)
  209. defer snap.Release()
  210. return snap.LocalSize()
  211. }
  212. func globalSize(t *testing.T, m Model, folder string) db.Counts {
  213. t.Helper()
  214. snap := dbSnapshot(t, m, folder)
  215. defer snap.Release()
  216. return snap.GlobalSize()
  217. }
  218. func receiveOnlyChangedSize(t *testing.T, m Model, folder string) db.Counts {
  219. t.Helper()
  220. snap := dbSnapshot(t, m, folder)
  221. defer snap.Release()
  222. return snap.ReceiveOnlyChangedSize()
  223. }
  224. func needSizeLocal(t *testing.T, m Model, folder string) db.Counts {
  225. t.Helper()
  226. snap := dbSnapshot(t, m, folder)
  227. defer snap.Release()
  228. return snap.NeedSize(protocol.LocalDeviceID)
  229. }
  230. func dbSnapshot(t *testing.T, m Model, folder string) *db.Snapshot {
  231. t.Helper()
  232. snap, err := m.DBSnapshot(folder)
  233. if err != nil {
  234. t.Fatal(err)
  235. }
  236. return snap
  237. }
  238. func fsetSnapshot(t *testing.T, fset *db.FileSet) *db.Snapshot {
  239. t.Helper()
  240. snap, err := fset.Snapshot()
  241. if err != nil {
  242. t.Fatal(err)
  243. }
  244. return snap
  245. }
  246. // Reach in and update the ignore matcher to one that always does
  247. // reloads when asked to, instead of checking file mtimes. This is
  248. // because we will be changing the files on disk often enough that the
  249. // mtimes will be unreliable to determine change status.
  250. func folderIgnoresAlwaysReload(t testing.TB, m *testModel, fcfg config.FolderConfiguration) {
  251. t.Helper()
  252. m.removeFolder(fcfg)
  253. fset := newFileSet(t, fcfg.ID, m.db)
  254. ignores := ignore.New(fcfg.Filesystem(nil), ignore.WithCache(true), ignore.WithChangeDetector(newAlwaysChanged()))
  255. m.fmut.Lock()
  256. m.addAndStartFolderLockedWithIgnores(fcfg, fset, ignores)
  257. m.fmut.Unlock()
  258. }
  259. func basicClusterConfig(local, remote protocol.DeviceID, folders ...string) protocol.ClusterConfig {
  260. var cc protocol.ClusterConfig
  261. for _, folder := range folders {
  262. cc.Folders = append(cc.Folders, protocol.Folder{
  263. ID: folder,
  264. Devices: []protocol.Device{
  265. {
  266. ID: local,
  267. },
  268. {
  269. ID: remote,
  270. },
  271. },
  272. })
  273. }
  274. return cc
  275. }
  276. func localIndexUpdate(m *testModel, folder string, fs []protocol.FileInfo) {
  277. m.fmut.RLock()
  278. fset := m.folderFiles[folder]
  279. m.fmut.RUnlock()
  280. fset.Update(protocol.LocalDeviceID, fs)
  281. seq := fset.Sequence(protocol.LocalDeviceID)
  282. filenames := make([]string, len(fs))
  283. for i, file := range fs {
  284. filenames[i] = file.Name
  285. }
  286. m.evLogger.Log(events.LocalIndexUpdated, map[string]interface{}{
  287. "folder": folder,
  288. "items": len(fs),
  289. "filenames": filenames,
  290. "sequence": seq,
  291. "version": seq, // legacy for sequence
  292. })
  293. }
  294. func newDeviceConfiguration(defaultCfg config.DeviceConfiguration, id protocol.DeviceID, name string) config.DeviceConfiguration {
  295. cfg := defaultCfg.Copy()
  296. cfg.DeviceID = id
  297. cfg.Name = name
  298. return cfg
  299. }
  300. func newFileSet(t testing.TB, folder string, ldb *db.Lowlevel) *db.FileSet {
  301. t.Helper()
  302. fset, err := db.NewFileSet(folder, ldb)
  303. if err != nil {
  304. t.Fatal(err)
  305. }
  306. return fset
  307. }
  308. func replace(t testing.TB, w config.Wrapper, to config.Configuration) {
  309. t.Helper()
  310. waiter, err := w.Modify(func(cfg *config.Configuration) {
  311. *cfg = to
  312. })
  313. if err != nil {
  314. t.Fatal(err)
  315. }
  316. waiter.Wait()
  317. }
  318. func pauseFolder(t testing.TB, w config.Wrapper, id string, paused bool) {
  319. t.Helper()
  320. waiter, err := w.Modify(func(cfg *config.Configuration) {
  321. _, i, _ := cfg.Folder(id)
  322. cfg.Folders[i].Paused = paused
  323. })
  324. if err != nil {
  325. t.Fatal(err)
  326. }
  327. waiter.Wait()
  328. }
  329. func setFolder(t testing.TB, w config.Wrapper, fcfg config.FolderConfiguration) {
  330. t.Helper()
  331. waiter, err := w.Modify(func(cfg *config.Configuration) {
  332. cfg.SetFolder(fcfg)
  333. })
  334. if err != nil {
  335. t.Fatal(err)
  336. }
  337. waiter.Wait()
  338. }
  339. func pauseDevice(t testing.TB, w config.Wrapper, id protocol.DeviceID, paused bool) {
  340. t.Helper()
  341. waiter, err := w.Modify(func(cfg *config.Configuration) {
  342. _, i, _ := cfg.Device(id)
  343. cfg.Devices[i].Paused = paused
  344. })
  345. if err != nil {
  346. t.Fatal(err)
  347. }
  348. waiter.Wait()
  349. }
  350. func setDevice(t testing.TB, w config.Wrapper, device config.DeviceConfiguration) {
  351. t.Helper()
  352. waiter, err := w.Modify(func(cfg *config.Configuration) {
  353. cfg.SetDevice(device)
  354. })
  355. if err != nil {
  356. t.Fatal(err)
  357. }
  358. waiter.Wait()
  359. }
  360. func addDevice2(t testing.TB, w config.Wrapper, fcfg config.FolderConfiguration) {
  361. waiter, err := w.Modify(func(cfg *config.Configuration) {
  362. cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2"))
  363. fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{DeviceID: device2})
  364. cfg.SetFolder(fcfg)
  365. })
  366. must(t, err)
  367. waiter.Wait()
  368. }
  369. func writeFile(t testing.TB, filesystem fs.Filesystem, name string, data []byte) {
  370. t.Helper()
  371. fd, err := filesystem.Create(name)
  372. must(t, err)
  373. defer fd.Close()
  374. _, err = fd.Write(data)
  375. must(t, err)
  376. }
  377. func writeFilePerm(t testing.TB, filesystem fs.Filesystem, name string, data []byte, perm fs.FileMode) {
  378. t.Helper()
  379. writeFile(t, filesystem, name, data)
  380. must(t, filesystem.Chmod(name, perm))
  381. }