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

     1  // Copyright 2013-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 stringsWorkerSuite struct {
    21  	coretesting.BaseSuite
    22  	worker worker.Worker
    23  	actor  *stringsHandler
    24  }
    25  
    26  var _ = gc.Suite(&stringsWorkerSuite{})
    27  
    28  func newStringsHandlerWorker(c *gc.C, setupError, handlerError, teardownError error) (*stringsHandler, worker.Worker) {
    29  	sh := &stringsHandler{
    30  		actions:       nil,
    31  		handled:       make(chan []string, 1),
    32  		setupError:    setupError,
    33  		teardownError: teardownError,
    34  		handlerError:  handlerError,
    35  		watcher:       newTestStringsWatcher(),
    36  		setupDone:     make(chan struct{}),
    37  	}
    38  	w, err := watcher.NewStringsWorker(watcher.StringsConfig{Handler: sh})
    39  	c.Assert(err, jc.ErrorIsNil)
    40  	select {
    41  	case <-sh.setupDone:
    42  	case <-time.After(coretesting.ShortWait):
    43  		c.Error("Failed waiting for stringsHandler.Setup to be called during SetUpTest")
    44  	}
    45  	return sh, w
    46  }
    47  
    48  func (s *stringsWorkerSuite) SetUpTest(c *gc.C) {
    49  	s.BaseSuite.SetUpTest(c)
    50  	s.actor, s.worker = newStringsHandlerWorker(c, nil, nil, nil)
    51  	s.AddCleanup(s.stopWorker)
    52  }
    53  
    54  type stringsHandler struct {
    55  	actions []string
    56  	mu      sync.Mutex
    57  	// Signal handled when we get a handle() call
    58  	handled       chan []string
    59  	setupError    error
    60  	teardownError error
    61  	handlerError  error
    62  	watcher       *testStringsWatcher
    63  	setupDone     chan struct{}
    64  }
    65  
    66  func (sh *stringsHandler) SetUp() (watcher.StringsWatcher, error) {
    67  	defer func() { sh.setupDone <- struct{}{} }()
    68  	sh.mu.Lock()
    69  	defer sh.mu.Unlock()
    70  	sh.actions = append(sh.actions, "setup")
    71  	if sh.watcher == nil {
    72  		return nil, sh.setupError
    73  	}
    74  	return sh.watcher, sh.setupError
    75  }
    76  
    77  func (sh *stringsHandler) TearDown() error {
    78  	sh.mu.Lock()
    79  	defer sh.mu.Unlock()
    80  	sh.actions = append(sh.actions, "teardown")
    81  	if sh.handled != nil {
    82  		close(sh.handled)
    83  	}
    84  	return sh.teardownError
    85  }
    86  
    87  func (sh *stringsHandler) Handle(_ <-chan struct{}, changes []string) error {
    88  	sh.mu.Lock()
    89  	defer sh.mu.Unlock()
    90  	sh.actions = append(sh.actions, "handler")
    91  	if sh.handled != nil {
    92  		// Unlock while we are waiting for the send
    93  		sh.mu.Unlock()
    94  		sh.handled <- changes
    95  		sh.mu.Lock()
    96  	}
    97  	return sh.handlerError
    98  }
    99  
   100  func (sh *stringsHandler) CheckActions(c *gc.C, actions ...string) {
   101  	sh.mu.Lock()
   102  	defer sh.mu.Unlock()
   103  	c.Check(sh.actions, gc.DeepEquals, actions)
   104  }
   105  
   106  // During teardown we try to stop the worker, but don't hang the test suite if
   107  // Stop never returns
   108  func (s *stringsWorkerSuite) stopWorker(c *gc.C) {
   109  	if s.worker == nil {
   110  		return
   111  	}
   112  	done := make(chan error)
   113  	go func() {
   114  		done <- worker.Stop(s.worker)
   115  	}()
   116  	err := waitForTimeout(c, done, coretesting.LongWait)
   117  	c.Check(err, jc.ErrorIsNil)
   118  	s.actor = nil
   119  	s.worker = nil
   120  }
   121  
   122  func newTestStringsWatcher() *testStringsWatcher {
   123  	w := &testStringsWatcher{
   124  		changes: make(chan []string),
   125  	}
   126  	w.tomb.Go(func() error {
   127  		<-w.tomb.Dying()
   128  		return nil
   129  	})
   130  	return w
   131  }
   132  
   133  type testStringsWatcher struct {
   134  	tomb      tomb.Tomb
   135  	changes   chan []string
   136  	mu        sync.Mutex
   137  	stopError error
   138  }
   139  
   140  func (tsw *testStringsWatcher) Changes() watcher.StringsChannel {
   141  	return tsw.changes
   142  }
   143  
   144  func (tsw *testStringsWatcher) Kill() {
   145  	tsw.mu.Lock()
   146  	tsw.tomb.Kill(tsw.stopError)
   147  	tsw.mu.Unlock()
   148  }
   149  
   150  func (tsw *testStringsWatcher) Wait() error {
   151  	return tsw.tomb.Wait()
   152  }
   153  
   154  func (tsw *testStringsWatcher) Stopped() bool {
   155  	select {
   156  	case <-tsw.tomb.Dead():
   157  		return true
   158  	default:
   159  		return false
   160  	}
   161  }
   162  
   163  func (tsw *testStringsWatcher) SetStopError(err error) {
   164  	tsw.mu.Lock()
   165  	tsw.stopError = err
   166  	tsw.mu.Unlock()
   167  }
   168  
   169  func (tsw *testStringsWatcher) TriggerChange(c *gc.C, changes []string) {
   170  	select {
   171  	case tsw.changes <- changes:
   172  	case <-time.After(coretesting.LongWait):
   173  		c.Errorf("timed out trying to trigger a change")
   174  	}
   175  }
   176  
   177  func waitForHandledStrings(c *gc.C, handled chan []string, expect []string) {
   178  	select {
   179  	case changes := <-handled:
   180  		c.Assert(changes, gc.DeepEquals, expect)
   181  	case <-time.After(coretesting.LongWait):
   182  		c.Errorf("handled failed to signal after %s", coretesting.LongWait)
   183  	}
   184  }
   185  
   186  func (s *stringsWorkerSuite) TestKill(c *gc.C) {
   187  	s.worker.Kill()
   188  	err := waitShort(c, s.worker)
   189  	c.Assert(err, jc.ErrorIsNil)
   190  }
   191  
   192  func (s *stringsWorkerSuite) TestStop(c *gc.C) {
   193  	err := worker.Stop(s.worker)
   194  	c.Assert(err, jc.ErrorIsNil)
   195  	// After stop, Wait should return right away
   196  	err = waitShort(c, s.worker)
   197  	c.Assert(err, jc.ErrorIsNil)
   198  }
   199  
   200  func (s *stringsWorkerSuite) TestWait(c *gc.C) {
   201  	done := make(chan error)
   202  	go func() {
   203  		done <- s.worker.Wait()
   204  	}()
   205  	// Wait should not return until we've killed the worker
   206  	select {
   207  	case err := <-done:
   208  		c.Errorf("Wait() didn't wait until we stopped it: %v", err)
   209  	case <-time.After(coretesting.ShortWait):
   210  	}
   211  	s.worker.Kill()
   212  	err := waitForTimeout(c, done, coretesting.LongWait)
   213  	c.Assert(err, jc.ErrorIsNil)
   214  }
   215  
   216  func (s *stringsWorkerSuite) TestCallSetUpAndTearDown(c *gc.C) {
   217  	// After calling NewStringsWorker, we should have called setup
   218  	s.actor.CheckActions(c, "setup")
   219  	// If we kill the worker, it should notice, and call teardown
   220  	s.worker.Kill()
   221  	err := waitShort(c, s.worker)
   222  	c.Check(err, jc.ErrorIsNil)
   223  	s.actor.CheckActions(c, "setup", "teardown")
   224  	c.Check(s.actor.watcher.Stopped(), jc.IsTrue)
   225  }
   226  
   227  func (s *stringsWorkerSuite) TestChangesTriggerHandler(c *gc.C) {
   228  	s.actor.CheckActions(c, "setup")
   229  	s.actor.watcher.TriggerChange(c, []string{"aa", "bb"})
   230  	waitForHandledStrings(c, s.actor.handled, []string{"aa", "bb"})
   231  	s.actor.CheckActions(c, "setup", "handler")
   232  	s.actor.watcher.TriggerChange(c, []string{"cc", "dd"})
   233  	waitForHandledStrings(c, s.actor.handled, []string{"cc", "dd"})
   234  	s.actor.watcher.TriggerChange(c, []string{"ee", "ff"})
   235  	waitForHandledStrings(c, s.actor.handled, []string{"ee", "ff"})
   236  	s.actor.CheckActions(c, "setup", "handler", "handler", "handler")
   237  	c.Assert(worker.Stop(s.worker), gc.IsNil)
   238  	s.actor.CheckActions(c, "setup", "handler", "handler", "handler", "teardown")
   239  }
   240  
   241  func (s *stringsWorkerSuite) TestSetUpFailureStopsWithTearDown(c *gc.C) {
   242  	// Stop the worker and SetUp again, this time with an error
   243  	s.stopWorker(c)
   244  	actor, w := newStringsHandlerWorker(c, errors.New("my special error"), nil, nil)
   245  	err := waitShort(c, w)
   246  	c.Check(err, gc.ErrorMatches, "my special error")
   247  	actor.CheckActions(c, "setup", "teardown")
   248  	c.Check(actor.watcher.Stopped(), jc.IsTrue)
   249  }
   250  
   251  func (s *stringsWorkerSuite) TestWatcherStopFailurePropagates(c *gc.C) {
   252  	s.actor.watcher.SetStopError(errors.New("error while stopping watcher"))
   253  	s.worker.Kill()
   254  	c.Assert(s.worker.Wait(), gc.ErrorMatches, "error while stopping watcher")
   255  	// We've already stopped the worker, don't let teardown notice the
   256  	// worker is in an error state
   257  	s.worker = nil
   258  }
   259  
   260  func (s *stringsWorkerSuite) TestCleanRunNoticesTearDownError(c *gc.C) {
   261  	s.actor.teardownError = errors.New("failed to tear down watcher")
   262  	s.worker.Kill()
   263  	c.Assert(s.worker.Wait(), gc.ErrorMatches, "failed to tear down watcher")
   264  	s.worker = nil
   265  }
   266  
   267  func (s *stringsWorkerSuite) TestHandleErrorStopsWorkerAndWatcher(c *gc.C) {
   268  	s.stopWorker(c)
   269  	actor, w := newStringsHandlerWorker(c, nil, errors.New("my handling error"), nil)
   270  	actor.watcher.TriggerChange(c, []string{"aa", "bb"})
   271  	waitForHandledStrings(c, actor.handled, []string{"aa", "bb"})
   272  	err := waitShort(c, w)
   273  	c.Check(err, gc.ErrorMatches, "my handling error")
   274  	actor.CheckActions(c, "setup", "handler", "teardown")
   275  	c.Check(actor.watcher.Stopped(), jc.IsTrue)
   276  }
   277  
   278  func (s *stringsWorkerSuite) TestNoticesStoppedWatcher(c *gc.C) {
   279  	// The default closedHandler doesn't panic if you have a genuine error
   280  	// (because it assumes you want to propagate a real error and then
   281  	// restart
   282  	s.actor.watcher.SetStopError(errors.New("Stopped Watcher"))
   283  	s.actor.watcher.Kill()
   284  	err := waitShort(c, s.worker)
   285  	c.Check(err, gc.ErrorMatches, "Stopped Watcher")
   286  	s.actor.CheckActions(c, "setup", "teardown")
   287  	// Worker is stopped, don't fail TearDownTest
   288  	s.worker = nil
   289  }
   290  
   291  func (s *stringsWorkerSuite) TestErrorsOnClosedChannel(c *gc.C) {
   292  	close(s.actor.watcher.changes)
   293  	err := waitShort(c, s.worker)
   294  	c.Check(err, gc.ErrorMatches, "change channel closed")
   295  	s.actor.CheckActions(c, "setup", "teardown")
   296  	s.worker = nil
   297  }