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 }