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(¬ifyWorkerSuite{}) 27 28 func newNotifyHandlerWorker(c *gc.C, setupError, handlerError, teardownError error) (*notifyHandler, worker.Worker) { 29 nh := ¬ifyHandler{ 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 }