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  }