github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/notifier_test.go (about) 1 package engine 2 3 import ( 4 "fmt" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/require" 10 "go.uber.org/atomic" 11 ) 12 13 // TestNotifier_PassByValue verifies that passing Notifier by value is safe 14 func TestNotifier_PassByValue(t *testing.T) { 15 t.Parallel() 16 notifier := NewNotifier() 17 18 var sent sync.WaitGroup 19 sent.Add(1) 20 go func(n Notifier) { 21 notifier.Notify() 22 sent.Done() 23 }(notifier) 24 sent.Wait() 25 26 select { 27 case <-notifier.Channel(): // expected 28 default: 29 t.Fail() 30 } 31 } 32 33 // TestNotifier_NoNotificationsAtStartup verifies that Notifier is initialized 34 // without notifications 35 func TestNotifier_NoNotificationsInitialization(t *testing.T) { 36 t.Parallel() 37 notifier := NewNotifier() 38 select { 39 case <-notifier.Channel(): 40 t.Fail() 41 default: //expected 42 } 43 } 44 45 // TestNotifier_ManyNotifications sends many notifications to the Notifier 46 // and verifies that: 47 // - the notifier accepts them all without a notification being consumed 48 // - only one notification is internally stored and subsequent attempts to 49 // read a notification would block 50 func TestNotifier_ManyNotifications(t *testing.T) { 51 t.Parallel() 52 notifier := NewNotifier() 53 54 var counter sync.WaitGroup 55 for i := 0; i < 10; i++ { 56 counter.Add(1) 57 go func() { 58 notifier.Notify() 59 counter.Done() 60 }() 61 } 62 counter.Wait() 63 64 // attempt to consume first notification: 65 // expect that one notification should be available 66 c := notifier.Channel() 67 select { 68 case <-c: // expected 69 default: 70 t.Fail() 71 } 72 73 // attempt to consume first notification 74 // expect that no notification is available 75 select { 76 case <-c: 77 t.Fail() 78 default: //expected 79 } 80 } 81 82 // TestNotifier_ManyConsumers spans many worker routines and 83 // sends just as many notifications with small delays. We require that 84 // all workers eventually get a notification. 85 func TestNotifier_ManyConsumers(t *testing.T) { 86 singleTestRun := func(t *testing.T) { 87 t.Parallel() 88 notifier := NewNotifier() 89 c := notifier.Channel() 90 91 // spawn 100 worker routines to each wait for a notification 92 var startingWorkers sync.WaitGroup 93 pendingWorkers := atomic.NewInt32(100) 94 for i := 0; i < 100; i++ { 95 startingWorkers.Add(1) 96 go func() { 97 startingWorkers.Done() 98 <-c 99 pendingWorkers.Dec() 100 }() 101 } 102 startingWorkers.Wait() 103 104 // send 100 notifications, with small delays 105 for i := 0; i < 100; i++ { 106 notifier.Notify() 107 time.Sleep(100 * time.Millisecond) 108 } 109 110 // require that all workers got a notification 111 if !conditionEventuallySatisfied(func() bool { return pendingWorkers.Load() == 0 }, 3*time.Second, 100*time.Millisecond) { 112 require.Fail(t, "timed out", "still awaiting %d workers to get notification", pendingWorkers.Load()) 113 } 114 } 115 116 for r := 0; r < 100; r++ { 117 t.Run(fmt.Sprintf("run %d", r), singleTestRun) 118 } 119 } 120 121 // TestNotifier_AllWorkProcessed spans many routines pushing work and fewer 122 // routines consuming work. We require that all worker is eventually processed. 123 func TestNotifier_AllWorkProcessed(t *testing.T) { 124 singleTestRun := func(t *testing.T) { 125 t.Parallel() 126 notifier := NewNotifier() 127 128 totalWork := int32(100) 129 pendingWorkQueue := make(chan struct{}, totalWork) 130 scheduledWork := atomic.NewInt32(0) 131 consumedWork := atomic.NewInt32(0) 132 133 // starts the consuming first, because if we starts the production first instead, then 134 // we might finish pushing all jobs, before any of our consumer has started listening 135 // to the queue. 136 var consumersAllReady sync.WaitGroup 137 consumersAllReady.Add(5) 138 139 // 5 routines consuming work 140 for i := 0; i < 5; i++ { 141 go func() { 142 consumersAllReady.Done() 143 for consumedWork.Load() < totalWork { 144 <-notifier.Channel() 145 L: 146 for { 147 select { 148 case <-pendingWorkQueue: 149 consumedWork.Inc() 150 default: 151 break L 152 } 153 } 154 } 155 }() 156 } 157 158 // wait long enough for all consumer to be ready for new notification. 159 consumersAllReady.Wait() 160 161 var workersAllReady sync.WaitGroup 162 workersAllReady.Add(10) 163 164 // 10 routines pushing work 165 for i := 0; i < 10; i++ { 166 go func() { 167 workersAllReady.Done() 168 for scheduledWork.Inc() <= totalWork { 169 pendingWorkQueue <- struct{}{} 170 notifier.Notify() 171 } 172 }() 173 } 174 175 // wait long enough for all workers to be started. 176 workersAllReady.Wait() 177 178 // require that all work is eventually consumed 179 if !conditionEventuallySatisfied(func() bool { return consumedWork.Load() == totalWork }, 3*time.Second, 100*time.Millisecond) { 180 require.Fail(t, "timed out", "only consumed %d units of work but expecting %d", consumedWork.Load(), totalWork) 181 } 182 } 183 184 for r := 0; r < 100; r++ { 185 t.Run(fmt.Sprintf("run %d", r), singleTestRun) 186 } 187 } 188 189 func conditionEventuallySatisfied(condition func() bool, waitFor time.Duration, tick time.Duration) bool { 190 done := make(chan struct{}) 191 192 go func() { 193 for range time.Tick(tick) { 194 if condition() { 195 close(done) 196 return 197 } 198 } 199 }() 200 201 select { 202 case <-time.After(waitFor): 203 return false 204 case <-done: 205 return true 206 } 207 }