| // Copyright 2024 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package synctest_test | |
| import ( | |
| "context" | |
| "fmt" | |
| "internal/synctest" | |
| "internal/testenv" | |
| "iter" | |
| "os" | |
| "reflect" | |
| "runtime" | |
| "slices" | |
| "strconv" | |
| "strings" | |
| "sync" | |
| "sync/atomic" | |
| "testing" | |
| "time" | |
| "weak" | |
| ) | |
| func TestNow(t *testing.T) { | |
| start := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).In(time.Local) | |
| synctest.Run(func() { | |
| // Time starts at 2000-1-1 00:00:00. | |
| if got, want := time.Now(), start; !got.Equal(want) { | |
| t.Errorf("at start: time.Now = %v, want %v", got, want) | |
| } | |
| go func() { | |
| // New goroutines see the same fake clock. | |
| if got, want := time.Now(), start; !got.Equal(want) { | |
| t.Errorf("time.Now = %v, want %v", got, want) | |
| } | |
| }() | |
| // Time advances after a sleep. | |
| time.Sleep(1 * time.Second) | |
| if got, want := time.Now(), start.Add(1*time.Second); !got.Equal(want) { | |
| t.Errorf("after sleep: time.Now = %v, want %v", got, want) | |
| } | |
| }) | |
| } | |
| // TestMonotonicClock exercises comparing times from within a bubble | |
| // with ones from outside the bubble. | |
| func TestMonotonicClock(t *testing.T) { | |
| start := time.Now() | |
| synctest.Run(func() { | |
| time.Sleep(time.Until(start.Round(0))) | |
| if got, want := time.Now().In(time.UTC), start.In(time.UTC); !got.Equal(want) { | |
| t.Fatalf("time.Now() = %v, want %v", got, want) | |
| } | |
| wait := 1 * time.Second | |
| time.Sleep(wait) | |
| if got := time.Since(start); got != wait { | |
| t.Fatalf("time.Since(start) = %v, want %v", got, wait) | |
| } | |
| if got := time.Now().Sub(start); got != wait { | |
| t.Fatalf("time.Now().Sub(start) = %v, want %v", got, wait) | |
| } | |
| }) | |
| } | |
| func TestRunEmpty(t *testing.T) { | |
| synctest.Run(func() { | |
| }) | |
| } | |
| func TestSimpleWait(t *testing.T) { | |
| synctest.Run(func() { | |
| synctest.Wait() | |
| }) | |
| } | |
| func TestGoroutineWait(t *testing.T) { | |
| synctest.Run(func() { | |
| go func() {}() | |
| synctest.Wait() | |
| }) | |
| } | |
| // TestWait starts a collection of goroutines. | |
| // It checks that synctest.Wait waits for all goroutines to exit before returning. | |
| func TestWait(t *testing.T) { | |
| synctest.Run(func() { | |
| done := false | |
| ch := make(chan int) | |
| var f func() | |
| f = func() { | |
| count := <-ch | |
| if count == 0 { | |
| done = true | |
| } else { | |
| go f() | |
| ch <- count - 1 | |
| } | |
| } | |
| go f() | |
| ch <- 100 | |
| synctest.Wait() | |
| if !done { | |
| t.Fatalf("done = false, want true") | |
| } | |
| }) | |
| } | |
| func TestMallocs(t *testing.T) { | |
| for i := 0; i < 100; i++ { | |
| synctest.Run(func() { | |
| done := false | |
| ch := make(chan []byte) | |
| var f func() | |
| f = func() { | |
| b := <-ch | |
| if len(b) == 0 { | |
| done = true | |
| } else { | |
| go f() | |
| ch <- make([]byte, len(b)-1) | |
| } | |
| } | |
| go f() | |
| ch <- make([]byte, 100) | |
| synctest.Wait() | |
| if !done { | |
| t.Fatalf("done = false, want true") | |
| } | |
| }) | |
| } | |
| } | |
| func TestTimerReadBeforeDeadline(t *testing.T) { | |
| synctest.Run(func() { | |
| start := time.Now() | |
| tm := time.NewTimer(5 * time.Second) | |
| <-tm.C | |
| if got, want := time.Since(start), 5*time.Second; got != want { | |
| t.Errorf("after sleep: time.Since(start) = %v, want %v", got, want) | |
| } | |
| }) | |
| } | |
| func TestTimerReadAfterDeadline(t *testing.T) { | |
| synctest.Run(func() { | |
| delay := 1 * time.Second | |
| want := time.Now().Add(delay) | |
| tm := time.NewTimer(delay) | |
| time.Sleep(2 * delay) | |
| got := <-tm.C | |
| if got != want { | |
| t.Errorf("<-tm.C = %v, want %v", got, want) | |
| } | |
| }) | |
| } | |
| func TestTimerReset(t *testing.T) { | |
| synctest.Run(func() { | |
| start := time.Now() | |
| tm := time.NewTimer(1 * time.Second) | |
| if got, want := <-tm.C, start.Add(1*time.Second); got != want { | |
| t.Errorf("first sleep: <-tm.C = %v, want %v", got, want) | |
| } | |
| tm.Reset(2 * time.Second) | |
| if got, want := <-tm.C, start.Add((1+2)*time.Second); got != want { | |
| t.Errorf("second sleep: <-tm.C = %v, want %v", got, want) | |
| } | |
| tm.Reset(3 * time.Second) | |
| time.Sleep(1 * time.Second) | |
| tm.Reset(3 * time.Second) | |
| if got, want := <-tm.C, start.Add((1+2+4)*time.Second); got != want { | |
| t.Errorf("third sleep: <-tm.C = %v, want %v", got, want) | |
| } | |
| }) | |
| } | |
| func TestTimeAfter(t *testing.T) { | |
| synctest.Run(func() { | |
| i := 0 | |
| time.AfterFunc(1*time.Second, func() { | |
| // Ensure synctest group membership propagates through the AfterFunc. | |
| i++ // 1 | |
| go func() { | |
| time.Sleep(1 * time.Second) | |
| i++ // 2 | |
| }() | |
| }) | |
| time.Sleep(3 * time.Second) | |
| synctest.Wait() | |
| if got, want := i, 2; got != want { | |
| t.Errorf("after sleep and wait: i = %v, want %v", got, want) | |
| } | |
| }) | |
| } | |
| func TestTimerAfterBubbleExit(t *testing.T) { | |
| run := false | |
| synctest.Run(func() { | |
| time.AfterFunc(1*time.Second, func() { | |
| run = true | |
| }) | |
| }) | |
| if run { | |
| t.Errorf("timer ran before bubble exit") | |
| } | |
| } | |
| func TestTimerFromOutsideBubble(t *testing.T) { | |
| tm := time.NewTimer(10 * time.Millisecond) | |
| synctest.Run(func() { | |
| <-tm.C | |
| }) | |
| if tm.Stop() { | |
| t.Errorf("synctest.Run unexpectedly returned before timer fired") | |
| } | |
| } | |
| // TestTimerNondeterminism verifies that timers firing at the same instant | |
| // don't always fire in exactly the same order. | |
| func TestTimerNondeterminism(t *testing.T) { | |
| synctest.Run(func() { | |
| const iterations = 1000 | |
| var seen1, seen2 bool | |
| for range iterations { | |
| tm1 := time.NewTimer(1) | |
| tm2 := time.NewTimer(1) | |
| select { | |
| case <-tm1.C: | |
| seen1 = true | |
| case <-tm2.C: | |
| seen2 = true | |
| } | |
| if seen1 && seen2 { | |
| return | |
| } | |
| synctest.Wait() | |
| } | |
| t.Errorf("after %v iterations, seen timer1:%v, timer2:%v; want both", iterations, seen1, seen2) | |
| }) | |
| } | |
| // TestSleepNondeterminism verifies that goroutines sleeping to the same instant | |
| // don't always schedule in exactly the same order. | |
| func TestSleepNondeterminism(t *testing.T) { | |
| synctest.Run(func() { | |
| const iterations = 1000 | |
| var seen1, seen2 bool | |
| for range iterations { | |
| var first atomic.Int32 | |
| go func() { | |
| time.Sleep(1) | |
| first.CompareAndSwap(0, 1) | |
| }() | |
| go func() { | |
| time.Sleep(1) | |
| first.CompareAndSwap(0, 2) | |
| }() | |
| time.Sleep(1) | |
| synctest.Wait() | |
| switch v := first.Load(); v { | |
| case 1: | |
| seen1 = true | |
| case 2: | |
| seen2 = true | |
| default: | |
| t.Fatalf("first = %v, want 1 or 2", v) | |
| } | |
| if seen1 && seen2 { | |
| return | |
| } | |
| synctest.Wait() | |
| } | |
| t.Errorf("after %v iterations, seen goroutine 1:%v, 2:%v; want both", iterations, seen1, seen2) | |
| }) | |
| } | |
| // TestTimerRunsImmediately verifies that a 0-duration timer sends on its channel | |
| // without waiting for the bubble to block. | |
| func TestTimerRunsImmediately(t *testing.T) { | |
| synctest.Run(func() { | |
| start := time.Now() | |
| tm := time.NewTimer(0) | |
| select { | |
| case got := <-tm.C: | |
| if !got.Equal(start) { | |
| t.Errorf("<-tm.C = %v, want %v", got, start) | |
| } | |
| default: | |
| t.Errorf("0-duration timer channel is not readable; want it to be") | |
| } | |
| }) | |
| } | |
| // TestTimerRunsLater verifies that reading from a timer's channel receives the | |
| // timer fired, even when that time is in reading from a timer's channel receives the | |
| // time the timer fired, even when that time is in the past. | |
| func TestTimerRanInPast(t *testing.T) { | |
| synctest.Run(func() { | |
| delay := 1 * time.Second | |
| want := time.Now().Add(delay) | |
| tm := time.NewTimer(delay) | |
| time.Sleep(2 * delay) | |
| select { | |
| case got := <-tm.C: | |
| if !got.Equal(want) { | |
| t.Errorf("<-tm.C = %v, want %v", got, want) | |
| } | |
| default: | |
| t.Errorf("0-duration timer channel is not readable; want it to be") | |
| } | |
| }) | |
| } | |
| // TestAfterFuncRunsImmediately verifies that a 0-duration AfterFunc is scheduled | |
| // without waiting for the bubble to block. | |
| func TestAfterFuncRunsImmediately(t *testing.T) { | |
| synctest.Run(func() { | |
| var b atomic.Bool | |
| time.AfterFunc(0, func() { | |
| b.Store(true) | |
| }) | |
| for !b.Load() { | |
| runtime.Gosched() | |
| } | |
| }) | |
| } | |
| // TestTimerResetZeroDoNotHang verifies that using timer.Reset(0) does not | |
| // cause the test to hang indefinitely. See https://go.dev/issue/76052. | |
| func TestTimerResetZeroDoNotHang(t *testing.T) { | |
| synctest.Run(func() { | |
| timer := time.NewTimer(0) | |
| ctx, cancel := context.WithCancel(context.Background()) | |
| go func() { | |
| for { | |
| select { | |
| case <-ctx.Done(): | |
| return | |
| case <-timer.C: | |
| } | |
| } | |
| }() | |
| synctest.Wait() | |
| timer.Reset(0) | |
| synctest.Wait() | |
| cancel() | |
| synctest.Wait() | |
| }) | |
| } | |
| func TestChannelFromOutsideBubble(t *testing.T) { | |
| choutside := make(chan struct{}) | |
| for _, test := range []struct { | |
| desc string | |
| outside func(ch chan int) | |
| inside func(ch chan int) | |
| }{{ | |
| desc: "read closed", | |
| outside: func(ch chan int) { close(ch) }, | |
| inside: func(ch chan int) { <-ch }, | |
| }, { | |
| desc: "read value", | |
| outside: func(ch chan int) { ch <- 0 }, | |
| inside: func(ch chan int) { <-ch }, | |
| }, { | |
| desc: "write value", | |
| outside: func(ch chan int) { <-ch }, | |
| inside: func(ch chan int) { ch <- 0 }, | |
| }, { | |
| desc: "select outside only", | |
| outside: func(ch chan int) { close(ch) }, | |
| inside: func(ch chan int) { | |
| select { | |
| case <-ch: | |
| case <-choutside: | |
| } | |
| }, | |
| }, { | |
| desc: "select mixed", | |
| outside: func(ch chan int) { close(ch) }, | |
| inside: func(ch chan int) { | |
| ch2 := make(chan struct{}) | |
| select { | |
| case <-ch: | |
| case <-ch2: | |
| } | |
| }, | |
| }} { | |
| t.Run(test.desc, func(t *testing.T) { | |
| ch := make(chan int) | |
| time.AfterFunc(1*time.Millisecond, func() { | |
| test.outside(ch) | |
| }) | |
| synctest.Run(func() { | |
| test.inside(ch) | |
| }) | |
| }) | |
| } | |
| } | |
| func TestChannelMovedOutOfBubble(t *testing.T) { | |
| for _, test := range []struct { | |
| desc string | |
| f func(chan struct{}) | |
| wantFatal string | |
| }{{ | |
| desc: "receive", | |
| f: func(ch chan struct{}) { | |
| <-ch | |
| }, | |
| wantFatal: "receive on synctest channel from outside bubble", | |
| }, { | |
| desc: "send", | |
| f: func(ch chan struct{}) { | |
| ch <- struct{}{} | |
| }, | |
| wantFatal: "send on synctest channel from outside bubble", | |
| }, { | |
| desc: "close", | |
| f: func(ch chan struct{}) { | |
| close(ch) | |
| }, | |
| wantFatal: "close of synctest channel from outside bubble", | |
| }} { | |
| t.Run(test.desc, func(t *testing.T) { | |
| // Bubbled channel accessed from outside any bubble. | |
| t.Run("outside_bubble", func(t *testing.T) { | |
| wantFatal(t, test.wantFatal, func() { | |
| donec := make(chan struct{}) | |
| ch := make(chan chan struct{}) | |
| go func() { | |
| defer close(donec) | |
| test.f(<-ch) | |
| }() | |
| synctest.Run(func() { | |
| ch <- make(chan struct{}) | |
| }) | |
| <-donec | |
| }) | |
| }) | |
| // Bubbled channel accessed from a different bubble. | |
| t.Run("different_bubble", func(t *testing.T) { | |
| wantFatal(t, test.wantFatal, func() { | |
| donec := make(chan struct{}) | |
| ch := make(chan chan struct{}) | |
| go func() { | |
| defer close(donec) | |
| c := <-ch | |
| synctest.Run(func() { | |
| test.f(c) | |
| }) | |
| }() | |
| synctest.Run(func() { | |
| ch <- make(chan struct{}) | |
| }) | |
| <-donec | |
| }) | |
| }) | |
| }) | |
| } | |
| } | |
| func TestTimerFromInsideBubble(t *testing.T) { | |
| for _, test := range []struct { | |
| desc string | |
| f func(tm *time.Timer) | |
| wantFatal string | |
| }{{ | |
| desc: "read channel", | |
| f: func(tm *time.Timer) { | |
| <-tm.C | |
| }, | |
| wantFatal: "receive on synctest channel from outside bubble", | |
| }, { | |
| desc: "Reset", | |
| f: func(tm *time.Timer) { | |
| tm.Reset(1 * time.Second) | |
| }, | |
| wantFatal: "reset of synctest timer from outside bubble", | |
| }, { | |
| desc: "Stop", | |
| f: func(tm *time.Timer) { | |
| tm.Stop() | |
| }, | |
| wantFatal: "stop of synctest timer from outside bubble", | |
| }} { | |
| t.Run(test.desc, func(t *testing.T) { | |
| wantFatal(t, test.wantFatal, func() { | |
| donec := make(chan struct{}) | |
| ch := make(chan *time.Timer) | |
| go func() { | |
| defer close(donec) | |
| test.f(<-ch) | |
| }() | |
| synctest.Run(func() { | |
| tm := time.NewTimer(1 * time.Second) | |
| ch <- tm | |
| }) | |
| <-donec | |
| }) | |
| }) | |
| } | |
| } | |
| func TestDeadlockRoot(t *testing.T) { | |
| defer wantPanic(t, "deadlock: all goroutines in bubble are blocked") | |
| synctest.Run(func() { | |
| select {} | |
| }) | |
| } | |
| func TestDeadlockChild(t *testing.T) { | |
| defer wantPanic(t, "deadlock: main bubble goroutine has exited but blocked goroutines remain") | |
| synctest.Run(func() { | |
| go func() { | |
| select {} | |
| }() | |
| }) | |
| } | |
| func TestDeadlockTicker(t *testing.T) { | |
| defer wantPanic(t, "deadlock: main bubble goroutine has exited but blocked goroutines remain") | |
| synctest.Run(func() { | |
| go func() { | |
| for range time.Tick(1 * time.Second) { | |
| t.Errorf("ticker unexpectedly ran") | |
| return | |
| } | |
| }() | |
| }) | |
| } | |
| func TestCond(t *testing.T) { | |
| synctest.Run(func() { | |
| var mu sync.Mutex | |
| cond := sync.NewCond(&mu) | |
| start := time.Now() | |
| const waitTime = 1 * time.Millisecond | |
| go func() { | |
| // Signal the cond. | |
| time.Sleep(waitTime) | |
| mu.Lock() | |
| cond.Signal() | |
| mu.Unlock() | |
| // Broadcast to the cond. | |
| time.Sleep(waitTime) | |
| mu.Lock() | |
| cond.Broadcast() | |
| mu.Unlock() | |
| }() | |
| // Wait for cond.Signal. | |
| mu.Lock() | |
| cond.Wait() | |
| mu.Unlock() | |
| if got, want := time.Since(start), waitTime; got != want { | |
| t.Errorf("after cond.Signal: time elapsed = %v, want %v", got, want) | |
| } | |
| // Wait for cond.Broadcast in two goroutines. | |
| waiterDone := false | |
| go func() { | |
| mu.Lock() | |
| cond.Wait() | |
| mu.Unlock() | |
| waiterDone = true | |
| }() | |
| mu.Lock() | |
| cond.Wait() | |
| mu.Unlock() | |
| synctest.Wait() | |
| if !waiterDone { | |
| t.Errorf("after cond.Broadcast: waiter not done") | |
| } | |
| if got, want := time.Since(start), 2*waitTime; got != want { | |
| t.Errorf("after cond.Broadcast: time elapsed = %v, want %v", got, want) | |
| } | |
| }) | |
| } | |
| func TestIteratorPush(t *testing.T) { | |
| synctest.Run(func() { | |
| seq := func(yield func(time.Time) bool) { | |
| for yield(time.Now()) { | |
| time.Sleep(1 * time.Second) | |
| } | |
| } | |
| var got []time.Time | |
| go func() { | |
| for now := range seq { | |
| got = append(got, now) | |
| if len(got) >= 3 { | |
| break | |
| } | |
| } | |
| }() | |
| want := []time.Time{ | |
| time.Now(), | |
| time.Now().Add(1 * time.Second), | |
| time.Now().Add(2 * time.Second), | |
| } | |
| time.Sleep(5 * time.Second) | |
| synctest.Wait() | |
| if !slices.Equal(got, want) { | |
| t.Errorf("got: %v; want: %v", got, want) | |
| } | |
| }) | |
| } | |
| func TestIteratorPull(t *testing.T) { | |
| synctest.Run(func() { | |
| seq := func(yield func(time.Time) bool) { | |
| for yield(time.Now()) { | |
| time.Sleep(1 * time.Second) | |
| } | |
| } | |
| var got []time.Time | |
| go func() { | |
| next, stop := iter.Pull(seq) | |
| defer stop() | |
| for len(got) < 3 { | |
| now, _ := next() | |
| got = append(got, now) | |
| } | |
| }() | |
| want := []time.Time{ | |
| time.Now(), | |
| time.Now().Add(1 * time.Second), | |
| time.Now().Add(2 * time.Second), | |
| } | |
| time.Sleep(5 * time.Second) | |
| synctest.Wait() | |
| if !slices.Equal(got, want) { | |
| t.Errorf("got: %v; want: %v", got, want) | |
| } | |
| }) | |
| } | |
| func TestReflectFuncOf(t *testing.T) { | |
| mkfunc := func(name string, i int) { | |
| reflect.FuncOf([]reflect.Type{ | |
| reflect.StructOf([]reflect.StructField{{ | |
| Name: name + strconv.Itoa(i), | |
| Type: reflect.TypeOf(0), | |
| }}), | |
| }, nil, false) | |
| } | |
| go func() { | |
| for i := 0; i < 100000; i++ { | |
| mkfunc("A", i) | |
| } | |
| }() | |
| synctest.Run(func() { | |
| for i := 0; i < 100000; i++ { | |
| mkfunc("A", i) | |
| } | |
| }) | |
| } | |
| func TestWaitGroupInBubble(t *testing.T) { | |
| synctest.Run(func() { | |
| var wg sync.WaitGroup | |
| wg.Add(1) | |
| const delay = 1 * time.Second | |
| go func() { | |
| time.Sleep(delay) | |
| wg.Done() | |
| }() | |
| start := time.Now() | |
| wg.Wait() | |
| if got := time.Since(start); got != delay { | |
| t.Fatalf("WaitGroup.Wait() took %v, want %v", got, delay) | |
| } | |
| }) | |
| } | |
| // https://go.dev/issue/74386 | |
| func TestWaitGroupRacingAdds(t *testing.T) { | |
| synctest.Run(func() { | |
| var wg sync.WaitGroup | |
| for range 100 { | |
| wg.Go(func() {}) | |
| } | |
| wg.Wait() | |
| }) | |
| } | |
| func TestWaitGroupOutOfBubble(t *testing.T) { | |
| var wg sync.WaitGroup | |
| wg.Add(1) | |
| donec := make(chan struct{}) | |
| go synctest.Run(func() { | |
| // Since wg.Add was called outside the bubble, Wait is not durably blocking | |
| // and this waits until wg.Done is called below. | |
| wg.Wait() | |
| close(donec) | |
| }) | |
| select { | |
| case <-donec: | |
| t.Fatalf("synctest.Run finished before WaitGroup.Done called") | |
| case <-time.After(1 * time.Millisecond): | |
| } | |
| wg.Done() | |
| <-donec | |
| } | |
| func TestWaitGroupMovedIntoBubble(t *testing.T) { | |
| wantFatal(t, "fatal error: sync: WaitGroup.Add called from inside and outside synctest bubble", func() { | |
| var wg sync.WaitGroup | |
| wg.Add(1) | |
| synctest.Run(func() { | |
| wg.Add(1) | |
| }) | |
| }) | |
| } | |
| func TestWaitGroupMovedOutOfBubble(t *testing.T) { | |
| wantFatal(t, "fatal error: sync: WaitGroup.Add called from inside and outside synctest bubble", func() { | |
| var wg sync.WaitGroup | |
| synctest.Run(func() { | |
| wg.Add(1) | |
| }) | |
| wg.Add(1) | |
| }) | |
| } | |
| func TestWaitGroupMovedBetweenBubblesWithNonZeroCount(t *testing.T) { | |
| wantFatal(t, "fatal error: sync: WaitGroup.Add called from multiple synctest bubbles", func() { | |
| var wg sync.WaitGroup | |
| synctest.Run(func() { | |
| wg.Add(1) | |
| }) | |
| synctest.Run(func() { | |
| wg.Add(1) | |
| }) | |
| }) | |
| } | |
| func TestWaitGroupDisassociateInWait(t *testing.T) { | |
| var wg sync.WaitGroup | |
| synctest.Run(func() { | |
| wg.Add(1) | |
| wg.Done() | |
| // Count and waiters are 0, so Wait disassociates the WaitGroup. | |
| wg.Wait() | |
| }) | |
| synctest.Run(func() { | |
| // Reusing the WaitGroup is safe, because it is no longer bubbled. | |
| wg.Add(1) | |
| wg.Done() | |
| }) | |
| } | |
| func TestWaitGroupDisassociateInAdd(t *testing.T) { | |
| var wg sync.WaitGroup | |
| synctest.Run(func() { | |
| wg.Add(1) | |
| go wg.Wait() | |
| synctest.Wait() // wait for Wait to block | |
| // Count is 0 and waiters != 0, so Done wakes the waiters and | |
| // disassociates the WaitGroup. | |
| wg.Done() | |
| }) | |
| synctest.Run(func() { | |
| // Reusing the WaitGroup is safe, because it is no longer bubbled. | |
| wg.Add(1) | |
| wg.Done() | |
| }) | |
| } | |
| var testWaitGroupLinkerAllocatedWG sync.WaitGroup | |
| func TestWaitGroupLinkerAllocated(t *testing.T) { | |
| synctest.Run(func() { | |
| // This WaitGroup is probably linker-allocated and has no span, | |
| // so we won't be able to add a special to it associating it with | |
| // this bubble. | |
| // | |
| // Operations on it may not be durably blocking, | |
| // but they shouldn't fail. | |
| testWaitGroupLinkerAllocatedWG.Go(func() {}) | |
| testWaitGroupLinkerAllocatedWG.Wait() | |
| }) | |
| } | |
| var testWaitGroupHeapAllocatedWG = new(sync.WaitGroup) | |
| func TestWaitGroupHeapAllocated(t *testing.T) { | |
| synctest.Run(func() { | |
| // This package-scoped WaitGroup var should have been heap-allocated, | |
| // so we can associate it with a bubble. | |
| testWaitGroupHeapAllocatedWG.Add(1) | |
| go testWaitGroupHeapAllocatedWG.Wait() | |
| synctest.Wait() | |
| testWaitGroupHeapAllocatedWG.Done() | |
| }) | |
| } | |
| // Issue #75134: Many racing bubble associations. | |
| func TestWaitGroupManyBubbles(t *testing.T) { | |
| var wg sync.WaitGroup | |
| for range 100 { | |
| wg.Go(func() { | |
| synctest.Run(func() { | |
| cancelc := make(chan struct{}) | |
| var wg2 sync.WaitGroup | |
| for range 100 { | |
| wg2.Go(func() { | |
| <-cancelc | |
| }) | |
| } | |
| synctest.Wait() | |
| close(cancelc) | |
| wg2.Wait() | |
| }) | |
| }) | |
| } | |
| wg.Wait() | |
| } | |
| func TestHappensBefore(t *testing.T) { | |
| // Use two parallel goroutines accessing different vars to ensure that | |
| // we correctly account for multiple goroutines in the bubble. | |
| var v1 int | |
| var v2 int | |
| synctest.Run(func() { | |
| v1++ // 1 | |
| v2++ // 1 | |
| // Wait returns after these goroutines exit. | |
| go func() { | |
| v1++ // 2 | |
| }() | |
| go func() { | |
| v2++ // 2 | |
| }() | |
| synctest.Wait() | |
| v1++ // 3 | |
| v2++ // 3 | |
| // Wait returns after these goroutines block. | |
| ch1 := make(chan struct{}) | |
| go func() { | |
| v1++ // 4 | |
| <-ch1 | |
| }() | |
| go func() { | |
| v2++ // 4 | |
| <-ch1 | |
| }() | |
| synctest.Wait() | |
| v1++ // 5 | |
| v2++ // 5 | |
| close(ch1) | |
| // Wait returns after these timers run. | |
| time.AfterFunc(0, func() { | |
| v1++ // 6 | |
| }) | |
| time.AfterFunc(0, func() { | |
| v2++ // 6 | |
| }) | |
| synctest.Wait() | |
| v1++ // 7 | |
| v2++ // 7 | |
| // Wait returns after these timer goroutines block. | |
| ch2 := make(chan struct{}) | |
| time.AfterFunc(0, func() { | |
| v1++ // 8 | |
| <-ch2 | |
| }) | |
| time.AfterFunc(0, func() { | |
| v2++ // 8 | |
| <-ch2 | |
| }) | |
| synctest.Wait() | |
| v1++ // 9 | |
| v2++ // 9 | |
| close(ch2) | |
| }) | |
| // This Run happens after the previous Run returns. | |
| synctest.Run(func() { | |
| go func() { | |
| go func() { | |
| v1++ // 10 | |
| }() | |
| }() | |
| go func() { | |
| go func() { | |
| v2++ // 10 | |
| }() | |
| }() | |
| }) | |
| // These tests happen after Run returns. | |
| if got, want := v1, 10; got != want { | |
| t.Errorf("v1 = %v, want %v", got, want) | |
| } | |
| if got, want := v2, 10; got != want { | |
| t.Errorf("v2 = %v, want %v", got, want) | |
| } | |
| } | |
| // https://go.dev/issue/73817 | |
| func TestWeak(t *testing.T) { | |
| synctest.Run(func() { | |
| for range 5 { | |
| runtime.GC() | |
| b := make([]byte, 1024) | |
| weak.Make(&b) | |
| } | |
| }) | |
| } | |
| func wantPanic(t *testing.T, want string) { | |
| if e := recover(); e != nil { | |
| if got := fmt.Sprint(e); got != want { | |
| t.Errorf("got panic message %q, want %q", got, want) | |
| } | |
| } else { | |
| t.Errorf("got no panic, want one") | |
| } | |
| } | |
| func wantFatal(t *testing.T, want string, f func()) { | |
| t.Helper() | |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { | |
| f() | |
| return | |
| } | |
| cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^"+t.Name()+"$") | |
| cmd = testenv.CleanCmdEnv(cmd) | |
| cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1") | |
| out, err := cmd.CombinedOutput() | |
| if err == nil { | |
| t.Errorf("expected test function to panic, but test returned successfully") | |
| } | |
| if !strings.Contains(string(out), want) { | |
| t.Errorf("wanted test output contaiing %q; got %q", want, string(out)) | |
| } | |
| } |