github.com/brandur/modulir@v0.0.0-20240305213423-94ee82929cbd/watch_test.go (about) 1 package modulir 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/fsnotify/fsnotify" 8 assert "github.com/stretchr/testify/require" 9 ) 10 11 func TestBuildWithinSameFileQuiesce(t *testing.T) { 12 baseTime := time.Now() 13 14 lastChanges := map[string]struct{}{"a/path": {}} 15 sameLastChanges := map[string]struct{}{"a/path": {}} 16 diffLastChanges := map[string]struct{}{"b/path": {}} 17 18 // No last changes 19 assert.False(t, buildWithinSameFileQuiesce( 20 baseTime, baseTime, lastChanges, nil, 21 )) 22 23 // Rebuild after quiesce time 24 assert.False(t, buildWithinSameFileQuiesce( 25 baseTime, baseTime.Add(10*time.Second), lastChanges, sameLastChanges, 26 )) 27 28 // Different set of canges 29 assert.False(t, buildWithinSameFileQuiesce( 30 baseTime, baseTime, lastChanges, diffLastChanges, 31 )) 32 33 // Within quiesce time and same set of changes 34 assert.True(t, buildWithinSameFileQuiesce( 35 baseTime, baseTime, lastChanges, sameLastChanges, 36 )) 37 } 38 39 func TestCompareKeys(t *testing.T) { 40 assert.True(t, compareKeys( 41 map[string]struct{}{"a": {}, "b": {}}, 42 map[string]struct{}{"b": {}, "a": {}}, 43 )) 44 45 assert.True(t, compareKeys( 46 map[string]struct{}{"a": {}, "b": {}, "c": {}}, 47 map[string]struct{}{"c": {}, "b": {}, "a": {}}, 48 )) 49 50 assert.False(t, compareKeys( 51 map[string]struct{}{"a": {}}, 52 map[string]struct{}{"b": {}}, 53 )) 54 55 assert.False(t, compareKeys( 56 map[string]struct{}{"a": {}, "b": {}}, 57 map[string]struct{}{"c": {}, "a": {}}, 58 )) 59 60 assert.False(t, compareKeys( 61 map[string]struct{}{"a": {}}, 62 nil, 63 )) 64 65 assert.False(t, compareKeys( 66 nil, 67 map[string]struct{}{"a": {}}, 68 )) 69 } 70 71 func TestShouldRebuild(t *testing.T) { 72 // Most things signal a rebuild 73 assert.Equal(t, true, shouldRebuild("a/path", fsnotify.Create)) 74 assert.Equal(t, true, shouldRebuild("a/path", fsnotify.Remove)) 75 assert.Equal(t, true, shouldRebuild("a/path", fsnotify.Write)) 76 77 // With just a few special cases that don't 78 assert.Equal(t, false, shouldRebuild("a/path", fsnotify.Chmod)) 79 assert.Equal(t, false, shouldRebuild("a/path", fsnotify.Rename)) 80 assert.Equal(t, false, shouldRebuild("a/.DS_Store", fsnotify.Create)) 81 assert.Equal(t, false, shouldRebuild("a/4913", fsnotify.Create)) 82 assert.Equal(t, false, shouldRebuild("a/path~", fsnotify.Create)) 83 } 84 85 func TestWatchChanges(t *testing.T) { 86 watchEvents := make(chan fsnotify.Event, 1) 87 watchErrors := make(chan error, 1) 88 rebuild := make(chan map[string]struct{}, 1) 89 rebuildDone := make(chan struct{}, 1) 90 91 go watchChanges(newContext(), watchEvents, watchErrors, 92 rebuild, rebuildDone) 93 94 { 95 // An ineligible even that will be ignored. 96 watchEvents <- fsnotify.Event{Name: "a/path~", Op: fsnotify.Create} 97 98 select { 99 case <-rebuild: 100 assert.Fail(t, "Should not have received rebuild on ineligible event") 101 case <-time.After(50 * time.Millisecond): 102 } 103 } 104 105 { 106 // An valid event. 107 watchEvents <- fsnotify.Event{Name: "a/path", Op: fsnotify.Create} 108 109 select { 110 case sources := <-rebuild: 111 assert.Equal(t, map[string]struct{}{"a/path": {}}, sources) 112 case <-time.After(50 * time.Millisecond): 113 assert.Fail(t, "Should have received a rebuild signal") 114 } 115 116 // While we're rebuilding, the watcher will accumulate events. Send a 117 // few more that are eligible and one that's not. 118 watchEvents <- fsnotify.Event{Name: "a/path1", Op: fsnotify.Create} 119 watchEvents <- fsnotify.Event{Name: "a/path2", Op: fsnotify.Create} 120 watchEvents <- fsnotify.Event{Name: "a/path~", Op: fsnotify.Create} 121 122 // Signal that the build is finished 123 rebuildDone <- struct{}{} 124 125 // Now verify that we got the accumulated changes. 126 select { 127 case sources := <-rebuild: 128 assert.Equal(t, map[string]struct{}{ 129 "a/path1": {}, 130 "a/path2": {}, 131 }, sources) 132 case <-time.After(50 * time.Millisecond): 133 assert.Fail(t, "Should have received a rebuild signal") 134 } 135 136 // Send one more rebuild done so the watcher can continue 137 rebuildDone <- struct{}{} 138 } 139 140 // Finish up by closing the channel to stop the loop 141 close(watchEvents) 142 } 143 144 // Helper to easily create a new Modulir context. 145 func newContext() *Context { 146 return NewContext(&Args{Log: &Logger{Level: LevelInfo}}) 147 }