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 TestTimer(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 TestTimeAfter(t *testing.T) {
   120  	synctest.Run(func() {
   121  		i := 0
   122  		time.AfterFunc(1*time.Second, func() {
   123  			// Ensure synctest group membership propagates through the AfterFunc.
   124  			i++ // 1
   125  			go func() {
   126  				time.Sleep(1 * time.Second)
   127  				i++ // 2
   128  			}()
   129  		})
   130  		time.Sleep(3 * time.Second)
   131  		synctest.Wait()
   132  		if got, want := i, 2; got != want {
   133  			t.Errorf("after sleep and wait: i = %v, want %v", got, want)
   134  		}
   135  	})
   136  }
   137  
   138  func TestTimerFromOutsideBubble(t *testing.T) {
   139  	tm := time.NewTimer(10 * time.Millisecond)
   140  	synctest.Run(func() {
   141  		defer wantPanic(t, "timer moved between synctest groups")
   142  		<-tm.C
   143  	})
   144  }
   145  
   146  func TestChannelFromOutsideBubble(t *testing.T) {
   147  	choutside := make(chan struct{})
   148  	for _, test := range []struct {
   149  		desc    string
   150  		outside func(ch chan int)
   151  		inside  func(ch chan int)
   152  	}{{
   153  		desc:    "read closed",
   154  		outside: func(ch chan int) { close(ch) },
   155  		inside:  func(ch chan int) { <-ch },
   156  	}, {
   157  		desc:    "read value",
   158  		outside: func(ch chan int) { ch <- 0 },
   159  		inside:  func(ch chan int) { <-ch },
   160  	}, {
   161  		desc:    "write value",
   162  		outside: func(ch chan int) { <-ch },
   163  		inside:  func(ch chan int) { ch <- 0 },
   164  	}, {
   165  		desc:    "select outside only",
   166  		outside: func(ch chan int) { close(ch) },
   167  		inside: func(ch chan int) {
   168  			select {
   169  			case <-ch:
   170  			case <-choutside:
   171  			}
   172  		},
   173  	}, {
   174  		desc:    "select mixed",
   175  		outside: func(ch chan int) { close(ch) },
   176  		inside: func(ch chan int) {
   177  			ch2 := make(chan struct{})
   178  			select {
   179  			case <-ch:
   180  			case <-ch2:
   181  			}
   182  		},
   183  	}} {
   184  		t.Run(test.desc, func(t *testing.T) {
   185  			ch := make(chan int)
   186  			time.AfterFunc(1*time.Millisecond, func() {
   187  				test.outside(ch)
   188  			})
   189  			synctest.Run(func() {
   190  				test.inside(ch)
   191  			})
   192  		})
   193  	}
   194  }
   195  
   196  func TestTimerFromInsideBubble(t *testing.T) {
   197  	for _, test := range []struct {
   198  		desc      string
   199  		f         func(tm *time.Timer)
   200  		wantPanic string
   201  	}{{
   202  		desc: "read channel",
   203  		f: func(tm *time.Timer) {
   204  			<-tm.C
   205  		},
   206  		wantPanic: "receive on synctest channel from outside bubble",
   207  	}, {
   208  		desc: "Reset",
   209  		f: func(tm *time.Timer) {
   210  			tm.Reset(1 * time.Second)
   211  		},
   212  		wantPanic: "reset of synctest timer from outside bubble",
   213  	}, {
   214  		desc: "Stop",
   215  		f: func(tm *time.Timer) {
   216  			tm.Stop()
   217  		},
   218  		wantPanic: "stop of synctest timer from outside bubble",
   219  	}} {
   220  		t.Run(test.desc, func(t *testing.T) {
   221  			donec := make(chan struct{})
   222  			ch := make(chan *time.Timer)
   223  			go func() {
   224  				defer close(donec)
   225  				defer wantPanic(t, test.wantPanic)
   226  				test.f(<-ch)
   227  			}()
   228  			synctest.Run(func() {
   229  				tm := time.NewTimer(1 * time.Second)
   230  				ch <- tm
   231  			})
   232  			<-donec
   233  		})
   234  	}
   235  }
   236  
   237  func TestDeadlockRoot(t *testing.T) {
   238  	defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
   239  	synctest.Run(func() {
   240  		select {}
   241  	})
   242  }
   243  
   244  func TestDeadlockChild(t *testing.T) {
   245  	defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
   246  	synctest.Run(func() {
   247  		go func() {
   248  			select {}
   249  		}()
   250  	})
   251  }
   252  
   253  func TestCond(t *testing.T) {
   254  	synctest.Run(func() {
   255  		var mu sync.Mutex
   256  		cond := sync.NewCond(&mu)
   257  		start := time.Now()
   258  		const waitTime = 1 * time.Millisecond
   259  
   260  		go func() {
   261  			// Signal the cond.
   262  			time.Sleep(waitTime)
   263  			mu.Lock()
   264  			cond.Signal()
   265  			mu.Unlock()
   266  
   267  			// Broadcast to the cond.
   268  			time.Sleep(waitTime)
   269  			mu.Lock()
   270  			cond.Broadcast()
   271  			mu.Unlock()
   272  		}()
   273  
   274  		// Wait for cond.Signal.
   275  		mu.Lock()
   276  		cond.Wait()
   277  		mu.Unlock()
   278  		if got, want := time.Since(start), waitTime; got != want {
   279  			t.Errorf("after cond.Signal: time elapsed = %v, want %v", got, want)
   280  		}
   281  
   282  		// Wait for cond.Broadcast in two goroutines.
   283  		waiterDone := false
   284  		go func() {
   285  			mu.Lock()
   286  			cond.Wait()
   287  			mu.Unlock()
   288  			waiterDone = true
   289  		}()
   290  		mu.Lock()
   291  		cond.Wait()
   292  		mu.Unlock()
   293  		synctest.Wait()
   294  		if !waiterDone {
   295  			t.Errorf("after cond.Broadcast: waiter not done")
   296  		}
   297  		if got, want := time.Since(start), 2*waitTime; got != want {
   298  			t.Errorf("after cond.Broadcast: time elapsed = %v, want %v", got, want)
   299  		}
   300  	})
   301  }
   302  
   303  func TestIteratorPush(t *testing.T) {
   304  	synctest.Run(func() {
   305  		seq := func(yield func(time.Time) bool) {
   306  			for yield(time.Now()) {
   307  				time.Sleep(1 * time.Second)
   308  			}
   309  		}
   310  		var got []time.Time
   311  		go func() {
   312  			for now := range seq {
   313  				got = append(got, now)
   314  				if len(got) >= 3 {
   315  					break
   316  				}
   317  			}
   318  		}()
   319  		want := []time.Time{
   320  			time.Now(),
   321  			time.Now().Add(1 * time.Second),
   322  			time.Now().Add(2 * time.Second),
   323  		}
   324  		time.Sleep(5 * time.Second)
   325  		synctest.Wait()
   326  		if !slices.Equal(got, want) {
   327  			t.Errorf("got: %v; want: %v", got, want)
   328  		}
   329  	})
   330  }
   331  
   332  func TestIteratorPull(t *testing.T) {
   333  	synctest.Run(func() {
   334  		seq := func(yield func(time.Time) bool) {
   335  			for yield(time.Now()) {
   336  				time.Sleep(1 * time.Second)
   337  			}
   338  		}
   339  		var got []time.Time
   340  		go func() {
   341  			next, stop := iter.Pull(seq)
   342  			defer stop()
   343  			for len(got) < 3 {
   344  				now, _ := next()
   345  				got = append(got, now)
   346  			}
   347  		}()
   348  		want := []time.Time{
   349  			time.Now(),
   350  			time.Now().Add(1 * time.Second),
   351  			time.Now().Add(2 * time.Second),
   352  		}
   353  		time.Sleep(5 * time.Second)
   354  		synctest.Wait()
   355  		if !slices.Equal(got, want) {
   356  			t.Errorf("got: %v; want: %v", got, want)
   357  		}
   358  	})
   359  }
   360  
   361  func TestReflectFuncOf(t *testing.T) {
   362  	mkfunc := func(name string, i int) {
   363  		reflect.FuncOf([]reflect.Type{
   364  			reflect.StructOf([]reflect.StructField{{
   365  				Name: name + strconv.Itoa(i),
   366  				Type: reflect.TypeOf(0),
   367  			}}),
   368  		}, nil, false)
   369  	}
   370  	go func() {
   371  		for i := 0; i < 100000; i++ {
   372  			mkfunc("A", i)
   373  		}
   374  	}()
   375  	synctest.Run(func() {
   376  		for i := 0; i < 100000; i++ {
   377  			mkfunc("A", i)
   378  		}
   379  	})
   380  }
   381  
   382  func TestWaitGroup(t *testing.T) {
   383  	synctest.Run(func() {
   384  		var wg sync.WaitGroup
   385  		wg.Add(1)
   386  		const delay = 1 * time.Second
   387  		go func() {
   388  			time.Sleep(delay)
   389  			wg.Done()
   390  		}()
   391  		start := time.Now()
   392  		wg.Wait()
   393  		if got := time.Since(start); got != delay {
   394  			t.Fatalf("WaitGroup.Wait() took %v, want %v", got, delay)
   395  		}
   396  	})
   397  }
   398  
   399  func wantPanic(t *testing.T, want string) {
   400  	if e := recover(); e != nil {
   401  		if got := fmt.Sprint(e); got != want {
   402  			t.Errorf("got panic message %q, want %q", got, want)
   403  		}
   404  	} else {
   405  		t.Errorf("got no panic, want one")
   406  	}
   407  }
   408  

View as plain text