github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/tests/goroutine_test.go (about) 1 package tests 2 3 import ( 4 "context" 5 "fmt" 6 "runtime" 7 "sync/atomic" 8 "testing" 9 "time" 10 11 "github.com/gopherjs/gopherjs/js" 12 ) 13 14 var expectedI int 15 16 func checkI(t *testing.T, i int) { 17 if i != expectedI { 18 t.Errorf("expected %d, got %d", expectedI, i) 19 } 20 expectedI++ 21 } 22 23 func TestDefer(t *testing.T) { 24 expectedI = 1 25 defer func() { 26 checkI(t, 2) 27 testDefer1(t) 28 checkI(t, 6) 29 }() 30 checkI(t, 1) 31 } 32 33 func testDefer1(t *testing.T) { 34 defer func() { 35 checkI(t, 4) 36 time.Sleep(0) 37 checkI(t, 5) 38 }() 39 checkI(t, 3) 40 } 41 42 func TestPanic(t *testing.T) { 43 expectedI = 1 44 defer func() { 45 checkI(t, 8) 46 err := recover() 47 time.Sleep(0) 48 checkI(t, err.(int)) 49 }() 50 checkI(t, 1) 51 testPanic1(t) 52 checkI(t, -1) 53 } 54 55 func testPanic1(t *testing.T) { 56 defer func() { 57 checkI(t, 6) 58 time.Sleep(0) 59 err := recover() 60 checkI(t, err.(int)) 61 panic(9) 62 }() 63 checkI(t, 2) 64 testPanic2(t) 65 checkI(t, -2) 66 } 67 68 func testPanic2(t *testing.T) { 69 defer func() { 70 checkI(t, 5) 71 }() 72 checkI(t, 3) 73 time.Sleep(0) 74 checkI(t, 4) 75 panic(7) 76 checkI(t, -3) //nolint:govet // Unreachable code is intentional for panic test 77 } 78 79 func TestPanicAdvanced(t *testing.T) { 80 expectedI = 1 81 defer func() { 82 recover() 83 checkI(t, 3) 84 testPanicAdvanced2(t) 85 checkI(t, 6) 86 }() 87 testPanicAdvanced1(t) 88 checkI(t, -1) 89 } 90 91 func testPanicAdvanced1(t *testing.T) { 92 defer func() { 93 checkI(t, 2) 94 }() 95 checkI(t, 1) 96 panic("") 97 } 98 99 func testPanicAdvanced2(t *testing.T) { 100 defer func() { 101 checkI(t, 5) 102 }() 103 checkI(t, 4) 104 } 105 106 func TestPanicIssue1030(t *testing.T) { 107 throwException := func() { 108 t.Log("Will throw now...") 109 js.Global.Call("eval", "throw 'original panic';") 110 } 111 112 wrapException := func() { 113 defer func() { 114 err := recover() 115 if err == nil { 116 t.Fatal("Should never happen: no original panic.") 117 } 118 t.Log("Got original panic: ", err) 119 panic("replacement panic") 120 }() 121 122 throwException() 123 } 124 125 panicing := false 126 127 expectPanic := func() { 128 defer func() { 129 t.Log("No longer panicing.") 130 panicing = false 131 }() 132 defer func() { 133 err := recover() 134 if err == nil { 135 t.Fatal("Should never happen: no wrapped panic.") 136 } 137 t.Log("Got wrapped panic: ", err) 138 }() 139 140 wrapException() 141 } 142 143 expectPanic() 144 145 if panicing { 146 t.Fatal("Deferrals were not executed correctly!") 147 } 148 } 149 150 func TestSelect(t *testing.T) { 151 expectedI = 1 152 a := make(chan int) 153 b := make(chan int) 154 c := make(chan int) 155 go func() { 156 select { 157 case <-a: 158 case <-b: 159 } 160 }() 161 go func() { 162 checkI(t, 1) 163 a <- 1 164 select { 165 case b <- 1: 166 checkI(t, -1) 167 default: 168 checkI(t, 2) 169 } 170 c <- 1 171 }() 172 <-c 173 checkI(t, 3) 174 } 175 176 func TestCloseAfterReceiving(t *testing.T) { 177 ch := make(chan struct{}) 178 go func() { 179 <-ch 180 close(ch) 181 }() 182 ch <- struct{}{} 183 } 184 185 func TestDeferWithBlocking(t *testing.T) { 186 ch := make(chan struct{}) 187 go func() { ch <- struct{}{} }() 188 defer func() { <-ch }() 189 fmt.Print("") 190 return 191 } 192 193 // counter, sideEffect and withBlockingDeferral are defined as top-level symbols 194 // to make compiler generate simplest code possible without any closures. 195 var counter = 0 196 197 func sideEffect() int { 198 counter++ 199 return 42 200 } 201 202 func withBlockingDeferral() int { 203 defer time.Sleep(0) 204 return sideEffect() 205 } 206 207 func TestReturnWithBlockingDefer(t *testing.T) { 208 // See: https://github.com/gopherjs/gopherjs/issues/603. 209 counter = 0 210 211 got := withBlockingDeferral() 212 if got != 42 { 213 t.Errorf("Unexpected return value %v. Want: 42.", got) 214 } 215 if counter != 1 { 216 t.Errorf("Return value was computed %d times. Want: exactly 1.", counter) 217 } 218 } 219 220 func BenchmarkGoroutineSwitching(b *testing.B) { 221 // This benchmark is designed to measure the cost of goroutine switching. 222 // The two goroutines communicate through an unbuffered channel, which forces 223 // the control to be passed between them on each iteraction of the benchmark. 224 // Although the cost of channel operations is also included in the measurement, 225 // it still allows relative comparison of changes to goroutine scheduling 226 // performance. 227 c := make(chan bool) 228 go func() { 229 for i := 0; i < b.N; i++ { 230 c <- true 231 } 232 close(c) 233 }() 234 235 b.ResetTimer() 236 count := 0 237 for range c { 238 count++ 239 } 240 } 241 242 func TestEventLoopStarvation(t *testing.T) { 243 // See: https://github.com/gopherjs/gopherjs/issues/1078. 244 c := make(chan bool) 245 ctx, cancel := context.WithCancel(context.Background()) 246 go func() { 247 time.Sleep(100 * time.Millisecond) 248 cancel() 249 }() 250 go func() { 251 for { 252 select { 253 case c <- true: 254 case <-ctx.Done(): 255 return 256 } 257 } 258 }() 259 go func() { 260 for { 261 select { 262 case <-c: 263 case <-ctx.Done(): 264 return 265 } 266 } 267 }() 268 <-ctx.Done() 269 } 270 271 func TestGoroutineBuiltin(t *testing.T) { 272 // Test that a built-in function can be a goroutine body. 273 // https://github.com/gopherjs/gopherjs/issues/547. 274 c := make(chan bool) 275 go close(c) 276 <-c // Wait until goroutine executes successfully. 277 } 278 279 func TestGoroutineJsObject(t *testing.T) { 280 // Test that js.Object methods can be a goroutine body. 281 // https://github.com/gopherjs/gopherjs/issues/547. 282 if !(runtime.GOOS == "js" || runtime.GOARCH == "js") { 283 t.Skip("Test requires GopherJS") 284 } 285 o := js.Global.Get("Object").New() 286 go o.Set("x", "y") 287 // Wait until the goroutine executes successfully. Can't use locks here 288 // because goroutine body must be a bare js.Object method call. 289 for o.Get("x").String() != "y" { 290 runtime.Gosched() 291 } 292 } 293 294 func issue1106() { 295 select { 296 default: 297 } 298 } 299 300 func TestIssue1106(t *testing.T) { 301 // https://github.com/gopherjs/gopherjs/issues/1106#issuecomment-1046323374 302 var done int32 = 0 303 go func() { 304 f := issue1106 305 f() 306 atomic.AddInt32(&done, 1) 307 }() 308 309 // Will get stuck here if #1106 is not fixed. 310 for !atomic.CompareAndSwapInt32(&done, 1, 1) { 311 // Maintain one active goroutine to prevent Node from exiting. 312 runtime.Gosched() 313 } 314 }