1 // Copyright 2013 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.
22 func TestDo(t *testing.T) {
24 v, err, _ := g.Do("key", func() (interface{}, error) {
27 if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
28 t.Errorf("Do = %v; want %v", got, want)
31 t.Errorf("Do error = %v", err)
35 func TestDoErr(t *testing.T) {
37 someErr := errors.New("Some error")
38 v, err, _ := g.Do("key", func() (interface{}, error) {
42 t.Errorf("Do error = %v; want someErr %v", err, someErr)
45 t.Errorf("unexpected non-nil value %#v", v)
49 func TestDoDupSuppress(t *testing.T) {
51 var wg1, wg2 sync.WaitGroup
52 c := make(chan string, 1)
54 fn := func() (interface{}, error) {
55 if atomic.AddInt32(&calls, 1) == 1 {
60 c <- v // pump; make available for any future calls
62 time.Sleep(10 * time.Millisecond) // let more goroutines enter Do
69 for i := 0; i < n; i++ {
75 v, err, _ := g.Do("key", fn)
77 t.Errorf("Do error: %v", err)
80 if s, _ := v.(string); s != "bar" {
81 t.Errorf("Do = %T %v; want %q", v, v, "bar")
86 // At least one goroutine is in fn now and all of them have at
87 // least reached the line before the Do.
90 if got := atomic.LoadInt32(&calls); got <= 0 || got >= n {
91 t.Errorf("number of calls = %d; want over 0 and less than %d", got, n)
95 // Test that singleflight behaves correctly after Forget called.
96 // See https://github.com/golang/go/issues/31420
97 func TestForget(t *testing.T) {
101 firstStarted = make(chan struct{})
102 unblockFirst = make(chan struct{})
103 firstFinished = make(chan struct{})
107 g.Do("key", func() (i interface{}, e error) {
117 unblockSecond := make(chan struct{})
118 secondResult := g.DoChan("key", func() (i interface{}, e error) {
126 thirdResult := g.DoChan("key", func() (i interface{}, e error) {
134 t.Errorf("We should receive result produced by second call, expected: 2, got %d", r.Val)
138 func TestDoChan(t *testing.T) {
140 ch := g.DoChan("key", func() (interface{}, error) {
147 if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
148 t.Errorf("Do = %v; want %v", got, want)
151 t.Errorf("Do error = %v", err)
155 // Test singleflight behaves correctly after Do panic.
156 // See https://github.com/golang/go/issues/41133
157 func TestPanicDo(t *testing.T) {
159 fn := func() (interface{}, error) {
160 panic("invalid memory address or nil pointer dereference")
165 panicCount := int32(0)
166 done := make(chan struct{})
167 for i := 0; i < n; i++ {
170 if err := recover(); err != nil {
171 t.Logf("Got panic: %v\n%s", err, debug.Stack())
172 atomic.AddInt32(&panicCount, 1)
175 if atomic.AddInt32(&waited, -1) == 0 {
187 t.Errorf("Expect %d panic, but got %d", n, panicCount)
189 case <-time.After(time.Second):
194 func TestGoexitDo(t *testing.T) {
196 fn := func() (interface{}, error) {
203 done := make(chan struct{})
204 for i := 0; i < n; i++ {
209 t.Errorf("Error should be nil, but got: %v", err)
211 if atomic.AddInt32(&waited, -1) == 0 {
215 _, err, _ = g.Do("key", fn)
221 case <-time.After(time.Second):
226 func TestPanicDoChan(t *testing.T) {
227 if runtime.GOOS == "js" {
228 t.Skipf("js does not support exec")
231 if os.Getenv("TEST_PANIC_DOCHAN") != "" {
237 ch := g.DoChan("", func() (interface{}, error) {
238 panic("Panicking in DoChan")
241 t.Fatalf("DoChan unexpectedly returned")
246 cmd := exec.Command(os.Args[0], "-test.run="+t.Name(), "-test.v")
247 cmd.Env = append(os.Environ(), "TEST_PANIC_DOCHAN=1")
248 out := new(bytes.Buffer)
251 if err := cmd.Start(); err != nil {
256 t.Logf("%s:\n%s", strings.Join(cmd.Args, " "), out)
258 t.Errorf("Test subprocess passed; want a crash due to panic in DoChan")
260 if bytes.Contains(out.Bytes(), []byte("DoChan unexpectedly")) {
261 t.Errorf("Test subprocess failed with an unexpected failure mode.")
263 if !bytes.Contains(out.Bytes(), []byte("Panicking in DoChan")) {
264 t.Errorf("Test subprocess failed, but the crash isn't caused by panicking in DoChan")
268 func TestPanicDoSharedByDoChan(t *testing.T) {
269 if runtime.GOOS == "js" {
270 t.Skipf("js does not support exec")
273 if os.Getenv("TEST_PANIC_DOCHAN") != "" {
274 blocked := make(chan struct{})
275 unblock := make(chan struct{})
282 g.Do("", func() (interface{}, error) {
285 panic("Panicking in Do")
290 ch := g.DoChan("", func() (interface{}, error) {
291 panic("DoChan unexpectedly executed callback")
295 t.Fatalf("DoChan unexpectedly returned")
300 cmd := exec.Command(os.Args[0], "-test.run="+t.Name(), "-test.v")
301 cmd.Env = append(os.Environ(), "TEST_PANIC_DOCHAN=1")
302 out := new(bytes.Buffer)
305 if err := cmd.Start(); err != nil {
310 t.Logf("%s:\n%s", strings.Join(cmd.Args, " "), out)
312 t.Errorf("Test subprocess passed; want a crash due to panic in Do shared by DoChan")
314 if bytes.Contains(out.Bytes(), []byte("DoChan unexpectedly")) {
315 t.Errorf("Test subprocess failed with an unexpected failure mode.")
317 if !bytes.Contains(out.Bytes(), []byte("Panicking in Do")) {
318 t.Errorf("Test subprocess failed, but the crash isn't caused by panicking in Do")