Source file src/internal/synctest/synctest_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package synctest_test
     6  
     7  import (
     8  	"fmt"
     9  	"internal/synctest"
    10  	"internal/testenv"
    11  	"iter"
    12  	"os"
    13  	"reflect"
    14  	"runtime"
    15  	"slices"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    19  	"sync/atomic"
    20  	"testing"
    21  	"time"
    22  	"weak"
    23  )
    24  
    25  func TestNow(t *testing.T) {
    26  	start := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).In(time.Local)
    27  	synctest.Run(func() {
    28  		// Time starts at 2000-1-1 00:00:00.
    29  		if got, want := time.Now(), start; !got.Equal(want) {
    30  			t.Errorf("at start: time.Now = %v, want %v", got, want)
    31  		}
    32  		go func() {
    33  			// New goroutines see the same fake clock.
    34  			if got, want := time.Now(), start; !got.Equal(want) {
    35  				t.Errorf("time.Now = %v, want %v", got, want)
    36  			}
    37  		}()
    38  		// Time advances after a sleep.
    39  		time.Sleep(1 * time.Second)
    40  		if got, want := time.Now(), start.Add(1*time.Second); !got.Equal(want) {
    41  			t.Errorf("after sleep: time.Now = %v, want %v", got, want)
    42  		}
    43  	})
    44  }
    45  
    46  // TestMonotonicClock exercises comparing times from within a bubble
    47  // with ones from outside the bubble.
    48  func TestMonotonicClock(t *testing.T) {
    49  	start := time.Now()
    50  	synctest.Run(func() {
    51  		time.Sleep(time.Until(start.Round(0)))
    52  		if got, want := time.Now().In(time.UTC), start.In(time.UTC); !got.Equal(want) {
    53  			t.Fatalf("time.Now() = %v, want %v", got, want)
    54  		}
    55  
    56  		wait := 1 * time.Second
    57  		time.Sleep(wait)
    58  		if got := time.Since(start); got != wait {
    59  			t.Fatalf("time.Since(start) = %v, want %v", got, wait)
    60  		}
    61  		if got := time.Now().Sub(start); got != wait {
    62  			t.Fatalf("time.Now().Sub(start) = %v, want %v", got, wait)
    63  		}
    64  	})
    65  }
    66  
    67  func TestRunEmpty(t *testing.T) {
    68  	synctest.Run(func() {
    69  	})
    70  }
    71  
    72  func TestSimpleWait(t *testing.T) {
    73  	synctest.Run(func() {
    74  		synctest.Wait()
    75  	})
    76  }
    77  
    78  func TestGoroutineWait(t *testing.T) {
    79  	synctest.Run(func() {
    80  		go func() {}()
    81  		synctest.Wait()
    82  	})
    83  }
    84  
    85  // TestWait starts a collection of goroutines.
    86  // It checks that synctest.Wait waits for all goroutines to exit before returning.
    87  func TestWait(t *testing.T) {
    88  	synctest.Run(func() {
    89  		done := false
    90  		ch := make(chan int)
    91  		var f func()
    92  		f = func() {
    93  			count := <-ch
    94  			if count == 0 {
    95  				done = true
    96  			} else {
    97  				go f()
    98  				ch <- count - 1
    99  			}
   100  		}
   101  		go f()
   102  		ch <- 100
   103  		synctest.Wait()
   104  		if !done {
   105  			t.Fatalf("done = false, want true")
   106  		}
   107  	})
   108  }
   109  
   110  func TestMallocs(t *testing.T) {
   111  	for i := 0; i < 100; i++ {
   112  		synctest.Run(func() {
   113  			done := false
   114  			ch := make(chan []byte)
   115  			var f func()
   116  			f = func() {
   117  				b := <-ch
   118  				if len(b) == 0 {
   119  					done = true
   120  				} else {
   121  					go f()
   122  					ch <- make([]byte, len(b)-1)
   123  				}
   124  			}
   125  			go f()
   126  			ch <- make([]byte, 100)
   127  			synctest.Wait()
   128  			if !done {
   129  				t.Fatalf("done = false, want true")
   130  			}
   131  		})
   132  	}
   133  }
   134  
   135  func TestTimerReadBeforeDeadline(t *testing.T) {
   136  	synctest.Run(func() {
   137  		start := time.Now()
   138  		tm := time.NewTimer(5 * time.Second)
   139  		<-tm.C
   140  		if got, want := time.Since(start), 5*time.Second; got != want {
   141  			t.Errorf("after sleep: time.Since(start) = %v, want %v", got, want)
   142  		}
   143  	})
   144  }
   145  
   146  func TestTimerReadAfterDeadline(t *testing.T) {
   147  	synctest.Run(func() {
   148  		delay := 1 * time.Second
   149  		want := time.Now().Add(delay)
   150  		tm := time.NewTimer(delay)
   151  		time.Sleep(2 * delay)
   152  		got := <-tm.C
   153  		if got != want {
   154  			t.Errorf("<-tm.C = %v, want %v", got, want)
   155  		}
   156  	})
   157  }
   158  
   159  func TestTimerReset(t *testing.T) {
   160  	synctest.Run(func() {
   161  		start := time.Now()
   162  		tm := time.NewTimer(1 * time.Second)
   163  		if got, want := <-tm.C, start.Add(1*time.Second); got != want {
   164  			t.Errorf("first sleep: <-tm.C = %v, want %v", got, want)
   165  		}
   166  
   167  		tm.Reset(2 * time.Second)
   168  		if got, want := <-tm.C, start.Add((1+2)*time.Second); got != want {
   169  			t.Errorf("second sleep: <-tm.C = %v, want %v", got, want)
   170  		}
   171  
   172  		tm.Reset(3 * time.Second)
   173  		time.Sleep(1 * time.Second)
   174  		tm.Reset(3 * time.Second)
   175  		if got, want := <-tm.C, start.Add((1+2+4)*time.Second); got != want {
   176  			t.Errorf("third sleep: <-tm.C = %v, want %v", got, want)
   177  		}
   178  	})
   179  }
   180  
   181  func TestTimeAfter(t *testing.T) {
   182  	synctest.Run(func() {
   183  		i := 0
   184  		time.AfterFunc(1*time.Second, func() {
   185  			// Ensure synctest group membership propagates through the AfterFunc.
   186  			i++ // 1
   187  			go func() {
   188  				time.Sleep(1 * time.Second)
   189  				i++ // 2
   190  			}()
   191  		})
   192  		time.Sleep(3 * time.Second)
   193  		synctest.Wait()
   194  		if got, want := i, 2; got != want {
   195  			t.Errorf("after sleep and wait: i = %v, want %v", got, want)
   196  		}
   197  	})
   198  }
   199  
   200  func TestTimerAfterBubbleExit(t *testing.T) {
   201  	run := false
   202  	synctest.Run(func() {
   203  		time.AfterFunc(1*time.Second, func() {
   204  			run = true
   205  		})
   206  	})
   207  	if run {
   208  		t.Errorf("timer ran before bubble exit")
   209  	}
   210  }
   211  
   212  func TestTimerFromOutsideBubble(t *testing.T) {
   213  	tm := time.NewTimer(10 * time.Millisecond)
   214  	synctest.Run(func() {
   215  		<-tm.C
   216  	})
   217  	if tm.Stop() {
   218  		t.Errorf("synctest.Run unexpectedly returned before timer fired")
   219  	}
   220  }
   221  
   222  // TestTimerNondeterminism verifies that timers firing at the same instant
   223  // don't always fire in exactly the same order.
   224  func TestTimerNondeterminism(t *testing.T) {
   225  	synctest.Run(func() {
   226  		const iterations = 1000
   227  		var seen1, seen2 bool
   228  		for range iterations {
   229  			tm1 := time.NewTimer(1)
   230  			tm2 := time.NewTimer(1)
   231  			select {
   232  			case <-tm1.C:
   233  				seen1 = true
   234  			case <-tm2.C:
   235  				seen2 = true
   236  			}
   237  			if seen1 && seen2 {
   238  				return
   239  			}
   240  			synctest.Wait()
   241  		}
   242  		t.Errorf("after %v iterations, seen timer1:%v, timer2:%v; want both", iterations, seen1, seen2)
   243  	})
   244  }
   245  
   246  // TestSleepNondeterminism verifies that goroutines sleeping to the same instant
   247  // don't always schedule in exactly the same order.
   248  func TestSleepNondeterminism(t *testing.T) {
   249  	synctest.Run(func() {
   250  		const iterations = 1000
   251  		var seen1, seen2 bool
   252  		for range iterations {
   253  			var first atomic.Int32
   254  			go func() {
   255  				time.Sleep(1)
   256  				first.CompareAndSwap(0, 1)
   257  			}()
   258  			go func() {
   259  				time.Sleep(1)
   260  				first.CompareAndSwap(0, 2)
   261  			}()
   262  			time.Sleep(1)
   263  			synctest.Wait()
   264  			switch v := first.Load(); v {
   265  			case 1:
   266  				seen1 = true
   267  			case 2:
   268  				seen2 = true
   269  			default:
   270  				t.Fatalf("first = %v, want 1 or 2", v)
   271  			}
   272  			if seen1 && seen2 {
   273  				return
   274  			}
   275  			synctest.Wait()
   276  		}
   277  		t.Errorf("after %v iterations, seen goroutine 1:%v, 2:%v; want both", iterations, seen1, seen2)
   278  	})
   279  }
   280  
   281  // TestTimerRunsImmediately verifies that a 0-duration timer sends on its channel
   282  // without waiting for the bubble to block.
   283  func TestTimerRunsImmediately(t *testing.T) {
   284  	synctest.Run(func() {
   285  		start := time.Now()
   286  		tm := time.NewTimer(0)
   287  		select {
   288  		case got := <-tm.C:
   289  			if !got.Equal(start) {
   290  				t.Errorf("<-tm.C = %v, want %v", got, start)
   291  			}
   292  		default:
   293  			t.Errorf("0-duration timer channel is not readable; want it to be")
   294  		}
   295  	})
   296  }
   297  
   298  // TestTimerRunsLater verifies that reading from a timer's channel receives the
   299  // timer fired, even when that time is in reading from a timer's channel receives the
   300  // time the timer fired, even when that time is in the past.
   301  func TestTimerRanInPast(t *testing.T) {
   302  	synctest.Run(func() {
   303  		delay := 1 * time.Second
   304  		want := time.Now().Add(delay)
   305  		tm := time.NewTimer(delay)
   306  		time.Sleep(2 * delay)
   307  		select {
   308  		case got := <-tm.C:
   309  			if !got.Equal(want) {
   310  				t.Errorf("<-tm.C = %v, want %v", got, want)
   311  			}
   312  		default:
   313  			t.Errorf("0-duration timer channel is not readable; want it to be")
   314  		}
   315  	})
   316  }
   317  
   318  // TestAfterFuncRunsImmediately verifies that a 0-duration AfterFunc is scheduled
   319  // without waiting for the bubble to block.
   320  func TestAfterFuncRunsImmediately(t *testing.T) {
   321  	synctest.Run(func() {
   322  		var b atomic.Bool
   323  		time.AfterFunc(0, func() {
   324  			b.Store(true)
   325  		})
   326  		for !b.Load() {
   327  			runtime.Gosched()
   328  		}
   329  	})
   330  }
   331  
   332  func TestChannelFromOutsideBubble(t *testing.T) {
   333  	choutside := make(chan struct{})
   334  	for _, test := range []struct {
   335  		desc    string
   336  		outside func(ch chan int)
   337  		inside  func(ch chan int)
   338  	}{{
   339  		desc:    "read closed",
   340  		outside: func(ch chan int) { close(ch) },
   341  		inside:  func(ch chan int) { <-ch },
   342  	}, {
   343  		desc:    "read value",
   344  		outside: func(ch chan int) { ch <- 0 },
   345  		inside:  func(ch chan int) { <-ch },
   346  	}, {
   347  		desc:    "write value",
   348  		outside: func(ch chan int) { <-ch },
   349  		inside:  func(ch chan int) { ch <- 0 },
   350  	}, {
   351  		desc:    "select outside only",
   352  		outside: func(ch chan int) { close(ch) },
   353  		inside: func(ch chan int) {
   354  			select {
   355  			case <-ch:
   356  			case <-choutside:
   357  			}
   358  		},
   359  	}, {
   360  		desc:    "select mixed",
   361  		outside: func(ch chan int) { close(ch) },
   362  		inside: func(ch chan int) {
   363  			ch2 := make(chan struct{})
   364  			select {
   365  			case <-ch:
   366  			case <-ch2:
   367  			}
   368  		},
   369  	}} {
   370  		t.Run(test.desc, func(t *testing.T) {
   371  			ch := make(chan int)
   372  			time.AfterFunc(1*time.Millisecond, func() {
   373  				test.outside(ch)
   374  			})
   375  			synctest.Run(func() {
   376  				test.inside(ch)
   377  			})
   378  		})
   379  	}
   380  }
   381  
   382  func TestChannelMovedOutOfBubble(t *testing.T) {
   383  	for _, test := range []struct {
   384  		desc      string
   385  		f         func(chan struct{})
   386  		wantFatal string
   387  	}{{
   388  		desc: "receive",
   389  		f: func(ch chan struct{}) {
   390  			<-ch
   391  		},
   392  		wantFatal: "receive on synctest channel from outside bubble",
   393  	}, {
   394  		desc: "send",
   395  		f: func(ch chan struct{}) {
   396  			ch <- struct{}{}
   397  		},
   398  		wantFatal: "send on synctest channel from outside bubble",
   399  	}, {
   400  		desc: "close",
   401  		f: func(ch chan struct{}) {
   402  			close(ch)
   403  		},
   404  		wantFatal: "close of synctest channel from outside bubble",
   405  	}} {
   406  		t.Run(test.desc, func(t *testing.T) {
   407  			// Bubbled channel accessed from outside any bubble.
   408  			t.Run("outside_bubble", func(t *testing.T) {
   409  				wantFatal(t, test.wantFatal, func() {
   410  					donec := make(chan struct{})
   411  					ch := make(chan chan struct{})
   412  					go func() {
   413  						defer close(donec)
   414  						test.f(<-ch)
   415  					}()
   416  					synctest.Run(func() {
   417  						ch <- make(chan struct{})
   418  					})
   419  					<-donec
   420  				})
   421  			})
   422  			// Bubbled channel accessed from a different bubble.
   423  			t.Run("different_bubble", func(t *testing.T) {
   424  				wantFatal(t, test.wantFatal, func() {
   425  					donec := make(chan struct{})
   426  					ch := make(chan chan struct{})
   427  					go func() {
   428  						defer close(donec)
   429  						c := <-ch
   430  						synctest.Run(func() {
   431  							test.f(c)
   432  						})
   433  					}()
   434  					synctest.Run(func() {
   435  						ch <- make(chan struct{})
   436  					})
   437  					<-donec
   438  				})
   439  			})
   440  		})
   441  	}
   442  }
   443  
   444  func TestTimerFromInsideBubble(t *testing.T) {
   445  	for _, test := range []struct {
   446  		desc      string
   447  		f         func(tm *time.Timer)
   448  		wantFatal string
   449  	}{{
   450  		desc: "read channel",
   451  		f: func(tm *time.Timer) {
   452  			<-tm.C
   453  		},
   454  		wantFatal: "receive on synctest channel from outside bubble",
   455  	}, {
   456  		desc: "Reset",
   457  		f: func(tm *time.Timer) {
   458  			tm.Reset(1 * time.Second)
   459  		},
   460  		wantFatal: "reset of synctest timer from outside bubble",
   461  	}, {
   462  		desc: "Stop",
   463  		f: func(tm *time.Timer) {
   464  			tm.Stop()
   465  		},
   466  		wantFatal: "stop of synctest timer from outside bubble",
   467  	}} {
   468  		t.Run(test.desc, func(t *testing.T) {
   469  			wantFatal(t, test.wantFatal, func() {
   470  				donec := make(chan struct{})
   471  				ch := make(chan *time.Timer)
   472  				go func() {
   473  					defer close(donec)
   474  					test.f(<-ch)
   475  				}()
   476  				synctest.Run(func() {
   477  					tm := time.NewTimer(1 * time.Second)
   478  					ch <- tm
   479  				})
   480  				<-donec
   481  			})
   482  		})
   483  	}
   484  }
   485  
   486  func TestDeadlockRoot(t *testing.T) {
   487  	defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
   488  	synctest.Run(func() {
   489  		select {}
   490  	})
   491  }
   492  
   493  func TestDeadlockChild(t *testing.T) {
   494  	defer wantPanic(t, "deadlock: main bubble goroutine has exited but blocked goroutines remain")
   495  	synctest.Run(func() {
   496  		go func() {
   497  			select {}
   498  		}()
   499  	})
   500  }
   501  
   502  func TestDeadlockTicker(t *testing.T) {
   503  	defer wantPanic(t, "deadlock: main bubble goroutine has exited but blocked goroutines remain")
   504  	synctest.Run(func() {
   505  		go func() {
   506  			for range time.Tick(1 * time.Second) {
   507  				t.Errorf("ticker unexpectedly ran")
   508  				return
   509  			}
   510  		}()
   511  	})
   512  }
   513  
   514  func TestCond(t *testing.T) {
   515  	synctest.Run(func() {
   516  		var mu sync.Mutex
   517  		cond := sync.NewCond(&mu)
   518  		start := time.Now()
   519  		const waitTime = 1 * time.Millisecond
   520  
   521  		go func() {
   522  			// Signal the cond.
   523  			time.Sleep(waitTime)
   524  			mu.Lock()
   525  			cond.Signal()
   526  			mu.Unlock()
   527  
   528  			// Broadcast to the cond.
   529  			time.Sleep(waitTime)
   530  			mu.Lock()
   531  			cond.Broadcast()
   532  			mu.Unlock()
   533  		}()
   534  
   535  		// Wait for cond.Signal.
   536  		mu.Lock()
   537  		cond.Wait()
   538  		mu.Unlock()
   539  		if got, want := time.Since(start), waitTime; got != want {
   540  			t.Errorf("after cond.Signal: time elapsed = %v, want %v", got, want)
   541  		}
   542  
   543  		// Wait for cond.Broadcast in two goroutines.
   544  		waiterDone := false
   545  		go func() {
   546  			mu.Lock()
   547  			cond.Wait()
   548  			mu.Unlock()
   549  			waiterDone = true
   550  		}()
   551  		mu.Lock()
   552  		cond.Wait()
   553  		mu.Unlock()
   554  		synctest.Wait()
   555  		if !waiterDone {
   556  			t.Errorf("after cond.Broadcast: waiter not done")
   557  		}
   558  		if got, want := time.Since(start), 2*waitTime; got != want {
   559  			t.Errorf("after cond.Broadcast: time elapsed = %v, want %v", got, want)
   560  		}
   561  	})
   562  }
   563  
   564  func TestIteratorPush(t *testing.T) {
   565  	synctest.Run(func() {
   566  		seq := func(yield func(time.Time) bool) {
   567  			for yield(time.Now()) {
   568  				time.Sleep(1 * time.Second)
   569  			}
   570  		}
   571  		var got []time.Time
   572  		go func() {
   573  			for now := range seq {
   574  				got = append(got, now)
   575  				if len(got) >= 3 {
   576  					break
   577  				}
   578  			}
   579  		}()
   580  		want := []time.Time{
   581  			time.Now(),
   582  			time.Now().Add(1 * time.Second),
   583  			time.Now().Add(2 * time.Second),
   584  		}
   585  		time.Sleep(5 * time.Second)
   586  		synctest.Wait()
   587  		if !slices.Equal(got, want) {
   588  			t.Errorf("got: %v; want: %v", got, want)
   589  		}
   590  	})
   591  }
   592  
   593  func TestIteratorPull(t *testing.T) {
   594  	synctest.Run(func() {
   595  		seq := func(yield func(time.Time) bool) {
   596  			for yield(time.Now()) {
   597  				time.Sleep(1 * time.Second)
   598  			}
   599  		}
   600  		var got []time.Time
   601  		go func() {
   602  			next, stop := iter.Pull(seq)
   603  			defer stop()
   604  			for len(got) < 3 {
   605  				now, _ := next()
   606  				got = append(got, now)
   607  			}
   608  		}()
   609  		want := []time.Time{
   610  			time.Now(),
   611  			time.Now().Add(1 * time.Second),
   612  			time.Now().Add(2 * time.Second),
   613  		}
   614  		time.Sleep(5 * time.Second)
   615  		synctest.Wait()
   616  		if !slices.Equal(got, want) {
   617  			t.Errorf("got: %v; want: %v", got, want)
   618  		}
   619  	})
   620  }
   621  
   622  func TestReflectFuncOf(t *testing.T) {
   623  	mkfunc := func(name string, i int) {
   624  		reflect.FuncOf([]reflect.Type{
   625  			reflect.StructOf([]reflect.StructField{{
   626  				Name: name + strconv.Itoa(i),
   627  				Type: reflect.TypeOf(0),
   628  			}}),
   629  		}, nil, false)
   630  	}
   631  	go func() {
   632  		for i := 0; i < 100000; i++ {
   633  			mkfunc("A", i)
   634  		}
   635  	}()
   636  	synctest.Run(func() {
   637  		for i := 0; i < 100000; i++ {
   638  			mkfunc("A", i)
   639  		}
   640  	})
   641  }
   642  
   643  func TestWaitGroupInBubble(t *testing.T) {
   644  	synctest.Run(func() {
   645  		var wg sync.WaitGroup
   646  		wg.Add(1)
   647  		const delay = 1 * time.Second
   648  		go func() {
   649  			time.Sleep(delay)
   650  			wg.Done()
   651  		}()
   652  		start := time.Now()
   653  		wg.Wait()
   654  		if got := time.Since(start); got != delay {
   655  			t.Fatalf("WaitGroup.Wait() took %v, want %v", got, delay)
   656  		}
   657  	})
   658  }
   659  
   660  // https://go.dev/issue/74386
   661  func TestWaitGroupRacingAdds(t *testing.T) {
   662  	synctest.Run(func() {
   663  		var wg sync.WaitGroup
   664  		for range 100 {
   665  			wg.Go(func() {})
   666  		}
   667  		wg.Wait()
   668  	})
   669  }
   670  
   671  func TestWaitGroupOutOfBubble(t *testing.T) {
   672  	var wg sync.WaitGroup
   673  	wg.Add(1)
   674  	donec := make(chan struct{})
   675  	go synctest.Run(func() {
   676  		// Since wg.Add was called outside the bubble, Wait is not durably blocking
   677  		// and this waits until wg.Done is called below.
   678  		wg.Wait()
   679  		close(donec)
   680  	})
   681  	select {
   682  	case <-donec:
   683  		t.Fatalf("synctest.Run finished before WaitGroup.Done called")
   684  	case <-time.After(1 * time.Millisecond):
   685  	}
   686  	wg.Done()
   687  	<-donec
   688  }
   689  
   690  func TestWaitGroupMovedIntoBubble(t *testing.T) {
   691  	wantFatal(t, "fatal error: sync: WaitGroup.Add called from inside and outside synctest bubble", func() {
   692  		var wg sync.WaitGroup
   693  		wg.Add(1)
   694  		synctest.Run(func() {
   695  			wg.Add(1)
   696  		})
   697  	})
   698  }
   699  
   700  func TestWaitGroupMovedOutOfBubble(t *testing.T) {
   701  	wantFatal(t, "fatal error: sync: WaitGroup.Add called from inside and outside synctest bubble", func() {
   702  		var wg sync.WaitGroup
   703  		synctest.Run(func() {
   704  			wg.Add(1)
   705  		})
   706  		wg.Add(1)
   707  	})
   708  }
   709  
   710  func TestWaitGroupMovedBetweenBubblesWithNonZeroCount(t *testing.T) {
   711  	wantFatal(t, "fatal error: sync: WaitGroup.Add called from multiple synctest bubbles", func() {
   712  		var wg sync.WaitGroup
   713  		synctest.Run(func() {
   714  			wg.Add(1)
   715  		})
   716  		synctest.Run(func() {
   717  			wg.Add(1)
   718  		})
   719  	})
   720  }
   721  
   722  func TestWaitGroupDisassociateInWait(t *testing.T) {
   723  	var wg sync.WaitGroup
   724  	synctest.Run(func() {
   725  		wg.Add(1)
   726  		wg.Done()
   727  		// Count and waiters are 0, so Wait disassociates the WaitGroup.
   728  		wg.Wait()
   729  	})
   730  	synctest.Run(func() {
   731  		// Reusing the WaitGroup is safe, because it is no longer bubbled.
   732  		wg.Add(1)
   733  		wg.Done()
   734  	})
   735  }
   736  
   737  func TestWaitGroupDisassociateInAdd(t *testing.T) {
   738  	var wg sync.WaitGroup
   739  	synctest.Run(func() {
   740  		wg.Add(1)
   741  		go wg.Wait()
   742  		synctest.Wait() // wait for Wait to block
   743  		// Count is 0 and waiters != 0, so Done wakes the waiters and
   744  		// disassociates the WaitGroup.
   745  		wg.Done()
   746  	})
   747  	synctest.Run(func() {
   748  		// Reusing the WaitGroup is safe, because it is no longer bubbled.
   749  		wg.Add(1)
   750  		wg.Done()
   751  	})
   752  }
   753  
   754  var testWaitGroupLinkerAllocatedWG sync.WaitGroup
   755  
   756  func TestWaitGroupLinkerAllocated(t *testing.T) {
   757  	synctest.Run(func() {
   758  		// This WaitGroup is probably linker-allocated and has no span,
   759  		// so we won't be able to add a special to it associating it with
   760  		// this bubble.
   761  		//
   762  		// Operations on it may not be durably blocking,
   763  		// but they shouldn't fail.
   764  		testWaitGroupLinkerAllocatedWG.Go(func() {})
   765  		testWaitGroupLinkerAllocatedWG.Wait()
   766  	})
   767  }
   768  
   769  var testWaitGroupHeapAllocatedWG = new(sync.WaitGroup)
   770  
   771  func TestWaitGroupHeapAllocated(t *testing.T) {
   772  	synctest.Run(func() {
   773  		// This package-scoped WaitGroup var should have been heap-allocated,
   774  		// so we can associate it with a bubble.
   775  		testWaitGroupHeapAllocatedWG.Add(1)
   776  		go testWaitGroupHeapAllocatedWG.Wait()
   777  		synctest.Wait()
   778  		testWaitGroupHeapAllocatedWG.Done()
   779  	})
   780  }
   781  
   782  func TestHappensBefore(t *testing.T) {
   783  	// Use two parallel goroutines accessing different vars to ensure that
   784  	// we correctly account for multiple goroutines in the bubble.
   785  	var v1 int
   786  	var v2 int
   787  	synctest.Run(func() {
   788  		v1++ // 1
   789  		v2++ // 1
   790  
   791  		// Wait returns after these goroutines exit.
   792  		go func() {
   793  			v1++ // 2
   794  		}()
   795  		go func() {
   796  			v2++ // 2
   797  		}()
   798  		synctest.Wait()
   799  
   800  		v1++ // 3
   801  		v2++ // 3
   802  
   803  		// Wait returns after these goroutines block.
   804  		ch1 := make(chan struct{})
   805  		go func() {
   806  			v1++ // 4
   807  			<-ch1
   808  		}()
   809  		go func() {
   810  			v2++ // 4
   811  			<-ch1
   812  		}()
   813  		synctest.Wait()
   814  
   815  		v1++ // 5
   816  		v2++ // 5
   817  		close(ch1)
   818  
   819  		// Wait returns after these timers run.
   820  		time.AfterFunc(0, func() {
   821  			v1++ // 6
   822  		})
   823  		time.AfterFunc(0, func() {
   824  			v2++ // 6
   825  		})
   826  		synctest.Wait()
   827  
   828  		v1++ // 7
   829  		v2++ // 7
   830  
   831  		// Wait returns after these timer goroutines block.
   832  		ch2 := make(chan struct{})
   833  		time.AfterFunc(0, func() {
   834  			v1++ // 8
   835  			<-ch2
   836  		})
   837  		time.AfterFunc(0, func() {
   838  			v2++ // 8
   839  			<-ch2
   840  		})
   841  		synctest.Wait()
   842  
   843  		v1++ // 9
   844  		v2++ // 9
   845  		close(ch2)
   846  	})
   847  	// This Run happens after the previous Run returns.
   848  	synctest.Run(func() {
   849  		go func() {
   850  			go func() {
   851  				v1++ // 10
   852  			}()
   853  		}()
   854  		go func() {
   855  			go func() {
   856  				v2++ // 10
   857  			}()
   858  		}()
   859  	})
   860  	// These tests happen after Run returns.
   861  	if got, want := v1, 10; got != want {
   862  		t.Errorf("v1 = %v, want %v", got, want)
   863  	}
   864  	if got, want := v2, 10; got != want {
   865  		t.Errorf("v2 = %v, want %v", got, want)
   866  	}
   867  }
   868  
   869  // https://go.dev/issue/73817
   870  func TestWeak(t *testing.T) {
   871  	synctest.Run(func() {
   872  		for range 5 {
   873  			runtime.GC()
   874  			b := make([]byte, 1024)
   875  			weak.Make(&b)
   876  		}
   877  	})
   878  }
   879  
   880  func wantPanic(t *testing.T, want string) {
   881  	if e := recover(); e != nil {
   882  		if got := fmt.Sprint(e); got != want {
   883  			t.Errorf("got panic message %q, want %q", got, want)
   884  		}
   885  	} else {
   886  		t.Errorf("got no panic, want one")
   887  	}
   888  }
   889  
   890  func wantFatal(t *testing.T, want string, f func()) {
   891  	t.Helper()
   892  
   893  	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
   894  		f()
   895  		return
   896  	}
   897  
   898  	cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^"+t.Name()+"$")
   899  	cmd = testenv.CleanCmdEnv(cmd)
   900  	cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
   901  	out, err := cmd.CombinedOutput()
   902  	if err == nil {
   903  		t.Errorf("expected test function to panic, but test returned successfully")
   904  	}
   905  	if !strings.Contains(string(out), want) {
   906  		t.Errorf("wanted test output contaiing %q; got %q", want, string(out))
   907  	}
   908  }
   909  

View as plain text