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 }