1
2
3
4
5 package synctest_test
6
7 import (
8 "context"
9 "fmt"
10 "internal/synctest"
11 "internal/testenv"
12 "iter"
13 "os"
14 "reflect"
15 "runtime"
16 "slices"
17 "strconv"
18 "strings"
19 "sync"
20 "sync/atomic"
21 "testing"
22 "time"
23 "weak"
24 )
25
26 func TestNow(t *testing.T) {
27 start := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).In(time.Local)
28 synctest.Run(func() {
29
30 if got, want := time.Now(), start; !got.Equal(want) {
31 t.Errorf("at start: time.Now = %v, want %v", got, want)
32 }
33 go func() {
34
35 if got, want := time.Now(), start; !got.Equal(want) {
36 t.Errorf("time.Now = %v, want %v", got, want)
37 }
38 }()
39
40 time.Sleep(1 * time.Second)
41 if got, want := time.Now(), start.Add(1*time.Second); !got.Equal(want) {
42 t.Errorf("after sleep: time.Now = %v, want %v", got, want)
43 }
44 })
45 }
46
47
48
49 func TestMonotonicClock(t *testing.T) {
50 start := time.Now()
51 synctest.Run(func() {
52 time.Sleep(time.Until(start.Round(0)))
53 if got, want := time.Now().In(time.UTC), start.In(time.UTC); !got.Equal(want) {
54 t.Fatalf("time.Now() = %v, want %v", got, want)
55 }
56
57 wait := 1 * time.Second
58 time.Sleep(wait)
59 if got := time.Since(start); got != wait {
60 t.Fatalf("time.Since(start) = %v, want %v", got, wait)
61 }
62 if got := time.Now().Sub(start); got != wait {
63 t.Fatalf("time.Now().Sub(start) = %v, want %v", got, wait)
64 }
65 })
66 }
67
68 func TestRunEmpty(t *testing.T) {
69 synctest.Run(func() {
70 })
71 }
72
73 func TestSimpleWait(t *testing.T) {
74 synctest.Run(func() {
75 synctest.Wait()
76 })
77 }
78
79 func TestGoroutineWait(t *testing.T) {
80 synctest.Run(func() {
81 go func() {}()
82 synctest.Wait()
83 })
84 }
85
86
87
88 func TestWait(t *testing.T) {
89 synctest.Run(func() {
90 done := false
91 ch := make(chan int)
92 var f func()
93 f = func() {
94 count := <-ch
95 if count == 0 {
96 done = true
97 } else {
98 go f()
99 ch <- count - 1
100 }
101 }
102 go f()
103 ch <- 100
104 synctest.Wait()
105 if !done {
106 t.Fatalf("done = false, want true")
107 }
108 })
109 }
110
111 func TestMallocs(t *testing.T) {
112 for i := 0; i < 100; i++ {
113 synctest.Run(func() {
114 done := false
115 ch := make(chan []byte)
116 var f func()
117 f = func() {
118 b := <-ch
119 if len(b) == 0 {
120 done = true
121 } else {
122 go f()
123 ch <- make([]byte, len(b)-1)
124 }
125 }
126 go f()
127 ch <- make([]byte, 100)
128 synctest.Wait()
129 if !done {
130 t.Fatalf("done = false, want true")
131 }
132 })
133 }
134 }
135
136 func TestTimerReadBeforeDeadline(t *testing.T) {
137 synctest.Run(func() {
138 start := time.Now()
139 tm := time.NewTimer(5 * time.Second)
140 <-tm.C
141 if got, want := time.Since(start), 5*time.Second; got != want {
142 t.Errorf("after sleep: time.Since(start) = %v, want %v", got, want)
143 }
144 })
145 }
146
147 func TestTimerReadAfterDeadline(t *testing.T) {
148 synctest.Run(func() {
149 delay := 1 * time.Second
150 want := time.Now().Add(delay)
151 tm := time.NewTimer(delay)
152 time.Sleep(2 * delay)
153 got := <-tm.C
154 if got != want {
155 t.Errorf("<-tm.C = %v, want %v", got, want)
156 }
157 })
158 }
159
160 func TestTimerReset(t *testing.T) {
161 synctest.Run(func() {
162 start := time.Now()
163 tm := time.NewTimer(1 * time.Second)
164 if got, want := <-tm.C, start.Add(1*time.Second); got != want {
165 t.Errorf("first sleep: <-tm.C = %v, want %v", got, want)
166 }
167
168 tm.Reset(2 * time.Second)
169 if got, want := <-tm.C, start.Add((1+2)*time.Second); got != want {
170 t.Errorf("second sleep: <-tm.C = %v, want %v", got, want)
171 }
172
173 tm.Reset(3 * time.Second)
174 time.Sleep(1 * time.Second)
175 tm.Reset(3 * time.Second)
176 if got, want := <-tm.C, start.Add((1+2+4)*time.Second); got != want {
177 t.Errorf("third sleep: <-tm.C = %v, want %v", got, want)
178 }
179 })
180 }
181
182 func TestTimeAfter(t *testing.T) {
183 synctest.Run(func() {
184 i := 0
185 time.AfterFunc(1*time.Second, func() {
186
187 i++
188 go func() {
189 time.Sleep(1 * time.Second)
190 i++
191 }()
192 })
193 time.Sleep(3 * time.Second)
194 synctest.Wait()
195 if got, want := i, 2; got != want {
196 t.Errorf("after sleep and wait: i = %v, want %v", got, want)
197 }
198 })
199 }
200
201 func TestTimerAfterBubbleExit(t *testing.T) {
202 run := false
203 synctest.Run(func() {
204 time.AfterFunc(1*time.Second, func() {
205 run = true
206 })
207 })
208 if run {
209 t.Errorf("timer ran before bubble exit")
210 }
211 }
212
213 func TestTimerFromOutsideBubble(t *testing.T) {
214 tm := time.NewTimer(10 * time.Millisecond)
215 synctest.Run(func() {
216 <-tm.C
217 })
218 if tm.Stop() {
219 t.Errorf("synctest.Run unexpectedly returned before timer fired")
220 }
221 }
222
223
224
225 func TestTimerNondeterminism(t *testing.T) {
226 synctest.Run(func() {
227 const iterations = 1000
228 var seen1, seen2 bool
229 for range iterations {
230 tm1 := time.NewTimer(1)
231 tm2 := time.NewTimer(1)
232 select {
233 case <-tm1.C:
234 seen1 = true
235 case <-tm2.C:
236 seen2 = true
237 }
238 if seen1 && seen2 {
239 return
240 }
241 synctest.Wait()
242 }
243 t.Errorf("after %v iterations, seen timer1:%v, timer2:%v; want both", iterations, seen1, seen2)
244 })
245 }
246
247
248
249 func TestSleepNondeterminism(t *testing.T) {
250 synctest.Run(func() {
251 const iterations = 1000
252 var seen1, seen2 bool
253 for range iterations {
254 var first atomic.Int32
255 go func() {
256 time.Sleep(1)
257 first.CompareAndSwap(0, 1)
258 }()
259 go func() {
260 time.Sleep(1)
261 first.CompareAndSwap(0, 2)
262 }()
263 time.Sleep(1)
264 synctest.Wait()
265 switch v := first.Load(); v {
266 case 1:
267 seen1 = true
268 case 2:
269 seen2 = true
270 default:
271 t.Fatalf("first = %v, want 1 or 2", v)
272 }
273 if seen1 && seen2 {
274 return
275 }
276 synctest.Wait()
277 }
278 t.Errorf("after %v iterations, seen goroutine 1:%v, 2:%v; want both", iterations, seen1, seen2)
279 })
280 }
281
282
283
284 func TestTimerRunsImmediately(t *testing.T) {
285 synctest.Run(func() {
286 start := time.Now()
287 tm := time.NewTimer(0)
288 select {
289 case got := <-tm.C:
290 if !got.Equal(start) {
291 t.Errorf("<-tm.C = %v, want %v", got, start)
292 }
293 default:
294 t.Errorf("0-duration timer channel is not readable; want it to be")
295 }
296 })
297 }
298
299
300
301
302 func TestTimerRanInPast(t *testing.T) {
303 synctest.Run(func() {
304 delay := 1 * time.Second
305 want := time.Now().Add(delay)
306 tm := time.NewTimer(delay)
307 time.Sleep(2 * delay)
308 select {
309 case got := <-tm.C:
310 if !got.Equal(want) {
311 t.Errorf("<-tm.C = %v, want %v", got, want)
312 }
313 default:
314 t.Errorf("0-duration timer channel is not readable; want it to be")
315 }
316 })
317 }
318
319
320
321 func TestAfterFuncRunsImmediately(t *testing.T) {
322 synctest.Run(func() {
323 var b atomic.Bool
324 time.AfterFunc(0, func() {
325 b.Store(true)
326 })
327 for !b.Load() {
328 runtime.Gosched()
329 }
330 })
331 }
332
333
334
335 func TestTimerResetZeroDoNotHang(t *testing.T) {
336 synctest.Run(func() {
337 timer := time.NewTimer(0)
338 ctx, cancel := context.WithCancel(context.Background())
339
340 go func() {
341 for {
342 select {
343 case <-ctx.Done():
344 return
345 case <-timer.C:
346 }
347 }
348 }()
349
350 synctest.Wait()
351 timer.Reset(0)
352 synctest.Wait()
353 cancel()
354 synctest.Wait()
355 })
356 }
357
358 func TestChannelFromOutsideBubble(t *testing.T) {
359 choutside := make(chan struct{})
360 for _, test := range []struct {
361 desc string
362 outside func(ch chan int)
363 inside func(ch chan int)
364 }{{
365 desc: "read closed",
366 outside: func(ch chan int) { close(ch) },
367 inside: func(ch chan int) { <-ch },
368 }, {
369 desc: "read value",
370 outside: func(ch chan int) { ch <- 0 },
371 inside: func(ch chan int) { <-ch },
372 }, {
373 desc: "write value",
374 outside: func(ch chan int) { <-ch },
375 inside: func(ch chan int) { ch <- 0 },
376 }, {
377 desc: "select outside only",
378 outside: func(ch chan int) { close(ch) },
379 inside: func(ch chan int) {
380 select {
381 case <-ch:
382 case <-choutside:
383 }
384 },
385 }, {
386 desc: "select mixed",
387 outside: func(ch chan int) { close(ch) },
388 inside: func(ch chan int) {
389 ch2 := make(chan struct{})
390 select {
391 case <-ch:
392 case <-ch2:
393 }
394 },
395 }} {
396 t.Run(test.desc, func(t *testing.T) {
397 ch := make(chan int)
398 time.AfterFunc(1*time.Millisecond, func() {
399 test.outside(ch)
400 })
401 synctest.Run(func() {
402 test.inside(ch)
403 })
404 })
405 }
406 }
407
408 func TestChannelMovedOutOfBubble(t *testing.T) {
409 for _, test := range []struct {
410 desc string
411 f func(chan struct{})
412 wantFatal string
413 }{{
414 desc: "receive",
415 f: func(ch chan struct{}) {
416 <-ch
417 },
418 wantFatal: "receive on synctest channel from outside bubble",
419 }, {
420 desc: "send",
421 f: func(ch chan struct{}) {
422 ch <- struct{}{}
423 },
424 wantFatal: "send on synctest channel from outside bubble",
425 }, {
426 desc: "close",
427 f: func(ch chan struct{}) {
428 close(ch)
429 },
430 wantFatal: "close of synctest channel from outside bubble",
431 }} {
432 t.Run(test.desc, func(t *testing.T) {
433
434 t.Run("outside_bubble", func(t *testing.T) {
435 wantFatal(t, test.wantFatal, func() {
436 donec := make(chan struct{})
437 ch := make(chan chan struct{})
438 go func() {
439 defer close(donec)
440 test.f(<-ch)
441 }()
442 synctest.Run(func() {
443 ch <- make(chan struct{})
444 })
445 <-donec
446 })
447 })
448
449 t.Run("different_bubble", func(t *testing.T) {
450 wantFatal(t, test.wantFatal, func() {
451 donec := make(chan struct{})
452 ch := make(chan chan struct{})
453 go func() {
454 defer close(donec)
455 c := <-ch
456 synctest.Run(func() {
457 test.f(c)
458 })
459 }()
460 synctest.Run(func() {
461 ch <- make(chan struct{})
462 })
463 <-donec
464 })
465 })
466 })
467 }
468 }
469
470 func TestTimerFromInsideBubble(t *testing.T) {
471 for _, test := range []struct {
472 desc string
473 f func(tm *time.Timer)
474 wantFatal string
475 }{{
476 desc: "read channel",
477 f: func(tm *time.Timer) {
478 <-tm.C
479 },
480 wantFatal: "receive on synctest channel from outside bubble",
481 }, {
482 desc: "Reset",
483 f: func(tm *time.Timer) {
484 tm.Reset(1 * time.Second)
485 },
486 wantFatal: "reset of synctest timer from outside bubble",
487 }, {
488 desc: "Stop",
489 f: func(tm *time.Timer) {
490 tm.Stop()
491 },
492 wantFatal: "stop of synctest timer from outside bubble",
493 }} {
494 t.Run(test.desc, func(t *testing.T) {
495 wantFatal(t, test.wantFatal, func() {
496 donec := make(chan struct{})
497 ch := make(chan *time.Timer)
498 go func() {
499 defer close(donec)
500 test.f(<-ch)
501 }()
502 synctest.Run(func() {
503 tm := time.NewTimer(1 * time.Second)
504 ch <- tm
505 })
506 <-donec
507 })
508 })
509 }
510 }
511
512 func TestDeadlockRoot(t *testing.T) {
513 defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
514 synctest.Run(func() {
515 select {}
516 })
517 }
518
519 func TestDeadlockChild(t *testing.T) {
520 defer wantPanic(t, "deadlock: main bubble goroutine has exited but blocked goroutines remain")
521 synctest.Run(func() {
522 go func() {
523 select {}
524 }()
525 })
526 }
527
528 func TestDeadlockTicker(t *testing.T) {
529 defer wantPanic(t, "deadlock: main bubble goroutine has exited but blocked goroutines remain")
530 synctest.Run(func() {
531 go func() {
532 for range time.Tick(1 * time.Second) {
533 t.Errorf("ticker unexpectedly ran")
534 return
535 }
536 }()
537 })
538 }
539
540 func TestCond(t *testing.T) {
541 synctest.Run(func() {
542 var mu sync.Mutex
543 cond := sync.NewCond(&mu)
544 start := time.Now()
545 const waitTime = 1 * time.Millisecond
546
547 go func() {
548
549 time.Sleep(waitTime)
550 mu.Lock()
551 cond.Signal()
552 mu.Unlock()
553
554
555 time.Sleep(waitTime)
556 mu.Lock()
557 cond.Broadcast()
558 mu.Unlock()
559 }()
560
561
562 mu.Lock()
563 cond.Wait()
564 mu.Unlock()
565 if got, want := time.Since(start), waitTime; got != want {
566 t.Errorf("after cond.Signal: time elapsed = %v, want %v", got, want)
567 }
568
569
570 waiterDone := false
571 go func() {
572 mu.Lock()
573 cond.Wait()
574 mu.Unlock()
575 waiterDone = true
576 }()
577 mu.Lock()
578 cond.Wait()
579 mu.Unlock()
580 synctest.Wait()
581 if !waiterDone {
582 t.Errorf("after cond.Broadcast: waiter not done")
583 }
584 if got, want := time.Since(start), 2*waitTime; got != want {
585 t.Errorf("after cond.Broadcast: time elapsed = %v, want %v", got, want)
586 }
587 })
588 }
589
590 func TestIteratorPush(t *testing.T) {
591 synctest.Run(func() {
592 seq := func(yield func(time.Time) bool) {
593 for yield(time.Now()) {
594 time.Sleep(1 * time.Second)
595 }
596 }
597 var got []time.Time
598 go func() {
599 for now := range seq {
600 got = append(got, now)
601 if len(got) >= 3 {
602 break
603 }
604 }
605 }()
606 want := []time.Time{
607 time.Now(),
608 time.Now().Add(1 * time.Second),
609 time.Now().Add(2 * time.Second),
610 }
611 time.Sleep(5 * time.Second)
612 synctest.Wait()
613 if !slices.Equal(got, want) {
614 t.Errorf("got: %v; want: %v", got, want)
615 }
616 })
617 }
618
619 func TestIteratorPull(t *testing.T) {
620 synctest.Run(func() {
621 seq := func(yield func(time.Time) bool) {
622 for yield(time.Now()) {
623 time.Sleep(1 * time.Second)
624 }
625 }
626 var got []time.Time
627 go func() {
628 next, stop := iter.Pull(seq)
629 defer stop()
630 for len(got) < 3 {
631 now, _ := next()
632 got = append(got, now)
633 }
634 }()
635 want := []time.Time{
636 time.Now(),
637 time.Now().Add(1 * time.Second),
638 time.Now().Add(2 * time.Second),
639 }
640 time.Sleep(5 * time.Second)
641 synctest.Wait()
642 if !slices.Equal(got, want) {
643 t.Errorf("got: %v; want: %v", got, want)
644 }
645 })
646 }
647
648 func TestReflectFuncOf(t *testing.T) {
649 mkfunc := func(name string, i int) {
650 reflect.FuncOf([]reflect.Type{
651 reflect.StructOf([]reflect.StructField{{
652 Name: name + strconv.Itoa(i),
653 Type: reflect.TypeOf(0),
654 }}),
655 }, nil, false)
656 }
657 go func() {
658 for i := 0; i < 100000; i++ {
659 mkfunc("A", i)
660 }
661 }()
662 synctest.Run(func() {
663 for i := 0; i < 100000; i++ {
664 mkfunc("A", i)
665 }
666 })
667 }
668
669 func TestWaitGroupInBubble(t *testing.T) {
670 synctest.Run(func() {
671 var wg sync.WaitGroup
672 wg.Add(1)
673 const delay = 1 * time.Second
674 go func() {
675 time.Sleep(delay)
676 wg.Done()
677 }()
678 start := time.Now()
679 wg.Wait()
680 if got := time.Since(start); got != delay {
681 t.Fatalf("WaitGroup.Wait() took %v, want %v", got, delay)
682 }
683 })
684 }
685
686
687 func TestWaitGroupRacingAdds(t *testing.T) {
688 synctest.Run(func() {
689 var wg sync.WaitGroup
690 for range 100 {
691 wg.Go(func() {})
692 }
693 wg.Wait()
694 })
695 }
696
697 func TestWaitGroupOutOfBubble(t *testing.T) {
698 var wg sync.WaitGroup
699 wg.Add(1)
700 donec := make(chan struct{})
701 go synctest.Run(func() {
702
703
704 wg.Wait()
705 close(donec)
706 })
707 select {
708 case <-donec:
709 t.Fatalf("synctest.Run finished before WaitGroup.Done called")
710 case <-time.After(1 * time.Millisecond):
711 }
712 wg.Done()
713 <-donec
714 }
715
716 func TestWaitGroupMovedIntoBubble(t *testing.T) {
717 wantFatal(t, "fatal error: sync: WaitGroup.Add called from inside and outside synctest bubble", func() {
718 var wg sync.WaitGroup
719 wg.Add(1)
720 synctest.Run(func() {
721 wg.Add(1)
722 })
723 })
724 }
725
726 func TestWaitGroupMovedOutOfBubble(t *testing.T) {
727 wantFatal(t, "fatal error: sync: WaitGroup.Add called from inside and outside synctest bubble", func() {
728 var wg sync.WaitGroup
729 synctest.Run(func() {
730 wg.Add(1)
731 })
732 wg.Add(1)
733 })
734 }
735
736 func TestWaitGroupMovedBetweenBubblesWithNonZeroCount(t *testing.T) {
737 wantFatal(t, "fatal error: sync: WaitGroup.Add called from multiple synctest bubbles", func() {
738 var wg sync.WaitGroup
739 synctest.Run(func() {
740 wg.Add(1)
741 })
742 synctest.Run(func() {
743 wg.Add(1)
744 })
745 })
746 }
747
748 func TestWaitGroupDisassociateInWait(t *testing.T) {
749 var wg sync.WaitGroup
750 synctest.Run(func() {
751 wg.Add(1)
752 wg.Done()
753
754 wg.Wait()
755 })
756 synctest.Run(func() {
757
758 wg.Add(1)
759 wg.Done()
760 })
761 }
762
763 func TestWaitGroupDisassociateInAdd(t *testing.T) {
764 var wg sync.WaitGroup
765 synctest.Run(func() {
766 wg.Add(1)
767 go wg.Wait()
768 synctest.Wait()
769
770
771 wg.Done()
772 })
773 synctest.Run(func() {
774
775 wg.Add(1)
776 wg.Done()
777 })
778 }
779
780 var testWaitGroupLinkerAllocatedWG sync.WaitGroup
781
782 func TestWaitGroupLinkerAllocated(t *testing.T) {
783 synctest.Run(func() {
784
785
786
787
788
789
790 testWaitGroupLinkerAllocatedWG.Go(func() {})
791 testWaitGroupLinkerAllocatedWG.Wait()
792 })
793 }
794
795 var testWaitGroupHeapAllocatedWG = new(sync.WaitGroup)
796
797 func TestWaitGroupHeapAllocated(t *testing.T) {
798 synctest.Run(func() {
799
800
801 testWaitGroupHeapAllocatedWG.Add(1)
802 go testWaitGroupHeapAllocatedWG.Wait()
803 synctest.Wait()
804 testWaitGroupHeapAllocatedWG.Done()
805 })
806 }
807
808
809 func TestWaitGroupManyBubbles(t *testing.T) {
810 var wg sync.WaitGroup
811 for range 100 {
812 wg.Go(func() {
813 synctest.Run(func() {
814 cancelc := make(chan struct{})
815 var wg2 sync.WaitGroup
816 for range 100 {
817 wg2.Go(func() {
818 <-cancelc
819 })
820 }
821 synctest.Wait()
822 close(cancelc)
823 wg2.Wait()
824 })
825 })
826 }
827 wg.Wait()
828 }
829
830 func TestHappensBefore(t *testing.T) {
831
832
833 var v1 int
834 var v2 int
835 synctest.Run(func() {
836 v1++
837 v2++
838
839
840 go func() {
841 v1++
842 }()
843 go func() {
844 v2++
845 }()
846 synctest.Wait()
847
848 v1++
849 v2++
850
851
852 ch1 := make(chan struct{})
853 go func() {
854 v1++
855 <-ch1
856 }()
857 go func() {
858 v2++
859 <-ch1
860 }()
861 synctest.Wait()
862
863 v1++
864 v2++
865 close(ch1)
866
867
868 time.AfterFunc(0, func() {
869 v1++
870 })
871 time.AfterFunc(0, func() {
872 v2++
873 })
874 synctest.Wait()
875
876 v1++
877 v2++
878
879
880 ch2 := make(chan struct{})
881 time.AfterFunc(0, func() {
882 v1++
883 <-ch2
884 })
885 time.AfterFunc(0, func() {
886 v2++
887 <-ch2
888 })
889 synctest.Wait()
890
891 v1++
892 v2++
893 close(ch2)
894 })
895
896 synctest.Run(func() {
897 go func() {
898 go func() {
899 v1++
900 }()
901 }()
902 go func() {
903 go func() {
904 v2++
905 }()
906 }()
907 })
908
909 if got, want := v1, 10; got != want {
910 t.Errorf("v1 = %v, want %v", got, want)
911 }
912 if got, want := v2, 10; got != want {
913 t.Errorf("v2 = %v, want %v", got, want)
914 }
915 }
916
917
918 func TestWeak(t *testing.T) {
919 synctest.Run(func() {
920 for range 5 {
921 runtime.GC()
922 b := make([]byte, 1024)
923 weak.Make(&b)
924 }
925 })
926 }
927
928 func wantPanic(t *testing.T, want string) {
929 if e := recover(); e != nil {
930 if got := fmt.Sprint(e); got != want {
931 t.Errorf("got panic message %q, want %q", got, want)
932 }
933 } else {
934 t.Errorf("got no panic, want one")
935 }
936 }
937
938 func wantFatal(t *testing.T, want string, f func()) {
939 t.Helper()
940
941 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
942 f()
943 return
944 }
945
946 cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^"+t.Name()+"$")
947 cmd = testenv.CleanCmdEnv(cmd)
948 cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
949 out, err := cmd.CombinedOutput()
950 if err == nil {
951 t.Errorf("expected test function to panic, but test returned successfully")
952 }
953 if !strings.Contains(string(out), want) {
954 t.Errorf("wanted test output contaiing %q; got %q", want, string(out))
955 }
956 }
957
View as plain text