github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/presence/whitebox_pingbatcher_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package presence
     5  
     6  import (
     7  	"math/rand"
     8  	"time"
     9  
    10  	jc "github.com/juju/testing/checkers"
    11  	gc "gopkg.in/check.v1"
    12  
    13  	"github.com/juju/juju/testing"
    14  )
    15  
    16  // WhiteboxPingBatcherSuite tests pieces of PingBatcher that need direct inspection but don't need database access.
    17  type WhiteboxPingBatcherSuite struct {
    18  	testing.BaseSuite
    19  }
    20  
    21  var _ = gc.Suite(&WhiteboxPingBatcherSuite{})
    22  
    23  func checkSleepRange(c *gc.C, interval, minTime, maxTime time.Duration) {
    24  	pingBatcher := NewPingBatcher(nil, interval)
    25  	defer pingBatcher.Stop()
    26  	var lastTime time.Duration
    27  	var measuredMinTime time.Duration
    28  	var measuredMaxTime time.Duration
    29  
    30  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
    31  	for i := 0; i < 1000; i++ {
    32  		next := pingBatcher.nextSleep(r)
    33  		// We use Assert rather than Check because we don't want 100s of failures
    34  		c.Assert(next, jc.GreaterThan, minTime)
    35  		c.Assert(next, jc.LessThan, maxTime)
    36  		if lastTime == 0 {
    37  			measuredMinTime = next
    38  			measuredMaxTime = next
    39  		} else {
    40  			// We are using a range in 100s of milliseconds at a
    41  			// resolution of nanoseconds. The chance of getting the
    42  			// same random value 2x in a row is sufficiently low that
    43  			// we can just assert the value is changing.
    44  			// (Chance of collision is roughly 1 in 100 Million)
    45  			c.Assert(next, gc.Not(gc.Equals), lastTime)
    46  			if next < measuredMinTime {
    47  				measuredMinTime = next
    48  			}
    49  			if next > measuredMaxTime {
    50  				measuredMaxTime = next
    51  			}
    52  		}
    53  		lastTime = next
    54  	}
    55  	// Check that we're actually using the full range that was requested.
    56  	// Assert that after 1000 tries we've used a good portion of the range
    57  	// If we sampled perfectly, then we would have fully sampled the range,
    58  	// spread very 1/1000 of the range.
    59  	// If we set the required range to 1/100, then a given sample would fail 99%
    60  	// of the time, 1000 samples would fail 0.99^1000=4e-5 or ~1-in-20,000 times.
    61  	// (actual measurements showed 18 in 20,000, probably due to double ended range vs single ended)
    62  	// However, at 1/10 its 0.9^1000=1.7e-46, or 10^41 times less likely to fail.
    63  	// In 100,000 runs, a range of 1/10 never failed
    64  	expectedCloseness := (maxTime - minTime) / 10
    65  	c.Check(measuredMinTime, jc.LessThan, minTime+expectedCloseness)
    66  	c.Check(measuredMaxTime, jc.GreaterThan, maxTime-expectedCloseness)
    67  }
    68  
    69  func (s *WhiteboxPingBatcherSuite) TestNextSleep(c *gc.C) {
    70  	// nextSleep should select a random range of time to sleep before the
    71  	// next flush will be called, however it should be within a valid range
    72  	// of times
    73  	// range is [800ms, 1200ms] inclusive, but we only can easily assert exclusive
    74  	checkSleepRange(c, 1*time.Second, 799*time.Millisecond, 1201*time.Millisecond)
    75  	checkSleepRange(c, 2*time.Second, 1599*time.Millisecond, 2401*time.Millisecond)
    76  }
    77  
    78  func (s *WhiteboxPingBatcherSuite) TestSyncWaitsForFlush(c *gc.C) {
    79  	// We can do this without a database, because we don't actually Ping so
    80  	// we don't write to the database
    81  	// Don't let a flush happen based on time
    82  	pb := NewPingBatcher(nil, time.Hour)
    83  	pb.syncDelay = time.Hour
    84  	done := make(chan struct{})
    85  	go func() {
    86  		c.Check(pb.Sync(), jc.ErrorIsNil)
    87  		close(done)
    88  	}()
    89  	select {
    90  	case <-done:
    91  		c.Fatalf("done was closed before flush was called")
    92  	case <-time.After(testing.ShortWait):
    93  	}
    94  	// Now when we flush, we should be closed
    95  	pb.flush()
    96  	select {
    97  	case <-done:
    98  	case <-time.After(testing.LongWait):
    99  		c.Fatalf("done was not closed after flush")
   100  	}
   101  }
   102  
   103  func (s *WhiteboxPingBatcherSuite) TestFlushWakesUpAllSync(c *gc.C) {
   104  	// Don't let a flush happen based on time
   105  	pb := NewPingBatcher(nil, time.Hour)
   106  	pb.syncDelay = time.Hour
   107  	const count = 10
   108  	done := make(chan struct{}, count)
   109  	for i := 0; i < count; i++ {
   110  		go func() {
   111  			c.Check(pb.Sync(), jc.ErrorIsNil)
   112  			done <- struct{}{}
   113  		}()
   114  	}
   115  	select {
   116  	case <-done:
   117  		c.Fatalf("some routines finished before flush")
   118  	case <-time.After(testing.ShortWait):
   119  	}
   120  	// Now when we flush, all should have responded
   121  	pb.flush()
   122  	timeout := time.After(testing.LongWait)
   123  	for i := 0; i < count; i++ {
   124  		select {
   125  		case <-done:
   126  		case <-timeout:
   127  			c.Fatalf("not all callers were done after flush")
   128  		}
   129  	}
   130  }
   131  
   132  func (s *WhiteboxPingBatcherSuite) TestSyncReturnsOnShutdown(c *gc.C) {
   133  	// Don't let a flush happen based on time
   134  	pb := NewPingBatcher(nil, time.Hour)
   135  	pb.syncDelay = time.Hour
   136  	done := make(chan struct{})
   137  	go func() {
   138  		pb.Sync()
   139  		close(done)
   140  	}()
   141  	select {
   142  	case <-done:
   143  		c.Fatalf("done was closed before PingBatcher was stopped")
   144  	case <-time.After(testing.ShortWait):
   145  	}
   146  	pb.Kill()
   147  	timeout := time.After(testing.LongWait)
   148  	select {
   149  	case <-done:
   150  	case <-timeout:
   151  		c.Fatalf("not all callers were done after flush")
   152  	}
   153  	err := pb.Sync()
   154  	c.Assert(err, gc.ErrorMatches, "PingBatcher is stopped")
   155  }
   156  
   157  func (s *WhiteboxPingBatcherSuite) TestContinualSyncDoesntPreventFlush(c *gc.C) {
   158  	pb := NewPingBatcher(nil, time.Hour)
   159  	pb.syncDelay = 100 * time.Millisecond
   160  	// the first routine to call Sync gets the channel we synchronize on
   161  	done := make(chan struct{})
   162  	start := time.Now()
   163  	go func() {
   164  		c.Check(pb.Sync(), jc.ErrorIsNil)
   165  		close(done)
   166  	}()
   167  	finished := false
   168  	select {
   169  	case <-done:
   170  		c.Fatalf("we shouldn't be done already")
   171  	case <-time.After(time.Millisecond):
   172  	}
   173  	for i := 0; i < 1000; i++ {
   174  		select {
   175  		case <-done:
   176  			finished = true
   177  		case <-time.After(time.Millisecond):
   178  			// start another Sync, it should block, but only until
   179  			// the first request causes it to go off.
   180  			go pb.Sync()
   181  		}
   182  		if finished {
   183  			break
   184  		}
   185  	}
   186  	c.Logf("done was finally triggered after %v", time.Since(start))
   187  	c.Check(finished, jc.IsTrue)
   188  }