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