watcher_naive_test.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. //go:build !darwin
  2. // +build !darwin
  3. package watch
  4. import (
  5. "fmt"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "runtime"
  10. "strconv"
  11. "strings"
  12. "testing"
  13. "github.com/stretchr/testify/require"
  14. )
  15. func TestDontWatchEachFile(t *testing.T) {
  16. if runtime.GOOS != "linux" {
  17. t.Skip("This test uses linux-specific inotify checks")
  18. }
  19. // fsnotify is not recursive, so we need to watch each directory
  20. // you can watch individual files with fsnotify, but that is more prone to exhaust resources
  21. // this test uses a Linux way to get the number of watches to make sure we're watching
  22. // per-directory, not per-file
  23. f := newNotifyFixture(t)
  24. watched := f.TempDir("watched")
  25. // there are a few different cases we want to test for because the code paths are slightly
  26. // different:
  27. // 1) initial: data there before we ever call watch
  28. // 2) inplace: data we create while the watch is happening
  29. // 3) staged: data we create in another directory and then atomically move into place
  30. // initial
  31. f.WriteFile(f.JoinPath(watched, "initial.txt"), "initial data")
  32. initialDir := f.JoinPath(watched, "initial_dir")
  33. if err := os.Mkdir(initialDir, 0777); err != nil {
  34. t.Fatal(err)
  35. }
  36. for i := 0; i < 100; i++ {
  37. f.WriteFile(f.JoinPath(initialDir, fmt.Sprintf("%d", i)), "initial data")
  38. }
  39. f.watch(watched)
  40. f.fsync()
  41. if len(f.events) != 0 {
  42. t.Fatalf("expected 0 initial events; got %d events: %v", len(f.events), f.events)
  43. }
  44. f.events = nil
  45. // inplace
  46. inplace := f.JoinPath(watched, "inplace")
  47. if err := os.Mkdir(inplace, 0777); err != nil {
  48. t.Fatal(err)
  49. }
  50. f.WriteFile(f.JoinPath(inplace, "inplace.txt"), "inplace data")
  51. inplaceDir := f.JoinPath(inplace, "inplace_dir")
  52. if err := os.Mkdir(inplaceDir, 0777); err != nil {
  53. t.Fatal(err)
  54. }
  55. for i := 0; i < 100; i++ {
  56. f.WriteFile(f.JoinPath(inplaceDir, fmt.Sprintf("%d", i)), "inplace data")
  57. }
  58. f.fsync()
  59. if len(f.events) < 100 {
  60. t.Fatalf("expected >100 inplace events; got %d events: %v", len(f.events), f.events)
  61. }
  62. f.events = nil
  63. // staged
  64. staged := f.TempDir("staged")
  65. f.WriteFile(f.JoinPath(staged, "staged.txt"), "staged data")
  66. stagedDir := f.JoinPath(staged, "staged_dir")
  67. if err := os.Mkdir(stagedDir, 0777); err != nil {
  68. t.Fatal(err)
  69. }
  70. for i := 0; i < 100; i++ {
  71. f.WriteFile(f.JoinPath(stagedDir, fmt.Sprintf("%d", i)), "staged data")
  72. }
  73. if err := os.Rename(staged, f.JoinPath(watched, "staged")); err != nil {
  74. t.Fatal(err)
  75. }
  76. f.fsync()
  77. if len(f.events) < 100 {
  78. t.Fatalf("expected >100 staged events; got %d events: %v", len(f.events), f.events)
  79. }
  80. f.events = nil
  81. n, err := inotifyNodes()
  82. require.NoError(t, err)
  83. if n > 10 {
  84. t.Fatalf("watching more than 10 files: %d", n)
  85. }
  86. }
  87. func inotifyNodes() (int, error) {
  88. pid := os.Getpid()
  89. output, err := exec.Command("bash", "-c", fmt.Sprintf(
  90. "find /proc/%d/fd -lname anon_inode:inotify -printf '%%hinfo/%%f\n' | xargs cat | grep -c '^inotify'", pid)).Output()
  91. if err != nil {
  92. return 0, fmt.Errorf("error running command to determine number of watched files: %v", err)
  93. }
  94. n, err := strconv.Atoi(strings.TrimSpace(string(output)))
  95. if err != nil {
  96. return 0, fmt.Errorf("couldn't parse number of watched files: %v", err)
  97. }
  98. return n, nil
  99. }
  100. func TestDontRecurseWhenWatchingParentsOfNonExistentFiles(t *testing.T) {
  101. if runtime.GOOS != "linux" {
  102. t.Skip("This test uses linux-specific inotify checks")
  103. }
  104. f := newNotifyFixture(t)
  105. watched := f.TempDir("watched")
  106. f.watch(filepath.Join(watched, ".tiltignore"))
  107. excludedDir := f.JoinPath(watched, "excluded")
  108. for i := 0; i < 10; i++ {
  109. f.WriteFile(f.JoinPath(excludedDir, fmt.Sprintf("%d", i), "data.txt"), "initial data")
  110. }
  111. f.fsync()
  112. n, err := inotifyNodes()
  113. require.NoError(t, err)
  114. if n > 5 {
  115. t.Fatalf("watching more than 5 files: %d", n)
  116. }
  117. }