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  	"iter"
    11  	"reflect"
    12  	"slices"
    13  	"strconv"
    14  	"sync"
    15  	"testing"
    16  	"time"
    17  )
    18  
    19  func TestNow(t *testing.T) {
    20  	start := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).In(time.Local)
    21  	synctest.Run(func() {
    22  		// Time starts at 2000-1-1 00:00:00.
    23  		if got, want := time.Now(), start; !got.Equal(want) {
    24  			t.Errorf("at start: time.Now = %v, want %v", got, want)
    25  		}
    26  		go func() {
    27  			// New goroutines see the same fake clock.
    28  			if got, want := time.Now(), start; !got.Equal(want) {
    29  				t.Errorf("time.Now = %v, want %v", got, want)
    30  			}
    31  		}()
    32  		// Time advances after a sleep.
    33  		time.Sleep(1 * time.Second)
    34  		if got, want := time.Now(), start.Add(1*time.Second); !got.Equal(want) {
    35  			t.Errorf("after sleep: time.Now = %v, want %v", got, want)
    36  		}
    37  	})
    38  }
    39  
    40  // TestMonotonicClock exercises comparing times from within a bubble
    41  // with ones from outside the bubble.
    42  func TestMonotonicClock(t *testing.T) {
    43  	start := time.Now()
    44  	synctest.Run(func() {
    45  		time.Sleep(time.Until(start.Round(0)))
    46  		if got, want := time.Now().In(time.UTC), start.In(time.UTC); !got.Equal(want) {
    47  			t.Fatalf("time.Now() = %v, want %v", got, want)
    48  		}
    49  
    50  		wait := 1 * time.Second
    51  		time.Sleep(wait)
    52  		if got := time.Since(start); got != wait {
    53  			t.Fatalf("time.Since(start) = %v, want %v", got, wait)
    54  		}
    55  		if got := time.Now().Sub(start); got != wait {
    56  			t.Fatalf("time.Now().Sub(start) = %v, want %v", got, wait)
    57  		}
    58  	})
    59  }
    60  
    61  func TestRunEmpty(t *testing.T) {
    62  	synctest.Run(func() {
    63  	})
    64  }
    65  
    66  func TestSimpleWait(t *testing.T) {
    67  	synctest.Run(func() {
    68  		synctest.Wait()
    69  	})
    70  }
    71  
    72  func TestGoroutineWait(t *testing.T) {
    73  	synctest.Run(func() {
    74  		go func() {}()
    75  		synctest.Wait()
    76  	})
    77  }
    78  
    79  // TestWait starts a collection of goroutines.
    80  // It checks that synctest.Wait waits for all goroutines to exit before returning.
    81  func TestWait(t *testing.T) {
    82  	synctest.Run(func() {
    83  		done := false
    84  		ch := make(chan int)
    85  		var f func()
    86  		f = func() {
    87  			count := <-ch
    88  			if count == 0 {
    89  				done = true
    90  			} else {
    91  				go f()
    92  				ch <- count - 1
    93  			}
    94  		}
    95  		go f()
    96  		ch <- 100
    97  		synctest.Wait()
    98  		if !done {
    99  			t.Fatalf("done = false, want true")
   100  		}
   101  	})
   102  }
   103  
   104  func TestMallocs(t *testing.T) {
   105  	for i := 0; i < 100; i++ {
   106  		synctest.Run(func() {
   107  			done := false
   108  			ch := make(chan []byte)
   109  			var f func()
   110  			f = func() {
   111  				b := <-ch
   112  				if len(b) == 0 {
   113  					done = true
   114  				} else {
   115  					go f()
   116  					ch <- make([]byte, len(b)-1)
   117  				}
   118  			}
   119  			go f()
   120  			ch <- make([]byte, 100)
   121  			synctest.Wait()
   122  			if !done {
   123  				t.Fatalf("done = false, want true")
   124  			}
   125  		})
   126  	}
   127  }
   128  
   129  func TestTimerReadBeforeDeadline(t *testing.T) {
   130  	synctest.Run(func() {
   131  		start := time.Now()
   132  		tm := time.NewTimer(5 * time.Second)
   133  		<-tm.C
   134  		if got, want := time.Since(start), 5*time.Second; got != want {
   135  			t.Errorf("after sleep: time.Since(start) = %v, want %v", got, want)
   136  		}
   137  	})
   138  }
   139  
   140  func TestTimerReadAfterDeadline(t *testing.T) {
   141  	synctest.Run(func() {
   142  		delay := 1 * time.Second
   143  		want := time.Now().Add(delay)
   144  		tm := time.NewTimer(delay)
   145  		time.Sleep(2 * delay)
   146  		got := <-tm.C
   147  		if got != want {
   148  			t.Errorf("<-tm.C = %v, want %v", got, want)
   149  		}
   150  	})
   151  }
   152  
   153  func TestTimerReset(t *testing.T) {
   154  	synctest.Run(func() {
   155  		start := time.Now()
   156  		tm := time.NewTimer(1 * time.Second)
   157  		if got, want := <-tm.C, start.Add(1*time.Second); got != want {
   158  			t.Errorf("first sleep: <-tm.C = %v, want %v", got, want)
   159  		}
   160  
   161  		tm.Reset(2 * time.Second)
   162  		if got, want := <-tm.C, start.Add((1+2)*time.Second); got != want {
   163  			t.Errorf("second sleep: <-tm.C = %v, want %v", got, want)
   164  		}
   165  
   166  		tm.Reset(3 * time.Second)
   167  		time.Sleep(1 * time.Second)
   168  		tm.Reset(3 * time.Second)
   169  		if got, want := <-tm.C, start.Add((1+2+4)*time.Second); got != want {
   170  			t.Errorf("third sleep: <-tm.C = %v, want %v", got, want)
   171  		}
   172  	})
   173  }
   174  
   175  func TestTimeAfter(t *testing.T) {
   176  	synctest.Run(func() {
   177  		i := 0
   178  		time.AfterFunc(1*time.Second, func() {
   179  			// Ensure synctest group membership propagates through the AfterFunc.
   180  			i++ // 1
   181  			go func() {
   182  				time.Sleep(1 * time.Second)
   183  				i++ // 2
   184  			}()
   185  		})
   186  		time.Sleep(3 * time.Second)
   187  		synctest.Wait()
   188  		if got, want := i, 2; got != want {
   189  			t.Errorf("after sleep and wait: i = %v, want %v", got, want)
   190  		}
   191  	})
   192  }
   193  
   194  func TestTimerFromOutsideBubble(t *testing.T) {
   195  	tm := time.NewTimer(10 * time.Millisecond)
   196  	synctest.Run(func() {
   197  		<-tm.C
   198  	})
   199  	if tm.Stop() {
   200  		t.Errorf("synctest.Run unexpectedly returned before timer fired")
   201  	}
   202  }
   203  
   204  func TestChannelFromOutsideBubble(t *testing.T) {
   205  	choutside := make(chan struct{})
   206  	for _, test := range []struct {
   207  		desc    string
   208  		outside func(ch chan int)
   209  		inside  func(ch chan int)
   210  	}{{
   211  		desc:    "read closed",
   212  		outside: func(ch chan int) { close(ch) },
   213  		inside:  func(ch chan int) { <-ch },
   214  	}, {
   215  		desc:    "read value",
   216  		outside: func(ch chan int) { ch <- 0 },
   217  		inside:  func(ch chan int) { <-ch },
   218  	}, {
   219  		desc:    "write value",
   220  		outside: func(ch chan int) { <-ch },
   221  		inside:  func(ch chan int) { ch <- 0 },
   222  	}, {
   223  		desc:    "select outside only",
   224  		outside: func(ch chan int) { close(ch) },
   225  		inside: func(ch chan int) {
   226  			select {
   227  			case <-ch:
   228  			case <-choutside:
   229  			}
   230  		},
   231  	}, {
   232  		desc:    "select mixed",
   233  		outside: func(ch chan int) { close(ch) },
   234  		inside: func(ch chan int) {
   235  			ch2 := make(chan struct{})
   236  			select {
   237  			case <-ch:
   238  			case <-ch2:
   239  			}
   240  		},
   241  	}} {
   242  		t.Run(test.desc, func(t *testing.T) {
   243  			ch := make(chan int)
   244  			time.AfterFunc(1*time.Millisecond, func() {
   245  				test.outside(ch)
   246  			})
   247  			synctest.Run(func() {
   248  				test.inside(ch)
   249  			})
   250  		})
   251  	}
   252  }
   253  
   254  func TestTimerFromInsideBubble(t *testing.T) {
   255  	for _, test := range []struct {
   256  		desc      string
   257  		f         func(tm *time.Timer)
   258  		wantPanic string
   259  	}{{
   260  		desc: "read channel",
   261  		f: func(tm *time.Timer) {
   262  			<-tm.C
   263  		},
   264  		wantPanic: "receive on synctest channel from outside bubble",
   265  	}, {
   266  		desc: "Reset",
   267  		f: func(tm *time.Timer) {
   268  			tm.Reset(1 * time.Second)
   269  		},
   270  		wantPanic: "reset of synctest timer from outside bubble",
   271  	}, {
   272  		desc: "Stop",
   273  		f: func(tm *time.Timer) {
   274  			tm.Stop()
   275  		},
   276  		wantPanic: "stop of synctest timer from outside bubble",
   277  	}} {
   278  		t.Run(test.desc, func(t *testing.T) {
   279  			donec := make(chan struct{})
   280  			ch := make(chan *time.Timer)
   281  			go func() {
   282  				defer close(donec)
   283  				defer wantPanic(t, test.wantPanic)
   284  				test.f(<-ch)
   285  			}()
   286  			synctest.Run(func() {
   287  				tm := time.NewTimer(1 * time.Second)
   288  				ch <- tm
   289  			})
   290  			<-donec
   291  		})
   292  	}
   293  }
   294  
   295  func TestDeadlockRoot(t *testing.T) {
   296  	defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
   297  	synctest.Run(func() {
   298  		select {}
   299  	})
   300  }
   301  
   302  func TestDeadlockChild(t *testing.T) {
   303  	defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
   304  	synctest.Run(func() {
   305  		go func() {
   306  			select {}
   307  		}()
   308  	})
   309  }
   310  
   311  func TestCond(t *testing.T) {
   312  	synctest.Run(func() {
   313  		var mu sync.Mutex
   314  		cond := sync.NewCond(&mu)
   315  		start := time.Now()
   316  		const waitTime = 1 * time.Millisecond
   317  
   318  		go func() {
   319  			// Signal the cond.
   320  			time.Sleep(waitTime)
   321  			mu.Lock()
   322  			cond.Signal()
   323  			mu.Unlock()
   324  
   325  			// Broadcast to the cond.
   326  			time.Sleep(waitTime)
   327  			mu.Lock()
   328  			cond.Broadcast()
   329  			mu.Unlock()
   330  		}()
   331  
   332  		// Wait for cond.Signal.
   333  		mu.Lock()
   334  		cond.Wait()
   335  		mu.Unlock()
   336  		if got, want := time.Since(start), waitTime; got != want {
   337  			t.Errorf("after cond.Signal: time elapsed = %v, want %v", got, want)
   338  		}
   339  
   340  		// Wait for cond.Broadcast in two goroutines.
   341  		waiterDone := false
   342  		go func() {
   343  			mu.Lock()
   344  			cond.Wait()
   345  			mu.Unlock()
   346  			waiterDone = true
   347  		}()
   348  		mu.Lock()
   349  		cond.Wait()
   350  		mu.Unlock()
   351  		synctest.Wait()
   352  		if !waiterDone {
   353  			t.Errorf("after cond.Broadcast: waiter not done")
   354  		}
   355  		if got, want := time.Since(start), 2*waitTime; got != want {
   356  			t.Errorf("after cond.Broadcast: time elapsed = %v, want %v", got, want)
   357  		}
   358  	})
   359  }
   360  
   361  func TestIteratorPush(t *testing.T) {
   362  	synctest.Run(func() {
   363  		seq := func(yield func(time.Time) bool) {
   364  			for yield(time.Now()) {
   365  				time.Sleep(1 * time.Second)
   366  			}
   367  		}
   368  		var got []time.Time
   369  		go func() {
   370  			for now := range seq {
   371  				got = append(got, now)
   372  				if len(got) >= 3 {
   373  					break
   374  				}
   375  			}
   376  		}()
   377  		want := []time.Time{
   378  			time.Now(),
   379  			time.Now().Add(1 * time.Second),
   380  			time.Now().Add(2 * time.Second),
   381  		}
   382  		time.Sleep(5 * time.Second)
   383  		synctest.Wait()
   384  		if !slices.Equal(got, want) {
   385  			t.Errorf("got: %v; want: %v", got, want)
   386  		}
   387  	})
   388  }
   389  
   390  func TestIteratorPull(t *testing.T) {
   391  	synctest.Run(func() {
   392  		seq := func(yield func(time.Time) bool) {
   393  			for yield(time.Now()) {
   394  				time.Sleep(1 * time.Second)
   395  			}
   396  		}
   397  		var got []time.Time
   398  		go func() {
   399  			next, stop := iter.Pull(seq)
   400  			defer stop()
   401  			for len(got) < 3 {
   402  				now, _ := next()
   403  				got = append(got, now)
   404  			}
   405  		}()
   406  		want := []time.Time{
   407  			time.Now(),
   408  			time.Now().Add(1 * time.Second),
   409  			time.Now().Add(2 * time.Second),
   410  		}
   411  		time.Sleep(5 * time.Second)
   412  		synctest.Wait()
   413  		if !slices.Equal(got, want) {
   414  			t.Errorf("got: %v; want: %v", got, want)
   415  		}
   416  	})
   417  }
   418  
   419  func TestReflectFuncOf(t *testing.T) {
   420  	mkfunc := func(name string, i int) {
   421  		reflect.FuncOf([]reflect.Type{
   422  			reflect.StructOf([]reflect.StructField{{
   423  				Name: name + strconv.Itoa(i),
   424  				Type: reflect.TypeOf(0),
   425  			}}),
   426  		}, nil, false)
   427  	}
   428  	go func() {
   429  		for i := 0; i < 100000; i++ {
   430  			mkfunc("A", i)
   431  		}
   432  	}()
   433  	synctest.Run(func() {
   434  		for i := 0; i < 100000; i++ {
   435  			mkfunc("A", i)
   436  		}
   437  	})
   438  }
   439  
   440  func TestWaitGroup(t *testing.T) {
   441  	synctest.Run(func() {
   442  		var wg sync.WaitGroup
   443  		wg.Add(1)
   444  		const delay = 1 * time.Second
   445  		go func() {
   446  			time.Sleep(delay)
   447  			wg.Done()
   448  		}()
   449  		start := time.Now()
   450  		wg.Wait()
   451  		if got := time.Since(start); got != delay {
   452  			t.Fatalf("WaitGroup.Wait() took %v, want %v", got, delay)
   453  		}
   454  	})
   455  }
   456  
   457  func TestHappensBefore(t *testing.T) {
   458  	// Use two parallel goroutines accessing different vars to ensure that
   459  	// we correctly account for multiple goroutines in the bubble.
   460  	var v1 int
   461  	var v2 int
   462  	synctest.Run(func() {
   463  		v1++ // 1
   464  		v2++ // 1
   465  
   466  		// Wait returns after these goroutines exit.
   467  		go func() {
   468  			v1++ // 2
   469  		}()
   470  		go func() {
   471  			v2++ // 2
   472  		}()
   473  		synctest.Wait()
   474  
   475  		v1++ // 3
   476  		v2++ // 3
   477  
   478  		// Wait returns after these goroutines block.
   479  		ch1 := make(chan struct{})
   480  		go func() {
   481  			v1++ // 4
   482  			<-ch1
   483  		}()
   484  		go func() {
   485  			v2++ // 4
   486  			<-ch1
   487  		}()
   488  		synctest.Wait()
   489  
   490  		v1++ // 5
   491  		v2++ // 5
   492  		close(ch1)
   493  
   494  		// Wait returns after these timers run.
   495  		time.AfterFunc(0, func() {
   496  			v1++ // 6
   497  		})
   498  		time.AfterFunc(0, func() {
   499  			v2++ // 6
   500  		})
   501  		synctest.Wait()
   502  
   503  		v1++ // 7
   504  		v2++ // 7
   505  
   506  		// Wait returns after these timer goroutines block.
   507  		ch2 := make(chan struct{})
   508  		time.AfterFunc(0, func() {
   509  			v1++ // 8
   510  			<-ch2
   511  		})
   512  		time.AfterFunc(0, func() {
   513  			v2++ // 8
   514  			<-ch2
   515  		})
   516  		synctest.Wait()
   517  
   518  		v1++ // 9
   519  		v2++ // 9
   520  		close(ch2)
   521  	})
   522  	// This Run happens after the previous Run returns.
   523  	synctest.Run(func() {
   524  		go func() {
   525  			go func() {
   526  				v1++ // 10
   527  			}()
   528  		}()
   529  		go func() {
   530  			go func() {
   531  				v2++ // 10
   532  			}()
   533  		}()
   534  	})
   535  	// These tests happen after Run returns.
   536  	if got, want := v1, 10; got != want {
   537  		t.Errorf("v1 = %v, want %v", got, want)
   538  	}
   539  	if got, want := v2, 10; got != want {
   540  		t.Errorf("v2 = %v, want %v", got, want)
   541  	}
   542  }
   543  
   544  func wantPanic(t *testing.T, want string) {
   545  	if e := recover(); e != nil {
   546  		if got := fmt.Sprint(e); got != want {
   547  			t.Errorf("got panic message %q, want %q", got, want)
   548  		}
   549  	} else {
   550  		t.Errorf("got no panic, want one")
   551  	}
   552  }
   553  

View as plain text