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