testutils_test.go 10 KB

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