@@ -7,12 +7,130 @@ package singleflight
77import (
88 "errors"
99 "fmt"
10+ "io/ioutil"
11+ "os"
1012 "sync"
1113 "sync/atomic"
1214 "testing"
1315 "time"
1416)
1517
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+
16134func TestDo (t * testing.T ) {
17135 var g Group
18136 v , err , _ := g .Do ("key" , func () (interface {}, error ) {
0 commit comments