notify_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. package watch
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "io/ioutil"
  7. "os"
  8. "path/filepath"
  9. "runtime"
  10. "strings"
  11. "testing"
  12. "time"
  13. "github.com/stretchr/testify/require"
  14. "github.com/stretchr/testify/assert"
  15. "github.com/windmilleng/tilt/internal/dockerignore"
  16. "github.com/windmilleng/tilt/internal/testutils/tempdir"
  17. "github.com/windmilleng/tilt/pkg/logger"
  18. )
  19. // Each implementation of the notify interface should have the same basic
  20. // behavior.
  21. func TestNoEvents(t *testing.T) {
  22. f := newNotifyFixture(t)
  23. defer f.tearDown()
  24. f.assertEvents()
  25. }
  26. func TestNoWatches(t *testing.T) {
  27. f := newNotifyFixture(t)
  28. defer f.tearDown()
  29. f.paths = nil
  30. f.rebuildWatcher()
  31. f.assertEvents()
  32. }
  33. func TestEventOrdering(t *testing.T) {
  34. f := newNotifyFixture(t)
  35. defer f.tearDown()
  36. count := 8
  37. dirs := make([]string, count)
  38. for i := range dirs {
  39. dir := f.TempDir("watched")
  40. dirs[i] = dir
  41. f.watch(dir)
  42. }
  43. f.fsync()
  44. f.events = nil
  45. var expected []string
  46. for i, dir := range dirs {
  47. base := fmt.Sprintf("%d.txt", i)
  48. p := filepath.Join(dir, base)
  49. err := ioutil.WriteFile(p, []byte(base), os.FileMode(0777))
  50. if err != nil {
  51. t.Fatal(err)
  52. }
  53. expected = append(expected, filepath.Join(dir, base))
  54. }
  55. f.assertEvents(expected...)
  56. }
  57. // Simulate a git branch switch that creates a bunch
  58. // of directories, creates files in them, then deletes
  59. // them all quickly. Make sure there are no errors.
  60. func TestGitBranchSwitch(t *testing.T) {
  61. f := newNotifyFixture(t)
  62. defer f.tearDown()
  63. count := 10
  64. dirs := make([]string, count)
  65. for i := range dirs {
  66. dir := f.TempDir("watched")
  67. dirs[i] = dir
  68. f.watch(dir)
  69. }
  70. f.fsync()
  71. f.events = nil
  72. // consume all the events in the background
  73. ctx, cancel := context.WithCancel(context.Background())
  74. done := f.consumeEventsInBackground(ctx)
  75. for i, dir := range dirs {
  76. for j := 0; j < count; j++ {
  77. base := fmt.Sprintf("x/y/dir-%d/x.txt", j)
  78. p := filepath.Join(dir, base)
  79. f.WriteFile(p, "contents")
  80. }
  81. if i != 0 {
  82. err := os.RemoveAll(dir)
  83. require.NoError(t, err)
  84. }
  85. }
  86. cancel()
  87. err := <-done
  88. if err != nil {
  89. t.Fatal(err)
  90. }
  91. f.fsync()
  92. f.events = nil
  93. // Make sure the watch on the first dir still works.
  94. dir := dirs[0]
  95. path := filepath.Join(dir, "change")
  96. f.WriteFile(path, "hello\n")
  97. f.fsync()
  98. f.assertEvents(path)
  99. // Make sure there are no errors in the out stream
  100. assert.Equal(t, "", f.out.String())
  101. }
  102. func TestWatchesAreRecursive(t *testing.T) {
  103. f := newNotifyFixture(t)
  104. defer f.tearDown()
  105. root := f.TempDir("root")
  106. // add a sub directory
  107. subPath := filepath.Join(root, "sub")
  108. f.MkdirAll(subPath)
  109. // watch parent
  110. f.watch(root)
  111. f.fsync()
  112. f.events = nil
  113. // change sub directory
  114. changeFilePath := filepath.Join(subPath, "change")
  115. _, err := os.OpenFile(changeFilePath, os.O_RDONLY|os.O_CREATE, 0666)
  116. if err != nil {
  117. t.Fatal(err)
  118. }
  119. f.assertEvents(changeFilePath)
  120. }
  121. func TestNewDirectoriesAreRecursivelyWatched(t *testing.T) {
  122. f := newNotifyFixture(t)
  123. defer f.tearDown()
  124. root := f.TempDir("root")
  125. // watch parent
  126. f.watch(root)
  127. f.fsync()
  128. f.events = nil
  129. // add a sub directory
  130. subPath := filepath.Join(root, "sub")
  131. f.MkdirAll(subPath)
  132. // change something inside sub directory
  133. changeFilePath := filepath.Join(subPath, "change")
  134. _, err := os.OpenFile(changeFilePath, os.O_RDONLY|os.O_CREATE, 0666)
  135. if err != nil {
  136. t.Fatal(err)
  137. }
  138. f.assertEvents(subPath, changeFilePath)
  139. }
  140. func TestWatchNonExistentPath(t *testing.T) {
  141. f := newNotifyFixture(t)
  142. defer f.tearDown()
  143. root := f.TempDir("root")
  144. path := filepath.Join(root, "change")
  145. f.watch(path)
  146. f.fsync()
  147. d1 := "hello\ngo\n"
  148. f.WriteFile(path, d1)
  149. f.assertEvents(path)
  150. }
  151. func TestWatchNonExistentPathDoesNotFireSiblingEvent(t *testing.T) {
  152. f := newNotifyFixture(t)
  153. defer f.tearDown()
  154. root := f.TempDir("root")
  155. watchedFile := filepath.Join(root, "a.txt")
  156. unwatchedSibling := filepath.Join(root, "b.txt")
  157. f.watch(watchedFile)
  158. f.fsync()
  159. d1 := "hello\ngo\n"
  160. f.WriteFile(unwatchedSibling, d1)
  161. f.assertEvents()
  162. }
  163. func TestRemove(t *testing.T) {
  164. f := newNotifyFixture(t)
  165. defer f.tearDown()
  166. root := f.TempDir("root")
  167. path := filepath.Join(root, "change")
  168. d1 := "hello\ngo\n"
  169. f.WriteFile(path, d1)
  170. f.watch(path)
  171. f.fsync()
  172. f.events = nil
  173. err := os.Remove(path)
  174. if err != nil {
  175. t.Fatal(err)
  176. }
  177. f.assertEvents(path)
  178. }
  179. func TestRemoveAndAddBack(t *testing.T) {
  180. f := newNotifyFixture(t)
  181. defer f.tearDown()
  182. path := filepath.Join(f.paths[0], "change")
  183. d1 := []byte("hello\ngo\n")
  184. err := ioutil.WriteFile(path, d1, 0644)
  185. if err != nil {
  186. t.Fatal(err)
  187. }
  188. f.watch(path)
  189. f.assertEvents(path)
  190. err = os.Remove(path)
  191. if err != nil {
  192. t.Fatal(err)
  193. }
  194. f.assertEvents(path)
  195. f.events = nil
  196. err = ioutil.WriteFile(path, d1, 0644)
  197. if err != nil {
  198. t.Fatal(err)
  199. }
  200. f.assertEvents(path)
  201. }
  202. func TestSingleFile(t *testing.T) {
  203. f := newNotifyFixture(t)
  204. defer f.tearDown()
  205. root := f.TempDir("root")
  206. path := filepath.Join(root, "change")
  207. d1 := "hello\ngo\n"
  208. f.WriteFile(path, d1)
  209. f.watch(path)
  210. f.fsync()
  211. d2 := []byte("hello\nworld\n")
  212. err := ioutil.WriteFile(path, d2, 0644)
  213. if err != nil {
  214. t.Fatal(err)
  215. }
  216. f.assertEvents(path)
  217. }
  218. func TestWriteBrokenLink(t *testing.T) {
  219. f := newNotifyFixture(t)
  220. defer f.tearDown()
  221. link := filepath.Join(f.paths[0], "brokenLink")
  222. missingFile := filepath.Join(f.paths[0], "missingFile")
  223. err := os.Symlink(missingFile, link)
  224. if err != nil {
  225. t.Fatal(err)
  226. }
  227. f.assertEvents(link)
  228. }
  229. func TestWriteGoodLink(t *testing.T) {
  230. f := newNotifyFixture(t)
  231. defer f.tearDown()
  232. goodFile := filepath.Join(f.paths[0], "goodFile")
  233. err := ioutil.WriteFile(goodFile, []byte("hello"), 0644)
  234. if err != nil {
  235. t.Fatal(err)
  236. }
  237. link := filepath.Join(f.paths[0], "goodFileSymlink")
  238. err = os.Symlink(goodFile, link)
  239. if err != nil {
  240. t.Fatal(err)
  241. }
  242. f.assertEvents(goodFile, link)
  243. }
  244. func TestWatchBrokenLink(t *testing.T) {
  245. f := newNotifyFixture(t)
  246. defer f.tearDown()
  247. newRoot, err := NewDir(t.Name())
  248. if err != nil {
  249. t.Fatal(err)
  250. }
  251. defer func() {
  252. err := newRoot.TearDown()
  253. if err != nil {
  254. fmt.Printf("error tearing down temp dir: %v\n", err)
  255. }
  256. }()
  257. link := filepath.Join(newRoot.Path(), "brokenLink")
  258. missingFile := filepath.Join(newRoot.Path(), "missingFile")
  259. err = os.Symlink(missingFile, link)
  260. if err != nil {
  261. t.Fatal(err)
  262. }
  263. f.watch(newRoot.Path())
  264. err = os.Remove(link)
  265. require.NoError(t, err)
  266. f.assertEvents(link)
  267. }
  268. func TestMoveAndReplace(t *testing.T) {
  269. f := newNotifyFixture(t)
  270. defer f.tearDown()
  271. root := f.TempDir("root")
  272. file := filepath.Join(root, "myfile")
  273. f.WriteFile(file, "hello")
  274. f.watch(file)
  275. tmpFile := filepath.Join(root, ".myfile.swp")
  276. f.WriteFile(tmpFile, "world")
  277. err := os.Rename(tmpFile, file)
  278. if err != nil {
  279. t.Fatal(err)
  280. }
  281. f.assertEvents(file)
  282. }
  283. func TestWatchBothDirAndFile(t *testing.T) {
  284. f := newNotifyFixture(t)
  285. defer f.tearDown()
  286. dir := f.JoinPath("foo")
  287. fileA := f.JoinPath("foo", "a")
  288. fileB := f.JoinPath("foo", "b")
  289. f.WriteFile(fileA, "a")
  290. f.WriteFile(fileB, "b")
  291. f.watch(fileA)
  292. f.watch(dir)
  293. f.fsync()
  294. f.events = nil
  295. f.WriteFile(fileB, "b-new")
  296. f.assertEvents(fileB)
  297. }
  298. func TestWatchNonexistentFileInNonexistentDirectoryCreatedSimultaneously(t *testing.T) {
  299. f := newNotifyFixture(t)
  300. defer f.tearDown()
  301. root := f.JoinPath("root")
  302. err := os.Mkdir(root, 0777)
  303. if err != nil {
  304. t.Fatal(err)
  305. }
  306. file := f.JoinPath("root", "parent", "a")
  307. f.watch(file)
  308. f.fsync()
  309. f.events = nil
  310. f.WriteFile(file, "hello")
  311. f.assertEvents(file)
  312. }
  313. func TestWatchNonexistentDirectory(t *testing.T) {
  314. f := newNotifyFixture(t)
  315. defer f.tearDown()
  316. root := f.JoinPath("root")
  317. err := os.Mkdir(root, 0777)
  318. if err != nil {
  319. t.Fatal(err)
  320. }
  321. parent := f.JoinPath("parent")
  322. file := f.JoinPath("parent", "a")
  323. f.watch(parent)
  324. f.fsync()
  325. f.events = nil
  326. err = os.Mkdir(parent, 0777)
  327. if err != nil {
  328. t.Fatal(err)
  329. }
  330. if runtime.GOOS == "darwin" {
  331. // for directories that were the root of an Add, we don't report creation, cf. watcher_darwin.go
  332. f.assertEvents()
  333. } else {
  334. f.assertEvents(parent)
  335. }
  336. f.WriteFile(file, "hello")
  337. if runtime.GOOS == "darwin" {
  338. // mac doesn't return the dir change as part of file creation
  339. f.assertEvents(file)
  340. } else {
  341. f.assertEvents(parent, file)
  342. }
  343. }
  344. func TestWatchNonexistentFileInNonexistentDirectory(t *testing.T) {
  345. f := newNotifyFixture(t)
  346. defer f.tearDown()
  347. root := f.JoinPath("root")
  348. err := os.Mkdir(root, 0777)
  349. if err != nil {
  350. t.Fatal(err)
  351. }
  352. parent := f.JoinPath("parent")
  353. file := f.JoinPath("parent", "a")
  354. f.watch(file)
  355. f.assertEvents()
  356. err = os.Mkdir(parent, 0777)
  357. if err != nil {
  358. t.Fatal(err)
  359. }
  360. f.assertEvents()
  361. f.WriteFile(file, "hello")
  362. f.assertEvents(file)
  363. }
  364. func TestWatchCountInnerFile(t *testing.T) {
  365. f := newNotifyFixture(t)
  366. defer f.tearDown()
  367. root := f.paths[0]
  368. a := f.JoinPath(root, "a")
  369. b := f.JoinPath(a, "b")
  370. file := f.JoinPath(b, "bigFile")
  371. f.WriteFile(file, "hello")
  372. f.assertEvents(a, b, file)
  373. expectedWatches := 3
  374. if runtime.GOOS == "darwin" {
  375. expectedWatches = 1
  376. }
  377. assert.Equal(t, expectedWatches, int(numberOfWatches.Value()))
  378. }
  379. func TestWatchCountInnerFileWithIgnore(t *testing.T) {
  380. f := newNotifyFixture(t)
  381. defer f.tearDown()
  382. root := f.paths[0]
  383. ignore, _ := dockerignore.NewDockerPatternMatcher(root, []string{
  384. "a",
  385. "!a/b",
  386. })
  387. f.setIgnore(ignore)
  388. a := f.JoinPath(root, "a")
  389. b := f.JoinPath(a, "b")
  390. file := f.JoinPath(b, "bigFile")
  391. f.WriteFile(file, "hello")
  392. f.assertEvents(b, file)
  393. expectedWatches := 3
  394. if runtime.GOOS == "darwin" {
  395. expectedWatches = 1
  396. }
  397. assert.Equal(t, expectedWatches, int(numberOfWatches.Value()))
  398. }
  399. func TestIgnoreCreatedDir(t *testing.T) {
  400. f := newNotifyFixture(t)
  401. defer f.tearDown()
  402. root := f.paths[0]
  403. ignore, _ := dockerignore.NewDockerPatternMatcher(root, []string{"a/b"})
  404. f.setIgnore(ignore)
  405. a := f.JoinPath(root, "a")
  406. b := f.JoinPath(a, "b")
  407. file := f.JoinPath(b, "bigFile")
  408. f.WriteFile(file, "hello")
  409. f.assertEvents(a)
  410. expectedWatches := 2
  411. if runtime.GOOS == "darwin" {
  412. expectedWatches = 1
  413. }
  414. assert.Equal(t, expectedWatches, int(numberOfWatches.Value()))
  415. }
  416. func TestIgnoreCreatedDirWithExclusions(t *testing.T) {
  417. f := newNotifyFixture(t)
  418. defer f.tearDown()
  419. root := f.paths[0]
  420. ignore, _ := dockerignore.NewDockerPatternMatcher(root,
  421. []string{
  422. "a/b",
  423. "c",
  424. "!c/d",
  425. })
  426. f.setIgnore(ignore)
  427. a := f.JoinPath(root, "a")
  428. b := f.JoinPath(a, "b")
  429. file := f.JoinPath(b, "bigFile")
  430. f.WriteFile(file, "hello")
  431. f.assertEvents(a)
  432. expectedWatches := 2
  433. if runtime.GOOS == "darwin" {
  434. expectedWatches = 1
  435. }
  436. assert.Equal(t, expectedWatches, int(numberOfWatches.Value()))
  437. }
  438. func TestIgnoreInitialDir(t *testing.T) {
  439. f := newNotifyFixture(t)
  440. defer f.tearDown()
  441. root := f.TempDir("root")
  442. ignore, _ := dockerignore.NewDockerPatternMatcher(root, []string{"a/b"})
  443. f.setIgnore(ignore)
  444. a := f.JoinPath(root, "a")
  445. b := f.JoinPath(a, "b")
  446. file := f.JoinPath(b, "bigFile")
  447. f.WriteFile(file, "hello")
  448. f.watch(root)
  449. f.assertEvents()
  450. expectedWatches := 3
  451. if runtime.GOOS == "darwin" {
  452. expectedWatches = 2
  453. }
  454. assert.Equal(t, expectedWatches, int(numberOfWatches.Value()))
  455. }
  456. type notifyFixture struct {
  457. out *bytes.Buffer
  458. *tempdir.TempDirFixture
  459. notify Notify
  460. ignore PathMatcher
  461. paths []string
  462. events []FileEvent
  463. }
  464. func newNotifyFixture(t *testing.T) *notifyFixture {
  465. out := bytes.NewBuffer(nil)
  466. nf := &notifyFixture{
  467. TempDirFixture: tempdir.NewTempDirFixture(t),
  468. paths: []string{},
  469. ignore: EmptyMatcher{},
  470. out: out,
  471. }
  472. nf.watch(nf.TempDir("watched"))
  473. return nf
  474. }
  475. func (f *notifyFixture) setIgnore(ignore PathMatcher) {
  476. f.ignore = ignore
  477. f.rebuildWatcher()
  478. }
  479. func (f *notifyFixture) watch(path string) {
  480. f.paths = append(f.paths, path)
  481. f.rebuildWatcher()
  482. }
  483. func (f *notifyFixture) rebuildWatcher() {
  484. // sync any outstanding events and close the old watcher
  485. if f.notify != nil {
  486. f.fsync()
  487. f.closeWatcher()
  488. }
  489. // create a new watcher
  490. notify, err := NewWatcher(f.paths, f.ignore, logger.NewLogger(logger.DebugLvl, f.out))
  491. if err != nil {
  492. f.T().Fatal(err)
  493. }
  494. f.notify = notify
  495. err = f.notify.Start()
  496. if err != nil {
  497. f.T().Fatal(err)
  498. }
  499. }
  500. func (f *notifyFixture) assertEvents(expected ...string) {
  501. f.fsync()
  502. if len(f.events) != len(expected) {
  503. f.T().Fatalf("Got %d events (expected %d): %v %v", len(f.events), len(expected), f.events, expected)
  504. }
  505. for i, actual := range f.events {
  506. e := FileEvent{expected[i]}
  507. if actual != e {
  508. f.T().Fatalf("Got event %v (expected %v)", actual, e)
  509. }
  510. }
  511. }
  512. func (f *notifyFixture) consumeEventsInBackground(ctx context.Context) chan error {
  513. done := make(chan error)
  514. go func() {
  515. for {
  516. select {
  517. case <-ctx.Done():
  518. close(done)
  519. return
  520. case err := <-f.notify.Errors():
  521. done <- err
  522. close(done)
  523. return
  524. case <-f.notify.Events():
  525. }
  526. }
  527. }()
  528. return done
  529. }
  530. func (f *notifyFixture) fsync() {
  531. f.fsyncWithRetryCount(3)
  532. }
  533. func (f *notifyFixture) fsyncWithRetryCount(retryCount int) {
  534. if len(f.paths) == 0 {
  535. return
  536. }
  537. syncPathBase := fmt.Sprintf("sync-%d.txt", time.Now().UnixNano())
  538. syncPath := filepath.Join(f.paths[0], syncPathBase)
  539. anySyncPath := filepath.Join(f.paths[0], "sync-")
  540. timeout := time.After(250 * time.Millisecond)
  541. f.WriteFile(syncPath, time.Now().String())
  542. F:
  543. for {
  544. select {
  545. case err := <-f.notify.Errors():
  546. f.T().Fatal(err)
  547. case event := <-f.notify.Events():
  548. if strings.Contains(event.Path(), syncPath) {
  549. break F
  550. }
  551. if strings.Contains(event.Path(), anySyncPath) {
  552. continue
  553. }
  554. // Don't bother tracking duplicate changes to the same path
  555. // for testing.
  556. if len(f.events) > 0 && f.events[len(f.events)-1].Path() == event.Path() {
  557. continue
  558. }
  559. f.events = append(f.events, event)
  560. case <-timeout:
  561. if retryCount <= 0 {
  562. f.T().Fatalf("fsync: timeout")
  563. } else {
  564. f.fsyncWithRetryCount(retryCount - 1)
  565. }
  566. return
  567. }
  568. }
  569. }
  570. func (f *notifyFixture) closeWatcher() {
  571. notify := f.notify
  572. err := notify.Close()
  573. if err != nil {
  574. f.T().Fatal(err)
  575. }
  576. // drain channels from watcher
  577. go func() {
  578. for range notify.Events() {
  579. }
  580. }()
  581. go func() {
  582. for range notify.Errors() {
  583. }
  584. }()
  585. }
  586. func (f *notifyFixture) tearDown() {
  587. f.closeWatcher()
  588. f.TempDirFixture.TearDown()
  589. numberOfWatches.Set(0)
  590. }