testutils_test.go 9.9 KB

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