basicfs_watch_test.go 9.2 KB

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