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  }