folder_recvonly_test.go 9.1 KB

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