folder_recvonly_test.go 9.4 KB

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