basicfs_watch_test.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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 = NewFilesystem(FilesystemTypeBasic, testDirAbs)
  33. backendBuffer = 10
  34. defer func() {
  35. backendBuffer = 500
  36. }()
  37. os.Exit(m.Run())
  38. }
  39. const (
  40. testDir = "temporary_test_root"
  41. )
  42. var (
  43. testDirAbs string
  44. testFs Filesystem
  45. )
  46. func TestWatchIgnore(t *testing.T) {
  47. name := "ignore"
  48. file := "file"
  49. ignored := "ignored"
  50. testCase := func() {
  51. createTestFile(name, file)
  52. createTestFile(name, ignored)
  53. }
  54. expectedEvents := []Event{
  55. {file, NonRemove},
  56. }
  57. allowedEvents := []Event{
  58. {name, NonRemove},
  59. }
  60. testScenario(t, name, testCase, expectedEvents, allowedEvents, 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. allowedEvents := []Event{
  80. {name, NonRemove},
  81. }
  82. // set the "allow others" flag because we might get the create of
  83. // "oldfile" initially
  84. testScenario(t, name, testCase, expectedEvents, allowedEvents, "")
  85. }
  86. // TestWatchOutside checks that no changes from outside the folder make it in
  87. func TestWatchOutside(t *testing.T) {
  88. outChan := make(chan Event)
  89. backendChan := make(chan notify.EventInfo, backendBuffer)
  90. ctx, cancel := context.WithCancel(context.Background())
  91. // testFs is Filesystem, but we need BasicFilesystem here
  92. fs := newBasicFilesystem(testDirAbs)
  93. go func() {
  94. defer func() {
  95. if recover() == nil {
  96. t.Fatalf("Watch did not panic on receiving event outside of folder")
  97. }
  98. cancel()
  99. }()
  100. fs.watchLoop(".", testDirAbs, backendChan, outChan, fakeMatcher{}, ctx)
  101. }()
  102. backendChan <- fakeEventInfo(filepath.Join(filepath.Dir(testDirAbs), "outside"))
  103. }
  104. func TestWatchSubpath(t *testing.T) {
  105. outChan := make(chan Event)
  106. backendChan := make(chan notify.EventInfo, backendBuffer)
  107. ctx, cancel := context.WithCancel(context.Background())
  108. // testFs is Filesystem, but we need BasicFilesystem here
  109. fs := newBasicFilesystem(testDirAbs)
  110. abs, _ := fs.rooted("sub")
  111. go fs.watchLoop("sub", abs, backendChan, outChan, fakeMatcher{}, ctx)
  112. backendChan <- fakeEventInfo(filepath.Join(abs, "file"))
  113. timeout := time.NewTimer(2 * time.Second)
  114. select {
  115. case <-timeout.C:
  116. t.Errorf("Timed out before receiving an event")
  117. cancel()
  118. case ev := <-outChan:
  119. if ev.Name != filepath.Join("sub", "file") {
  120. t.Errorf("While watching a subfolder, received an event with unexpected path %v", ev.Name)
  121. }
  122. }
  123. cancel()
  124. }
  125. // TestWatchOverflow checks that an event at the root is sent when maxFiles is reached
  126. func TestWatchOverflow(t *testing.T) {
  127. name := "overflow"
  128. expectedEvents := []Event{
  129. {".", NonRemove},
  130. }
  131. allowedEvents := []Event{
  132. {name, NonRemove},
  133. }
  134. testCase := func() {
  135. for i := 0; i < 5*backendBuffer; i++ {
  136. file := "file" + strconv.Itoa(i)
  137. createTestFile(name, file)
  138. allowedEvents = append(allowedEvents, Event{file, NonRemove})
  139. }
  140. }
  141. testScenario(t, name, testCase, expectedEvents, allowedEvents, "")
  142. }
  143. // path relative to folder root, also creates parent dirs if necessary
  144. func createTestFile(name string, file string) string {
  145. joined := filepath.Join(name, file)
  146. if err := testFs.MkdirAll(filepath.Dir(joined), 0755); err != nil {
  147. panic(fmt.Sprintf("Failed to create parent directory for %s: %s", joined, err))
  148. }
  149. handle, err := testFs.Create(joined)
  150. if err != nil {
  151. panic(fmt.Sprintf("Failed to create test file %s: %s", joined, err))
  152. }
  153. handle.Close()
  154. return file
  155. }
  156. func renameTestFile(name string, old string, new string) {
  157. old = filepath.Join(name, old)
  158. new = filepath.Join(name, new)
  159. if err := testFs.Rename(old, new); err != nil {
  160. panic(fmt.Sprintf("Failed to rename %s to %s: %s", old, new, err))
  161. }
  162. }
  163. func sleepMs(ms int) {
  164. time.Sleep(time.Duration(ms) * time.Millisecond)
  165. }
  166. func testScenario(t *testing.T, name string, testCase func(), expectedEvents, allowedEvents []Event, ignored string) {
  167. if err := testFs.MkdirAll(name, 0755); err != nil {
  168. panic(fmt.Sprintf("Failed to create directory %s: %s", name, err))
  169. }
  170. defer testFs.RemoveAll(name)
  171. ctx, cancel := context.WithCancel(context.Background())
  172. defer cancel()
  173. if ignored != "" {
  174. ignored = filepath.Join(name, ignored)
  175. }
  176. eventChan, err := testFs.Watch(name, fakeMatcher{ignored}, ctx, false)
  177. if err != nil {
  178. panic(err)
  179. }
  180. go testWatchOutput(t, name, eventChan, expectedEvents, allowedEvents, ctx, cancel)
  181. testCase()
  182. select {
  183. case <-time.NewTimer(time.Minute).C:
  184. t.Errorf("Timed out before receiving all expected events")
  185. case <-ctx.Done():
  186. }
  187. }
  188. func testWatchOutput(t *testing.T, name string, in <-chan Event, expectedEvents, allowedEvents []Event, ctx context.Context, cancel context.CancelFunc) {
  189. var expected = make(map[Event]struct{})
  190. for _, ev := range expectedEvents {
  191. ev.Name = filepath.Join(name, ev.Name)
  192. expected[ev] = struct{}{}
  193. }
  194. var received Event
  195. var last Event
  196. for {
  197. if len(expected) == 0 {
  198. cancel()
  199. return
  200. }
  201. select {
  202. case <-ctx.Done():
  203. return
  204. case received = <-in:
  205. }
  206. // apparently the backend sometimes sends repeat events
  207. if last == received {
  208. continue
  209. }
  210. if _, ok := expected[received]; !ok {
  211. if len(allowedEvents) > 0 {
  212. sleepMs(100) // To facilitate overflow
  213. continue
  214. }
  215. t.Errorf("Received unexpected event %v expected one of %v", received, expected)
  216. cancel()
  217. return
  218. }
  219. delete(expected, received)
  220. last = received
  221. }
  222. }
  223. type fakeMatcher struct{ match string }
  224. func (fm fakeMatcher) ShouldIgnore(name string) bool {
  225. return name == fm.match
  226. }
  227. type fakeEventInfo string
  228. func (e fakeEventInfo) Path() string {
  229. return string(e)
  230. }
  231. func (e fakeEventInfo) Event() notify.Event {
  232. return notify.Write
  233. }
  234. func (e fakeEventInfo) Sys() interface{} {
  235. return nil
  236. }