github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/workers/fixture_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package workers_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/state/workers"
    16  	jujutesting "github.com/juju/juju/testing"
    17  	"github.com/juju/juju/worker"
    18  	"github.com/juju/juju/worker/workertest"
    19  )
    20  
    21  const (
    22  	fiveSeconds       = 5 * time.Second
    23  	almostFiveSeconds = fiveSeconds - time.Nanosecond
    24  )
    25  
    26  // Context gives a test func access to the harness driving the Workers
    27  // implementation under test.
    28  type Context interface {
    29  	Clock() *testing.Clock
    30  	Factory() workers.Factory
    31  	LWs() <-chan worker.Worker
    32  	SWs() <-chan worker.Worker
    33  	TLWs() <-chan worker.Worker
    34  	PWs() <-chan worker.Worker
    35  }
    36  
    37  // NextWorker reads a worker from the supplied channel and returns it,
    38  // or times out. The result might be nil.
    39  func NextWorker(c *gc.C, ch <-chan worker.Worker) worker.Worker {
    40  	select {
    41  	case worker := <-ch:
    42  		return worker
    43  	case <-time.After(jujutesting.LongWait):
    44  		c.Fatalf("expected worker never started")
    45  	}
    46  	panic("unreachable") // I hate doing this :-|.
    47  }
    48  
    49  // ErrFailStart can be used in any of a Fixture's *_errors fields to
    50  // indicate that we should fail to start a worker.
    51  var ErrFailStart = errors.New("test control value, should not be seen")
    52  
    53  // BasicFixture returns a Fixture that expects each worker to be started
    54  // once only, and to stop without error.
    55  func BasicFixture() Fixture {
    56  	return Fixture{
    57  		LW_errors:  []error{nil},
    58  		SW_errors:  []error{nil},
    59  		TLW_errors: []error{nil},
    60  		PW_errors:  []error{nil},
    61  	}
    62  }
    63  
    64  // Fixture allows you to run tests against a DumbWorkers or a
    65  // RestartWorkers by specifying a list of errors per worker.
    66  type Fixture struct {
    67  	LW_errors  []error
    68  	SW_errors  []error
    69  	TLW_errors []error
    70  	PW_errors  []error
    71  }
    72  
    73  // Run runs a test func inside a fresh Context.
    74  func (fix Fixture) Run(c *gc.C, test func(Context)) {
    75  	ctx := fix.newContext()
    76  	defer ctx.cleanup(c)
    77  	test(ctx)
    78  }
    79  
    80  // RunDumb starts a DumbWorkers inside a fresh Context and supplies it
    81  // to a test func.
    82  func (fix Fixture) RunDumb(c *gc.C, test func(Context, *workers.DumbWorkers)) {
    83  	fix.Run(c, func(ctx Context) {
    84  		dw, err := workers.NewDumbWorkers(workers.DumbConfig{
    85  			Factory: ctx.Factory(),
    86  			Logger:  loggo.GetLogger("test"),
    87  		})
    88  		c.Assert(err, jc.ErrorIsNil)
    89  		defer workertest.DirtyKill(c, dw)
    90  		test(ctx, dw)
    91  	})
    92  }
    93  
    94  // FailDumb verifies that a DumbWorkers cannot start successfully, and
    95  // checks that the returned error matches.
    96  func (fix Fixture) FailDumb(c *gc.C, match string) {
    97  	fix.Run(c, func(ctx Context) {
    98  		dw, err := workers.NewDumbWorkers(workers.DumbConfig{
    99  			Factory: ctx.Factory(),
   100  			Logger:  loggo.GetLogger("test"),
   101  		})
   102  		if !c.Check(dw, gc.IsNil) {
   103  			workertest.DirtyKill(c, dw)
   104  		}
   105  		c.Check(err, gc.ErrorMatches, match)
   106  	})
   107  }
   108  
   109  // RunRestart starts a RestartWorkers inside a fresh Context and
   110  // supplies it to a test func.
   111  func (fix Fixture) RunRestart(c *gc.C, test func(Context, *workers.RestartWorkers)) {
   112  	fix.Run(c, func(ctx Context) {
   113  		rw, err := workers.NewRestartWorkers(workers.RestartConfig{
   114  			Factory: ctx.Factory(),
   115  			Logger:  loggo.GetLogger("test"),
   116  			Clock:   ctx.Clock(),
   117  			Delay:   fiveSeconds,
   118  		})
   119  		c.Assert(err, jc.ErrorIsNil)
   120  		defer workertest.DirtyKill(c, rw)
   121  		test(ctx, rw)
   122  	})
   123  }
   124  
   125  // FailRestart verifies that a RestartWorkers cannot start successfully, and
   126  // checks that the returned error matches.
   127  func (fix Fixture) FailRestart(c *gc.C, match string) {
   128  	fix.Run(c, func(ctx Context) {
   129  		rw, err := workers.NewRestartWorkers(workers.RestartConfig{
   130  			Factory: ctx.Factory(),
   131  			Logger:  loggo.GetLogger("test"),
   132  			Clock:   ctx.Clock(),
   133  			Delay:   fiveSeconds,
   134  		})
   135  		if !c.Check(rw, gc.IsNil) {
   136  			workertest.DirtyKill(c, rw)
   137  		}
   138  		c.Check(err, gc.ErrorMatches, match)
   139  	})
   140  }
   141  
   142  func (fix Fixture) newContext() *context {
   143  	return &context{
   144  		clock:   testing.NewClock(time.Now()),
   145  		lwList:  newWorkerList(fix.LW_errors),
   146  		swList:  newWorkerList(fix.SW_errors),
   147  		tlwList: newWorkerList(fix.TLW_errors),
   148  		pwList:  newWorkerList(fix.PW_errors),
   149  	}
   150  }
   151  
   152  // newWorkerList converts the supplied errors into a list of workers to
   153  // be returned in order from the result's Next func (at which point they
   154  // are sent on the reports chan as well).
   155  func newWorkerList(errs []error) *workerList {
   156  	count := len(errs)
   157  	reports := make(chan worker.Worker, count)
   158  	workers := make([]worker.Worker, count)
   159  	for i, err := range errs {
   160  		if err == ErrFailStart {
   161  			workers[i] = nil
   162  		} else {
   163  			workers[i] = workertest.NewErrorWorker(err)
   164  		}
   165  	}
   166  	return &workerList{
   167  		workers: workers,
   168  		reports: reports,
   169  	}
   170  }
   171  
   172  type workerList struct {
   173  	next    int
   174  	workers []worker.Worker
   175  	reports chan worker.Worker
   176  }
   177  
   178  // Next starts and returns the next configured worker, or an error.
   179  // In either case, a value is sent on the worker channel.
   180  func (wl *workerList) Next() (worker.Worker, error) {
   181  	worker := wl.workers[wl.next]
   182  	wl.next++
   183  	wl.reports <- worker
   184  	if worker == nil {
   185  		return nil, errors.New("bad start")
   186  	}
   187  	return worker, nil
   188  }
   189  
   190  // cleanup checks that every expected worker has already been stopped by
   191  // the SUT. (i.e.: don't set up more workers than your fixture needs).
   192  func (wl *workerList) cleanup(c *gc.C) {
   193  	for _, w := range wl.workers {
   194  		if w != nil {
   195  			workertest.CheckKilled(c, w)
   196  		}
   197  	}
   198  }
   199  
   200  // context implements Context.
   201  type context struct {
   202  	clock   *testing.Clock
   203  	lwList  *workerList
   204  	swList  *workerList
   205  	tlwList *workerList
   206  	pwList  *workerList
   207  }
   208  
   209  func (ctx *context) cleanup(c *gc.C) {
   210  	c.Logf("cleaning up test context")
   211  	for _, list := range []*workerList{
   212  		ctx.lwList,
   213  		ctx.swList,
   214  		ctx.tlwList,
   215  		ctx.pwList,
   216  	} {
   217  		list.cleanup(c)
   218  	}
   219  }
   220  
   221  func (ctx *context) LWs() <-chan worker.Worker {
   222  	return ctx.lwList.reports
   223  }
   224  
   225  func (ctx *context) SWs() <-chan worker.Worker {
   226  	return ctx.swList.reports
   227  }
   228  
   229  func (ctx *context) TLWs() <-chan worker.Worker {
   230  	return ctx.tlwList.reports
   231  }
   232  
   233  func (ctx *context) PWs() <-chan worker.Worker {
   234  	return ctx.pwList.reports
   235  }
   236  
   237  func (ctx *context) Clock() *testing.Clock {
   238  	return ctx.clock
   239  }
   240  
   241  func (ctx *context) Factory() workers.Factory {
   242  	return &factory{ctx}
   243  }
   244  
   245  // factory implements workers.Factory for the convenience of the tests.
   246  type factory struct {
   247  	ctx *context
   248  }
   249  
   250  func (f *factory) NewLeadershipWorker() (workers.LeaseWorker, error) {
   251  	worker, err := f.ctx.lwList.Next()
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  	return fakeLeaseWorker{Worker: worker}, nil
   256  }
   257  
   258  func (f *factory) NewSingularWorker() (workers.LeaseWorker, error) {
   259  	worker, err := f.ctx.swList.Next()
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	return fakeLeaseWorker{Worker: worker}, nil
   264  }
   265  
   266  func (f *factory) NewTxnLogWorker() (workers.TxnLogWorker, error) {
   267  	worker, err := f.ctx.tlwList.Next()
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  	return fakeTxnLogWorker{Worker: worker}, nil
   272  }
   273  
   274  func (f *factory) NewPresenceWorker() (workers.PresenceWorker, error) {
   275  	worker, err := f.ctx.pwList.Next()
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  	return fakePresenceWorker{Worker: worker}, nil
   280  }
   281  
   282  type fakeLeaseWorker struct {
   283  	worker.Worker
   284  	workers.LeaseManager
   285  }
   286  
   287  type fakeTxnLogWorker struct {
   288  	worker.Worker
   289  	workers.TxnLogWatcher
   290  }
   291  
   292  type fakePresenceWorker struct {
   293  	worker.Worker
   294  	workers.PresenceWatcher
   295  }
   296  
   297  // IsWorker returns true if `wrapped` is one of the above fake*Worker
   298  // types (as returned by the factory methods) and also wraps the
   299  // `expect` worker.
   300  func IsWorker(wrapped interface{}, expect worker.Worker) bool {
   301  	if w, ok := wrapped.(workers.DynamicLeaseManager); ok {
   302  		wrapped = w.Underlying()
   303  	}
   304  	var actual worker.Worker
   305  	switch wrapped := wrapped.(type) {
   306  	case fakeLeaseWorker:
   307  		actual = wrapped.Worker
   308  	case fakeTxnLogWorker:
   309  		actual = wrapped.Worker
   310  	case fakePresenceWorker:
   311  		actual = wrapped.Worker
   312  	default:
   313  		return false
   314  	}
   315  	return actual == expect
   316  }
   317  
   318  // AssertWorker fails if IsWorker returns false.
   319  func AssertWorker(c *gc.C, wrapped interface{}, expect worker.Worker) {
   320  	c.Assert(IsWorker(wrapped, expect), jc.IsTrue)
   321  }
   322  
   323  func LM_getter(w workers.Workers) func() interface{} {
   324  	return func() interface{} { return w.LeadershipManager() }
   325  }
   326  
   327  func SM_getter(w workers.Workers) func() interface{} {
   328  	return func() interface{} { return w.SingularManager() }
   329  }
   330  
   331  func TLW_getter(w workers.Workers) func() interface{} {
   332  	return func() interface{} { return w.TxnLogWatcher() }
   333  }
   334  
   335  func PW_getter(w workers.Workers) func() interface{} {
   336  	return func() interface{} { return w.PresenceWatcher() }
   337  }
   338  
   339  // WaitWorker blocks until getter returns something that satifies
   340  // IsWorker, or until it times out.
   341  func WaitWorker(c *gc.C, getter func() interface{}, expect worker.Worker) {
   342  	var delay time.Duration
   343  	timeout := time.After(jujutesting.LongWait)
   344  	for {
   345  		select {
   346  		case <-timeout:
   347  			c.Fatalf("expected worker")
   348  		case <-time.After(delay):
   349  			delay = jujutesting.ShortWait
   350  		}
   351  		if IsWorker(getter(), expect) {
   352  			return
   353  		}
   354  	}
   355  }
   356  
   357  // WaitAlarms waits until the supplied clock has sent count values on
   358  // its Alarms channel.
   359  func WaitAlarms(c *gc.C, clock *testing.Clock, count int) {
   360  	timeout := time.After(jujutesting.LongWait)
   361  	for i := 0; i < count; i++ {
   362  		select {
   363  		case <-timeout:
   364  			c.Fatalf("never saw alarm %d", i)
   365  		case <-clock.Alarms():
   366  		}
   367  	}
   368  }