github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/lease/fixture_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lease_test
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/loggo"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	corelease "github.com/juju/juju/core/lease"
    17  	coretesting "github.com/juju/juju/testing"
    18  	"github.com/juju/juju/worker/lease"
    19  )
    20  
    21  const (
    22  	defaultMaxSleep = time.Hour
    23  	almostOneSecond = time.Second - time.Nanosecond
    24  )
    25  
    26  var (
    27  	defaultClockStart time.Time
    28  )
    29  
    30  func init() {
    31  	// We pick a time with a comfortable h:m:s component but:
    32  	//  (1) past the int32 unix epoch limit;
    33  	//  (2) at a 5ns offset to make sure we're not discarding precision;
    34  	//  (3) in a weird time zone.
    35  	value := "2073-03-03T01:00:00.000000005-08:40"
    36  	var err error
    37  	defaultClockStart, err = time.Parse(time.RFC3339Nano, value)
    38  	if err != nil {
    39  		panic(err)
    40  	}
    41  }
    42  
    43  // offset returns the result of defaultClockStart.Add(d); it exists to make
    44  // expiry tests easier to write.
    45  func offset(d time.Duration) time.Time {
    46  	return defaultClockStart.Add(d)
    47  }
    48  
    49  // almostSeconds returns a duration shorter than the supplied number of
    50  // seconds by one nanosecond.
    51  func almostSeconds(seconds int) time.Duration {
    52  	if seconds < 1 {
    53  		panic("expected positive time input")
    54  	}
    55  	return (time.Second * time.Duration(seconds)) - time.Nanosecond
    56  }
    57  
    58  // justAfterSeconds returns a duration longer than the supplied number of
    59  // seconds by one nanosecond.
    60  func justAfterSeconds(seconds int) time.Duration {
    61  	if seconds < 1 {
    62  		panic("expected positive time input")
    63  	}
    64  	return (time.Second * time.Duration(seconds)) + time.Nanosecond
    65  }
    66  
    67  // Fixture allows us to test a *lease.Manager with a usefully-mocked
    68  // clock.Clock and corelease.Store.
    69  type Fixture struct {
    70  
    71  	// leases contains the leases the corelease.Store should report when the
    72  	// test starts up.
    73  	leases map[corelease.Key]corelease.Info
    74  
    75  	// expectCalls contains the calls that should be made to the corelease.Store
    76  	// in the course of a test. By specifying a callback you can cause the
    77  	// reported leases to change.
    78  	expectCalls []call
    79  
    80  	// expectDirty should be set for tests that purposefully abuse the manager
    81  	// to the extent that it returns an error on Wait(); tests that don't set
    82  	// this flag will check that the manager's shutdown error is nil.
    83  	expectDirty bool
    84  
    85  	// autoexpire is whether the store should autoexpire.
    86  	autoexpire bool
    87  }
    88  
    89  // RunTest sets up a Manager and a Clock and passes them into the supplied
    90  // test function. The manager will be cleaned up afterwards.
    91  func (fix *Fixture) RunTest(c *gc.C, test func(*lease.Manager, *testclock.Clock)) {
    92  	clock := testclock.NewClock(defaultClockStart)
    93  	store := NewStore(fix.autoexpire, fix.leases, fix.expectCalls)
    94  	manager, err := lease.NewManager(lease.ManagerConfig{
    95  		Clock: clock,
    96  		Store: store,
    97  		Secretary: func(string) (lease.Secretary, error) {
    98  			return Secretary{}, nil
    99  		},
   100  		MaxSleep:             defaultMaxSleep,
   101  		Logger:               loggo.GetLogger("lease_test"),
   102  		PrometheusRegisterer: noopRegisterer{},
   103  	})
   104  	c.Assert(err, jc.ErrorIsNil)
   105  	var wg sync.WaitGroup
   106  	testDone := make(chan struct{})
   107  	storeDone := make(chan struct{})
   108  	wg.Add(1)
   109  	go func() {
   110  		defer wg.Done()
   111  		// Dirty tests will probably have stopped the manager anyway, but no
   112  		// sense leaving them around if things aren't exactly as we expect.
   113  		timeout := time.After(coretesting.LongWait)
   114  		select {
   115  		case <-testDone:
   116  		case <-timeout:
   117  			c.Errorf("test took >10s to complete")
   118  			// If we timed out here, then we need to timeout storeDone as well
   119  			timeout = time.After(0)
   120  		}
   121  		// Wait for the store to be done in the happy path, but
   122  		// don't wait any longer than 10s total.
   123  		select {
   124  		case <-storeDone:
   125  		case <-time.After(coretesting.LongWait):
   126  			c.Errorf("store took >10s to complete")
   127  		}
   128  		manager.Kill()
   129  		err := manager.Wait()
   130  		if !fix.expectDirty {
   131  			c.Check(err, jc.ErrorIsNil)
   132  		}
   133  	}()
   134  	wg.Add(1)
   135  	go func() {
   136  		defer wg.Done()
   137  		store.Wait(c)
   138  		close(storeDone)
   139  	}()
   140  	waitAlarms(c, clock, 1)
   141  	test(manager, clock)
   142  	close(testDone)
   143  	wg.Wait()
   144  }
   145  
   146  func waitAlarms(c *gc.C, clock *testclock.Clock, count int) {
   147  	timeout := time.After(coretesting.LongWait)
   148  	for i := 0; i < count; i++ {
   149  		select {
   150  		case <-clock.Alarms():
   151  		case <-timeout:
   152  			c.Fatalf("timed out waiting for %dth alarm set", i)
   153  		}
   154  	}
   155  }
   156  
   157  type noopRegisterer struct {
   158  	prometheus.Registerer
   159  }
   160  
   161  func (noopRegisterer) Register(prometheus.Collector) error {
   162  	return nil
   163  }
   164  
   165  func (noopRegisterer) Unregister(prometheus.Collector) bool {
   166  	return false
   167  }