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