1
2
3
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
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
34 if got, want := time.Now(), start; !got.Equal(want) {
35 t.Errorf("time.Now = %v, want %v", got, want)
36 }
37 }()
38
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
47
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
86
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
186 i++
187 go func() {
188 time.Sleep(1 * time.Second)
189 i++
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
223
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
247
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
282
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
299
300
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
319
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
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
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
523 time.Sleep(waitTime)
524 mu.Lock()
525 cond.Signal()
526 mu.Unlock()
527
528
529 time.Sleep(waitTime)
530 mu.Lock()
531 cond.Broadcast()
532 mu.Unlock()
533 }()
534
535
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
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
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
677
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
728 wg.Wait()
729 })
730 synctest.Run(func() {
731
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()
743
744
745 wg.Done()
746 })
747 synctest.Run(func() {
748
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
759
760
761
762
763
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
774
775 testWaitGroupHeapAllocatedWG.Add(1)
776 go testWaitGroupHeapAllocatedWG.Wait()
777 synctest.Wait()
778 testWaitGroupHeapAllocatedWG.Done()
779 })
780 }
781
782 func TestHappensBefore(t *testing.T) {
783
784
785 var v1 int
786 var v2 int
787 synctest.Run(func() {
788 v1++
789 v2++
790
791
792 go func() {
793 v1++
794 }()
795 go func() {
796 v2++
797 }()
798 synctest.Wait()
799
800 v1++
801 v2++
802
803
804 ch1 := make(chan struct{})
805 go func() {
806 v1++
807 <-ch1
808 }()
809 go func() {
810 v2++
811 <-ch1
812 }()
813 synctest.Wait()
814
815 v1++
816 v2++
817 close(ch1)
818
819
820 time.AfterFunc(0, func() {
821 v1++
822 })
823 time.AfterFunc(0, func() {
824 v2++
825 })
826 synctest.Wait()
827
828 v1++
829 v2++
830
831
832 ch2 := make(chan struct{})
833 time.AfterFunc(0, func() {
834 v1++
835 <-ch2
836 })
837 time.AfterFunc(0, func() {
838 v2++
839 <-ch2
840 })
841 synctest.Wait()
842
843 v1++
844 v2++
845 close(ch2)
846 })
847
848 synctest.Run(func() {
849 go func() {
850 go func() {
851 v1++
852 }()
853 }()
854 go func() {
855 go func() {
856 v2++
857 }()
858 }()
859 })
860
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
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