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