folder_recvonly_test.go 9.5 KB

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