github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 (s *notifyWorkerSuite) SetUpTest(c *gc.C) { 30 s.BaseSuite.SetUpTest(c) 31 s.actor = ¬ifyHandler{ 32 actions: nil, 33 handled: make(chan struct{}, 1), 34 watcher: &testNotifyWatcher{ 35 changes: make(chan struct{}), 36 }, 37 } 38 s.worker = worker.NewNotifyWorker(s.actor) 39 } 40 41 func (s *notifyWorkerSuite) TearDownTest(c *gc.C) { 42 worker.SetEnsureErr(nil) 43 s.stopWorker(c) 44 s.BaseSuite.TearDownTest(c) 45 } 46 47 type notifyHandler struct { 48 actions []string 49 mu sync.Mutex 50 // Signal handled when we get a handle() call 51 handled chan struct{} 52 setupError error 53 teardownError error 54 handlerError error 55 watcher *testNotifyWatcher 56 } 57 58 var _ worker.NotifyWatchHandler = (*notifyHandler)(nil) 59 60 func (nh *notifyHandler) SetUp() (apiWatcher.NotifyWatcher, error) { 61 nh.mu.Lock() 62 defer nh.mu.Unlock() 63 nh.actions = append(nh.actions, "setup") 64 if nh.watcher == nil { 65 return nil, nh.setupError 66 } 67 return nh.watcher, nh.setupError 68 } 69 70 func (nh *notifyHandler) TearDown() error { 71 nh.mu.Lock() 72 defer nh.mu.Unlock() 73 nh.actions = append(nh.actions, "teardown") 74 if nh.handled != nil { 75 close(nh.handled) 76 } 77 return nh.teardownError 78 } 79 80 func (nh *notifyHandler) Handle() error { 81 nh.mu.Lock() 82 defer nh.mu.Unlock() 83 nh.actions = append(nh.actions, "handler") 84 if nh.handled != nil { 85 // Unlock while we are waiting for the send 86 nh.mu.Unlock() 87 nh.handled <- struct{}{} 88 nh.mu.Lock() 89 } 90 return nh.handlerError 91 } 92 93 func (nh *notifyHandler) CheckActions(c *gc.C, actions ...string) { 94 nh.mu.Lock() 95 defer nh.mu.Unlock() 96 c.Check(nh.actions, gc.DeepEquals, actions) 97 } 98 99 // During teardown we try to stop the worker, but don't hang the test suite if 100 // Stop never returns 101 func (s *notifyWorkerSuite) stopWorker(c *gc.C) { 102 if s.worker == nil { 103 return 104 } 105 done := make(chan error) 106 go func() { 107 done <- worker.Stop(s.worker) 108 }() 109 err := waitForTimeout(c, done, coretesting.LongWait) 110 c.Check(err, jc.ErrorIsNil) 111 s.actor = nil 112 s.worker = nil 113 } 114 115 type testNotifyWatcher struct { 116 mu sync.Mutex 117 changes chan struct{} 118 stopped bool 119 stopError error 120 } 121 122 var _ apiWatcher.NotifyWatcher = (*testNotifyWatcher)(nil) 123 124 func (tnw *testNotifyWatcher) Changes() <-chan struct{} { 125 return tnw.changes 126 } 127 128 func (tnw *testNotifyWatcher) Err() error { 129 return tnw.stopError 130 } 131 132 func (tnw *testNotifyWatcher) Stop() error { 133 tnw.mu.Lock() 134 defer tnw.mu.Unlock() 135 if !tnw.stopped { 136 close(tnw.changes) 137 } 138 tnw.stopped = true 139 return tnw.stopError 140 } 141 142 func (tnw *testNotifyWatcher) SetStopError(err error) { 143 tnw.mu.Lock() 144 tnw.stopError = err 145 tnw.mu.Unlock() 146 } 147 148 func (tnw *testNotifyWatcher) TriggerChange(c *gc.C) { 149 select { 150 case tnw.changes <- struct{}{}: 151 case <-time.After(coretesting.LongWait): 152 c.Errorf("timed out trying to trigger a change") 153 } 154 } 155 156 func waitForTimeout(c *gc.C, ch <-chan error, timeout time.Duration) error { 157 select { 158 case err := <-ch: 159 return err 160 case <-time.After(timeout): 161 c.Errorf("timed out waiting to receive a change after %s", timeout) 162 } 163 return nil 164 } 165 166 func waitShort(c *gc.C, w worker.Worker) error { 167 done := make(chan error) 168 go func() { 169 done <- w.Wait() 170 }() 171 return waitForTimeout(c, done, coretesting.ShortWait) 172 } 173 174 func waitForHandledNotify(c *gc.C, handled chan struct{}) { 175 select { 176 case <-handled: 177 case <-time.After(coretesting.LongWait): 178 c.Errorf("handled failed to signal after %s", coretesting.LongWait) 179 } 180 } 181 182 func (s *notifyWorkerSuite) TestKill(c *gc.C) { 183 s.worker.Kill() 184 err := waitShort(c, s.worker) 185 c.Assert(err, jc.ErrorIsNil) 186 } 187 188 func (s *notifyWorkerSuite) TestStop(c *gc.C) { 189 err := worker.Stop(s.worker) 190 c.Assert(err, jc.ErrorIsNil) 191 // After stop, Wait should return right away 192 err = waitShort(c, s.worker) 193 c.Assert(err, jc.ErrorIsNil) 194 } 195 196 func (s *notifyWorkerSuite) TestWait(c *gc.C) { 197 done := make(chan error) 198 go func() { 199 done <- s.worker.Wait() 200 }() 201 // Wait should not return until we've killed the worker 202 select { 203 case err := <-done: 204 c.Errorf("Wait() didn't wait until we stopped it: %v", err) 205 case <-time.After(coretesting.ShortWait): 206 } 207 s.worker.Kill() 208 err := waitForTimeout(c, done, coretesting.LongWait) 209 c.Assert(err, jc.ErrorIsNil) 210 } 211 212 func (s *notifyWorkerSuite) TestCallSetUpAndTearDown(c *gc.C) { 213 // After calling NewNotifyWorker, we should have called setup 214 s.actor.CheckActions(c, "setup") 215 // If we kill the worker, it should notice, and call teardown 216 s.worker.Kill() 217 err := waitShort(c, s.worker) 218 c.Check(err, jc.ErrorIsNil) 219 s.actor.CheckActions(c, "setup", "teardown") 220 c.Check(s.actor.watcher.stopped, jc.IsTrue) 221 } 222 223 func (s *notifyWorkerSuite) TestChangesTriggerHandler(c *gc.C) { 224 s.actor.CheckActions(c, "setup") 225 s.actor.watcher.TriggerChange(c) 226 waitForHandledNotify(c, s.actor.handled) 227 s.actor.CheckActions(c, "setup", "handler") 228 s.actor.watcher.TriggerChange(c) 229 waitForHandledNotify(c, s.actor.handled) 230 s.actor.watcher.TriggerChange(c) 231 waitForHandledNotify(c, s.actor.handled) 232 s.actor.CheckActions(c, "setup", "handler", "handler", "handler") 233 c.Assert(worker.Stop(s.worker), gc.IsNil) 234 s.actor.CheckActions(c, "setup", "handler", "handler", "handler", "teardown") 235 } 236 237 func (s *notifyWorkerSuite) TestSetUpFailureStopsWithTearDown(c *gc.C) { 238 // Stop the worker and SetUp again, this time with an error 239 s.stopWorker(c) 240 actor := ¬ifyHandler{ 241 actions: nil, 242 handled: make(chan struct{}, 1), 243 setupError: fmt.Errorf("my special error"), 244 watcher: &testNotifyWatcher{ 245 changes: make(chan struct{}), 246 }, 247 } 248 w := worker.NewNotifyWorker(actor) 249 err := waitShort(c, w) 250 c.Check(err, gc.ErrorMatches, "my special error") 251 // TearDown is not called on SetUp error. 252 actor.CheckActions(c, "setup") 253 c.Check(actor.watcher.stopped, jc.IsTrue) 254 } 255 256 func (s *notifyWorkerSuite) TestWatcherStopFailurePropagates(c *gc.C) { 257 s.actor.watcher.SetStopError(fmt.Errorf("error while stopping watcher")) 258 s.worker.Kill() 259 c.Assert(s.worker.Wait(), gc.ErrorMatches, "error while stopping watcher") 260 // We've already stopped the worker, don't let teardown notice the 261 // worker is in an error state 262 s.worker = nil 263 } 264 265 func (s *notifyWorkerSuite) TestCleanRunNoticesTearDownError(c *gc.C) { 266 s.actor.teardownError = fmt.Errorf("failed to tear down watcher") 267 s.worker.Kill() 268 c.Assert(s.worker.Wait(), gc.ErrorMatches, "failed to tear down watcher") 269 s.worker = nil 270 } 271 272 func (s *notifyWorkerSuite) TestHandleErrorStopsWorkerAndWatcher(c *gc.C) { 273 s.stopWorker(c) 274 actor := ¬ifyHandler{ 275 actions: nil, 276 handled: make(chan struct{}, 1), 277 handlerError: fmt.Errorf("my handling error"), 278 watcher: &testNotifyWatcher{ 279 changes: make(chan struct{}), 280 }, 281 } 282 w := worker.NewNotifyWorker(actor) 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 }