testutils_test.go 11 KB

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