init_test.go (4456B)
1 // Copyright 2019 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package lazy
15
16 import (
17 "context"
18 "errors"
19 "math/rand"
20 "strings"
21 "sync"
22 "testing"
23 "time"
24
25 qt "github.com/frankban/quicktest"
26 )
27
28 var (
29 rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
30 bigOrSmall = func() int {
31 if rnd.Intn(10) < 5 {
32 return 10000 + rnd.Intn(100000)
33 }
34 return 1 + rnd.Intn(50)
35 }
36 )
37
38 func doWork() {
39 doWorkOfSize(bigOrSmall())
40 }
41
42 func doWorkOfSize(size int) {
43 _ = strings.Repeat("Hugo Rocks! ", size)
44 }
45
46 func TestInit(t *testing.T) {
47 c := qt.New(t)
48
49 var result string
50
51 f1 := func(name string) func() (any, error) {
52 return func() (any, error) {
53 result += name + "|"
54 doWork()
55 return name, nil
56 }
57 }
58
59 f2 := func() func() (any, error) {
60 return func() (any, error) {
61 doWork()
62 return nil, nil
63 }
64 }
65
66 root := New()
67
68 root.Add(f1("root(1)"))
69 root.Add(f1("root(2)"))
70
71 branch1 := root.Branch(f1("branch_1"))
72 branch1.Add(f1("branch_1_1"))
73 branch1_2 := branch1.Add(f1("branch_1_2"))
74 branch1_2_1 := branch1_2.Add(f1("branch_1_2_1"))
75
76 var wg sync.WaitGroup
77
78 // Add some concurrency and randomness to verify thread safety and
79 // init order.
80 for i := 0; i < 100; i++ {
81 wg.Add(1)
82 go func(i int) {
83 defer wg.Done()
84 var err error
85 if rnd.Intn(10) < 5 {
86 _, err = root.Do()
87 c.Assert(err, qt.IsNil)
88 }
89
90 // Add a new branch on the fly.
91 if rnd.Intn(10) > 5 {
92 branch := branch1_2.Branch(f2())
93 _, err = branch.Do()
94 c.Assert(err, qt.IsNil)
95 } else {
96 _, err = branch1_2_1.Do()
97 c.Assert(err, qt.IsNil)
98 }
99 _, err = branch1_2.Do()
100 c.Assert(err, qt.IsNil)
101 }(i)
102
103 wg.Wait()
104
105 c.Assert(result, qt.Equals, "root(1)|root(2)|branch_1|branch_1_1|branch_1_2|branch_1_2_1|")
106
107 }
108 }
109
110 func TestInitAddWithTimeout(t *testing.T) {
111 c := qt.New(t)
112
113 init := New().AddWithTimeout(100*time.Millisecond, func(ctx context.Context) (any, error) {
114 return nil, nil
115 })
116
117 _, err := init.Do()
118
119 c.Assert(err, qt.IsNil)
120 }
121
122 func TestInitAddWithTimeoutTimeout(t *testing.T) {
123 c := qt.New(t)
124
125 init := New().AddWithTimeout(100*time.Millisecond, func(ctx context.Context) (any, error) {
126 time.Sleep(500 * time.Millisecond)
127 select {
128 case <-ctx.Done():
129 return nil, nil
130 default:
131 }
132 t.Fatal("slept")
133 return nil, nil
134 })
135
136 _, err := init.Do()
137
138 c.Assert(err, qt.Not(qt.IsNil))
139
140 c.Assert(err.Error(), qt.Contains, "timed out")
141
142 time.Sleep(1 * time.Second)
143 }
144
145 func TestInitAddWithTimeoutError(t *testing.T) {
146 c := qt.New(t)
147
148 init := New().AddWithTimeout(100*time.Millisecond, func(ctx context.Context) (any, error) {
149 return nil, errors.New("failed")
150 })
151
152 _, err := init.Do()
153
154 c.Assert(err, qt.Not(qt.IsNil))
155 }
156
157 type T struct {
158 sync.Mutex
159 V1 string
160 V2 string
161 }
162
163 func (t *T) Add1(v string) {
164 t.Lock()
165 t.V1 += v
166 t.Unlock()
167 }
168
169 func (t *T) Add2(v string) {
170 t.Lock()
171 t.V2 += v
172 t.Unlock()
173 }
174
175 // https://github.com/gohugoio/hugo/issues/5901
176 func TestInitBranchOrder(t *testing.T) {
177 c := qt.New(t)
178
179 base := New()
180
181 work := func(size int, f func()) func() (any, error) {
182 return func() (any, error) {
183 doWorkOfSize(size)
184 if f != nil {
185 f()
186 }
187
188 return nil, nil
189 }
190 }
191
192 state := &T{}
193
194 base = base.Add(work(10000, func() {
195 state.Add1("A")
196 }))
197
198 inits := make([]*Init, 2)
199 for i := range inits {
200 inits[i] = base.Branch(work(i+1*100, func() {
201 // V1 is A
202 ab := state.V1 + "B"
203 state.Add2(ab)
204 }))
205 }
206
207 var wg sync.WaitGroup
208
209 for _, v := range inits {
210 v := v
211 wg.Add(1)
212 go func() {
213 defer wg.Done()
214 _, err := v.Do()
215 c.Assert(err, qt.IsNil)
216 }()
217 }
218
219 wg.Wait()
220
221 c.Assert(state.V2, qt.Equals, "ABAB")
222 }
223
224 // See issue 7043
225 func TestResetError(t *testing.T) {
226 c := qt.New(t)
227 r := false
228 i := New().Add(func() (any, error) {
229 if r {
230 return nil, nil
231 }
232 return nil, errors.New("r is false")
233 })
234 _, err := i.Do()
235 c.Assert(err, qt.IsNotNil)
236 i.Reset()
237 r = true
238 _, err = i.Do()
239 c.Assert(err, qt.IsNil)
240
241 }