mtimefs_test.go 7.6 KB

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