folder_recvonly_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. // Copyright (C) 2018 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. "bytes"
  9. "context"
  10. "path/filepath"
  11. "testing"
  12. "time"
  13. "github.com/syncthing/syncthing/internal/itererr"
  14. "github.com/syncthing/syncthing/lib/config"
  15. "github.com/syncthing/syncthing/lib/events"
  16. "github.com/syncthing/syncthing/lib/fs"
  17. "github.com/syncthing/syncthing/lib/protocol"
  18. "github.com/syncthing/syncthing/lib/scanner"
  19. )
  20. func TestRecvOnlyRevertDeletes(t *testing.T) {
  21. // Make sure that we delete extraneous files and directories when we hit
  22. // Revert.
  23. // Get us a model up and running
  24. m, f, wcfgCancel := setupROFolder(t)
  25. defer wcfgCancel()
  26. ffs := f.Filesystem()
  27. defer cleanupModel(m)
  28. conn := addFakeConn(m, device1, f.ID)
  29. // Create some test data
  30. for _, dir := range []string{".stfolder", "ignDir", "unknownDir"} {
  31. must(t, ffs.MkdirAll(dir, 0o755))
  32. }
  33. writeFilePerm(t, ffs, "ignDir/ignFile", []byte("hello\n"), 0o644)
  34. writeFilePerm(t, ffs, "unknownDir/unknownFile", []byte("hello\n"), 0o644)
  35. writeFilePerm(t, ffs, ".stignore", []byte("ignDir\n"), 0o644)
  36. knownFiles := setupKnownFiles(t, ffs, []byte("hello\n"))
  37. // Send and index update for the known stuff
  38. must(t, m.Index(conn, &protocol.Index{Folder: "ro", Files: knownFiles}))
  39. if err := f.updateLocalsFromScanning(knownFiles); err != nil {
  40. t.Fatal(err)
  41. }
  42. size := mustV(m.GlobalSize("ro"))
  43. if size.Files != 1 || size.Directories != 1 {
  44. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  45. }
  46. // Scan, should discover the other stuff in the folder
  47. must(t, m.ScanFolder("ro"))
  48. // We should now have two files and two directories, with global state unchanged.
  49. size = mustV(m.GlobalSize("ro"))
  50. if size.Files != 1 || size.Directories != 1 {
  51. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  52. }
  53. size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
  54. if size.Files != 2 || size.Directories != 2 {
  55. t.Fatalf("Local: expected 2 files and 2 directories: %+v", size)
  56. }
  57. size = mustV(m.ReceiveOnlySize("ro"))
  58. if size.Files+size.Directories == 0 {
  59. t.Fatalf("ROChanged: expected something: %+v", size)
  60. }
  61. // Revert should delete the unknown stuff
  62. m.Revert("ro")
  63. // These should still exist
  64. for _, p := range []string{"knownDir/knownFile", "ignDir/ignFile"} {
  65. if _, err := ffs.Stat(p); err != nil {
  66. t.Error("Unexpected error:", err)
  67. }
  68. }
  69. // These should have been removed
  70. for _, p := range []string{"unknownDir", "unknownDir/unknownFile"} {
  71. if _, err := ffs.Stat(p); !fs.IsNotExist(err) {
  72. t.Error("Unexpected existing thing:", p)
  73. }
  74. }
  75. // We should now have one file and directory again.
  76. size = mustV(m.GlobalSize("ro"))
  77. if size.Files != 1 || size.Directories != 1 {
  78. t.Fatalf("Global: expected 1 files and 1 directories: %+v", size)
  79. }
  80. size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
  81. if size.Files != 1 || size.Directories != 1 {
  82. t.Fatalf("Local: expected 1 files and 1 directories: %+v", size)
  83. }
  84. }
  85. func TestRecvOnlyRevertNeeds(t *testing.T) {
  86. // Make sure that a new file gets picked up and considered latest, then
  87. // gets considered old when we hit Revert.
  88. // Get us a model up and running
  89. m, f, wcfgCancel := setupROFolder(t)
  90. defer wcfgCancel()
  91. ffs := f.Filesystem()
  92. defer cleanupModel(m)
  93. conn := addFakeConn(m, device1, f.ID)
  94. // Create some test data
  95. must(t, ffs.MkdirAll(".stfolder", 0o755))
  96. oldData := []byte("hello\n")
  97. knownFiles := setupKnownFiles(t, ffs, oldData)
  98. // Send and index update for the known stuff
  99. must(t, m.Index(conn, &protocol.Index{Folder: "ro", Files: knownFiles}))
  100. f.updateLocalsFromScanning(knownFiles)
  101. // Scan the folder.
  102. must(t, m.ScanFolder("ro"))
  103. // Everything should be in sync.
  104. size := mustV(m.GlobalSize("ro"))
  105. if size.Files != 1 || size.Directories != 1 {
  106. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  107. }
  108. size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
  109. if size.Files != 1 || size.Directories != 1 {
  110. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  111. }
  112. size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
  113. if size.Files+size.Directories > 0 {
  114. t.Fatalf("Need: expected nothing: %+v", size)
  115. }
  116. size = mustV(m.ReceiveOnlySize("ro"))
  117. if size.Files+size.Directories > 0 {
  118. t.Fatalf("ROChanged: expected nothing: %+v", size)
  119. }
  120. // Update the file.
  121. newData := []byte("totally different data\n")
  122. writeFilePerm(t, ffs, "knownDir/knownFile", newData, 0o644)
  123. // Rescan.
  124. must(t, m.ScanFolder("ro"))
  125. // We now have a newer file than the rest of the cluster. Global state should reflect this.
  126. size = mustV(m.GlobalSize("ro"))
  127. const sizeOfDir = 128
  128. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
  129. t.Fatalf("Global: expected no change due to the new file: %+v", size)
  130. }
  131. size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
  132. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
  133. t.Fatalf("Local: expected the new file to be reflected: %+v", size)
  134. }
  135. size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
  136. if size.Files+size.Directories > 0 {
  137. t.Fatalf("Need: expected nothing: %+v", size)
  138. }
  139. size = mustV(m.ReceiveOnlySize("ro"))
  140. if size.Files+size.Directories == 0 {
  141. t.Fatalf("ROChanged: expected something: %+v", size)
  142. }
  143. // We hit the Revert button. The file that was new should become old.
  144. m.Revert("ro")
  145. size = mustV(m.GlobalSize("ro"))
  146. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
  147. t.Fatalf("Global: expected the global size to revert: %+v", size)
  148. }
  149. size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
  150. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
  151. t.Fatalf("Local: expected the local size to remain: %+v", size)
  152. }
  153. size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
  154. if size.Files != 1 || size.Bytes != int64(len(oldData)) {
  155. t.Fatalf("Local: expected to need the old file data: %+v", size)
  156. }
  157. }
  158. func TestRecvOnlyUndoChanges(t *testing.T) {
  159. // Get us a model up and running
  160. m, f, wcfgCancel := setupROFolder(t)
  161. defer wcfgCancel()
  162. ffs := f.Filesystem()
  163. defer cleanupModel(m)
  164. conn := addFakeConn(m, device1, f.ID)
  165. // Create some test data
  166. must(t, ffs.MkdirAll(".stfolder", 0o755))
  167. oldData := []byte("hello\n")
  168. knownFiles := setupKnownFiles(t, ffs, oldData)
  169. // Send an index update for the known stuff
  170. must(t, m.Index(conn, &protocol.Index{Folder: "ro", Files: knownFiles}))
  171. f.updateLocalsFromScanning(knownFiles)
  172. // Scan the folder.
  173. must(t, m.ScanFolder("ro"))
  174. // Everything should be in sync.
  175. size := mustV(m.GlobalSize("ro"))
  176. if size.Files != 1 || size.Directories != 1 {
  177. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  178. }
  179. size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
  180. if size.Files != 1 || size.Directories != 1 {
  181. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  182. }
  183. size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
  184. if size.Files+size.Directories > 0 {
  185. t.Fatalf("Need: expected nothing: %+v", size)
  186. }
  187. size = mustV(m.ReceiveOnlySize("ro"))
  188. if size.Files+size.Directories > 0 {
  189. t.Fatalf("ROChanged: expected nothing: %+v", size)
  190. }
  191. // Create a file and modify another
  192. const file = "foo"
  193. writeFilePerm(t, ffs, file, []byte("hello\n"), 0o644)
  194. writeFilePerm(t, ffs, "knownDir/knownFile", []byte("bye\n"), 0o644)
  195. must(t, m.ScanFolder("ro"))
  196. size = mustV(m.ReceiveOnlySize("ro"))
  197. if size.Files != 2 {
  198. t.Fatalf("Receive only: expected 2 files: %+v", size)
  199. }
  200. // Remove the file again and undo the modification
  201. must(t, ffs.Remove(file))
  202. writeFilePerm(t, ffs, "knownDir/knownFile", oldData, 0o644)
  203. must(t, ffs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime()))
  204. must(t, m.ScanFolder("ro"))
  205. size = mustV(m.ReceiveOnlySize("ro"))
  206. if size.Files+size.Directories+size.Deleted != 0 {
  207. t.Fatalf("Receive only: expected all zero: %+v", size)
  208. }
  209. }
  210. func TestRecvOnlyDeletedRemoteDrop(t *testing.T) {
  211. // Get us a model up and running
  212. m, f, wcfgCancel := setupROFolder(t)
  213. defer wcfgCancel()
  214. ffs := f.Filesystem()
  215. defer cleanupModel(m)
  216. conn := addFakeConn(m, device1, f.ID)
  217. // Create some test data
  218. must(t, ffs.MkdirAll(".stfolder", 0o755))
  219. oldData := []byte("hello\n")
  220. knownFiles := setupKnownFiles(t, ffs, oldData)
  221. // Send an index update for the known stuff
  222. must(t, m.Index(conn, &protocol.Index{Folder: "ro", Files: knownFiles}))
  223. f.updateLocalsFromScanning(knownFiles)
  224. // Scan the folder.
  225. must(t, m.ScanFolder("ro"))
  226. // Everything should be in sync.
  227. size := mustV(m.GlobalSize("ro"))
  228. if size.Files != 1 || size.Directories != 1 {
  229. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  230. }
  231. size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
  232. if size.Files != 1 || size.Directories != 1 {
  233. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  234. }
  235. size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
  236. if size.Files+size.Directories > 0 {
  237. t.Fatalf("Need: expected nothing: %+v", size)
  238. }
  239. size = mustV(m.ReceiveOnlySize("ro"))
  240. if size.Files+size.Directories > 0 {
  241. t.Fatalf("ROChanged: expected nothing: %+v", size)
  242. }
  243. // Delete our file
  244. must(t, ffs.Remove(knownFiles[1].Name))
  245. must(t, m.ScanFolder("ro"))
  246. size = mustV(m.ReceiveOnlySize("ro"))
  247. if size.Deleted != 1 {
  248. t.Fatalf("Receive only: expected 1 deleted: %+v", size)
  249. }
  250. // Drop the remote
  251. f.db.DropAllFiles("ro", device1)
  252. must(t, m.ScanFolder("ro"))
  253. size = mustV(m.ReceiveOnlySize("ro"))
  254. if size.Deleted != 0 {
  255. t.Fatalf("Receive only: expected no deleted: %+v", size)
  256. }
  257. }
  258. func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
  259. // Get us a model up and running
  260. m, f, wcfgCancel := setupROFolder(t)
  261. defer wcfgCancel()
  262. ffs := f.Filesystem()
  263. defer cleanupModel(m)
  264. conn := addFakeConn(m, device1, f.ID)
  265. // Create some test data
  266. must(t, ffs.MkdirAll(".stfolder", 0o755))
  267. oldData := []byte("hello\n")
  268. knownFiles := setupKnownFiles(t, ffs, oldData)
  269. // Send an index update for the known stuff
  270. must(t, m.Index(conn, &protocol.Index{Folder: "ro", Files: knownFiles}))
  271. f.updateLocalsFromScanning(knownFiles)
  272. // Scan the folder.
  273. must(t, m.ScanFolder("ro"))
  274. // Everything should be in sync.
  275. size := mustV(m.GlobalSize("ro"))
  276. if size.Files != 1 || size.Directories != 1 {
  277. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  278. }
  279. size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
  280. if size.Files != 1 || size.Directories != 1 {
  281. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  282. }
  283. size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
  284. if size.Files+size.Directories > 0 {
  285. t.Fatalf("Need: expected nothing: %+v", size)
  286. }
  287. size = mustV(m.ReceiveOnlySize("ro"))
  288. if size.Files+size.Directories > 0 {
  289. t.Fatalf("ROChanged: expected nothing: %+v", size)
  290. }
  291. // Create a file and modify another
  292. const file = "foo"
  293. knownFile := filepath.Join("knownDir", "knownFile")
  294. writeFilePerm(t, ffs, file, []byte("hello\n"), 0o644)
  295. writeFilePerm(t, ffs, knownFile, []byte("bye\n"), 0o644)
  296. must(t, m.ScanFolder("ro"))
  297. size = mustV(m.ReceiveOnlySize("ro"))
  298. if size.Files != 2 {
  299. t.Fatalf("Receive only: expected 2 files: %+v", size)
  300. }
  301. // Do the same changes on the remote
  302. files := make([]protocol.FileInfo, 0, 2)
  303. for f, err := range itererr.Zip(f.db.AllLocalFiles("ro", protocol.LocalDeviceID)) {
  304. if err != nil {
  305. t.Fatal(err)
  306. }
  307. if f.Name != file && f.Name != knownFile {
  308. continue
  309. }
  310. f.LocalFlags = 0
  311. f.Version = protocol.Vector{}.Update(device1.Short())
  312. files = append(files, f)
  313. }
  314. must(t, m.IndexUpdate(conn, &protocol.IndexUpdate{Folder: "ro", Files: files}))
  315. // Ensure the pull to resolve conflicts (content identical) happened
  316. must(t, f.doInSync(func() error {
  317. f.pull()
  318. return nil
  319. }))
  320. size, err := m.ReceiveOnlySize("ro")
  321. if err != nil {
  322. t.Fatal(err)
  323. }
  324. if size.Files+size.Directories+size.Deleted != 0 {
  325. t.Fatalf("Receive only: expected all zero: %+v", size)
  326. }
  327. }
  328. func TestRecvOnlyRevertOwnID(t *testing.T) {
  329. // If the folder was receive-only in the past, the global item might have
  330. // only our id in the version vector and be valid. There was a bug based on
  331. // the incorrect assumption that this can never happen.
  332. // Get us a model up and running
  333. m, f, wcfgCancel := setupROFolder(t)
  334. defer wcfgCancel()
  335. ffs := f.Filesystem()
  336. defer cleanupModel(m)
  337. conn := addFakeConn(m, device1, f.ID)
  338. // Create some test data
  339. must(t, ffs.MkdirAll(".stfolder", 0o755))
  340. data := []byte("hello\n")
  341. name := "foo"
  342. writeFilePerm(t, ffs, name, data, 0o644)
  343. // Make sure the file is scanned and locally changed
  344. must(t, m.ScanFolder("ro"))
  345. fi, ok := m.testCurrentFolderFile(f.ID, name)
  346. if !ok {
  347. t.Fatal("File missing")
  348. } else if !fi.IsReceiveOnlyChanged() {
  349. t.Fatal("File should be receiveonly changed")
  350. }
  351. fi.LocalFlags = 0
  352. v := fi.Version.Counters[0].Value
  353. fi.Version.Counters[0].Value = uint64(time.Unix(int64(v), 0).Add(-10 * time.Second).Unix())
  354. // Monitor the outcome
  355. sub := f.evLogger.Subscribe(events.LocalIndexUpdated)
  356. defer sub.Unsubscribe()
  357. ctx, cancel := context.WithCancel(context.Background())
  358. defer cancel()
  359. go func() {
  360. defer cancel()
  361. for {
  362. select {
  363. case <-ctx.Done():
  364. return
  365. case <-sub.C():
  366. if file, _ := m.testCurrentFolderFile(f.ID, name); file.Deleted {
  367. t.Error("local file was deleted")
  368. return
  369. } else if file.IsEquivalent(fi, f.modTimeWindow) {
  370. return // That's what we are waiting for
  371. }
  372. }
  373. }
  374. }()
  375. // Receive an index update with an older version, but valid and then revert
  376. must(t, m.Index(conn, &protocol.Index{Folder: f.ID, Files: []protocol.FileInfo{fi}}))
  377. f.Revert()
  378. select {
  379. case <-ctx.Done():
  380. case <-time.After(10 * time.Second):
  381. t.Fatal("timed out")
  382. }
  383. }
  384. func TestRecvOnlyLocalChangeDoesNotCauseConflict(t *testing.T) {
  385. // Get us a model up and running
  386. m, f, wcfgCancel := setupROFolder(t)
  387. defer wcfgCancel()
  388. ffs := f.Filesystem()
  389. defer cleanupModel(m)
  390. conn := addFakeConn(m, device1, f.ID)
  391. // Create some test data
  392. must(t, ffs.MkdirAll(".stfolder", 0o755))
  393. oldData := []byte("hello\n")
  394. knownFiles := setupKnownFiles(t, ffs, oldData)
  395. // Send an index update for the known stuff
  396. must(t, m.Index(conn, &protocol.Index{Folder: "ro", Files: knownFiles}))
  397. f.updateLocalsFromScanning(knownFiles)
  398. // Scan the folder.
  399. must(t, m.ScanFolder("ro"))
  400. // Everything should be in sync.
  401. size := mustV(m.GlobalSize("ro"))
  402. if size.Files != 1 || size.Directories != 1 {
  403. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  404. }
  405. size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
  406. if size.Files != 1 || size.Directories != 1 {
  407. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  408. }
  409. size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
  410. if size.Files+size.Directories > 0 {
  411. t.Fatalf("Need: expected nothing: %+v", size)
  412. }
  413. size = mustV(m.ReceiveOnlySize("ro"))
  414. if size.Files+size.Directories > 0 {
  415. t.Fatalf("ROChanged: expected nothing: %+v", size)
  416. }
  417. // Modify the file
  418. writeFilePerm(t, ffs, "knownDir/knownFile", []byte("change1\n"), 0o644)
  419. must(t, m.ScanFolder("ro"))
  420. size = mustV(m.ReceiveOnlySize("ro"))
  421. if size.Files != 1 {
  422. t.Fatalf("Receive only: expected 1 file: %+v", size)
  423. }
  424. // Perform another modification. This should not cause the file to be needed.
  425. // This is a regression test: Previously on scan the file version was changed to conflict with the global
  426. // version, thus being needed and creating a conflict copy on next pull.
  427. writeFilePerm(t, ffs, "knownDir/knownFile", []byte("change2\n"), 0o644)
  428. must(t, m.ScanFolder("ro"))
  429. size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
  430. if size.Files != 0 {
  431. t.Fatalf("Need: expected nothing: %+v", size)
  432. }
  433. }
  434. func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.FileInfo {
  435. t.Helper()
  436. must(t, ffs.MkdirAll("knownDir", 0o755))
  437. writeFilePerm(t, ffs, "knownDir/knownFile", data, 0o644)
  438. t0 := time.Now().Add(-1 * time.Minute)
  439. must(t, ffs.Chtimes("knownDir/knownFile", t0, t0))
  440. fi, err := ffs.Stat("knownDir/knownFile")
  441. if err != nil {
  442. t.Fatal(err)
  443. }
  444. blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil)
  445. knownFiles := []protocol.FileInfo{
  446. {
  447. Name: "knownDir",
  448. Type: protocol.FileInfoTypeDirectory,
  449. Permissions: 0o755,
  450. Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
  451. Sequence: 42,
  452. },
  453. {
  454. Name: "knownDir/knownFile",
  455. Type: protocol.FileInfoTypeFile,
  456. Permissions: 0o644,
  457. Size: fi.Size(),
  458. ModifiedS: fi.ModTime().Unix(),
  459. ModifiedNs: int32(fi.ModTime().Nanosecond()),
  460. Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
  461. Sequence: 43,
  462. Blocks: blocks,
  463. },
  464. }
  465. return knownFiles
  466. }
  467. func setupROFolder(t *testing.T) (*testModel, *receiveOnlyFolder, context.CancelFunc) {
  468. t.Helper()
  469. w, cancel := newConfigWrapper(defaultCfg)
  470. cfg := w.RawCopy()
  471. fcfg := newFolderConfig()
  472. fcfg.ID = "ro"
  473. fcfg.Label = "ro"
  474. fcfg.Type = config.FolderTypeReceiveOnly
  475. cfg.Folders = []config.FolderConfiguration{fcfg}
  476. replace(t, w, cfg)
  477. m := newModel(t, w, myID, nil)
  478. m.ServeBackground()
  479. <-m.started
  480. must(t, m.ScanFolder("ro"))
  481. m.mut.RLock()
  482. defer m.mut.RUnlock()
  483. r, _ := m.folderRunners.Get("ro")
  484. f := r.(*receiveOnlyFolder)
  485. return m, f, cancel
  486. }