basicfs_watch_test.go 10 KB

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