notify_test.go 13 KB

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