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