@@ -7,12 +7,130 @@ package singleflight
7
7
import (
8
8
"errors"
9
9
"fmt"
10
+ "io/ioutil"
11
+ "os"
10
12
"sync"
11
13
"sync/atomic"
12
14
"testing"
13
15
"time"
14
16
)
15
17
18
+ func testConcurrentHelper (t * testing.T , inGoroutine func (routineIndex , goroutineCount int )) {
19
+ var wg , wgGoroutines sync.WaitGroup
20
+ const callers = 4
21
+ //ref := make([]RefCounter, callers)
22
+ wgGoroutines .Add (callers )
23
+ for i := 0 ; i < callers ; i ++ {
24
+ wg .Add (1 )
25
+ go func (index int ) {
26
+ defer wg .Done ()
27
+
28
+ wgGoroutines .Done ()
29
+ wgGoroutines .Wait () // ensure that all goroutines started and reached this point
30
+
31
+ inGoroutine (i , callers )
32
+ }(i )
33
+ }
34
+ wg .Wait ()
35
+
36
+ }
37
+
38
+ func TestUse (t * testing.T ) {
39
+ var g Group
40
+ var newCount , handleCount , disposeCount int64
41
+
42
+ testConcurrentHelper (
43
+ t ,
44
+ func (index , goroutineCount int ) {
45
+ g .Use (
46
+ "key" ,
47
+ // 'new' is a slow function that creates a temp resource
48
+ func () (interface {}, error ) {
49
+ time .Sleep (200 * time .Millisecond ) // let more goroutines enter Do
50
+ atomic .AddInt64 (& newCount , 1 )
51
+ return "bar" , nil
52
+ },
53
+ // 'fn' to be called by each goroutine
54
+ func (s interface {}, e error ) error {
55
+ // handle s
56
+ if newCount != 1 {
57
+ t .Errorf ("goroutine %v: newCount(%v) expected to be set prior to this function getting called" , index , newCount )
58
+ }
59
+ atomic .AddInt64 (& handleCount , 1 )
60
+ if disposeCount > 0 {
61
+ t .Errorf ("goroutine %v: disposeCount(%v) should not be incremented until all fn are completed" , index , disposeCount )
62
+ }
63
+ return e
64
+ },
65
+ // 'dispose' - to be called once at the end
66
+ func (s interface {}) {
67
+ // cleaning up "bar"
68
+ atomic .AddInt64 (& disposeCount , 1 )
69
+ if handleCount != int64 (goroutineCount ) {
70
+ t .Errorf ("dispose is expected to be called when all %v fn been completed, but %v have been completed instead" , goroutineCount , handleCount )
71
+ }
72
+ },
73
+ )
74
+ },
75
+ )
76
+
77
+ if newCount != 1 {
78
+ t .Errorf ("new expected to be called exactly once, was called %v" , newCount )
79
+ }
80
+ if disposeCount != 1 {
81
+ t .Errorf ("dispose expected to be called exactly once, was called %v" , disposeCount )
82
+ }
83
+ }
84
+
85
+ func TestUseWithResource (t * testing.T ) {
86
+ // use this "global" var for checkes after that testConcurrentHelper call
87
+ var tempFileName string
88
+
89
+ var g Group
90
+ testConcurrentHelper (
91
+ t ,
92
+ func (_ , _ int ) {
93
+ g .Use (
94
+ "key" ,
95
+ // 'new' is a slow function that creates a temp resource
96
+ func () (interface {}, error ) {
97
+ time .Sleep (200 * time .Millisecond ) // let more goroutines enter Do
98
+ f , e := ioutil .TempFile ("" , "pat" )
99
+ if e != nil {
100
+ return nil , e
101
+ }
102
+ defer f .Close ()
103
+ tempFileName = f .Name ()
104
+
105
+ // fill temp file with sequence of n.Write(...) calls
106
+
107
+ return f .Name (), e
108
+ },
109
+ // 'fn' to be called by each goroutine
110
+ func (s interface {}, e error ) error {
111
+ // handle s
112
+ if e != nil {
113
+ // send alternative payload
114
+ }
115
+ if e == nil {
116
+ /*tempFileName*/ _ = s .(string )
117
+ // send Content of tempFileName to HTTPWriter
118
+ }
119
+ return e
120
+ },
121
+ // 'dispose' - to be called once at the end
122
+ func (s interface {}) {
123
+ // cleaning up "bar"
124
+ os .Remove (s .(string ))
125
+ },
126
+ )
127
+ },
128
+ )
129
+ if _ , e := os .Stat (tempFileName ); ! os .IsNotExist (e ) {
130
+ t .Errorf ("test has created a temp file '%v', but failed to cleaned it" , tempFileName )
131
+ }
132
+ }
133
+
16
134
func TestDo (t * testing.T ) {
17
135
var g Group
18
136
v , err , _ := g .Do ("key" , func () (interface {}, error ) {
0 commit comments