basicfs_watch_test.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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 http://mozilla.org/MPL/2.0/.
  6. // +build !solaris,!darwin solaris,cgo darwin,cgo
  7. package fs
  8. import (
  9. "context"
  10. "fmt"
  11. "os"
  12. "path/filepath"
  13. "runtime"
  14. "strconv"
  15. "testing"
  16. "time"
  17. "github.com/zillode/notify"
  18. )
  19. func TestMain(m *testing.M) {
  20. if err := os.RemoveAll(testDir); err != nil {
  21. panic(err)
  22. }
  23. dir, err := filepath.Abs(".")
  24. if err != nil {
  25. panic("Cannot get absolute path to working dir")
  26. }
  27. dir, err = filepath.EvalSymlinks(dir)
  28. if err != nil {
  29. panic("Cannot get real path to working dir")
  30. }
  31. testDirAbs = filepath.Join(dir, testDir)
  32. testFs = newBasicFilesystem(testDirAbs)
  33. if l.ShouldDebug("filesystem") {
  34. testFs = &logFilesystem{testFs}
  35. }
  36. backendBuffer = 10
  37. defer func() {
  38. backendBuffer = 500
  39. }()
  40. os.Exit(m.Run())
  41. }
  42. const (
  43. testDir = "temporary_test_root"
  44. )
  45. var (
  46. testDirAbs string
  47. testFs Filesystem
  48. )
  49. func TestWatchIgnore(t *testing.T) {
  50. name := "ignore"
  51. file := "file"
  52. ignored := "ignored"
  53. testCase := func() {
  54. createTestFile(name, file)
  55. createTestFile(name, ignored)
  56. }
  57. expectedEvents := []Event{
  58. {file, NonRemove},
  59. }
  60. testScenario(t, name, testCase, expectedEvents, false, ignored)
  61. }
  62. func TestWatchRename(t *testing.T) {
  63. name := "rename"
  64. old := createTestFile(name, "oldfile")
  65. new := "newfile"
  66. testCase := func() {
  67. renameTestFile(name, old, new)
  68. }
  69. destEvent := Event{new, Remove}
  70. // Only on these platforms the removed file can be differentiated from
  71. // the created file during renaming
  72. if runtime.GOOS == "windows" || runtime.GOOS == "linux" || runtime.GOOS == "solaris" {
  73. destEvent = Event{new, NonRemove}
  74. }
  75. expectedEvents := []Event{
  76. {old, Remove},
  77. destEvent,
  78. }
  79. testScenario(t, name, testCase, expectedEvents, false, "")
  80. }
  81. // TestWatchOutside checks that no changes from outside the folder make it in
  82. func TestWatchOutside(t *testing.T) {
  83. outChan := make(chan Event)
  84. backendChan := make(chan notify.EventInfo, backendBuffer)
  85. ctx, cancel := context.WithCancel(context.Background())
  86. // testFs is Filesystem, but we need BasicFilesystem here
  87. fs := newBasicFilesystem(testDirAbs)
  88. go func() {
  89. defer func() {
  90. if recover() == nil {
  91. t.Fatalf("Watch did not panic on receiving event outside of folder")
  92. }
  93. cancel()
  94. }()
  95. fs.watchLoop(".", testDirAbs, backendChan, outChan, fakeMatcher{}, ctx)
  96. }()
  97. backendChan <- fakeEventInfo(filepath.Join(filepath.Dir(testDirAbs), "outside"))
  98. }
  99. func TestWatchSubpath(t *testing.T) {
  100. outChan := make(chan Event)
  101. backendChan := make(chan notify.EventInfo, backendBuffer)
  102. ctx, cancel := context.WithCancel(context.Background())
  103. // testFs is Filesystem, but we need BasicFilesystem here
  104. fs := newBasicFilesystem(testDirAbs)
  105. abs, _ := fs.rooted("sub")
  106. go fs.watchLoop("sub", abs, backendChan, outChan, fakeMatcher{}, ctx)
  107. backendChan <- fakeEventInfo(filepath.Join(abs, "file"))
  108. timeout := time.NewTimer(2 * time.Second)
  109. select {
  110. case <-timeout.C:
  111. t.Errorf("Timed out before receiving an event")
  112. cancel()
  113. case ev := <-outChan:
  114. if ev.Name != filepath.Join("sub", "file") {
  115. t.Errorf("While watching a subfolder, received an event with unexpected path %v", ev.Name)
  116. }
  117. }
  118. cancel()
  119. }
  120. // TestWatchOverflow checks that an event at the root is sent when maxFiles is reached
  121. func TestWatchOverflow(t *testing.T) {
  122. name := "overflow"
  123. testCase := func() {
  124. for i := 0; i < 5*backendBuffer; i++ {
  125. createTestFile(name, "file"+strconv.Itoa(i))
  126. }
  127. }
  128. expectedEvents := []Event{
  129. {".", NonRemove},
  130. }
  131. testScenario(t, name, testCase, expectedEvents, true, "")
  132. }
  133. // path relative to folder root, also creates parent dirs if necessary
  134. func createTestFile(name string, file string) string {
  135. joined := filepath.Join(name, file)
  136. if err := testFs.MkdirAll(filepath.Dir(joined), 0755); err != nil {
  137. panic(fmt.Sprintf("Failed to create parent directory for %s: %s", joined, err))
  138. }
  139. handle, err := testFs.Create(joined)
  140. if err != nil {
  141. panic(fmt.Sprintf("Failed to create test file %s: %s", joined, err))
  142. }
  143. handle.Close()
  144. return file
  145. }
  146. func renameTestFile(name string, old string, new string) {
  147. old = filepath.Join(name, old)
  148. new = filepath.Join(name, new)
  149. if err := testFs.Rename(old, new); err != nil {
  150. panic(fmt.Sprintf("Failed to rename %s to %s: %s", old, new, err))
  151. }
  152. }
  153. func sleepMs(ms int) {
  154. time.Sleep(time.Duration(ms) * time.Millisecond)
  155. }
  156. func testScenario(t *testing.T, name string, testCase func(), expectedEvents []Event, allowOthers bool, ignored string) {
  157. if err := testFs.MkdirAll(name, 0755); err != nil {
  158. panic(fmt.Sprintf("Failed to create directory %s: %s", name, err))
  159. }
  160. // Tests pick up the previously created files/dirs, probably because
  161. // they get flushed to disk with a delay.
  162. initDelayMs := 500
  163. if runtime.GOOS == "darwin" {
  164. initDelayMs = 2000
  165. }
  166. sleepMs(initDelayMs)
  167. ctx, cancel := context.WithCancel(context.Background())
  168. if ignored != "" {
  169. ignored = filepath.Join(name, ignored)
  170. }
  171. eventChan, err := testFs.Watch(name, fakeMatcher{ignored}, ctx, false)
  172. if err != nil {
  173. panic(err)
  174. }
  175. go testWatchOutput(t, name, eventChan, expectedEvents, allowOthers, ctx, cancel)
  176. timeoutDuration := 2 * time.Second
  177. if runtime.GOOS == "darwin" {
  178. timeoutDuration *= 2
  179. }
  180. timeout := time.NewTimer(timeoutDuration)
  181. testCase()
  182. select {
  183. case <-timeout.C:
  184. t.Errorf("Timed out before receiving all expected events")
  185. cancel()
  186. case <-ctx.Done():
  187. }
  188. if err := testFs.RemoveAll(name); err != nil {
  189. panic(fmt.Sprintf("Failed to remove directory %s: %s", name, err))
  190. }
  191. }
  192. func testWatchOutput(t *testing.T, name string, in <-chan Event, expectedEvents []Event, allowOthers bool, ctx context.Context, cancel context.CancelFunc) {
  193. var expected = make(map[Event]struct{})
  194. for _, ev := range expectedEvents {
  195. ev.Name = filepath.Join(name, ev.Name)
  196. expected[ev] = struct{}{}
  197. }
  198. var received Event
  199. var last Event
  200. for {
  201. if len(expected) == 0 {
  202. cancel()
  203. return
  204. }
  205. select {
  206. case <-ctx.Done():
  207. return
  208. case received = <-in:
  209. }
  210. // apparently the backend sometimes sends repeat events
  211. if last == received {
  212. continue
  213. }
  214. if _, ok := expected[received]; !ok {
  215. if allowOthers {
  216. sleepMs(100) // To facilitate overflow
  217. continue
  218. }
  219. t.Errorf("Received unexpected event %v expected one of %v", received, expected)
  220. cancel()
  221. return
  222. }
  223. delete(expected, received)
  224. last = received
  225. }
  226. }
  227. type fakeMatcher struct{ match string }
  228. func (fm fakeMatcher) ShouldIgnore(name string) bool {
  229. return name == fm.match
  230. }
  231. type fakeEventInfo string
  232. func (e fakeEventInfo) Path() string {
  233. return string(e)
  234. }
  235. func (e fakeEventInfo) Event() notify.Event {
  236. return notify.Write
  237. }
  238. func (e fakeEventInfo) Sys() interface{} {
  239. return nil
  240. }