github.com/safing/portbase@v0.19.5/modules/microtasks_test.go (about) 1 package modules 2 3 import ( 4 "context" 5 "runtime" 6 "strings" 7 "sync" 8 "sync/atomic" 9 "testing" 10 "time" 11 ) 12 13 var ( 14 mtTestName = "microtask test" 15 mtModule = initNewModule("microtask test module", nil, nil, nil) 16 ) 17 18 func init() { 19 go microTaskScheduler() 20 } 21 22 func TestMicroTaskWaiting(t *testing.T) { //nolint:paralleltest // Too much interference expected. 23 24 // Check if the state is clean. 25 if atomic.LoadInt32(microTasks) != 0 { 26 t.Fatalf("cannot start test with dirty state: %d microtasks", atomic.LoadInt32(microTasks)) 27 } 28 29 // skip 30 if testing.Short() { 31 t.Skip("skipping test in short mode, as it is not fully deterministic") 32 } 33 34 // init 35 mtwWaitGroup := new(sync.WaitGroup) 36 mtwOutputChannel := make(chan string, 100) 37 mtwExpectedOutput := "1234567" 38 mtwSleepDuration := 10 * time.Millisecond 39 40 // TEST 41 mtwWaitGroup.Add(4) 42 43 // ensure we only execute one microtask at once 44 atomic.StoreInt32(microTasksThreshhold, 1) 45 46 // High Priority - slot 1-5 47 go func() { 48 defer mtwWaitGroup.Done() 49 // exec at slot 1 50 _ = mtModule.RunHighPriorityMicroTask(mtTestName, func(ctx context.Context) error { 51 mtwOutputChannel <- "1" // slot 1 52 time.Sleep(mtwSleepDuration * 5) 53 mtwOutputChannel <- "2" // slot 5 54 return nil 55 }) 56 }() 57 58 time.Sleep(mtwSleepDuration * 1) 59 60 // clear clearances 61 _ = mtModule.RunHighPriorityMicroTask(mtTestName, func(ctx context.Context) error { 62 return nil 63 }) 64 65 // Low Priority - slot 16 66 go func() { 67 defer mtwWaitGroup.Done() 68 // exec at slot 2 69 _ = mtModule.RunLowPriorityMicroTask(mtTestName, 0, func(ctx context.Context) error { 70 mtwOutputChannel <- "7" // slot 16 71 return nil 72 }) 73 }() 74 75 time.Sleep(mtwSleepDuration * 1) 76 77 // High Priority - slot 10-15 78 go func() { 79 defer mtwWaitGroup.Done() 80 time.Sleep(mtwSleepDuration * 8) 81 // exec at slot 10 82 _ = mtModule.RunHighPriorityMicroTask(mtTestName, func(ctx context.Context) error { 83 mtwOutputChannel <- "4" // slot 10 84 time.Sleep(mtwSleepDuration * 5) 85 mtwOutputChannel <- "6" // slot 15 86 return nil 87 }) 88 }() 89 90 // Medium Priority - slot 6-13 91 go func() { 92 defer mtwWaitGroup.Done() 93 // exec at slot 3 94 _ = mtModule.RunMicroTask(mtTestName, 0, func(ctx context.Context) error { 95 mtwOutputChannel <- "3" // slot 6 96 time.Sleep(mtwSleepDuration * 7) 97 mtwOutputChannel <- "5" // slot 13 98 return nil 99 }) 100 }() 101 102 // wait for test to finish 103 mtwWaitGroup.Wait() 104 105 // collect output 106 close(mtwOutputChannel) 107 completeOutput := "" 108 for s := <-mtwOutputChannel; s != ""; s = <-mtwOutputChannel { 109 completeOutput += s 110 } 111 // check if test succeeded 112 t.Logf("microTask wait order: %s", completeOutput) 113 if completeOutput != mtwExpectedOutput { 114 t.Errorf("MicroTask waiting test failed, expected sequence %s, got %s", mtwExpectedOutput, completeOutput) 115 } 116 117 // Check if the state is clean. 118 time.Sleep(10 * time.Millisecond) 119 if atomic.LoadInt32(microTasks) != 0 { 120 t.Fatalf("test ends with dirty state: %d microtasks", atomic.LoadInt32(microTasks)) 121 } 122 } 123 124 // Test Microtask ordering. 125 126 // Microtask test globals. 127 128 var ( 129 mtoWaitGroup sync.WaitGroup 130 mtoOutputChannel chan string 131 mtoWaitCh chan struct{} 132 ) 133 134 // Microtask test functions. 135 136 func highPrioTaskTester() { 137 defer mtoWaitGroup.Done() 138 <-mtoWaitCh 139 _ = mtModule.RunHighPriorityMicroTask(mtTestName, func(ctx context.Context) error { 140 mtoOutputChannel <- "0" 141 time.Sleep(2 * time.Millisecond) 142 return nil 143 }) 144 } 145 146 func highPrioSignalledTaskTester() { 147 defer mtoWaitGroup.Done() 148 <-mtoWaitCh 149 go func() { 150 done := mtModule.SignalHighPriorityMicroTask() 151 defer done() 152 153 mtoOutputChannel <- "0" 154 time.Sleep(2 * time.Millisecond) 155 }() 156 } 157 158 func mediumPrioTaskTester() { 159 defer mtoWaitGroup.Done() 160 <-mtoWaitCh 161 _ = mtModule.RunMicroTask(mtTestName, 0, func(ctx context.Context) error { 162 mtoOutputChannel <- "1" 163 time.Sleep(2 * time.Millisecond) 164 return nil 165 }) 166 } 167 168 func mediumPrioSignalledTaskTester() { 169 defer mtoWaitGroup.Done() 170 <-mtoWaitCh 171 go func() { 172 done := mtModule.SignalMicroTask(0) 173 defer done() 174 175 mtoOutputChannel <- "1" 176 time.Sleep(2 * time.Millisecond) 177 }() 178 } 179 180 func lowPrioTaskTester() { 181 defer mtoWaitGroup.Done() 182 <-mtoWaitCh 183 _ = mtModule.RunLowPriorityMicroTask(mtTestName, 0, func(ctx context.Context) error { 184 mtoOutputChannel <- "2" 185 time.Sleep(2 * time.Millisecond) 186 return nil 187 }) 188 } 189 190 func lowPrioSignalledTaskTester() { 191 defer mtoWaitGroup.Done() 192 <-mtoWaitCh 193 go func() { 194 done := mtModule.SignalLowPriorityMicroTask(0) 195 defer done() 196 197 mtoOutputChannel <- "2" 198 time.Sleep(2 * time.Millisecond) 199 }() 200 } 201 202 func TestMicroTaskOrdering(t *testing.T) { //nolint:paralleltest // Too much interference expected. 203 204 // Check if the state is clean. 205 if atomic.LoadInt32(microTasks) != 0 { 206 t.Fatalf("cannot start test with dirty state: %d microtasks", atomic.LoadInt32(microTasks)) 207 } 208 209 // skip 210 if testing.Short() { 211 t.Skip("skipping test in short mode, as it is not fully deterministic") 212 } 213 214 // Only allow a single concurrent task for testing. 215 atomic.StoreInt32(microTasksThreshhold, 1) 216 defer SetMaxConcurrentMicroTasks(runtime.GOMAXPROCS(0)) 217 218 // init 219 mtoOutputChannel = make(chan string, 100) 220 mtoWaitCh = make(chan struct{}) 221 222 // TEST 223 224 // init all in waiting state 225 for i := 0; i < 5; i++ { 226 mtoWaitGroup.Add(6) 227 go lowPrioTaskTester() 228 go lowPrioSignalledTaskTester() 229 go mediumPrioTaskTester() 230 go mediumPrioSignalledTaskTester() 231 go highPrioTaskTester() 232 go highPrioSignalledTaskTester() 233 } 234 235 // wait for all goroutines to be ready 236 time.Sleep(10 * time.Millisecond) 237 238 // sync all goroutines 239 close(mtoWaitCh) 240 // trigger 241 select { 242 case microTaskFinished <- struct{}{}: 243 default: 244 } 245 246 // wait for test to finish 247 mtoWaitGroup.Wait() 248 249 // collect output 250 close(mtoOutputChannel) 251 completeOutput := "" 252 for s := range mtoOutputChannel { 253 completeOutput += s 254 } 255 256 // check if test succeeded 257 t.Logf("microTask exec order: %s", completeOutput) 258 if !strings.Contains(completeOutput, "000") || 259 !strings.Contains(completeOutput, "1111") || 260 !strings.Contains(completeOutput, "22222") { 261 t.Errorf("MicroTask ordering test failed, output was %s. This happens occasionally, please run the test multiple times to verify", completeOutput) 262 } 263 264 // Check if the state is clean. 265 time.Sleep(10 * time.Millisecond) 266 if atomic.LoadInt32(microTasks) != 0 { 267 t.Fatalf("test ends with dirty state: %d microtasks", atomic.LoadInt32(microTasks)) 268 } 269 }