trashcan_test.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. // Copyright (C) 2015 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 versioner
  7. import (
  8. "context"
  9. "io"
  10. "os"
  11. "path/filepath"
  12. "testing"
  13. "time"
  14. "github.com/syncthing/syncthing/lib/config"
  15. "github.com/syncthing/syncthing/lib/fs"
  16. )
  17. func TestTrashcanArchiveRestoreSwitcharoo(t *testing.T) {
  18. // This tests that trashcan versioner restoration correctly archives existing file, because trashcan versioner
  19. // files are untagged, archiving existing file to replace with a restored version technically should collide in
  20. // in names.
  21. tmpDir1 := t.TempDir()
  22. tmpDir2 := t.TempDir()
  23. cfg := config.FolderConfiguration{
  24. FilesystemType: fs.FilesystemTypeBasic,
  25. Path: tmpDir1,
  26. Versioning: config.VersioningConfiguration{
  27. FSType: fs.FilesystemTypeBasic,
  28. FSPath: tmpDir2,
  29. },
  30. }
  31. folderFs := cfg.Filesystem(nil)
  32. versionsFs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir2)
  33. writeFile(t, folderFs, "file", "A")
  34. versioner := newTrashcan(cfg)
  35. if err := versioner.Archive("file"); err != nil {
  36. t.Fatal(err)
  37. }
  38. if _, err := folderFs.Stat("file"); !fs.IsNotExist(err) {
  39. t.Fatal(err)
  40. }
  41. // Check versions
  42. versions, err := versioner.GetVersions()
  43. if err != nil {
  44. t.Fatal(err)
  45. }
  46. fileVersions := versions["file"]
  47. if len(fileVersions) != 1 {
  48. t.Fatalf("unexpected number of versions: %d != 1", len(fileVersions))
  49. }
  50. fileVersion := fileVersions[0]
  51. if !fileVersion.ModTime.Equal(fileVersion.VersionTime) {
  52. t.Error("time mismatch")
  53. }
  54. if content := readFile(t, versionsFs, "file"); content != "A" {
  55. t.Errorf("expected A got %s", content)
  56. }
  57. writeFile(t, folderFs, "file", "B")
  58. versionInfo, err := versionsFs.Stat("file")
  59. if err != nil {
  60. t.Fatal(err)
  61. }
  62. if !versionInfo.ModTime().Truncate(time.Second).Equal(fileVersion.ModTime) {
  63. t.Error("time mismatch")
  64. }
  65. if err := versioner.Restore("file", fileVersion.VersionTime); err != nil {
  66. t.Fatal(err)
  67. }
  68. if content := readFile(t, folderFs, "file"); content != "A" {
  69. t.Errorf("expected A got %s", content)
  70. }
  71. if content := readFile(t, versionsFs, "file"); content != "B" {
  72. t.Errorf("expected B got %s", content)
  73. }
  74. }
  75. func TestTrashcanRestoreDeletedFile(t *testing.T) {
  76. // This tests that the Trash Can restore function works correctly when the file
  77. // to be restored was deleted/nonexistent in the folder where the file/folder is
  78. // going to be restored in. (Issue: #7965)
  79. tmpDir1 := t.TempDir()
  80. tmpDir2 := t.TempDir()
  81. cfg := config.FolderConfiguration{
  82. FilesystemType: fs.FilesystemTypeBasic,
  83. Path: tmpDir1,
  84. Versioning: config.VersioningConfiguration{
  85. FSType: fs.FilesystemTypeBasic,
  86. FSPath: tmpDir2,
  87. },
  88. }
  89. folderFs := cfg.Filesystem(nil)
  90. versionsFs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir2)
  91. versioner := newTrashcan(cfg)
  92. writeFile(t, folderFs, "file", "Some content")
  93. if err := versioner.Archive("file"); err != nil {
  94. t.Fatal(err)
  95. }
  96. // Shouldn't be in the default folder anymore, thus "deleted"
  97. if _, err := folderFs.Stat("file"); !fs.IsNotExist(err) {
  98. t.Fatal(err)
  99. }
  100. // It should, however, be in the archive
  101. if _, err := versionsFs.Lstat("file"); fs.IsNotExist(err) {
  102. t.Fatal(err)
  103. }
  104. versions, err := versioner.GetVersions()
  105. if err != nil {
  106. t.Fatal(err)
  107. }
  108. fileVersions := versions["file"]
  109. if len(fileVersions) != 1 {
  110. t.Fatalf("unexpected number of versions: %d != 1", len(fileVersions))
  111. }
  112. fileVersion := fileVersions[0]
  113. if !fileVersion.ModTime.Equal(fileVersion.VersionTime) {
  114. t.Error("time mismatch")
  115. }
  116. // Restore the file from the archive.
  117. if err := versioner.Restore("file", fileVersion.VersionTime); err != nil {
  118. t.Fatal(err)
  119. }
  120. // The file should be correctly restored
  121. if content := readFile(t, folderFs, "file"); content != "Some content" {
  122. t.Errorf("expected A got %s", content)
  123. }
  124. // It should no longer be in the archive
  125. if _, err := versionsFs.Lstat("file"); !fs.IsNotExist(err) {
  126. t.Fatal(err)
  127. }
  128. }
  129. func readFile(t *testing.T, filesystem fs.Filesystem, name string) string {
  130. t.Helper()
  131. fd, err := filesystem.Open(name)
  132. if err != nil {
  133. t.Fatal(err)
  134. }
  135. defer fd.Close()
  136. buf, err := io.ReadAll(fd)
  137. if err != nil {
  138. t.Fatal(err)
  139. }
  140. return string(buf)
  141. }
  142. func writeFile(t *testing.T, filesystem fs.Filesystem, name, content string) {
  143. fd, err := filesystem.OpenFile(name, fs.OptReadWrite|fs.OptCreate, 0777)
  144. if err != nil {
  145. t.Fatal(err)
  146. }
  147. defer fd.Close()
  148. if err := fd.Truncate(int64(len(content))); err != nil {
  149. t.Fatal(err)
  150. }
  151. if n, err := fd.Write([]byte(content)); err != nil || n != len(content) {
  152. t.Fatal(n, len(content), err)
  153. }
  154. }
  155. func TestTrashcanCleanOut(t *testing.T) {
  156. testDir := t.TempDir()
  157. cfg := config.FolderConfiguration{
  158. FilesystemType: fs.FilesystemTypeBasic,
  159. Path: testDir,
  160. Versioning: config.VersioningConfiguration{
  161. Params: map[string]string{
  162. "cleanoutDays": "7",
  163. },
  164. },
  165. }
  166. fs := cfg.Filesystem(nil)
  167. v := newTrashcan(cfg)
  168. var testcases = map[string]bool{
  169. ".stversions/file1": false,
  170. ".stversions/file2": true,
  171. ".stversions/keep1/file1": false,
  172. ".stversions/keep1/file2": false,
  173. ".stversions/keep2/file1": false,
  174. ".stversions/keep2/file2": true,
  175. ".stversions/keep3/keepsubdir/file1": false,
  176. ".stversions/remove/file1": true,
  177. ".stversions/remove/file2": true,
  178. ".stversions/remove/removesubdir/file1": true,
  179. }
  180. t.Run("trashcan versioner trashcan clean up", func(t *testing.T) {
  181. oldTime := time.Now().Add(-8 * 24 * time.Hour)
  182. for file, shouldRemove := range testcases {
  183. fs.MkdirAll(filepath.Dir(file), 0777)
  184. writeFile(t, fs, file, "some content")
  185. if shouldRemove {
  186. if err := fs.Chtimes(file, oldTime, oldTime); err != nil {
  187. t.Fatal(err)
  188. }
  189. }
  190. }
  191. if err := v.Clean(context.Background()); err != nil {
  192. t.Fatal(err)
  193. }
  194. for file, shouldRemove := range testcases {
  195. _, err := fs.Lstat(file)
  196. if shouldRemove && !os.IsNotExist(err) {
  197. t.Error(file, "should have been removed")
  198. } else if !shouldRemove && err != nil {
  199. t.Error(file, "should not have been removed")
  200. }
  201. }
  202. if _, err := fs.Lstat(".stversions/keep3"); os.IsNotExist(err) {
  203. t.Error("directory with non empty subdirs should not be removed")
  204. }
  205. if _, err := fs.Lstat(".stversions/remove"); !os.IsNotExist(err) {
  206. t.Error("empty directory should have been removed")
  207. }
  208. })
  209. }