github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/presence/util_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package presence_test
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils/clock"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/juju/names.v2"
    16  
    17  	"github.com/juju/juju/apiserver/presence"
    18  	"github.com/juju/juju/worker"
    19  	"github.com/juju/juju/worker/workertest"
    20  )
    21  
    22  var (
    23  	fiveSeconds       = 5 * time.Second
    24  	almostFiveSeconds = fiveSeconds - time.Nanosecond
    25  )
    26  
    27  // Context exposes useful functionality to fixture tests.
    28  type Context interface {
    29  
    30  	// WaitPinger() returns the first pinger started by the SUT that
    31  	// has not already been returned from this method.
    32  	WaitPinger() worker.Worker
    33  
    34  	// WaitAlarms() returns once the SUT has set (but not
    35  	// necessarily responded to) N alarms (e.g. calls to
    36  	// clock.After).
    37  	WaitAlarms(int)
    38  
    39  	// AdvanceClock() advances the SUT's clock by the duration. If
    40  	// you're testing alarms, be sure that you've waited for the
    41  	// relevant alarm to be set before you advance the clock.
    42  	AdvanceClock(time.Duration)
    43  }
    44  
    45  // FixtureTest is called with a Context and a running Worker.
    46  type FixtureTest func(Context, *presence.Worker)
    47  
    48  func NewFixture(errors ...error) *Fixture {
    49  	return &Fixture{errors}
    50  }
    51  
    52  // Fixture makes it easy to manipulate a running worker's environment
    53  // and test its behaviour in response.
    54  type Fixture struct {
    55  	errors []error
    56  }
    57  
    58  // Run runs test against a fresh Stub, which is returned to the client
    59  // for further analysis.
    60  func (fix *Fixture) Run(c *gc.C, test FixtureTest) *testing.Stub {
    61  	stub := &testing.Stub{}
    62  	stub.SetErrors(fix.errors...)
    63  	run(c, stub, test)
    64  	return stub
    65  }
    66  
    67  func run(c *gc.C, stub *testing.Stub, test FixtureTest) {
    68  	context := &context{
    69  		c:       c,
    70  		stub:    stub,
    71  		clock:   testing.NewClock(time.Now()),
    72  		timeout: time.After(time.Second),
    73  		starts:  make(chan worker.Worker, 1000),
    74  	}
    75  	defer context.checkCleanedUp()
    76  
    77  	worker, err := presence.New(presence.Config{
    78  		Identity:   names.NewMachineTag("1"),
    79  		Start:      context.startPinger,
    80  		Clock:      context.clock,
    81  		RetryDelay: fiveSeconds,
    82  	})
    83  	c.Assert(err, jc.ErrorIsNil)
    84  	defer workertest.CleanKill(c, worker)
    85  
    86  	test(context, worker)
    87  }
    88  
    89  // context implements Context.
    90  type context struct {
    91  	c       *gc.C
    92  	stub    *testing.Stub
    93  	clock   *testing.Clock
    94  	timeout <-chan time.Time
    95  
    96  	starts  chan worker.Worker
    97  	mu      sync.Mutex
    98  	current worker.Worker
    99  }
   100  
   101  // WaitPinger is part of the Context interface.
   102  func (context *context) WaitPinger() worker.Worker {
   103  	context.c.Logf("waiting for pinger...")
   104  	select {
   105  	case pinger := <-context.starts:
   106  		return pinger
   107  	case <-context.timeout:
   108  		context.c.Fatalf("timed out waiting for pinger")
   109  		return nil
   110  	}
   111  }
   112  
   113  // WaitAlarms is part of the Context interface.
   114  func (context *context) WaitAlarms(count int) {
   115  	context.c.Logf("waiting for %d alarms...", count)
   116  	for i := 0; i < count; i++ {
   117  		select {
   118  		case <-context.clock.Alarms():
   119  		case <-context.timeout:
   120  			context.c.Fatalf("timed out waiting for alarm %d", i)
   121  		}
   122  	}
   123  }
   124  
   125  // AdvanceClock is part of the Context interface.
   126  func (context *context) AdvanceClock(d time.Duration) {
   127  	context.clock.Advance(d)
   128  }
   129  
   130  func (context *context) startPinger() (presence.Pinger, error) {
   131  	context.stub.AddCall("Start")
   132  	context.checkCleanedUp()
   133  	if startErr := context.stub.NextErr(); startErr != nil {
   134  		return nil, startErr
   135  	}
   136  
   137  	context.mu.Lock()
   138  	defer context.mu.Unlock()
   139  	pingerErr := context.stub.NextErr()
   140  	context.current = workertest.NewErrorWorker(pingerErr)
   141  	context.starts <- context.current
   142  	return mockPinger{context.current}, nil
   143  }
   144  
   145  func (context *context) checkCleanedUp() {
   146  	context.c.Logf("checking no active current pinger")
   147  	context.mu.Lock()
   148  	defer context.mu.Unlock()
   149  	if context.current != nil {
   150  		workertest.CheckKilled(context.c, context.current)
   151  	}
   152  }
   153  
   154  // mockPinger implements presence.Pinger for the convenience of the
   155  // tests.
   156  type mockPinger struct {
   157  	worker.Worker
   158  }
   159  
   160  func (mock mockPinger) Stop() error {
   161  	return worker.Stop(mock.Worker)
   162  }
   163  
   164  func (mock mockPinger) Wait() error {
   165  	return mock.Worker.Wait()
   166  }
   167  
   168  // validConfig returns a presence.Config that will validate, but fail
   169  // violently if actually used for anything.
   170  func validConfig() presence.Config {
   171  	return presence.Config{
   172  		Identity:   struct{ names.Tag }{},
   173  		Start:      func() (presence.Pinger, error) { panic("no") },
   174  		Clock:      struct{ clock.Clock }{},
   175  		RetryDelay: time.Nanosecond,
   176  	}
   177  }
   178  
   179  func checkInvalid(c *gc.C, config presence.Config, message string) {
   180  	check := func(err error) {
   181  		c.Check(err, gc.ErrorMatches, message)
   182  		c.Check(err, jc.Satisfies, errors.IsNotValid)
   183  	}
   184  
   185  	err := config.Validate()
   186  	check(err)
   187  
   188  	worker, err := presence.New(config)
   189  	if !c.Check(worker, gc.IsNil) {
   190  		workertest.CleanKill(c, worker)
   191  	}
   192  	check(err)
   193  }