github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/watcher/notify_test.go (about)

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