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