blob: c2310375f7d732e8e05f271e067691f84eab5bbb [file] [log] [blame]
Dan Willemsen38f2dba2016-07-08 14:54:35 -07001// Copyright 2013 The Go Authors. All rights reserved.
Dan Willemsen09eb3b12015-09-16 14:34:17 -07002// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package singleflight
6
7import (
8 "errors"
9 "fmt"
10 "sync"
11 "sync/atomic"
12 "testing"
13 "time"
14)
15
16func TestDo(t *testing.T) {
17 var g Group
Dan Willemsenbc60c3c2021-12-15 01:09:00 -080018 v, err, _ := g.Do("key", func() (any, error) {
Dan Willemsen09eb3b12015-09-16 14:34:17 -070019 return "bar", nil
20 })
21 if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
22 t.Errorf("Do = %v; want %v", got, want)
23 }
24 if err != nil {
25 t.Errorf("Do error = %v", err)
26 }
27}
28
29func TestDoErr(t *testing.T) {
30 var g Group
Patrice Arruda748609c2020-06-25 12:12:21 -070031 someErr := errors.New("some error")
Dan Willemsenbc60c3c2021-12-15 01:09:00 -080032 v, err, _ := g.Do("key", func() (any, error) {
Dan Willemsen09eb3b12015-09-16 14:34:17 -070033 return nil, someErr
34 })
35 if err != someErr {
36 t.Errorf("Do error = %v; want someErr %v", err, someErr)
37 }
38 if v != nil {
39 t.Errorf("unexpected non-nil value %#v", v)
40 }
41}
42
43func TestDoDupSuppress(t *testing.T) {
44 var g Group
45 var wg1, wg2 sync.WaitGroup
46 c := make(chan string, 1)
47 var calls int32
Dan Willemsenbc60c3c2021-12-15 01:09:00 -080048 fn := func() (any, error) {
Dan Willemsen09eb3b12015-09-16 14:34:17 -070049 if atomic.AddInt32(&calls, 1) == 1 {
50 // First invocation.
51 wg1.Done()
52 }
53 v := <-c
54 c <- v // pump; make available for any future calls
55
56 time.Sleep(10 * time.Millisecond) // let more goroutines enter Do
57
58 return v, nil
59 }
60
61 const n = 10
62 wg1.Add(1)
63 for i := 0; i < n; i++ {
64 wg1.Add(1)
65 wg2.Add(1)
66 go func() {
67 defer wg2.Done()
68 wg1.Done()
69 v, err, _ := g.Do("key", fn)
70 if err != nil {
71 t.Errorf("Do error: %v", err)
72 return
73 }
74 if s, _ := v.(string); s != "bar" {
75 t.Errorf("Do = %T %v; want %q", v, v, "bar")
76 }
77 }()
78 }
79 wg1.Wait()
80 // At least one goroutine is in fn now and all of them have at
81 // least reached the line before the Do.
82 c <- "bar"
83 wg2.Wait()
84 if got := atomic.LoadInt32(&calls); got <= 0 || got >= n {
85 t.Errorf("number of calls = %d; want over 0 and less than %d", got, n)
86 }
87}