basicfs_watch_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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. "errors"
  11. "fmt"
  12. "os"
  13. "path/filepath"
  14. "runtime"
  15. "strconv"
  16. "syscall"
  17. "testing"
  18. "time"
  19. "github.com/syncthing/notify"
  20. )
  21. func TestMain(m *testing.M) {
  22. if err := os.RemoveAll(testDir); err != nil {
  23. panic(err)
  24. }
  25. dir, err := filepath.Abs(".")
  26. if err != nil {
  27. panic("Cannot get absolute path to working dir")
  28. }
  29. dir, err = filepath.EvalSymlinks(dir)
  30. if err != nil {
  31. panic("Cannot get real path to working dir")
  32. }
  33. testDirAbs = filepath.Join(dir, testDir)
  34. testFs = NewFilesystem(FilesystemTypeBasic, testDirAbs)
  35. backendBuffer = 10
  36. defer func() {
  37. backendBuffer = 500
  38. os.RemoveAll(testDir)
  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. allowedEvents := []Event{
  61. {name, NonRemove},
  62. }
  63. testScenario(t, name, testCase, expectedEvents, allowedEvents, ignored)
  64. }
  65. func TestWatchRename(t *testing.T) {
  66. name := "rename"
  67. old := createTestFile(name, "oldfile")
  68. new := "newfile"
  69. testCase := func() {
  70. renameTestFile(name, old, new)
  71. }
  72. destEvent := Event{new, Remove}
  73. // Only on these platforms the removed file can be differentiated from
  74. // the created file during renaming
  75. if runtime.GOOS == "windows" || runtime.GOOS == "linux" || runtime.GOOS == "solaris" {
  76. destEvent = Event{new, NonRemove}
  77. }
  78. expectedEvents := []Event{
  79. {old, Remove},
  80. destEvent,
  81. }
  82. allowedEvents := []Event{
  83. {name, NonRemove},
  84. }
  85. // set the "allow others" flag because we might get the create of
  86. // "oldfile" initially
  87. testScenario(t, name, testCase, expectedEvents, allowedEvents, "")
  88. }
  89. // TestWatchOutside checks that no changes from outside the folder make it in
  90. func TestWatchOutside(t *testing.T) {
  91. outChan := make(chan Event)
  92. backendChan := make(chan notify.EventInfo, backendBuffer)
  93. ctx, cancel := context.WithCancel(context.Background())
  94. // testFs is Filesystem, but we need BasicFilesystem here
  95. fs := newBasicFilesystem(testDirAbs)
  96. go func() {
  97. defer func() {
  98. if recover() == nil {
  99. t.Fatalf("Watch did not panic on receiving event outside of folder")
  100. }
  101. cancel()
  102. }()
  103. fs.watchLoop(".", backendChan, outChan, fakeMatcher{}, ctx)
  104. }()
  105. backendChan <- fakeEventInfo(filepath.Join(filepath.Dir(testDirAbs), "outside"))
  106. }
  107. func TestWatchSubpath(t *testing.T) {
  108. outChan := make(chan Event)
  109. backendChan := make(chan notify.EventInfo, backendBuffer)
  110. ctx, cancel := context.WithCancel(context.Background())
  111. // testFs is Filesystem, but we need BasicFilesystem here
  112. fs := newBasicFilesystem(testDirAbs)
  113. abs, _ := fs.rooted("sub")
  114. go fs.watchLoop("sub", backendChan, outChan, fakeMatcher{}, ctx)
  115. backendChan <- fakeEventInfo(filepath.Join(abs, "file"))
  116. timeout := time.NewTimer(2 * time.Second)
  117. select {
  118. case <-timeout.C:
  119. t.Errorf("Timed out before receiving an event")
  120. cancel()
  121. case ev := <-outChan:
  122. if ev.Name != filepath.Join("sub", "file") {
  123. t.Errorf("While watching a subfolder, received an event with unexpected path %v", ev.Name)
  124. }
  125. }
  126. cancel()
  127. }
  128. // TestWatchOverflow checks that an event at the root is sent when maxFiles is reached
  129. func TestWatchOverflow(t *testing.T) {
  130. name := "overflow"
  131. expectedEvents := []Event{
  132. {".", NonRemove},
  133. }
  134. allowedEvents := []Event{
  135. {name, NonRemove},
  136. }
  137. testCase := func() {
  138. for i := 0; i < 5*backendBuffer; i++ {
  139. file := "file" + strconv.Itoa(i)
  140. createTestFile(name, file)
  141. allowedEvents = append(allowedEvents, Event{file, NonRemove})
  142. }
  143. }
  144. testScenario(t, name, testCase, expectedEvents, allowedEvents, "")
  145. }
  146. func TestWatchErrorLinuxInterpretation(t *testing.T) {
  147. if runtime.GOOS != "linux" {
  148. t.Skip("testing of linux specific error codes")
  149. }
  150. var errTooManyFiles = &os.PathError{
  151. Op: "error while traversing",
  152. Path: "foo",
  153. Err: syscall.Errno(24),
  154. }
  155. var errNoSpace = &os.PathError{
  156. Op: "error while traversing",
  157. Path: "bar",
  158. Err: syscall.Errno(28),
  159. }
  160. if !reachedMaxUserWatches(errTooManyFiles) {
  161. t.Error("Underlying error syscall.Errno(24) should be recognised to be about inotify limits.")
  162. }
  163. if !reachedMaxUserWatches(errNoSpace) {
  164. t.Error("Underlying error syscall.Errno(28) should be recognised to be about inotify limits.")
  165. }
  166. err := errors.New("Another error")
  167. if reachedMaxUserWatches(err) {
  168. t.Errorf("This error does not concern inotify limits: %#v", err)
  169. }
  170. }
  171. func TestWatchSymlinkedRoot(t *testing.T) {
  172. if runtime.GOOS == "windows" {
  173. t.Skip("Involves symlinks")
  174. }
  175. name := "symlinkedRoot"
  176. if err := testFs.MkdirAll(name, 0755); err != nil {
  177. panic(fmt.Sprintf("Failed to create directory %s: %s", name, err))
  178. }
  179. defer testFs.RemoveAll(name)
  180. root := filepath.Join(name, "root")
  181. if err := testFs.MkdirAll(root, 0777); err != nil {
  182. panic(err)
  183. }
  184. link := filepath.Join(name, "link")
  185. if err := testFs.CreateSymlink(filepath.Join(testFs.URI(), root), link); err != nil {
  186. panic(err)
  187. }
  188. linkedFs := NewFilesystem(FilesystemTypeBasic, filepath.Join(testFs.URI(), link))
  189. ctx, cancel := context.WithCancel(context.Background())
  190. defer cancel()
  191. if _, err := linkedFs.Watch(".", fakeMatcher{}, ctx, false); err != nil {
  192. panic(err)
  193. }
  194. if err := linkedFs.MkdirAll("foo", 0777); err != nil {
  195. panic(err)
  196. }
  197. // Give the panic some time to happen
  198. sleepMs(100)
  199. }
  200. func TestUnrootedChecked(t *testing.T) {
  201. var unrooted string
  202. defer func() {
  203. if recover() == nil {
  204. t.Fatal("unrootedChecked did not panic on outside path, but returned", unrooted)
  205. }
  206. }()
  207. fs := newBasicFilesystem(testDirAbs)
  208. unrooted = fs.unrootedChecked("/random/other/path")
  209. }
  210. // path relative to folder root, also creates parent dirs if necessary
  211. func createTestFile(name string, file string) string {
  212. joined := filepath.Join(name, file)
  213. if err := testFs.MkdirAll(filepath.Dir(joined), 0755); err != nil {
  214. panic(fmt.Sprintf("Failed to create parent directory for %s: %s", joined, err))
  215. }
  216. handle, err := testFs.Create(joined)
  217. if err != nil {
  218. panic(fmt.Sprintf("Failed to create test file %s: %s", joined, err))
  219. }
  220. handle.Close()
  221. return file
  222. }
  223. func renameTestFile(name string, old string, new string) {
  224. old = filepath.Join(name, old)
  225. new = filepath.Join(name, new)
  226. if err := testFs.Rename(old, new); err != nil {
  227. panic(fmt.Sprintf("Failed to rename %s to %s: %s", old, new, err))
  228. }
  229. }
  230. func sleepMs(ms int) {
  231. time.Sleep(time.Duration(ms) * time.Millisecond)
  232. }
  233. func testScenario(t *testing.T, name string, testCase func(), expectedEvents, allowedEvents []Event, ignored string) {
  234. if err := testFs.MkdirAll(name, 0755); err != nil {
  235. panic(fmt.Sprintf("Failed to create directory %s: %s", name, err))
  236. }
  237. defer testFs.RemoveAll(name)
  238. ctx, cancel := context.WithCancel(context.Background())
  239. defer cancel()
  240. if ignored != "" {
  241. ignored = filepath.Join(name, ignored)
  242. }
  243. eventChan, err := testFs.Watch(name, fakeMatcher{ignored}, ctx, false)
  244. if err != nil {
  245. panic(err)
  246. }
  247. go testWatchOutput(t, name, eventChan, expectedEvents, allowedEvents, ctx, cancel)
  248. testCase()
  249. select {
  250. case <-time.NewTimer(time.Minute).C:
  251. t.Errorf("Timed out before receiving all expected events")
  252. case <-ctx.Done():
  253. }
  254. }
  255. func testWatchOutput(t *testing.T, name string, in <-chan Event, expectedEvents, allowedEvents []Event, ctx context.Context, cancel context.CancelFunc) {
  256. var expected = make(map[Event]struct{})
  257. for _, ev := range expectedEvents {
  258. ev.Name = filepath.Join(name, ev.Name)
  259. expected[ev] = struct{}{}
  260. }
  261. var received Event
  262. var last Event
  263. for {
  264. if len(expected) == 0 {
  265. cancel()
  266. return
  267. }
  268. select {
  269. case <-ctx.Done():
  270. return
  271. case received = <-in:
  272. }
  273. // apparently the backend sometimes sends repeat events
  274. if last == received {
  275. continue
  276. }
  277. if _, ok := expected[received]; !ok {
  278. if len(allowedEvents) > 0 {
  279. sleepMs(100) // To facilitate overflow
  280. continue
  281. }
  282. t.Errorf("Received unexpected event %v expected one of %v", received, expected)
  283. cancel()
  284. return
  285. }
  286. delete(expected, received)
  287. last = received
  288. }
  289. }
  290. type fakeMatcher struct{ match string }
  291. func (fm fakeMatcher) ShouldIgnore(name string) bool {
  292. return name == fm.match
  293. }
  294. type fakeEventInfo string
  295. func (e fakeEventInfo) Path() string {
  296. return string(e)
  297. }
  298. func (e fakeEventInfo) Event() notify.Event {
  299. return notify.Write
  300. }
  301. func (e fakeEventInfo) Sys() interface{} {
  302. return nil
  303. }