github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/state/workers/fixture_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package workers_test 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 15 "github.com/juju/juju/state/workers" 16 jujutesting "github.com/juju/juju/testing" 17 "github.com/juju/juju/worker" 18 "github.com/juju/juju/worker/workertest" 19 ) 20 21 const ( 22 fiveSeconds = 5 * time.Second 23 almostFiveSeconds = fiveSeconds - time.Nanosecond 24 ) 25 26 // Context gives a test func access to the harness driving the Workers 27 // implementation under test. 28 type Context interface { 29 Clock() *testing.Clock 30 Factory() workers.Factory 31 LWs() <-chan worker.Worker 32 SWs() <-chan worker.Worker 33 TLWs() <-chan worker.Worker 34 PWs() <-chan worker.Worker 35 } 36 37 // NextWorker reads a worker from the supplied channel and returns it, 38 // or times out. The result might be nil. 39 func NextWorker(c *gc.C, ch <-chan worker.Worker) worker.Worker { 40 select { 41 case worker := <-ch: 42 return worker 43 case <-time.After(jujutesting.LongWait): 44 c.Fatalf("expected worker never started") 45 } 46 panic("unreachable") // I hate doing this :-|. 47 } 48 49 // ErrFailStart can be used in any of a Fixture's *_errors fields to 50 // indicate that we should fail to start a worker. 51 var ErrFailStart = errors.New("test control value, should not be seen") 52 53 // BasicFixture returns a Fixture that expects each worker to be started 54 // once only, and to stop without error. 55 func BasicFixture() Fixture { 56 return Fixture{ 57 LW_errors: []error{nil}, 58 SW_errors: []error{nil}, 59 TLW_errors: []error{nil}, 60 PW_errors: []error{nil}, 61 } 62 } 63 64 // Fixture allows you to run tests against a DumbWorkers or a 65 // RestartWorkers by specifying a list of errors per worker. 66 type Fixture struct { 67 LW_errors []error 68 SW_errors []error 69 TLW_errors []error 70 PW_errors []error 71 } 72 73 // Run runs a test func inside a fresh Context. 74 func (fix Fixture) Run(c *gc.C, test func(Context)) { 75 ctx := fix.newContext() 76 defer ctx.cleanup(c) 77 test(ctx) 78 } 79 80 // RunDumb starts a DumbWorkers inside a fresh Context and supplies it 81 // to a test func. 82 func (fix Fixture) RunDumb(c *gc.C, test func(Context, *workers.DumbWorkers)) { 83 fix.Run(c, func(ctx Context) { 84 dw, err := workers.NewDumbWorkers(workers.DumbConfig{ 85 Factory: ctx.Factory(), 86 Logger: loggo.GetLogger("test"), 87 }) 88 c.Assert(err, jc.ErrorIsNil) 89 defer workertest.DirtyKill(c, dw) 90 test(ctx, dw) 91 }) 92 } 93 94 // FailDumb verifies that a DumbWorkers cannot start successfully, and 95 // checks that the returned error matches. 96 func (fix Fixture) FailDumb(c *gc.C, match string) { 97 fix.Run(c, func(ctx Context) { 98 dw, err := workers.NewDumbWorkers(workers.DumbConfig{ 99 Factory: ctx.Factory(), 100 Logger: loggo.GetLogger("test"), 101 }) 102 if !c.Check(dw, gc.IsNil) { 103 workertest.DirtyKill(c, dw) 104 } 105 c.Check(err, gc.ErrorMatches, match) 106 }) 107 } 108 109 // RunRestart starts a RestartWorkers inside a fresh Context and 110 // supplies it to a test func. 111 func (fix Fixture) RunRestart(c *gc.C, test func(Context, *workers.RestartWorkers)) { 112 fix.Run(c, func(ctx Context) { 113 rw, err := workers.NewRestartWorkers(workers.RestartConfig{ 114 Factory: ctx.Factory(), 115 Logger: loggo.GetLogger("test"), 116 Clock: ctx.Clock(), 117 Delay: fiveSeconds, 118 }) 119 c.Assert(err, jc.ErrorIsNil) 120 defer workertest.DirtyKill(c, rw) 121 test(ctx, rw) 122 }) 123 } 124 125 // FailRestart verifies that a RestartWorkers cannot start successfully, and 126 // checks that the returned error matches. 127 func (fix Fixture) FailRestart(c *gc.C, match string) { 128 fix.Run(c, func(ctx Context) { 129 rw, err := workers.NewRestartWorkers(workers.RestartConfig{ 130 Factory: ctx.Factory(), 131 Logger: loggo.GetLogger("test"), 132 Clock: ctx.Clock(), 133 Delay: fiveSeconds, 134 }) 135 if !c.Check(rw, gc.IsNil) { 136 workertest.DirtyKill(c, rw) 137 } 138 c.Check(err, gc.ErrorMatches, match) 139 }) 140 } 141 142 func (fix Fixture) newContext() *context { 143 return &context{ 144 clock: testing.NewClock(time.Now()), 145 lwList: newWorkerList(fix.LW_errors), 146 swList: newWorkerList(fix.SW_errors), 147 tlwList: newWorkerList(fix.TLW_errors), 148 pwList: newWorkerList(fix.PW_errors), 149 } 150 } 151 152 // newWorkerList converts the supplied errors into a list of workers to 153 // be returned in order from the result's Next func (at which point they 154 // are sent on the reports chan as well). 155 func newWorkerList(errs []error) *workerList { 156 count := len(errs) 157 reports := make(chan worker.Worker, count) 158 workers := make([]worker.Worker, count) 159 for i, err := range errs { 160 if err == ErrFailStart { 161 workers[i] = nil 162 } else { 163 workers[i] = workertest.NewErrorWorker(err) 164 } 165 } 166 return &workerList{ 167 workers: workers, 168 reports: reports, 169 } 170 } 171 172 type workerList struct { 173 next int 174 workers []worker.Worker 175 reports chan worker.Worker 176 } 177 178 // Next starts and returns the next configured worker, or an error. 179 // In either case, a value is sent on the worker channel. 180 func (wl *workerList) Next() (worker.Worker, error) { 181 worker := wl.workers[wl.next] 182 wl.next++ 183 wl.reports <- worker 184 if worker == nil { 185 return nil, errors.New("bad start") 186 } 187 return worker, nil 188 } 189 190 // cleanup checks that every expected worker has already been stopped by 191 // the SUT. (i.e.: don't set up more workers than your fixture needs). 192 func (wl *workerList) cleanup(c *gc.C) { 193 for _, w := range wl.workers { 194 if w != nil { 195 workertest.CheckKilled(c, w) 196 } 197 } 198 } 199 200 // context implements Context. 201 type context struct { 202 clock *testing.Clock 203 lwList *workerList 204 swList *workerList 205 tlwList *workerList 206 pwList *workerList 207 } 208 209 func (ctx *context) cleanup(c *gc.C) { 210 c.Logf("cleaning up test context") 211 for _, list := range []*workerList{ 212 ctx.lwList, 213 ctx.swList, 214 ctx.tlwList, 215 ctx.pwList, 216 } { 217 list.cleanup(c) 218 } 219 } 220 221 func (ctx *context) LWs() <-chan worker.Worker { 222 return ctx.lwList.reports 223 } 224 225 func (ctx *context) SWs() <-chan worker.Worker { 226 return ctx.swList.reports 227 } 228 229 func (ctx *context) TLWs() <-chan worker.Worker { 230 return ctx.tlwList.reports 231 } 232 233 func (ctx *context) PWs() <-chan worker.Worker { 234 return ctx.pwList.reports 235 } 236 237 func (ctx *context) Clock() *testing.Clock { 238 return ctx.clock 239 } 240 241 func (ctx *context) Factory() workers.Factory { 242 return &factory{ctx} 243 } 244 245 // factory implements workers.Factory for the convenience of the tests. 246 type factory struct { 247 ctx *context 248 } 249 250 func (f *factory) NewLeadershipWorker() (workers.LeaseWorker, error) { 251 worker, err := f.ctx.lwList.Next() 252 if err != nil { 253 return nil, err 254 } 255 return fakeLeaseWorker{Worker: worker}, nil 256 } 257 258 func (f *factory) NewSingularWorker() (workers.LeaseWorker, error) { 259 worker, err := f.ctx.swList.Next() 260 if err != nil { 261 return nil, err 262 } 263 return fakeLeaseWorker{Worker: worker}, nil 264 } 265 266 func (f *factory) NewTxnLogWorker() (workers.TxnLogWorker, error) { 267 worker, err := f.ctx.tlwList.Next() 268 if err != nil { 269 return nil, err 270 } 271 return fakeTxnLogWorker{Worker: worker}, nil 272 } 273 274 func (f *factory) NewPresenceWorker() (workers.PresenceWorker, error) { 275 worker, err := f.ctx.pwList.Next() 276 if err != nil { 277 return nil, err 278 } 279 return fakePresenceWorker{Worker: worker}, nil 280 } 281 282 type fakeLeaseWorker struct { 283 worker.Worker 284 workers.LeaseManager 285 } 286 287 type fakeTxnLogWorker struct { 288 worker.Worker 289 workers.TxnLogWatcher 290 } 291 292 type fakePresenceWorker struct { 293 worker.Worker 294 workers.PresenceWatcher 295 } 296 297 // IsWorker returns true if `wrapped` is one of the above fake*Worker 298 // types (as returned by the factory methods) and also wraps the 299 // `expect` worker. 300 func IsWorker(wrapped interface{}, expect worker.Worker) bool { 301 if w, ok := wrapped.(workers.DynamicLeaseManager); ok { 302 wrapped = w.Underlying() 303 } 304 var actual worker.Worker 305 switch wrapped := wrapped.(type) { 306 case fakeLeaseWorker: 307 actual = wrapped.Worker 308 case fakeTxnLogWorker: 309 actual = wrapped.Worker 310 case fakePresenceWorker: 311 actual = wrapped.Worker 312 default: 313 return false 314 } 315 return actual == expect 316 } 317 318 // AssertWorker fails if IsWorker returns false. 319 func AssertWorker(c *gc.C, wrapped interface{}, expect worker.Worker) { 320 c.Assert(IsWorker(wrapped, expect), jc.IsTrue) 321 } 322 323 func LM_getter(w workers.Workers) func() interface{} { 324 return func() interface{} { return w.LeadershipManager() } 325 } 326 327 func SM_getter(w workers.Workers) func() interface{} { 328 return func() interface{} { return w.SingularManager() } 329 } 330 331 func TLW_getter(w workers.Workers) func() interface{} { 332 return func() interface{} { return w.TxnLogWatcher() } 333 } 334 335 func PW_getter(w workers.Workers) func() interface{} { 336 return func() interface{} { return w.PresenceWatcher() } 337 } 338 339 // WaitWorker blocks until getter returns something that satifies 340 // IsWorker, or until it times out. 341 func WaitWorker(c *gc.C, getter func() interface{}, expect worker.Worker) { 342 var delay time.Duration 343 timeout := time.After(jujutesting.LongWait) 344 for { 345 select { 346 case <-timeout: 347 c.Fatalf("expected worker") 348 case <-time.After(delay): 349 delay = jujutesting.ShortWait 350 } 351 if IsWorker(getter(), expect) { 352 return 353 } 354 } 355 } 356 357 // WaitAlarms waits until the supplied clock has sent count values on 358 // its Alarms channel. 359 func WaitAlarms(c *gc.C, clock *testing.Clock, count int) { 360 timeout := time.After(jujutesting.LongWait) 361 for i := 0; i < count; i++ { 362 select { 363 case <-timeout: 364 c.Fatalf("never saw alarm %d", i) 365 case <-clock.Alarms(): 366 } 367 } 368 }