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  func TestRunEmpty(t *testing.T) {
    41  	synctest.Run(func() {
    42  	})
    43  }
    44  
    45  func TestSimpleWait(t *testing.T) {
    46  	synctest.Run(func() {
    47  		synctest.Wait()
    48  	})
    49  }
    50  
    51  func TestGoroutineWait(t *testing.T) {
    52  	synctest.Run(func() {
    53  		go func() {}()
    54  		synctest.Wait()
    55  	})
    56  }
    57  
    58  // TestWait starts a collection of goroutines.
    59  // It checks that synctest.Wait waits for all goroutines to exit before returning.
    60  func TestWait(t *testing.T) {
    61  	synctest.Run(func() {
    62  		done := false
    63  		ch := make(chan int)
    64  		var f func()
    65  		f = func() {
    66  			count := <-ch
    67  			if count == 0 {
    68  				done = true
    69  			} else {
    70  				go f()
    71  				ch <- count - 1
    72  			}
    73  		}
    74  		go f()
    75  		ch <- 100
    76  		synctest.Wait()
    77  		if !done {
    78  			t.Fatalf("done = false, want true")
    79  		}
    80  	})
    81  }
    82  
    83  func TestMallocs(t *testing.T) {
    84  	for i := 0; i < 100; i++ {
    85  		synctest.Run(func() {
    86  			done := false
    87  			ch := make(chan []byte)
    88  			var f func()
    89  			f = func() {
    90  				b := <-ch
    91  				if len(b) == 0 {
    92  					done = true
    93  				} else {
    94  					go f()
    95  					ch <- make([]byte, len(b)-1)
    96  				}
    97  			}
    98  			go f()
    99  			ch <- make([]byte, 100)
   100  			synctest.Wait()
   101  			if !done {
   102  				t.Fatalf("done = false, want true")
   103  			}
   104  		})
   105  	}
   106  }
   107  
   108  func TestTimerReadBeforeDeadline(t *testing.T) {
   109  	synctest.Run(func() {
   110  		start := time.Now()
   111  		tm := time.NewTimer(5 * time.Second)
   112  		<-tm.C
   113  		if got, want := time.Since(start), 5*time.Second; got != want {
   114  			t.Errorf("after sleep: time.Since(start) = %v, want %v", got, want)
   115  		}
   116  	})
   117  }
   118  
   119  func TestTimerReadAfterDeadline(t *testing.T) {
   120  	synctest.Run(func() {
   121  		delay := 1 * time.Second
   122  		want := time.Now().Add(delay)
   123  		tm := time.NewTimer(delay)
   124  		time.Sleep(2 * delay)
   125  		got := <-tm.C
   126  		if got != want {
   127  			t.Errorf("<-tm.C = %v, want %v", got, want)
   128  		}
   129  	})
   130  }
   131  
   132  func TestTimerReset(t *testing.T) {
   133  	synctest.Run(func() {
   134  		start := time.Now()
   135  		tm := time.NewTimer(1 * time.Second)
   136  		if got, want := <-tm.C, start.Add(1*time.Second); got != want {
   137  			t.Errorf("first sleep: <-tm.C = %v, want %v", got, want)
   138  		}
   139  
   140  		tm.Reset(2 * time.Second)
   141  		if got, want := <-tm.C, start.Add((1+2)*time.Second); got != want {
   142  			t.Errorf("second sleep: <-tm.C = %v, want %v", got, want)
   143  		}
   144  
   145  		tm.Reset(3 * time.Second)
   146  		time.Sleep(1 * time.Second)
   147  		tm.Reset(3 * time.Second)
   148  		if got, want := <-tm.C, start.Add((1+2+4)*time.Second); got != want {
   149  			t.Errorf("third sleep: <-tm.C = %v, want %v", got, want)
   150  		}
   151  	})
   152  }
   153  
   154  func TestTimeAfter(t *testing.T) {
   155  	synctest.Run(func() {
   156  		i := 0
   157  		time.AfterFunc(1*time.Second, func() {
   158  			// Ensure synctest group membership propagates through the AfterFunc.
   159  			i++ // 1
   160  			go func() {
   161  				time.Sleep(1 * time.Second)
   162  				i++ // 2
   163  			}()
   164  		})
   165  		time.Sleep(3 * time.Second)
   166  		synctest.Wait()
   167  		if got, want := i, 2; got != want {
   168  			t.Errorf("after sleep and wait: i = %v, want %v", got, want)
   169  		}
   170  	})
   171  }
   172  
   173  func TestTimerFromOutsideBubble(t *testing.T) {
   174  	tm := time.NewTimer(10 * time.Millisecond)
   175  	synctest.Run(func() {
   176  		<-tm.C
   177  	})
   178  	if tm.Stop() {
   179  		t.Errorf("synctest.Run unexpectedly returned before timer fired")
   180  	}
   181  }
   182  
   183  func TestChannelFromOutsideBubble(t *testing.T) {
   184  	choutside := make(chan struct{})
   185  	for _, test := range []struct {
   186  		desc    string
   187  		outside func(ch chan int)
   188  		inside  func(ch chan int)
   189  	}{{
   190  		desc:    "read closed",
   191  		outside: func(ch chan int) { close(ch) },
   192  		inside:  func(ch chan int) { <-ch },
   193  	}, {
   194  		desc:    "read value",
   195  		outside: func(ch chan int) { ch <- 0 },
   196  		inside:  func(ch chan int) { <-ch },
   197  	}, {
   198  		desc:    "write value",
   199  		outside: func(ch chan int) { <-ch },
   200  		inside:  func(ch chan int) { ch <- 0 },
   201  	}, {
   202  		desc:    "select outside only",
   203  		outside: func(ch chan int) { close(ch) },
   204  		inside: func(ch chan int) {
   205  			select {
   206  			case <-ch:
   207  			case <-choutside:
   208  			}
   209  		},
   210  	}, {
   211  		desc:    "select mixed",
   212  		outside: func(ch chan int) { close(ch) },
   213  		inside: func(ch chan int) {
   214  			ch2 := make(chan struct{})
   215  			select {
   216  			case <-ch:
   217  			case <-ch2:
   218  			}
   219  		},
   220  	}} {
   221  		t.Run(test.desc, func(t *testing.T) {
   222  			ch := make(chan int)
   223  			time.AfterFunc(1*time.Millisecond, func() {
   224  				test.outside(ch)
   225  			})
   226  			synctest.Run(func() {
   227  				test.inside(ch)
   228  			})
   229  		})
   230  	}
   231  }
   232  
   233  func TestTimerFromInsideBubble(t *testing.T) {
   234  	for _, test := range []struct {
   235  		desc      string
   236  		f         func(tm *time.Timer)
   237  		wantPanic string
   238  	}{{
   239  		desc: "read channel",
   240  		f: func(tm *time.Timer) {
   241  			<-tm.C
   242  		},
   243  		wantPanic: "receive on synctest channel from outside bubble",
   244  	}, {
   245  		desc: "Reset",
   246  		f: func(tm *time.Timer) {
   247  			tm.Reset(1 * time.Second)
   248  		},
   249  		wantPanic: "reset of synctest timer from outside bubble",
   250  	}, {
   251  		desc: "Stop",
   252  		f: func(tm *time.Timer) {
   253  			tm.Stop()
   254  		},
   255  		wantPanic: "stop of synctest timer from outside bubble",
   256  	}} {
   257  		t.Run(test.desc, func(t *testing.T) {
   258  			donec := make(chan struct{})
   259  			ch := make(chan *time.Timer)
   260  			go func() {
   261  				defer close(donec)
   262  				defer wantPanic(t, test.wantPanic)
   263  				test.f(<-ch)
   264  			}()
   265  			synctest.Run(func() {
   266  				tm := time.NewTimer(1 * time.Second)
   267  				ch <- tm
   268  			})
   269  			<-donec
   270  		})
   271  	}
   272  }
   273  
   274  func TestDeadlockRoot(t *testing.T) {
   275  	defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
   276  	synctest.Run(func() {
   277  		select {}
   278  	})
   279  }
   280  
   281  func TestDeadlockChild(t *testing.T) {
   282  	defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
   283  	synctest.Run(func() {
   284  		go func() {
   285  			select {}
   286  		}()
   287  	})
   288  }
   289  
   290  func TestCond(t *testing.T) {
   291  	synctest.Run(func() {
   292  		var mu sync.Mutex
   293  		cond := sync.NewCond(&mu)
   294  		start := time.Now()
   295  		const waitTime = 1 * time.Millisecond
   296  
   297  		go func() {
   298  			// Signal the cond.
   299  			time.Sleep(waitTime)
   300  			mu.Lock()
   301  			cond.Signal()
   302  			mu.Unlock()
   303  
   304  			// Broadcast to the cond.
   305  			time.Sleep(waitTime)
   306  			mu.Lock()
   307  			cond.Broadcast()
   308  			mu.Unlock()
   309  		}()
   310  
   311  		// Wait for cond.Signal.
   312  		mu.Lock()
   313  		cond.Wait()
   314  		mu.Unlock()
   315  		if got, want := time.Since(start), waitTime; got != want {
   316  			t.Errorf("after cond.Signal: time elapsed = %v, want %v", got, want)
   317  		}
   318  
   319  		// Wait for cond.Broadcast in two goroutines.
   320  		waiterDone := false
   321  		go func() {
   322  			mu.Lock()
   323  			cond.Wait()
   324  			mu.Unlock()
   325  			waiterDone = true
   326  		}()
   327  		mu.Lock()
   328  		cond.Wait()
   329  		mu.Unlock()
   330  		synctest.Wait()
   331  		if !waiterDone {
   332  			t.Errorf("after cond.Broadcast: waiter not done")
   333  		}
   334  		if got, want := time.Since(start), 2*waitTime; got != want {
   335  			t.Errorf("after cond.Broadcast: time elapsed = %v, want %v", got, want)
   336  		}
   337  	})
   338  }
   339  
   340  func TestIteratorPush(t *testing.T) {
   341  	synctest.Run(func() {
   342  		seq := func(yield func(time.Time) bool) {
   343  			for yield(time.Now()) {
   344  				time.Sleep(1 * time.Second)
   345  			}
   346  		}
   347  		var got []time.Time
   348  		go func() {
   349  			for now := range seq {
   350  				got = append(got, now)
   351  				if len(got) >= 3 {
   352  					break
   353  				}
   354  			}
   355  		}()
   356  		want := []time.Time{
   357  			time.Now(),
   358  			time.Now().Add(1 * time.Second),
   359  			time.Now().Add(2 * time.Second),
   360  		}
   361  		time.Sleep(5 * time.Second)
   362  		synctest.Wait()
   363  		if !slices.Equal(got, want) {
   364  			t.Errorf("got: %v; want: %v", got, want)
   365  		}
   366  	})
   367  }
   368  
   369  func TestIteratorPull(t *testing.T) {
   370  	synctest.Run(func() {
   371  		seq := func(yield func(time.Time) bool) {
   372  			for yield(time.Now()) {
   373  				time.Sleep(1 * time.Second)
   374  			}
   375  		}
   376  		var got []time.Time
   377  		go func() {
   378  			next, stop := iter.Pull(seq)
   379  			defer stop()
   380  			for len(got) < 3 {
   381  				now, _ := next()
   382  				got = append(got, now)
   383  			}
   384  		}()
   385  		want := []time.Time{
   386  			time.Now(),
   387  			time.Now().Add(1 * time.Second),
   388  			time.Now().Add(2 * time.Second),
   389  		}
   390  		time.Sleep(5 * time.Second)
   391  		synctest.Wait()
   392  		if !slices.Equal(got, want) {
   393  			t.Errorf("got: %v; want: %v", got, want)
   394  		}
   395  	})
   396  }
   397  
   398  func TestReflectFuncOf(t *testing.T) {
   399  	mkfunc := func(name string, i int) {
   400  		reflect.FuncOf([]reflect.Type{
   401  			reflect.StructOf([]reflect.StructField{{
   402  				Name: name + strconv.Itoa(i),
   403  				Type: reflect.TypeOf(0),
   404  			}}),
   405  		}, nil, false)
   406  	}
   407  	go func() {
   408  		for i := 0; i < 100000; i++ {
   409  			mkfunc("A", i)
   410  		}
   411  	}()
   412  	synctest.Run(func() {
   413  		for i := 0; i < 100000; i++ {
   414  			mkfunc("A", i)
   415  		}
   416  	})
   417  }
   418  
   419  func TestWaitGroup(t *testing.T) {
   420  	synctest.Run(func() {
   421  		var wg sync.WaitGroup
   422  		wg.Add(1)
   423  		const delay = 1 * time.Second
   424  		go func() {
   425  			time.Sleep(delay)
   426  			wg.Done()
   427  		}()
   428  		start := time.Now()
   429  		wg.Wait()
   430  		if got := time.Since(start); got != delay {
   431  			t.Fatalf("WaitGroup.Wait() took %v, want %v", got, delay)
   432  		}
   433  	})
   434  }
   435  
   436  func wantPanic(t *testing.T, want string) {
   437  	if e := recover(); e != nil {
   438  		if got := fmt.Sprint(e); got != want {
   439  			t.Errorf("got panic message %q, want %q", got, want)
   440  		}
   441  	} else {
   442  		t.Errorf("got no panic, want one")
   443  	}
   444  }
   445  

View as plain text