github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/lazy/init_test.go (about) 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(context.Context) (any, error) { 52 return func(context.Context) (any, error) { 53 result += name + "|" 54 doWork() 55 return name, nil 56 } 57 } 58 59 f2 := func() func(context.Context) (any, error) { 60 return func(context.Context) (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 ctx := context.Background() 79 80 // Add some concurrency and randomness to verify thread safety and 81 // init order. 82 for i := 0; i < 100; i++ { 83 wg.Add(1) 84 go func(i int) { 85 defer wg.Done() 86 var err error 87 if rnd.Intn(10) < 5 { 88 _, err = root.Do(ctx) 89 c.Assert(err, qt.IsNil) 90 } 91 92 // Add a new branch on the fly. 93 if rnd.Intn(10) > 5 { 94 branch := branch1_2.Branch(f2()) 95 _, err = branch.Do(ctx) 96 c.Assert(err, qt.IsNil) 97 } else { 98 _, err = branch1_2_1.Do(ctx) 99 c.Assert(err, qt.IsNil) 100 } 101 _, err = branch1_2.Do(ctx) 102 c.Assert(err, qt.IsNil) 103 }(i) 104 105 wg.Wait() 106 107 c.Assert(result, qt.Equals, "root(1)|root(2)|branch_1|branch_1_1|branch_1_2|branch_1_2_1|") 108 109 } 110 } 111 112 func TestInitAddWithTimeout(t *testing.T) { 113 c := qt.New(t) 114 115 init := New().AddWithTimeout(100*time.Millisecond, func(ctx context.Context) (any, error) { 116 return nil, nil 117 }) 118 119 _, err := init.Do(context.Background()) 120 121 c.Assert(err, qt.IsNil) 122 } 123 124 func TestInitAddWithTimeoutTimeout(t *testing.T) { 125 c := qt.New(t) 126 127 init := New().AddWithTimeout(100*time.Millisecond, func(ctx context.Context) (any, error) { 128 time.Sleep(500 * time.Millisecond) 129 return nil, nil 130 }) 131 132 _, err := init.Do(context.Background()) 133 134 c.Assert(err, qt.Not(qt.IsNil)) 135 136 c.Assert(err.Error(), qt.Contains, "timed out") 137 138 time.Sleep(1 * time.Second) 139 } 140 141 func TestInitAddWithTimeoutError(t *testing.T) { 142 c := qt.New(t) 143 144 init := New().AddWithTimeout(100*time.Millisecond, func(ctx context.Context) (any, error) { 145 return nil, errors.New("failed") 146 }) 147 148 _, err := init.Do(context.Background()) 149 150 c.Assert(err, qt.Not(qt.IsNil)) 151 } 152 153 type T struct { 154 sync.Mutex 155 V1 string 156 V2 string 157 } 158 159 func (t *T) Add1(v string) { 160 t.Lock() 161 t.V1 += v 162 t.Unlock() 163 } 164 165 func (t *T) Add2(v string) { 166 t.Lock() 167 t.V2 += v 168 t.Unlock() 169 } 170 171 // https://github.com/gohugoio/hugo/issues/5901 172 func TestInitBranchOrder(t *testing.T) { 173 c := qt.New(t) 174 175 base := New() 176 177 work := func(size int, f func()) func(context.Context) (any, error) { 178 return func(context.Context) (any, error) { 179 doWorkOfSize(size) 180 if f != nil { 181 f() 182 } 183 184 return nil, nil 185 } 186 } 187 188 state := &T{} 189 190 base = base.Add(work(10000, func() { 191 state.Add1("A") 192 })) 193 194 inits := make([]*Init, 2) 195 for i := range inits { 196 inits[i] = base.Branch(work(i+1*100, func() { 197 // V1 is A 198 ab := state.V1 + "B" 199 state.Add2(ab) 200 })) 201 } 202 203 var wg sync.WaitGroup 204 ctx := context.Background() 205 206 for _, v := range inits { 207 v := v 208 wg.Add(1) 209 go func() { 210 defer wg.Done() 211 _, err := v.Do(ctx) 212 c.Assert(err, qt.IsNil) 213 }() 214 } 215 216 wg.Wait() 217 218 c.Assert(state.V2, qt.Equals, "ABAB") 219 } 220 221 // See issue 7043 222 func TestResetError(t *testing.T) { 223 c := qt.New(t) 224 r := false 225 i := New().Add(func(context.Context) (any, error) { 226 if r { 227 return nil, nil 228 } 229 return nil, errors.New("r is false") 230 }) 231 _, err := i.Do(context.Background()) 232 c.Assert(err, qt.IsNotNil) 233 i.Reset() 234 r = true 235 _, err = i.Do(context.Background()) 236 c.Assert(err, qt.IsNil) 237 238 }