testutils_test.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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. wrapper := config.Wrap("", cfg, myID, events.NoopLogger)
  78. ctx, cancel := context.WithCancel(context.Background())
  79. go wrapper.Serve(ctx)
  80. return wrapper, cancel
  81. }
  82. func newDefaultCfgWrapper() (config.Wrapper, config.FolderConfiguration, context.CancelFunc) {
  83. w, cancel := newConfigWrapper(defaultCfgWrapper.RawCopy())
  84. fcfg := newFolderConfig()
  85. _, _ = w.Modify(func(cfg *config.Configuration) {
  86. cfg.SetFolder(fcfg)
  87. })
  88. return w, fcfg, cancel
  89. }
  90. func newFolderConfig() config.FolderConfiguration {
  91. cfg := newFolderConfiguration(defaultCfgWrapper, "default", "default", config.FilesystemTypeFake, rand.String(32)+"?content=true")
  92. cfg.FSWatcherEnabled = false
  93. cfg.PullerDelayS = 0
  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. mdb, err := sqlite.Open(t.TempDir())
  130. if err != nil {
  131. t.Fatal(err)
  132. }
  133. t.Cleanup(func() {
  134. mdb.Close()
  135. })
  136. m := NewModel(cfg, id, mdb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model)
  137. ctx, cancel := context.WithCancel(context.Background())
  138. go evLogger.Serve(ctx)
  139. return &testModel{
  140. model: m,
  141. evCancel: cancel,
  142. stopped: make(chan struct{}),
  143. t: t,
  144. }
  145. }
  146. func (m *testModel) ServeBackground() {
  147. ctx, cancel := context.WithCancel(context.Background())
  148. m.cancel = cancel
  149. go func() {
  150. m.model.Serve(ctx)
  151. close(m.stopped)
  152. }()
  153. <-m.started
  154. }
  155. func (m *testModel) testCurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
  156. f, ok, err := m.model.CurrentFolderFile(folder, file)
  157. must(m.t, err)
  158. return f, ok
  159. }
  160. func (m *testModel) testCompletion(device protocol.DeviceID, folder string) FolderCompletion {
  161. comp, err := m.Completion(device, folder)
  162. must(m.t, err)
  163. return comp
  164. }
  165. func cleanupModel(m *testModel) {
  166. if m.cancel != nil {
  167. m.cancel()
  168. <-m.stopped
  169. }
  170. m.evCancel()
  171. m.sdb.Close()
  172. os.Remove(m.cfg.ConfigPath())
  173. }
  174. func cleanupModelAndRemoveDir(m *testModel, dir string) {
  175. cleanupModel(m)
  176. os.RemoveAll(dir)
  177. }
  178. type alwaysChangedKey struct {
  179. fs fs.Filesystem
  180. name string
  181. }
  182. // alwaysChanges is an ignore.ChangeDetector that always returns true on Changed()
  183. type alwaysChanged struct {
  184. seen map[alwaysChangedKey]struct{}
  185. }
  186. func newAlwaysChanged() *alwaysChanged {
  187. return &alwaysChanged{
  188. seen: make(map[alwaysChangedKey]struct{}),
  189. }
  190. }
  191. func (c *alwaysChanged) Remember(fs fs.Filesystem, name string, _ time.Time) {
  192. c.seen[alwaysChangedKey{fs, name}] = struct{}{}
  193. }
  194. func (c *alwaysChanged) Reset() {
  195. c.seen = make(map[alwaysChangedKey]struct{})
  196. }
  197. func (c *alwaysChanged) Seen(fs fs.Filesystem, name string) bool {
  198. _, ok := c.seen[alwaysChangedKey{fs, name}]
  199. return ok
  200. }
  201. func (*alwaysChanged) Changed() bool {
  202. return true
  203. }
  204. // Reach in and update the ignore matcher to one that always does
  205. // reloads when asked to, instead of checking file mtimes. This is
  206. // because we will be changing the files on disk often enough that the
  207. // mtimes will be unreliable to determine change status.
  208. func folderIgnoresAlwaysReload(t testing.TB, m *testModel, fcfg config.FolderConfiguration) {
  209. t.Helper()
  210. m.removeFolder(fcfg)
  211. ignores := ignore.New(fcfg.Filesystem(), ignore.WithCache(true), ignore.WithChangeDetector(newAlwaysChanged()))
  212. m.mut.Lock()
  213. m.addAndStartFolderLockedWithIgnores(fcfg, ignores)
  214. m.mut.Unlock()
  215. }
  216. func basicClusterConfig(local, remote protocol.DeviceID, folders ...string) *protocol.ClusterConfig {
  217. var cc protocol.ClusterConfig
  218. for _, folder := range folders {
  219. cc.Folders = append(cc.Folders, protocol.Folder{
  220. ID: folder,
  221. Devices: []protocol.Device{
  222. {
  223. ID: local,
  224. },
  225. {
  226. ID: remote,
  227. },
  228. },
  229. })
  230. }
  231. return &cc
  232. }
  233. func localIndexUpdate(m *testModel, folder string, fs []protocol.FileInfo) {
  234. m.sdb.Update(folder, protocol.LocalDeviceID, fs)
  235. seq, err := m.sdb.GetDeviceSequence(folder, protocol.LocalDeviceID)
  236. if err != nil {
  237. panic(err)
  238. }
  239. filenames := make([]string, len(fs))
  240. for i, file := range fs {
  241. filenames[i] = file.Name
  242. }
  243. m.evLogger.Log(events.LocalIndexUpdated, map[string]interface{}{
  244. "folder": folder,
  245. "items": len(fs),
  246. "filenames": filenames,
  247. "sequence": seq,
  248. "version": seq, // legacy for sequence
  249. })
  250. }
  251. func newDeviceConfiguration(defaultCfg config.DeviceConfiguration, id protocol.DeviceID, name string) config.DeviceConfiguration {
  252. cfg := defaultCfg.Copy()
  253. cfg.DeviceID = id
  254. cfg.Name = name
  255. return cfg
  256. }
  257. func replace(t testing.TB, w config.Wrapper, to config.Configuration) {
  258. t.Helper()
  259. waiter, err := w.Modify(func(cfg *config.Configuration) {
  260. *cfg = to
  261. })
  262. if err != nil {
  263. t.Fatal(err)
  264. }
  265. waiter.Wait()
  266. }
  267. func pauseFolder(t testing.TB, w config.Wrapper, id string, paused bool) {
  268. t.Helper()
  269. waiter, err := w.Modify(func(cfg *config.Configuration) {
  270. _, i, _ := cfg.Folder(id)
  271. cfg.Folders[i].Paused = paused
  272. })
  273. if err != nil {
  274. t.Fatal(err)
  275. }
  276. waiter.Wait()
  277. }
  278. func setFolder(t testing.TB, w config.Wrapper, fcfg config.FolderConfiguration) {
  279. t.Helper()
  280. waiter, err := w.Modify(func(cfg *config.Configuration) {
  281. cfg.SetFolder(fcfg)
  282. })
  283. if err != nil {
  284. t.Fatal(err)
  285. }
  286. waiter.Wait()
  287. }
  288. func pauseDevice(t testing.TB, w config.Wrapper, id protocol.DeviceID, paused bool) {
  289. t.Helper()
  290. waiter, err := w.Modify(func(cfg *config.Configuration) {
  291. _, i, _ := cfg.Device(id)
  292. cfg.Devices[i].Paused = paused
  293. })
  294. if err != nil {
  295. t.Fatal(err)
  296. }
  297. waiter.Wait()
  298. }
  299. func setDevice(t testing.TB, w config.Wrapper, device config.DeviceConfiguration) {
  300. t.Helper()
  301. waiter, err := w.Modify(func(cfg *config.Configuration) {
  302. cfg.SetDevice(device)
  303. })
  304. if err != nil {
  305. t.Fatal(err)
  306. }
  307. waiter.Wait()
  308. }
  309. func addDevice2(t testing.TB, w config.Wrapper, fcfg config.FolderConfiguration) {
  310. waiter, err := w.Modify(func(cfg *config.Configuration) {
  311. cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2"))
  312. fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{DeviceID: device2})
  313. cfg.SetFolder(fcfg)
  314. })
  315. must(t, err)
  316. waiter.Wait()
  317. }
  318. func writeFile(t testing.TB, filesystem fs.Filesystem, name string, data []byte) {
  319. t.Helper()
  320. fd, err := filesystem.Create(name)
  321. must(t, err)
  322. defer fd.Close()
  323. _, err = fd.Write(data)
  324. must(t, err)
  325. }
  326. func writeFilePerm(t testing.TB, filesystem fs.Filesystem, name string, data []byte, perm fs.FileMode) {
  327. t.Helper()
  328. writeFile(t, filesystem, name, data)
  329. must(t, filesystem.Chmod(name, perm))
  330. }