testutils_test.go 12 KB

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