github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 (s *notifyWorkerSuite) SetUpTest(c *gc.C) {
    30  	s.BaseSuite.SetUpTest(c)
    31  	s.actor = &notifyHandler{
    32  		actions: nil,
    33  		handled: make(chan struct{}, 1),
    34  		watcher: &testNotifyWatcher{
    35  			changes: make(chan struct{}),
    36  		},
    37  	}
    38  	s.worker = worker.NewNotifyWorker(s.actor)
    39  }
    40  
    41  func (s *notifyWorkerSuite) TearDownTest(c *gc.C) {
    42  	worker.SetEnsureErr(nil)
    43  	s.stopWorker(c)
    44  	s.BaseSuite.TearDownTest(c)
    45  }
    46  
    47  type notifyHandler struct {
    48  	actions []string
    49  	mu      sync.Mutex
    50  	// Signal handled when we get a handle() call
    51  	handled       chan struct{}
    52  	setupError    error
    53  	teardownError error
    54  	handlerError  error
    55  	watcher       *testNotifyWatcher
    56  }
    57  
    58  var _ worker.NotifyWatchHandler = (*notifyHandler)(nil)
    59  
    60  func (nh *notifyHandler) SetUp() (apiWatcher.NotifyWatcher, error) {
    61  	nh.mu.Lock()
    62  	defer nh.mu.Unlock()
    63  	nh.actions = append(nh.actions, "setup")
    64  	if nh.watcher == nil {
    65  		return nil, nh.setupError
    66  	}
    67  	return nh.watcher, nh.setupError
    68  }
    69  
    70  func (nh *notifyHandler) TearDown() error {
    71  	nh.mu.Lock()
    72  	defer nh.mu.Unlock()
    73  	nh.actions = append(nh.actions, "teardown")
    74  	if nh.handled != nil {
    75  		close(nh.handled)
    76  	}
    77  	return nh.teardownError
    78  }
    79  
    80  func (nh *notifyHandler) Handle() error {
    81  	nh.mu.Lock()
    82  	defer nh.mu.Unlock()
    83  	nh.actions = append(nh.actions, "handler")
    84  	if nh.handled != nil {
    85  		// Unlock while we are waiting for the send
    86  		nh.mu.Unlock()
    87  		nh.handled <- struct{}{}
    88  		nh.mu.Lock()
    89  	}
    90  	return nh.handlerError
    91  }
    92  
    93  func (nh *notifyHandler) CheckActions(c *gc.C, actions ...string) {
    94  	nh.mu.Lock()
    95  	defer nh.mu.Unlock()
    96  	c.Check(nh.actions, gc.DeepEquals, actions)
    97  }
    98  
    99  // During teardown we try to stop the worker, but don't hang the test suite if
   100  // Stop never returns
   101  func (s *notifyWorkerSuite) stopWorker(c *gc.C) {
   102  	if s.worker == nil {
   103  		return
   104  	}
   105  	done := make(chan error)
   106  	go func() {
   107  		done <- worker.Stop(s.worker)
   108  	}()
   109  	err := waitForTimeout(c, done, coretesting.LongWait)
   110  	c.Check(err, jc.ErrorIsNil)
   111  	s.actor = nil
   112  	s.worker = nil
   113  }
   114  
   115  type testNotifyWatcher struct {
   116  	mu        sync.Mutex
   117  	changes   chan struct{}
   118  	stopped   bool
   119  	stopError error
   120  }
   121  
   122  var _ apiWatcher.NotifyWatcher = (*testNotifyWatcher)(nil)
   123  
   124  func (tnw *testNotifyWatcher) Changes() <-chan struct{} {
   125  	return tnw.changes
   126  }
   127  
   128  func (tnw *testNotifyWatcher) Err() error {
   129  	return tnw.stopError
   130  }
   131  
   132  func (tnw *testNotifyWatcher) Stop() error {
   133  	tnw.mu.Lock()
   134  	defer tnw.mu.Unlock()
   135  	if !tnw.stopped {
   136  		close(tnw.changes)
   137  	}
   138  	tnw.stopped = true
   139  	return tnw.stopError
   140  }
   141  
   142  func (tnw *testNotifyWatcher) SetStopError(err error) {
   143  	tnw.mu.Lock()
   144  	tnw.stopError = err
   145  	tnw.mu.Unlock()
   146  }
   147  
   148  func (tnw *testNotifyWatcher) TriggerChange(c *gc.C) {
   149  	select {
   150  	case tnw.changes <- struct{}{}:
   151  	case <-time.After(coretesting.LongWait):
   152  		c.Errorf("timed out trying to trigger a change")
   153  	}
   154  }
   155  
   156  func waitForTimeout(c *gc.C, ch <-chan error, timeout time.Duration) error {
   157  	select {
   158  	case err := <-ch:
   159  		return err
   160  	case <-time.After(timeout):
   161  		c.Errorf("timed out waiting to receive a change after %s", timeout)
   162  	}
   163  	return nil
   164  }
   165  
   166  func waitShort(c *gc.C, w worker.Worker) error {
   167  	done := make(chan error)
   168  	go func() {
   169  		done <- w.Wait()
   170  	}()
   171  	return waitForTimeout(c, done, coretesting.ShortWait)
   172  }
   173  
   174  func waitForHandledNotify(c *gc.C, handled chan struct{}) {
   175  	select {
   176  	case <-handled:
   177  	case <-time.After(coretesting.LongWait):
   178  		c.Errorf("handled failed to signal after %s", coretesting.LongWait)
   179  	}
   180  }
   181  
   182  func (s *notifyWorkerSuite) TestKill(c *gc.C) {
   183  	s.worker.Kill()
   184  	err := waitShort(c, s.worker)
   185  	c.Assert(err, jc.ErrorIsNil)
   186  }
   187  
   188  func (s *notifyWorkerSuite) TestStop(c *gc.C) {
   189  	err := worker.Stop(s.worker)
   190  	c.Assert(err, jc.ErrorIsNil)
   191  	// After stop, Wait should return right away
   192  	err = waitShort(c, s.worker)
   193  	c.Assert(err, jc.ErrorIsNil)
   194  }
   195  
   196  func (s *notifyWorkerSuite) TestWait(c *gc.C) {
   197  	done := make(chan error)
   198  	go func() {
   199  		done <- s.worker.Wait()
   200  	}()
   201  	// Wait should not return until we've killed the worker
   202  	select {
   203  	case err := <-done:
   204  		c.Errorf("Wait() didn't wait until we stopped it: %v", err)
   205  	case <-time.After(coretesting.ShortWait):
   206  	}
   207  	s.worker.Kill()
   208  	err := waitForTimeout(c, done, coretesting.LongWait)
   209  	c.Assert(err, jc.ErrorIsNil)
   210  }
   211  
   212  func (s *notifyWorkerSuite) TestCallSetUpAndTearDown(c *gc.C) {
   213  	// After calling NewNotifyWorker, we should have called setup
   214  	s.actor.CheckActions(c, "setup")
   215  	// If we kill the worker, it should notice, and call teardown
   216  	s.worker.Kill()
   217  	err := waitShort(c, s.worker)
   218  	c.Check(err, jc.ErrorIsNil)
   219  	s.actor.CheckActions(c, "setup", "teardown")
   220  	c.Check(s.actor.watcher.stopped, jc.IsTrue)
   221  }
   222  
   223  func (s *notifyWorkerSuite) TestChangesTriggerHandler(c *gc.C) {
   224  	s.actor.CheckActions(c, "setup")
   225  	s.actor.watcher.TriggerChange(c)
   226  	waitForHandledNotify(c, s.actor.handled)
   227  	s.actor.CheckActions(c, "setup", "handler")
   228  	s.actor.watcher.TriggerChange(c)
   229  	waitForHandledNotify(c, s.actor.handled)
   230  	s.actor.watcher.TriggerChange(c)
   231  	waitForHandledNotify(c, s.actor.handled)
   232  	s.actor.CheckActions(c, "setup", "handler", "handler", "handler")
   233  	c.Assert(worker.Stop(s.worker), gc.IsNil)
   234  	s.actor.CheckActions(c, "setup", "handler", "handler", "handler", "teardown")
   235  }
   236  
   237  func (s *notifyWorkerSuite) TestSetUpFailureStopsWithTearDown(c *gc.C) {
   238  	// Stop the worker and SetUp again, this time with an error
   239  	s.stopWorker(c)
   240  	actor := &notifyHandler{
   241  		actions:    nil,
   242  		handled:    make(chan struct{}, 1),
   243  		setupError: fmt.Errorf("my special error"),
   244  		watcher: &testNotifyWatcher{
   245  			changes: make(chan struct{}),
   246  		},
   247  	}
   248  	w := worker.NewNotifyWorker(actor)
   249  	err := waitShort(c, w)
   250  	c.Check(err, gc.ErrorMatches, "my special error")
   251  	// TearDown is not called on SetUp error.
   252  	actor.CheckActions(c, "setup")
   253  	c.Check(actor.watcher.stopped, jc.IsTrue)
   254  }
   255  
   256  func (s *notifyWorkerSuite) TestWatcherStopFailurePropagates(c *gc.C) {
   257  	s.actor.watcher.SetStopError(fmt.Errorf("error while stopping watcher"))
   258  	s.worker.Kill()
   259  	c.Assert(s.worker.Wait(), gc.ErrorMatches, "error while stopping watcher")
   260  	// We've already stopped the worker, don't let teardown notice the
   261  	// worker is in an error state
   262  	s.worker = nil
   263  }
   264  
   265  func (s *notifyWorkerSuite) TestCleanRunNoticesTearDownError(c *gc.C) {
   266  	s.actor.teardownError = fmt.Errorf("failed to tear down watcher")
   267  	s.worker.Kill()
   268  	c.Assert(s.worker.Wait(), gc.ErrorMatches, "failed to tear down watcher")
   269  	s.worker = nil
   270  }
   271  
   272  func (s *notifyWorkerSuite) TestHandleErrorStopsWorkerAndWatcher(c *gc.C) {
   273  	s.stopWorker(c)
   274  	actor := &notifyHandler{
   275  		actions:      nil,
   276  		handled:      make(chan struct{}, 1),
   277  		handlerError: fmt.Errorf("my handling error"),
   278  		watcher: &testNotifyWatcher{
   279  			changes: make(chan struct{}),
   280  		},
   281  	}
   282  	w := worker.NewNotifyWorker(actor)
   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  }