github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/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() (interface{}, error) {
    52  		return func() (interface{}, error) {
    53  			result += name + "|"
    54  			doWork()
    55  			return name, nil
    56  		}
    57  	}
    58  
    59  	f2 := func() func() (interface{}, error) {
    60  		return func() (interface{}, 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) (interface{}, 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) (interface{}, 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) (interface{}, 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() (interface{}, error) {
   182  		return func() (interface{}, 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  }