folder_recvonly_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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. "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/fs"
  16. "github.com/syncthing/syncthing/lib/protocol"
  17. "github.com/syncthing/syncthing/lib/scanner"
  18. )
  19. func TestRecvOnlyRevertDeletes(t *testing.T) {
  20. // Make sure that we delete extraneous files and directories when we hit
  21. // Revert.
  22. // Get us a model up and running
  23. m, f := setupROFolder(t)
  24. ffs := f.Filesystem()
  25. defer cleanupModel(m)
  26. // Create some test data
  27. for _, dir := range []string{".stfolder", "ignDir", "unknownDir"} {
  28. must(t, ffs.MkdirAll(dir, 0755))
  29. }
  30. must(t, writeFile(ffs, "ignDir/ignFile", []byte("hello\n"), 0644))
  31. must(t, writeFile(ffs, "unknownDir/unknownFile", []byte("hello\n"), 0644))
  32. must(t, writeFile(ffs, ".stignore", []byte("ignDir\n"), 0644))
  33. knownFiles := setupKnownFiles(t, ffs, []byte("hello\n"))
  34. // Send and index update for the known stuff
  35. m.Index(device1, "ro", knownFiles)
  36. f.updateLocalsFromScanning(knownFiles)
  37. size := globalSize(t, m, "ro")
  38. if size.Files != 1 || size.Directories != 1 {
  39. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  40. }
  41. // Scan, should discover the other stuff in the folder
  42. must(t, m.ScanFolder("ro"))
  43. // We should now have two files and two directories, with global state unchanged.
  44. size = globalSize(t, m, "ro")
  45. if size.Files != 1 || size.Directories != 1 {
  46. t.Fatalf("Global: expected 2 files and 2 directories: %+v", size)
  47. }
  48. size = localSize(t, m, "ro")
  49. if size.Files != 2 || size.Directories != 2 {
  50. t.Fatalf("Local: expected 2 files and 2 directories: %+v", size)
  51. }
  52. size = receiveOnlyChangedSize(t, m, "ro")
  53. if size.Files+size.Directories == 0 {
  54. t.Fatalf("ROChanged: expected something: %+v", size)
  55. }
  56. // Revert should delete the unknown stuff
  57. m.Revert("ro")
  58. // These should still exist
  59. for _, p := range []string{"knownDir/knownFile", "ignDir/ignFile"} {
  60. if _, err := ffs.Stat(p); err != nil {
  61. t.Error("Unexpected error:", err)
  62. }
  63. }
  64. // These should have been removed
  65. for _, p := range []string{"unknownDir", "unknownDir/unknownFile"} {
  66. if _, err := ffs.Stat(p); !fs.IsNotExist(err) {
  67. t.Error("Unexpected existing thing:", p)
  68. }
  69. }
  70. // We should now have one file and directory again.
  71. size = globalSize(t, m, "ro")
  72. if size.Files != 1 || size.Directories != 1 {
  73. t.Fatalf("Global: expected 1 files and 1 directories: %+v", size)
  74. }
  75. size = localSize(t, m, "ro")
  76. if size.Files != 1 || size.Directories != 1 {
  77. t.Fatalf("Local: expected 1 files and 1 directories: %+v", size)
  78. }
  79. }
  80. func TestRecvOnlyRevertNeeds(t *testing.T) {
  81. // Make sure that a new file gets picked up and considered latest, then
  82. // gets considered old when we hit Revert.
  83. // Get us a model up and running
  84. m, f := setupROFolder(t)
  85. ffs := f.Filesystem()
  86. defer cleanupModel(m)
  87. // Create some test data
  88. must(t, ffs.MkdirAll(".stfolder", 0755))
  89. oldData := []byte("hello\n")
  90. knownFiles := setupKnownFiles(t, ffs, oldData)
  91. // Send and index update for the known stuff
  92. m.Index(device1, "ro", knownFiles)
  93. f.updateLocalsFromScanning(knownFiles)
  94. // Scan the folder.
  95. must(t, m.ScanFolder("ro"))
  96. // Everything should be in sync.
  97. size := globalSize(t, m, "ro")
  98. if size.Files != 1 || size.Directories != 1 {
  99. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  100. }
  101. size = localSize(t, m, "ro")
  102. if size.Files != 1 || size.Directories != 1 {
  103. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  104. }
  105. size = needSize(t, m, "ro")
  106. if size.Files+size.Directories > 0 {
  107. t.Fatalf("Need: expected nothing: %+v", size)
  108. }
  109. size = receiveOnlyChangedSize(t, m, "ro")
  110. if size.Files+size.Directories > 0 {
  111. t.Fatalf("ROChanged: expected nothing: %+v", size)
  112. }
  113. // Update the file.
  114. newData := []byte("totally different data\n")
  115. must(t, writeFile(ffs, "knownDir/knownFile", newData, 0644))
  116. // Rescan.
  117. must(t, m.ScanFolder("ro"))
  118. // We now have a newer file than the rest of the cluster. Global state should reflect this.
  119. size = globalSize(t, m, "ro")
  120. const sizeOfDir = 128
  121. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
  122. t.Fatalf("Global: expected no change due to the new file: %+v", size)
  123. }
  124. size = localSize(t, m, "ro")
  125. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
  126. t.Fatalf("Local: expected the new file to be reflected: %+v", size)
  127. }
  128. size = needSize(t, m, "ro")
  129. if size.Files+size.Directories > 0 {
  130. t.Fatalf("Need: expected nothing: %+v", size)
  131. }
  132. size = receiveOnlyChangedSize(t, m, "ro")
  133. if size.Files+size.Directories == 0 {
  134. t.Fatalf("ROChanged: expected something: %+v", size)
  135. }
  136. // We hit the Revert button. The file that was new should become old.
  137. m.Revert("ro")
  138. size = globalSize(t, m, "ro")
  139. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
  140. t.Fatalf("Global: expected the global size to revert: %+v", size)
  141. }
  142. size = localSize(t, m, "ro")
  143. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
  144. t.Fatalf("Local: expected the local size to remain: %+v", size)
  145. }
  146. size = needSize(t, m, "ro")
  147. if size.Files != 1 || size.Bytes != int64(len(oldData)) {
  148. t.Fatalf("Local: expected to need the old file data: %+v", size)
  149. }
  150. }
  151. func TestRecvOnlyUndoChanges(t *testing.T) {
  152. // Get us a model up and running
  153. m, f := setupROFolder(t)
  154. ffs := f.Filesystem()
  155. defer cleanupModel(m)
  156. // Create some test data
  157. must(t, ffs.MkdirAll(".stfolder", 0755))
  158. oldData := []byte("hello\n")
  159. knownFiles := setupKnownFiles(t, ffs, oldData)
  160. // Send an index update for the known stuff
  161. m.Index(device1, "ro", knownFiles)
  162. f.updateLocalsFromScanning(knownFiles)
  163. // Scan the folder.
  164. must(t, m.ScanFolder("ro"))
  165. // Everything should be in sync.
  166. size := globalSize(t, m, "ro")
  167. if size.Files != 1 || size.Directories != 1 {
  168. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  169. }
  170. size = localSize(t, m, "ro")
  171. if size.Files != 1 || size.Directories != 1 {
  172. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  173. }
  174. size = needSize(t, m, "ro")
  175. if size.Files+size.Directories > 0 {
  176. t.Fatalf("Need: expected nothing: %+v", size)
  177. }
  178. size = receiveOnlyChangedSize(t, m, "ro")
  179. if size.Files+size.Directories > 0 {
  180. t.Fatalf("ROChanged: expected nothing: %+v", size)
  181. }
  182. // Create a file and modify another
  183. const file = "foo"
  184. must(t, writeFile(ffs, file, []byte("hello\n"), 0644))
  185. must(t, writeFile(ffs, "knownDir/knownFile", []byte("bye\n"), 0644))
  186. must(t, m.ScanFolder("ro"))
  187. size = receiveOnlyChangedSize(t, m, "ro")
  188. if size.Files != 2 {
  189. t.Fatalf("Receive only: expected 2 files: %+v", size)
  190. }
  191. // Remove the file again and undo the modification
  192. must(t, ffs.Remove(file))
  193. must(t, writeFile(ffs, "knownDir/knownFile", oldData, 0644))
  194. must(t, ffs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime()))
  195. must(t, m.ScanFolder("ro"))
  196. size = receiveOnlyChangedSize(t, m, "ro")
  197. if size.Files+size.Directories+size.Deleted != 0 {
  198. t.Fatalf("Receive only: expected all zero: %+v", size)
  199. }
  200. }
  201. func TestRecvOnlyDeletedRemoteDrop(t *testing.T) {
  202. // Get us a model up and running
  203. m, f := setupROFolder(t)
  204. ffs := f.Filesystem()
  205. defer cleanupModel(m)
  206. // Create some test data
  207. must(t, ffs.MkdirAll(".stfolder", 0755))
  208. oldData := []byte("hello\n")
  209. knownFiles := setupKnownFiles(t, ffs, oldData)
  210. // Send an index update for the known stuff
  211. m.Index(device1, "ro", knownFiles)
  212. f.updateLocalsFromScanning(knownFiles)
  213. // Scan the folder.
  214. must(t, m.ScanFolder("ro"))
  215. // Everything should be in sync.
  216. size := globalSize(t, m, "ro")
  217. if size.Files != 1 || size.Directories != 1 {
  218. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  219. }
  220. size = localSize(t, m, "ro")
  221. if size.Files != 1 || size.Directories != 1 {
  222. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  223. }
  224. size = needSize(t, m, "ro")
  225. if size.Files+size.Directories > 0 {
  226. t.Fatalf("Need: expected nothing: %+v", size)
  227. }
  228. size = receiveOnlyChangedSize(t, m, "ro")
  229. if size.Files+size.Directories > 0 {
  230. t.Fatalf("ROChanged: expected nothing: %+v", size)
  231. }
  232. // Delete our file
  233. must(t, ffs.Remove(knownFiles[1].Name))
  234. must(t, m.ScanFolder("ro"))
  235. size = receiveOnlyChangedSize(t, m, "ro")
  236. if size.Deleted != 1 {
  237. t.Fatalf("Receive only: expected 1 deleted: %+v", size)
  238. }
  239. // Drop the remote
  240. f.fset.Drop(device1)
  241. must(t, m.ScanFolder("ro"))
  242. size = receiveOnlyChangedSize(t, m, "ro")
  243. if size.Deleted != 0 {
  244. t.Fatalf("Receive only: expected no deleted: %+v", size)
  245. }
  246. }
  247. func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.FileInfo {
  248. t.Helper()
  249. must(t, ffs.MkdirAll("knownDir", 0755))
  250. must(t, writeFile(ffs, "knownDir/knownFile", data, 0644))
  251. t0 := time.Now().Add(-1 * time.Minute)
  252. must(t, ffs.Chtimes("knownDir/knownFile", t0, t0))
  253. fi, err := ffs.Stat("knownDir/knownFile")
  254. if err != nil {
  255. t.Fatal(err)
  256. }
  257. blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil, true)
  258. knownFiles := []protocol.FileInfo{
  259. {
  260. Name: "knownDir",
  261. Type: protocol.FileInfoTypeDirectory,
  262. Permissions: 0755,
  263. Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
  264. Sequence: 42,
  265. },
  266. {
  267. Name: "knownDir/knownFile",
  268. Type: protocol.FileInfoTypeFile,
  269. Permissions: 0644,
  270. Size: fi.Size(),
  271. ModifiedS: fi.ModTime().Unix(),
  272. ModifiedNs: int32(fi.ModTime().UnixNano() % 1e9),
  273. Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
  274. Sequence: 42,
  275. Blocks: blocks,
  276. },
  277. }
  278. return knownFiles
  279. }
  280. func setupROFolder(t *testing.T) (*model, *receiveOnlyFolder) {
  281. t.Helper()
  282. w := createTmpWrapper(defaultCfg)
  283. cfg := w.RawCopy()
  284. fcfg := testFolderConfigFake()
  285. fcfg.ID = "ro"
  286. fcfg.Label = "ro"
  287. fcfg.Type = config.FolderTypeReceiveOnly
  288. cfg.Folders = []config.FolderConfiguration{fcfg}
  289. w.Replace(cfg)
  290. m := newModel(w, myID, "syncthing", "dev", db.NewLowlevel(backend.OpenMemory()), nil)
  291. m.ServeBackground()
  292. must(t, m.ScanFolder("ro"))
  293. m.fmut.RLock()
  294. defer m.fmut.RUnlock()
  295. f := m.folderRunners["ro"].(*receiveOnlyFolder)
  296. return m, f
  297. }
  298. func writeFile(fs fs.Filesystem, filename string, data []byte, perm fs.FileMode) error {
  299. fd, err := fs.Create(filename)
  300. if err != nil {
  301. return err
  302. }
  303. _, err = fd.Write(data)
  304. if err != nil {
  305. return err
  306. }
  307. if err := fd.Close(); err != nil {
  308. return err
  309. }
  310. return fs.Chmod(filename, perm)
  311. }