testutils_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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. "io/ioutil"
  10. "os"
  11. "testing"
  12. "time"
  13. "github.com/thejerf/suture/v4"
  14. "github.com/syncthing/syncthing/lib/config"
  15. "github.com/syncthing/syncthing/lib/db"
  16. "github.com/syncthing/syncthing/lib/db/backend"
  17. "github.com/syncthing/syncthing/lib/events"
  18. "github.com/syncthing/syncthing/lib/fs"
  19. "github.com/syncthing/syncthing/lib/ignore"
  20. "github.com/syncthing/syncthing/lib/protocol"
  21. "github.com/syncthing/syncthing/lib/rand"
  22. )
  23. var (
  24. myID, device1, device2 protocol.DeviceID
  25. defaultCfgWrapper config.Wrapper
  26. defaultCfgWrapperCancel context.CancelFunc
  27. defaultFolderConfig config.FolderConfiguration
  28. defaultFs fs.Filesystem
  29. defaultCfg config.Configuration
  30. defaultAutoAcceptCfg config.Configuration
  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. defaultFolderConfig = testFolderConfig("testdata")
  37. defaultFs = defaultFolderConfig.Filesystem()
  38. defaultCfgWrapper, defaultCfgWrapperCancel = createTmpWrapper(config.New(myID))
  39. waiter, _ := defaultCfgWrapper.Modify(func(cfg *config.Configuration) {
  40. cfg.SetDevice(config.NewDeviceConfiguration(device1, "device1"))
  41. cfg.SetFolder(defaultFolderConfig)
  42. cfg.Options.KeepTemporariesH = 1
  43. })
  44. waiter.Wait()
  45. defaultCfg = defaultCfgWrapper.RawCopy()
  46. defaultAutoAcceptCfg = config.Configuration{
  47. Version: config.CurrentVersion,
  48. Devices: []config.DeviceConfiguration{
  49. {
  50. DeviceID: myID, // self
  51. },
  52. {
  53. DeviceID: device1,
  54. AutoAcceptFolders: true,
  55. },
  56. {
  57. DeviceID: device2,
  58. AutoAcceptFolders: true,
  59. },
  60. },
  61. Options: config.OptionsConfiguration{
  62. DefaultFolderPath: ".",
  63. },
  64. }
  65. }
  66. func createTmpWrapper(cfg config.Configuration) (config.Wrapper, context.CancelFunc) {
  67. tmpFile, err := ioutil.TempFile("", "syncthing-testConfig-")
  68. if err != nil {
  69. panic(err)
  70. }
  71. wrapper := config.Wrap(tmpFile.Name(), cfg, myID, events.NoopLogger)
  72. tmpFile.Close()
  73. if cfgService, ok := wrapper.(suture.Service); ok {
  74. ctx, cancel := context.WithCancel(context.Background())
  75. go cfgService.Serve(ctx)
  76. return wrapper, cancel
  77. }
  78. return wrapper, func() {}
  79. }
  80. func tmpDefaultWrapper() (config.Wrapper, config.FolderConfiguration, context.CancelFunc) {
  81. w, cancel := createTmpWrapper(defaultCfgWrapper.RawCopy())
  82. fcfg := testFolderConfigTmp()
  83. _, _ = w.Modify(func(cfg *config.Configuration) {
  84. cfg.SetFolder(fcfg)
  85. })
  86. return w, fcfg, cancel
  87. }
  88. func testFolderConfigTmp() config.FolderConfiguration {
  89. tmpDir := createTmpDir()
  90. return testFolderConfig(tmpDir)
  91. }
  92. func testFolderConfig(path string) config.FolderConfiguration {
  93. cfg := config.NewFolderConfiguration(myID, "default", "default", fs.FilesystemTypeBasic, path)
  94. cfg.FSWatcherEnabled = false
  95. cfg.Devices = append(cfg.Devices, config.FolderDeviceConfiguration{DeviceID: device1})
  96. return cfg
  97. }
  98. func testFolderConfigFake() config.FolderConfiguration {
  99. cfg := config.NewFolderConfiguration(myID, "default", "default", fs.FilesystemTypeFake, rand.String(32)+"?content=true")
  100. cfg.FSWatcherEnabled = false
  101. cfg.Devices = append(cfg.Devices, config.FolderDeviceConfiguration{DeviceID: device1})
  102. return cfg
  103. }
  104. func setupModelWithConnection(t testing.TB) (*testModel, *fakeConnection, config.FolderConfiguration, context.CancelFunc) {
  105. t.Helper()
  106. w, fcfg, cancel := tmpDefaultWrapper()
  107. m, fc := setupModelWithConnectionFromWrapper(t, w)
  108. return m, fc, fcfg, cancel
  109. }
  110. func setupModelWithConnectionFromWrapper(t testing.TB, w config.Wrapper) (*testModel, *fakeConnection) {
  111. t.Helper()
  112. m := setupModel(t, w)
  113. fc := addFakeConn(m, device1)
  114. fc.folder = "default"
  115. _ = m.ScanFolder("default")
  116. return m, fc
  117. }
  118. func setupModel(t testing.TB, w config.Wrapper) *testModel {
  119. t.Helper()
  120. m := newModel(t, w, myID, "syncthing", "dev", nil)
  121. m.ServeBackground()
  122. <-m.started
  123. m.ScanFolders()
  124. return m
  125. }
  126. type testModel struct {
  127. *model
  128. cancel context.CancelFunc
  129. evCancel context.CancelFunc
  130. stopped chan struct{}
  131. }
  132. func newModel(t testing.TB, cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, protectedFiles []string) *testModel {
  133. t.Helper()
  134. evLogger := events.NewLogger()
  135. ldb, err := db.NewLowlevel(backend.OpenMemory(), evLogger)
  136. if err != nil {
  137. t.Fatal(err)
  138. }
  139. m := NewModel(cfg, id, clientName, clientVersion, ldb, protectedFiles, evLogger).(*model)
  140. ctx, cancel := context.WithCancel(context.Background())
  141. go evLogger.Serve(ctx)
  142. return &testModel{
  143. model: m,
  144. evCancel: cancel,
  145. stopped: make(chan struct{}),
  146. }
  147. }
  148. func (m *testModel) ServeBackground() {
  149. ctx, cancel := context.WithCancel(context.Background())
  150. m.cancel = cancel
  151. go func() {
  152. m.model.Serve(ctx)
  153. close(m.stopped)
  154. }()
  155. <-m.started
  156. }
  157. func cleanupModel(m *testModel) {
  158. if m.cancel != nil {
  159. m.cancel()
  160. <-m.stopped
  161. }
  162. m.evCancel()
  163. m.db.Close()
  164. os.Remove(m.cfg.ConfigPath())
  165. }
  166. func cleanupModelAndRemoveDir(m *testModel, dir string) {
  167. cleanupModel(m)
  168. os.RemoveAll(dir)
  169. }
  170. func createTmpDir() string {
  171. tmpDir, err := ioutil.TempDir("", "syncthing_testFolder-")
  172. if err != nil {
  173. panic("Failed to create temporary testing dir")
  174. }
  175. return tmpDir
  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 (c *alwaysChanged) Changed() bool {
  201. return true
  202. }
  203. func localSize(t *testing.T, m Model, folder string) db.Counts {
  204. t.Helper()
  205. snap := dbSnapshot(t, m, folder)
  206. defer snap.Release()
  207. return snap.LocalSize()
  208. }
  209. func globalSize(t *testing.T, m Model, folder string) db.Counts {
  210. t.Helper()
  211. snap := dbSnapshot(t, m, folder)
  212. defer snap.Release()
  213. return snap.GlobalSize()
  214. }
  215. func receiveOnlyChangedSize(t *testing.T, m Model, folder string) db.Counts {
  216. t.Helper()
  217. snap := dbSnapshot(t, m, folder)
  218. defer snap.Release()
  219. return snap.ReceiveOnlyChangedSize()
  220. }
  221. func needSize(t *testing.T, m Model, folder string) db.Counts {
  222. t.Helper()
  223. snap := dbSnapshot(t, m, folder)
  224. defer snap.Release()
  225. return snap.NeedSize(protocol.LocalDeviceID)
  226. }
  227. func dbSnapshot(t *testing.T, m Model, folder string) *db.Snapshot {
  228. t.Helper()
  229. snap, err := m.DBSnapshot(folder)
  230. if err != nil {
  231. t.Fatal(err)
  232. }
  233. return snap
  234. }
  235. // Reach in and update the ignore matcher to one that always does
  236. // reloads when asked to, instead of checking file mtimes. This is
  237. // because we will be changing the files on disk often enough that the
  238. // mtimes will be unreliable to determine change status.
  239. func folderIgnoresAlwaysReload(t testing.TB, m *testModel, fcfg config.FolderConfiguration) {
  240. t.Helper()
  241. m.removeFolder(fcfg)
  242. fset := newFileSet(t, fcfg.ID, fcfg.Filesystem(), m.db)
  243. ignores := ignore.New(fcfg.Filesystem(), ignore.WithCache(true), ignore.WithChangeDetector(newAlwaysChanged()))
  244. m.fmut.Lock()
  245. m.addAndStartFolderLockedWithIgnores(fcfg, fset, ignores)
  246. m.fmut.Unlock()
  247. }
  248. func basicClusterConfig(local, remote protocol.DeviceID, folders ...string) protocol.ClusterConfig {
  249. var cc protocol.ClusterConfig
  250. for _, folder := range folders {
  251. cc.Folders = append(cc.Folders, protocol.Folder{
  252. ID: folder,
  253. Devices: []protocol.Device{
  254. {
  255. ID: local,
  256. },
  257. {
  258. ID: remote,
  259. },
  260. },
  261. })
  262. }
  263. return cc
  264. }
  265. func localIndexUpdate(m *testModel, folder string, fs []protocol.FileInfo) {
  266. m.fmut.RLock()
  267. fset := m.folderFiles[folder]
  268. m.fmut.RUnlock()
  269. fset.Update(protocol.LocalDeviceID, fs)
  270. seq := fset.Sequence(protocol.LocalDeviceID)
  271. filenames := make([]string, len(fs))
  272. for i, file := range fs {
  273. filenames[i] = file.Name
  274. }
  275. m.evLogger.Log(events.LocalIndexUpdated, map[string]interface{}{
  276. "folder": folder,
  277. "items": len(fs),
  278. "filenames": filenames,
  279. "sequence": seq,
  280. "version": seq, // legacy for sequence
  281. })
  282. }
  283. func newFileSet(t testing.TB, folder string, fs fs.Filesystem, ldb *db.Lowlevel) *db.FileSet {
  284. t.Helper()
  285. fset, err := db.NewFileSet(folder, fs, ldb)
  286. if err != nil {
  287. t.Fatal(err)
  288. }
  289. return fset
  290. }
  291. func replace(t testing.TB, w config.Wrapper, to config.Configuration) {
  292. t.Helper()
  293. waiter, err := w.Modify(func(cfg *config.Configuration) {
  294. *cfg = to
  295. })
  296. if err != nil {
  297. t.Fatal(err)
  298. }
  299. waiter.Wait()
  300. }
  301. func pauseFolder(t testing.TB, w config.Wrapper, id string, paused bool) {
  302. t.Helper()
  303. waiter, err := w.Modify(func(cfg *config.Configuration) {
  304. _, i, _ := cfg.Folder(id)
  305. cfg.Folders[i].Paused = paused
  306. })
  307. if err != nil {
  308. t.Fatal(err)
  309. }
  310. waiter.Wait()
  311. }
  312. func setFolder(t testing.TB, w config.Wrapper, fcfg config.FolderConfiguration) {
  313. t.Helper()
  314. waiter, err := w.Modify(func(cfg *config.Configuration) {
  315. cfg.SetFolder(fcfg)
  316. })
  317. if err != nil {
  318. t.Fatal(err)
  319. }
  320. waiter.Wait()
  321. }
  322. func pauseDevice(t testing.TB, w config.Wrapper, id protocol.DeviceID, paused bool) {
  323. t.Helper()
  324. waiter, err := w.Modify(func(cfg *config.Configuration) {
  325. _, i, _ := cfg.Device(id)
  326. cfg.Devices[i].Paused = paused
  327. })
  328. if err != nil {
  329. t.Fatal(err)
  330. }
  331. waiter.Wait()
  332. }
  333. func setDevice(t testing.TB, w config.Wrapper, device config.DeviceConfiguration) {
  334. t.Helper()
  335. waiter, err := w.Modify(func(cfg *config.Configuration) {
  336. cfg.SetDevice(device)
  337. })
  338. if err != nil {
  339. t.Fatal(err)
  340. }
  341. waiter.Wait()
  342. }
  343. func addDevice2(t testing.TB, w config.Wrapper, fcfg config.FolderConfiguration) {
  344. waiter, err := w.Modify(func(cfg *config.Configuration) {
  345. cfg.SetDevice(config.NewDeviceConfiguration(device2, "device2"))
  346. fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{DeviceID: device2})
  347. cfg.SetFolder(fcfg)
  348. })
  349. must(t, err)
  350. waiter.Wait()
  351. }