github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/notifyworker_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package worker_test
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"launchpad.net/tomb"
    14  
    15  	apiWatcher "github.com/juju/juju/api/watcher"
    16  	"github.com/juju/juju/state/watcher"
    17  	coretesting "github.com/juju/juju/testing"
    18  	"github.com/juju/juju/worker"
    19  )
    20  
    21  type notifyWorkerSuite struct {
    22  	coretesting.BaseSuite
    23  	worker worker.Worker
    24  	actor  *notifyHandler
    25  }
    26  
    27  var _ = gc.Suite(&notifyWorkerSuite{})
    28  
    29  func newNotifyHandlerWorker(c *gc.C, setupError, handlerError, teardownError error) (*notifyHandler, worker.Worker) {
    30  	nh := &notifyHandler{
    31  		actions:       nil,
    32  		handled:       make(chan struct{}, 1),
    33  		setupError:    setupError,
    34  		teardownError: teardownError,
    35  		handlerError:  handlerError,
    36  		watcher: &testNotifyWatcher{
    37  			changes: make(chan struct{}),
    38  		},
    39  		setupDone: make(chan struct{}),
    40  	}
    41  	w := worker.NewNotifyWorker(nh)
    42  	select {
    43  	case <-nh.setupDone:
    44  	case <-time.After(coretesting.ShortWait):
    45  		c.Error("Failed waiting for notifyHandler.Setup to be called during SetUpTest")
    46  	}
    47  	return nh, w
    48  }
    49  
    50  func (s *notifyWorkerSuite) SetUpTest(c *gc.C) {
    51  	s.BaseSuite.SetUpTest(c)
    52  	s.actor, s.worker = newNotifyHandlerWorker(c, nil, nil, nil)
    53  }
    54  
    55  func (s *notifyWorkerSuite) TearDownTest(c *gc.C) {
    56  	worker.SetEnsureErr(nil)
    57  	s.stopWorker(c)
    58  	s.BaseSuite.TearDownTest(c)
    59  }
    60  
    61  type notifyHandler struct {
    62  	actions []string
    63  	mu      sync.Mutex
    64  	// Signal handled when we get a handle() call
    65  	handled       chan struct{}
    66  	setupError    error
    67  	teardownError error
    68  	handlerError  error
    69  	watcher       *testNotifyWatcher
    70  	setupDone     chan struct{}
    71  }
    72  
    73  var _ worker.NotifyWatchHandler = (*notifyHandler)(nil)
    74  
    75  func (nh *notifyHandler) SetUp() (apiWatcher.NotifyWatcher, error) {
    76  	defer func() { nh.setupDone <- struct{}{} }()
    77  	nh.mu.Lock()
    78  	defer nh.mu.Unlock()
    79  	nh.actions = append(nh.actions, "setup")
    80  	if nh.watcher == nil {
    81  		return nil, nh.setupError
    82  	}
    83  	return nh.watcher, nh.setupError
    84  }
    85  
    86  func (nh *notifyHandler) TearDown() error {
    87  	nh.mu.Lock()
    88  	defer nh.mu.Unlock()
    89  	nh.actions = append(nh.actions, "teardown")
    90  	if nh.handled != nil {
    91  		close(nh.handled)
    92  	}
    93  	return nh.teardownError
    94  }
    95  
    96  func (nh *notifyHandler) Handle(_ <-chan struct{}) error {
    97  	nh.mu.Lock()
    98  	defer nh.mu.Unlock()
    99  	nh.actions = append(nh.actions, "handler")
   100  	if nh.handled != nil {
   101  		// Unlock while we are waiting for the send
   102  		nh.mu.Unlock()
   103  		nh.handled <- struct{}{}
   104  		nh.mu.Lock()
   105  	}
   106  	return nh.handlerError
   107  }
   108  
   109  func (nh *notifyHandler) CheckActions(c *gc.C, actions ...string) {
   110  	nh.mu.Lock()
   111  	defer nh.mu.Unlock()
   112  	c.Check(nh.actions, gc.DeepEquals, actions)
   113  }
   114  
   115  // During teardown we try to stop the worker, but don't hang the test suite if
   116  // Stop never returns
   117  func (s *notifyWorkerSuite) stopWorker(c *gc.C) {
   118  	if s.worker == nil {
   119  		return
   120  	}
   121  	done := make(chan error)
   122  	go func() {
   123  		done <- worker.Stop(s.worker)
   124  	}()
   125  	err := waitForTimeout(c, done, coretesting.LongWait)
   126  	c.Check(err, jc.ErrorIsNil)
   127  	s.actor = nil
   128  	s.worker = nil
   129  }
   130  
   131  type testNotifyWatcher struct {
   132  	mu        sync.Mutex
   133  	changes   chan struct{}
   134  	stopped   bool
   135  	stopError error
   136  }
   137  
   138  var _ apiWatcher.NotifyWatcher = (*testNotifyWatcher)(nil)
   139  
   140  func (tnw *testNotifyWatcher) Changes() <-chan struct{} {
   141  	return tnw.changes
   142  }
   143  
   144  func (tnw *testNotifyWatcher) Err() error {
   145  	return tnw.stopError
   146  }
   147  
   148  func (tnw *testNotifyWatcher) Stop() error {
   149  	tnw.mu.Lock()
   150  	defer tnw.mu.Unlock()
   151  	if !tnw.stopped {
   152  		close(tnw.changes)
   153  	}
   154  	tnw.stopped = true
   155  	return tnw.stopError
   156  }
   157  
   158  func (tnw *testNotifyWatcher) SetStopError(err error) {
   159  	tnw.mu.Lock()
   160  	tnw.stopError = err
   161  	tnw.mu.Unlock()
   162  }
   163  
   164  func (tnw *testNotifyWatcher) TriggerChange(c *gc.C) {
   165  	select {
   166  	case tnw.changes <- struct{}{}:
   167  	case <-time.After(coretesting.LongWait):
   168  		c.Errorf("timed out trying to trigger a change")
   169  	}
   170  }
   171  
   172  func waitForTimeout(c *gc.C, ch <-chan error, timeout time.Duration) error {
   173  	select {
   174  	case err := <-ch:
   175  		return err
   176  	case <-time.After(timeout):
   177  		c.Errorf("timed out waiting to receive a change after %s", timeout)
   178  	}
   179  	return nil
   180  }
   181  
   182  func waitShort(c *gc.C, w worker.Worker) error {
   183  	done := make(chan error)
   184  	go func() {
   185  		done <- w.Wait()
   186  	}()
   187  	return waitForTimeout(c, done, coretesting.ShortWait)
   188  }
   189  
   190  func waitForHandledNotify(c *gc.C, handled chan struct{}) {
   191  	select {
   192  	case <-handled:
   193  	case <-time.After(coretesting.LongWait):
   194  		c.Errorf("handled failed to signal after %s", coretesting.LongWait)
   195  	}
   196  }
   197  
   198  func (s *notifyWorkerSuite) TestKill(c *gc.C) {
   199  	s.worker.Kill()
   200  	err := waitShort(c, s.worker)
   201  	c.Assert(err, jc.ErrorIsNil)
   202  }
   203  
   204  func (s *notifyWorkerSuite) TestStop(c *gc.C) {
   205  	err := worker.Stop(s.worker)
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	// After stop, Wait should return right away
   208  	err = waitShort(c, s.worker)
   209  	c.Assert(err, jc.ErrorIsNil)
   210  }
   211  
   212  func (s *notifyWorkerSuite) TestWait(c *gc.C) {
   213  	done := make(chan error)
   214  	go func() {
   215  		done <- s.worker.Wait()
   216  	}()
   217  	// Wait should not return until we've killed the worker
   218  	select {
   219  	case err := <-done:
   220  		c.Errorf("Wait() didn't wait until we stopped it: %v", err)
   221  	case <-time.After(coretesting.ShortWait):
   222  	}
   223  	s.worker.Kill()
   224  	err := waitForTimeout(c, done, coretesting.LongWait)
   225  	c.Assert(err, jc.ErrorIsNil)
   226  }
   227  
   228  func (s *notifyWorkerSuite) TestCallSetUpAndTearDown(c *gc.C) {
   229  	// After calling NewNotifyWorker, we should have called setup
   230  	s.actor.CheckActions(c, "setup")
   231  	// If we kill the worker, it should notice, and call teardown
   232  	s.worker.Kill()
   233  	err := waitShort(c, s.worker)
   234  	c.Check(err, jc.ErrorIsNil)
   235  	s.actor.CheckActions(c, "setup", "teardown")
   236  	c.Check(s.actor.watcher.stopped, jc.IsTrue)
   237  }
   238  
   239  func (s *notifyWorkerSuite) TestChangesTriggerHandler(c *gc.C) {
   240  	s.actor.CheckActions(c, "setup")
   241  	s.actor.watcher.TriggerChange(c)
   242  	waitForHandledNotify(c, s.actor.handled)
   243  	s.actor.CheckActions(c, "setup", "handler")
   244  	s.actor.watcher.TriggerChange(c)
   245  	waitForHandledNotify(c, s.actor.handled)
   246  	s.actor.watcher.TriggerChange(c)
   247  	waitForHandledNotify(c, s.actor.handled)
   248  	s.actor.CheckActions(c, "setup", "handler", "handler", "handler")
   249  	c.Assert(worker.Stop(s.worker), gc.IsNil)
   250  	s.actor.CheckActions(c, "setup", "handler", "handler", "handler", "teardown")
   251  }
   252  
   253  func (s *notifyWorkerSuite) TestSetUpFailureStopsWithTearDown(c *gc.C) {
   254  	// Stop the worker and SetUp again, this time with an error
   255  	s.stopWorker(c)
   256  	actor, w := newNotifyHandlerWorker(c, fmt.Errorf("my special error"), nil, nil)
   257  	err := waitShort(c, w)
   258  	c.Check(err, gc.ErrorMatches, "my special error")
   259  	// TearDown is not called on SetUp error.
   260  	actor.CheckActions(c, "setup")
   261  	c.Check(actor.watcher.stopped, jc.IsTrue)
   262  }
   263  
   264  func (s *notifyWorkerSuite) TestWatcherStopFailurePropagates(c *gc.C) {
   265  	s.actor.watcher.SetStopError(fmt.Errorf("error while stopping watcher"))
   266  	s.worker.Kill()
   267  	c.Assert(s.worker.Wait(), gc.ErrorMatches, "error while stopping watcher")
   268  	// We've already stopped the worker, don't let teardown notice the
   269  	// worker is in an error state
   270  	s.worker = nil
   271  }
   272  
   273  func (s *notifyWorkerSuite) TestCleanRunNoticesTearDownError(c *gc.C) {
   274  	s.actor.teardownError = fmt.Errorf("failed to tear down watcher")
   275  	s.worker.Kill()
   276  	c.Assert(s.worker.Wait(), gc.ErrorMatches, "failed to tear down watcher")
   277  	s.worker = nil
   278  }
   279  
   280  func (s *notifyWorkerSuite) TestHandleErrorStopsWorkerAndWatcher(c *gc.C) {
   281  	s.stopWorker(c)
   282  	actor, w := newNotifyHandlerWorker(c, nil, fmt.Errorf("my handling error"), nil)
   283  	actor.watcher.TriggerChange(c)
   284  	waitForHandledNotify(c, actor.handled)
   285  	err := waitShort(c, w)
   286  	c.Check(err, gc.ErrorMatches, "my handling error")
   287  	actor.CheckActions(c, "setup", "handler", "teardown")
   288  	c.Check(actor.watcher.stopped, jc.IsTrue)
   289  }
   290  
   291  func (s *notifyWorkerSuite) TestNoticesStoppedWatcher(c *gc.C) {
   292  	// The default closedHandler doesn't panic if you have a genuine error
   293  	// (because it assumes you want to propagate a real error and then
   294  	// restart
   295  	s.actor.watcher.SetStopError(fmt.Errorf("Stopped Watcher"))
   296  	s.actor.watcher.Stop()
   297  	err := waitShort(c, s.worker)
   298  	c.Check(err, gc.ErrorMatches, "Stopped Watcher")
   299  	s.actor.CheckActions(c, "setup", "teardown")
   300  	// Worker is stopped, don't fail TearDownTest
   301  	s.worker = nil
   302  }
   303  
   304  func noopHandler(watcher.Errer) error {
   305  	return nil
   306  }
   307  
   308  type CannedErrer struct {
   309  	err error
   310  }
   311  
   312  func (c CannedErrer) Err() error {
   313  	return c.err
   314  }
   315  
   316  func (s *notifyWorkerSuite) TestDefaultClosedHandler(c *gc.C) {
   317  	// Roundabout check for function equality.
   318  	// Is this test really worth it?
   319  	c.Assert(fmt.Sprintf("%p", worker.EnsureErr()), gc.Equals, fmt.Sprintf("%p", watcher.EnsureErr))
   320  }
   321  
   322  func (s *notifyWorkerSuite) TestErrorsOnStillAliveButClosedChannel(c *gc.C) {
   323  	foundErr := fmt.Errorf("did not get an error")
   324  	triggeredHandler := func(errer watcher.Errer) error {
   325  		foundErr = errer.Err()
   326  		return foundErr
   327  	}
   328  	worker.SetEnsureErr(triggeredHandler)
   329  	s.actor.watcher.SetStopError(tomb.ErrStillAlive)
   330  	s.actor.watcher.Stop()
   331  	err := waitShort(c, s.worker)
   332  	c.Check(foundErr, gc.Equals, tomb.ErrStillAlive)
   333  	// ErrStillAlive is trapped by the Stop logic and gets turned into a
   334  	// 'nil' when stopping. However TestDefaultClosedHandler can assert
   335  	// that it would have triggered a panic.
   336  	c.Check(err, jc.ErrorIsNil)
   337  	s.actor.CheckActions(c, "setup", "teardown")
   338  	// Worker is stopped, don't fail TearDownTest
   339  	s.worker = nil
   340  }
   341  
   342  func (s *notifyWorkerSuite) TestErrorsOnClosedChannel(c *gc.C) {
   343  	foundErr := fmt.Errorf("did not get an error")
   344  	triggeredHandler := func(errer watcher.Errer) error {
   345  		foundErr = errer.Err()
   346  		return foundErr
   347  	}
   348  	worker.SetEnsureErr(triggeredHandler)
   349  	s.actor.watcher.Stop()
   350  	err := waitShort(c, s.worker)
   351  	// If the foundErr is nil, we would have panic-ed (see TestDefaultClosedHandler)
   352  	c.Check(foundErr, gc.IsNil)
   353  	c.Check(err, jc.ErrorIsNil)
   354  	s.actor.CheckActions(c, "setup", "teardown")
   355  }