mtimefs_test.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // Copyright (C) 2016 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 fs
  7. import (
  8. "errors"
  9. "os"
  10. "path/filepath"
  11. "runtime"
  12. "testing"
  13. "time"
  14. )
  15. func TestMtimeFS(t *testing.T) {
  16. os.RemoveAll("testdata")
  17. defer os.RemoveAll("testdata")
  18. os.Mkdir("testdata", 0755)
  19. os.WriteFile("testdata/exists0", []byte("hello"), 0644)
  20. os.WriteFile("testdata/exists1", []byte("hello"), 0644)
  21. os.WriteFile("testdata/exists2", []byte("hello"), 0644)
  22. // a random time with nanosecond precision
  23. testTime := time.Unix(1234567890, 123456789)
  24. mtimefs := newMtimeFS(".", make(mapStore))
  25. // Do one Chtimes call that will go through to the normal filesystem
  26. mtimefs.chtimes = os.Chtimes
  27. if err := mtimefs.Chtimes("testdata/exists0", testTime, testTime); err != nil {
  28. t.Error("Should not have failed:", err)
  29. }
  30. // Do one call that gets an error back from the underlying Chtimes
  31. mtimefs.chtimes = failChtimes
  32. if err := mtimefs.Chtimes("testdata/exists1", testTime, testTime); err != nil {
  33. t.Error("Should not have failed:", err)
  34. }
  35. // Do one call that gets struck by an exceptionally evil Chtimes
  36. mtimefs.chtimes = evilChtimes
  37. if err := mtimefs.Chtimes("testdata/exists2", testTime, testTime); err != nil {
  38. t.Error("Should not have failed:", err)
  39. }
  40. // All of the calls were successful, so an Lstat on them should return
  41. // the test timestamp.
  42. for _, file := range []string{"testdata/exists0", "testdata/exists1", "testdata/exists2"} {
  43. if info, err := mtimefs.Lstat(file); err != nil {
  44. t.Error("Lstat shouldn't fail:", err)
  45. } else if !info.ModTime().Equal(testTime) {
  46. t.Errorf("Time mismatch; %v != expected %v", info.ModTime(), testTime)
  47. }
  48. }
  49. // The two last files should certainly not have the correct timestamp
  50. // when looking directly on disk though.
  51. for _, file := range []string{"testdata/exists1", "testdata/exists2"} {
  52. if info, err := os.Lstat(file); err != nil {
  53. t.Error("Lstat shouldn't fail:", err)
  54. } else if info.ModTime().Equal(testTime) {
  55. t.Errorf("Unexpected time match; %v == %v", info.ModTime(), testTime)
  56. }
  57. }
  58. // Changing the timestamp on disk should be reflected in a new Lstat
  59. // call. Choose a time that is likely to be able to be on all reasonable
  60. // filesystems.
  61. testTime = time.Now().Add(5 * time.Hour).Truncate(time.Minute)
  62. os.Chtimes("testdata/exists0", testTime, testTime)
  63. if info, err := mtimefs.Lstat("testdata/exists0"); err != nil {
  64. t.Error("Lstat shouldn't fail:", err)
  65. } else if !info.ModTime().Equal(testTime) {
  66. t.Errorf("Time mismatch; %v != expected %v", info.ModTime(), testTime)
  67. }
  68. }
  69. func TestMtimeFSWalk(t *testing.T) {
  70. dir, err := os.MkdirTemp("", "")
  71. if err != nil {
  72. t.Fatal(err)
  73. }
  74. defer func() { _ = os.RemoveAll(dir) }()
  75. mtimefs, walkFs := newMtimeFSWithWalk(dir, make(mapStore))
  76. underlying := mtimefs.Filesystem
  77. mtimefs.chtimes = failChtimes
  78. if err := os.WriteFile(filepath.Join(dir, "file"), []byte("hello"), 0644); err != nil {
  79. t.Fatal(err)
  80. }
  81. oldStat, err := mtimefs.Lstat("file")
  82. if err != nil {
  83. t.Fatal(err)
  84. }
  85. newTime := time.Now().Add(-2 * time.Hour)
  86. if err := mtimefs.Chtimes("file", newTime, newTime); err != nil {
  87. t.Fatal(err)
  88. }
  89. if newStat, err := mtimefs.Lstat("file"); err != nil {
  90. t.Fatal(err)
  91. } else if !newStat.ModTime().Equal(newTime) {
  92. t.Errorf("expected time %v, lstat time %v", newTime, newStat.ModTime())
  93. }
  94. if underlyingStat, err := underlying.Lstat("file"); err != nil {
  95. t.Fatal(err)
  96. } else if !underlyingStat.ModTime().Equal(oldStat.ModTime()) {
  97. t.Errorf("expected time %v, lstat time %v", oldStat.ModTime(), underlyingStat.ModTime())
  98. }
  99. found := false
  100. _ = walkFs.Walk("", func(path string, info FileInfo, err error) error {
  101. if path == "file" {
  102. found = true
  103. if !info.ModTime().Equal(newTime) {
  104. t.Errorf("expected time %v, lstat time %v", newTime, info.ModTime())
  105. }
  106. }
  107. return nil
  108. })
  109. if !found {
  110. t.Error("did not find")
  111. }
  112. }
  113. func TestMtimeFSOpen(t *testing.T) {
  114. dir, err := os.MkdirTemp("", "")
  115. if err != nil {
  116. t.Fatal(err)
  117. }
  118. defer func() { _ = os.RemoveAll(dir) }()
  119. mtimefs := newMtimeFS(dir, make(mapStore))
  120. underlying := mtimefs.Filesystem
  121. mtimefs.chtimes = failChtimes
  122. if err := os.WriteFile(filepath.Join(dir, "file"), []byte("hello"), 0644); err != nil {
  123. t.Fatal(err)
  124. }
  125. oldStat, err := mtimefs.Lstat("file")
  126. if err != nil {
  127. t.Fatal(err)
  128. }
  129. newTime := time.Now().Add(-2 * time.Hour)
  130. if err := mtimefs.Chtimes("file", newTime, newTime); err != nil {
  131. t.Fatal(err)
  132. }
  133. if newStat, err := mtimefs.Lstat("file"); err != nil {
  134. t.Fatal(err)
  135. } else if !newStat.ModTime().Equal(newTime) {
  136. t.Errorf("expected time %v, lstat time %v", newTime, newStat.ModTime())
  137. }
  138. if underlyingStat, err := underlying.Lstat("file"); err != nil {
  139. t.Fatal(err)
  140. } else if !underlyingStat.ModTime().Equal(oldStat.ModTime()) {
  141. t.Errorf("expected time %v, lstat time %v", oldStat.ModTime(), underlyingStat.ModTime())
  142. }
  143. fd, err := mtimefs.Open("file")
  144. if err != nil {
  145. t.Fatal(err)
  146. }
  147. info, err := fd.Stat()
  148. if err != nil {
  149. t.Fatal(err)
  150. }
  151. if !info.ModTime().Equal(newTime) {
  152. t.Errorf("expected time %v, lstat time %v", newTime, info.ModTime())
  153. }
  154. }
  155. func TestMtimeFSInsensitive(t *testing.T) {
  156. switch runtime.GOOS {
  157. case "darwin", "windows":
  158. // blatantly assume file systems here are case insensitive. Might be
  159. // a spurious failure on oddly configured systems.
  160. default:
  161. t.Skip("need case insensitive FS")
  162. }
  163. theTest := func(t *testing.T, fs *mtimeFS, shouldSucceed bool) {
  164. os.RemoveAll("testdata")
  165. defer os.RemoveAll("testdata")
  166. os.Mkdir("testdata", 0755)
  167. os.WriteFile("testdata/FiLe", []byte("hello"), 0644)
  168. // a random time with nanosecond precision
  169. testTime := time.Unix(1234567890, 123456789)
  170. // Do one call that gets struck by an exceptionally evil Chtimes, with a
  171. // different case from what is on disk.
  172. fs.chtimes = evilChtimes
  173. if err := fs.Chtimes("testdata/fIlE", testTime, testTime); err != nil {
  174. t.Error("Should not have failed:", err)
  175. }
  176. // Check that we get back the mtime we set, if we were supposed to succed.
  177. info, err := fs.Lstat("testdata/FILE")
  178. if err != nil {
  179. t.Error("Lstat shouldn't fail:", err)
  180. } else if info.ModTime().Equal(testTime) != shouldSucceed {
  181. t.Errorf("Time mismatch; got %v, comparison %v, expected equal=%v", info.ModTime(), testTime, shouldSucceed)
  182. }
  183. }
  184. // The test should fail with a case sensitive mtimefs
  185. t.Run("with case sensitive mtimefs", func(t *testing.T) {
  186. theTest(t, newMtimeFS(".", make(mapStore)), false)
  187. })
  188. // And succeed with a case insensitive one.
  189. t.Run("with case insensitive mtimefs", func(t *testing.T) {
  190. theTest(t, newMtimeFS(".", make(mapStore), WithCaseInsensitivity(true)), true)
  191. })
  192. }
  193. // The mapStore is a simple database
  194. type mapStore map[string][]byte
  195. func (s mapStore) PutBytes(key string, data []byte) error {
  196. s[key] = data
  197. return nil
  198. }
  199. func (s mapStore) Bytes(key string) (data []byte, ok bool, err error) {
  200. data, ok = s[key]
  201. return
  202. }
  203. func (s mapStore) Delete(key string) error {
  204. delete(s, key)
  205. return nil
  206. }
  207. // failChtimes does nothing, and fails
  208. func failChtimes(name string, mtime, atime time.Time) error {
  209. return errors.New("no")
  210. }
  211. // evilChtimes will set an mtime that's 300 days in the future of what was
  212. // asked for, and truncate the time to the closest hour.
  213. func evilChtimes(name string, mtime, atime time.Time) error {
  214. return os.Chtimes(name, mtime.Add(300*time.Hour).Truncate(time.Hour), atime.Add(300*time.Hour).Truncate(time.Hour))
  215. }
  216. func newMtimeFS(path string, db database, options ...MtimeFSOption) *mtimeFS {
  217. mtimefs, _ := newMtimeFSWithWalk(path, db, options...)
  218. return mtimefs
  219. }
  220. func newMtimeFSWithWalk(path string, db database, options ...MtimeFSOption) (*mtimeFS, *walkFilesystem) {
  221. wfs := NewFilesystem(FilesystemTypeBasic, path, NewMtimeOption(db, options...)).(*walkFilesystem)
  222. return wfs.Filesystem.(*mtimeFS), wfs
  223. }