testutils_test.go 11 KB

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