~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/worker/dependency/engine_test.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package dependency_test
 
5
 
 
6
import (
 
7
        "time"
 
8
 
 
9
        "github.com/juju/errors"
 
10
        "github.com/juju/testing"
 
11
        jc "github.com/juju/testing/checkers"
 
12
        gc "gopkg.in/check.v1"
 
13
 
 
14
        coretesting "github.com/juju/juju/testing"
 
15
        "github.com/juju/juju/worker"
 
16
        "github.com/juju/juju/worker/dependency"
 
17
        "github.com/juju/juju/worker/workertest"
 
18
)
 
19
 
 
20
type EngineSuite struct {
 
21
        testing.IsolationSuite
 
22
        fix *engineFixture
 
23
}
 
24
 
 
25
var _ = gc.Suite(&EngineSuite{})
 
26
 
 
27
func (s *EngineSuite) SetUpTest(c *gc.C) {
 
28
        s.IsolationSuite.SetUpTest(c)
 
29
        s.fix = &engineFixture{}
 
30
}
 
31
 
 
32
func (s *EngineSuite) TestInstallConvenienceWrapper(c *gc.C) {
 
33
        s.fix.run(c, func(engine *dependency.Engine) {
 
34
                mh1 := newManifoldHarness()
 
35
                mh2 := newManifoldHarness()
 
36
                mh3 := newManifoldHarness()
 
37
 
 
38
                err := dependency.Install(engine, dependency.Manifolds{
 
39
                        "mh1": mh1.Manifold(),
 
40
                        "mh2": mh2.Manifold(),
 
41
                        "mh3": mh3.Manifold(),
 
42
                })
 
43
                c.Assert(err, jc.ErrorIsNil)
 
44
 
 
45
                mh1.AssertOneStart(c)
 
46
                mh2.AssertOneStart(c)
 
47
                mh3.AssertOneStart(c)
 
48
        })
 
49
}
 
50
 
 
51
func (s *EngineSuite) TestInstallNoInputs(c *gc.C) {
 
52
        s.fix.run(c, func(engine *dependency.Engine) {
 
53
 
 
54
                // Install a worker, check it starts.
 
55
                mh1 := newManifoldHarness()
 
56
                err := engine.Install("some-task", mh1.Manifold())
 
57
                c.Assert(err, jc.ErrorIsNil)
 
58
                mh1.AssertOneStart(c)
 
59
 
 
60
                // Install a second independent worker; check the first in untouched.
 
61
                mh2 := newManifoldHarness()
 
62
                err = engine.Install("other-task", mh2.Manifold())
 
63
                c.Assert(err, jc.ErrorIsNil)
 
64
                mh2.AssertOneStart(c)
 
65
                mh1.AssertNoStart(c)
 
66
        })
 
67
}
 
68
 
 
69
func (s *EngineSuite) TestInstallUnknownInputs(c *gc.C) {
 
70
        s.fix.run(c, func(engine *dependency.Engine) {
 
71
 
 
72
                // Install a worker with an unmet dependency, check it doesn't start
 
73
                // (because the implementation returns ErrMissing).
 
74
                mh1 := newManifoldHarness("later-task")
 
75
                err := engine.Install("some-task", mh1.Manifold())
 
76
                c.Assert(err, jc.ErrorIsNil)
 
77
                mh1.AssertNoStart(c)
 
78
 
 
79
                // Install its dependency; check both start.
 
80
                mh2 := newManifoldHarness()
 
81
                err = engine.Install("later-task", mh2.Manifold())
 
82
                c.Assert(err, jc.ErrorIsNil)
 
83
                mh2.AssertOneStart(c)
 
84
                mh1.AssertOneStart(c)
 
85
        })
 
86
}
 
87
 
 
88
func (s *EngineSuite) TestDoubleInstall(c *gc.C) {
 
89
        s.fix.run(c, func(engine *dependency.Engine) {
 
90
 
 
91
                // Install a worker.
 
92
                mh := newManifoldHarness()
 
93
                err := engine.Install("some-task", mh.Manifold())
 
94
                c.Assert(err, jc.ErrorIsNil)
 
95
                mh.AssertOneStart(c)
 
96
 
 
97
                // Can't install another worker with the same name.
 
98
                err = engine.Install("some-task", mh.Manifold())
 
99
                c.Assert(err, gc.ErrorMatches, `"some-task" manifold already installed`)
 
100
                mh.AssertNoStart(c)
 
101
        })
 
102
}
 
103
 
 
104
func (s *EngineSuite) TestInstallCycle(c *gc.C) {
 
105
        s.fix.run(c, func(engine *dependency.Engine) {
 
106
 
 
107
                // Install a worker with an unmet dependency.
 
108
                mh1 := newManifoldHarness("robin-hood")
 
109
                err := engine.Install("friar-tuck", mh1.Manifold())
 
110
                c.Assert(err, jc.ErrorIsNil)
 
111
                mh1.AssertNoStart(c)
 
112
 
 
113
                // Can't install another worker that creates a dependency cycle.
 
114
                mh2 := newManifoldHarness("friar-tuck")
 
115
                err = engine.Install("robin-hood", mh2.Manifold())
 
116
                c.Assert(err, gc.ErrorMatches, `cannot install "robin-hood" manifold: cycle detected at .*`)
 
117
                mh2.AssertNoStart(c)
 
118
        })
 
119
}
 
120
 
 
121
func (s *EngineSuite) TestInstallAlreadyStopped(c *gc.C) {
 
122
        s.fix.run(c, func(engine *dependency.Engine) {
 
123
 
 
124
                // Shut down the engine.
 
125
                err := worker.Stop(engine)
 
126
                c.Assert(err, jc.ErrorIsNil)
 
127
 
 
128
                // Can't start a new task.
 
129
                mh := newManifoldHarness()
 
130
                err = engine.Install("some-task", mh.Manifold())
 
131
                c.Assert(err, gc.ErrorMatches, "engine is shutting down")
 
132
                mh.AssertNoStart(c)
 
133
        })
 
134
}
 
135
 
 
136
func (s *EngineSuite) TestStartGetExistenceOnly(c *gc.C) {
 
137
        s.fix.run(c, func(engine *dependency.Engine) {
 
138
 
 
139
                // Start a task with a dependency.
 
140
                mh1 := newManifoldHarness()
 
141
                err := engine.Install("some-task", mh1.Manifold())
 
142
                c.Assert(err, jc.ErrorIsNil)
 
143
                mh1.AssertOneStart(c)
 
144
 
 
145
                // Start another task that depends on it, ourselves depending on the
 
146
                // implementation of manifoldHarness, which calls Get(foo, nil).
 
147
                mh2 := newManifoldHarness("some-task")
 
148
                err = engine.Install("other-task", mh2.Manifold())
 
149
                c.Assert(err, jc.ErrorIsNil)
 
150
                mh2.AssertOneStart(c)
 
151
        })
 
152
}
 
153
 
 
154
func (s *EngineSuite) TestStartGetUndeclaredName(c *gc.C) {
 
155
        s.fix.run(c, func(engine *dependency.Engine) {
 
156
 
 
157
                // Install a task and make sure it's started.
 
158
                mh1 := newManifoldHarness()
 
159
                err := engine.Install("some-task", mh1.Manifold())
 
160
                c.Assert(err, jc.ErrorIsNil)
 
161
                mh1.AssertOneStart(c)
 
162
 
 
163
                // Install another task with an undeclared dependency on the started task.
 
164
                done := make(chan struct{})
 
165
                err = engine.Install("other-task", dependency.Manifold{
 
166
                        Start: func(context dependency.Context) (worker.Worker, error) {
 
167
                                err := context.Get("some-task", nil)
 
168
                                c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing)
 
169
                                c.Check(err, gc.ErrorMatches, `"some-task" not declared: dependency not available`)
 
170
                                close(done)
 
171
                                // Return a real worker so we don't keep restarting and potentially double-closing.
 
172
                                return startMinimalWorker(context)
 
173
                        },
 
174
                })
 
175
                c.Assert(err, jc.ErrorIsNil)
 
176
 
 
177
                // Wait for the check to complete before we stop.
 
178
                select {
 
179
                case <-done:
 
180
                case <-time.After(coretesting.LongWait):
 
181
                        c.Fatalf("dependent task never started")
 
182
                }
 
183
        })
 
184
}
 
185
 
 
186
func (s *EngineSuite) testStartGet(c *gc.C, outErr error) {
 
187
        s.fix.run(c, func(engine *dependency.Engine) {
 
188
 
 
189
                // Start a task with an Output func that checks what it's passed, and wait for it to start.
 
190
                var target interface{}
 
191
                expectTarget := &target
 
192
                mh1 := newManifoldHarness()
 
193
                manifold := mh1.Manifold()
 
194
                manifold.Output = func(worker worker.Worker, target interface{}) error {
 
195
                        // Check we got passed what we expect regardless...
 
196
                        c.Check(target, gc.DeepEquals, expectTarget)
 
197
                        // ...and return the configured error.
 
198
                        return outErr
 
199
                }
 
200
                err := engine.Install("some-task", manifold)
 
201
                c.Assert(err, jc.ErrorIsNil)
 
202
                mh1.AssertOneStart(c)
 
203
 
 
204
                // Start another that tries to use the above dependency.
 
205
                done := make(chan struct{})
 
206
                err = engine.Install("other-task", dependency.Manifold{
 
207
                        Inputs: []string{"some-task"},
 
208
                        Start: func(context dependency.Context) (worker.Worker, error) {
 
209
                                err := context.Get("some-task", &target)
 
210
                                // Check the result from some-task's Output func matches what we expect.
 
211
                                c.Check(err, gc.Equals, outErr)
 
212
                                close(done)
 
213
                                // Return a real worker so we don't keep restarting and potentially double-closing.
 
214
                                return startMinimalWorker(context)
 
215
                        },
 
216
                })
 
217
                c.Check(err, jc.ErrorIsNil)
 
218
 
 
219
                // Wait for the check to complete before we stop.
 
220
                select {
 
221
                case <-done:
 
222
                case <-time.After(coretesting.LongWait):
 
223
                        c.Fatalf("other-task never started")
 
224
                }
 
225
        })
 
226
}
 
227
 
 
228
func (s *EngineSuite) TestStartGetAccept(c *gc.C) {
 
229
        s.testStartGet(c, nil)
 
230
}
 
231
 
 
232
func (s *EngineSuite) TestStartGetReject(c *gc.C) {
 
233
        s.testStartGet(c, errors.New("not good enough"))
 
234
}
 
235
 
 
236
func (s *EngineSuite) TestStartAbortOnEngineKill(c *gc.C) {
 
237
        s.fix.run(c, func(engine *dependency.Engine) {
 
238
                starts := make(chan struct{}, 1000)
 
239
                manifold := dependency.Manifold{
 
240
                        Start: func(context dependency.Context) (worker.Worker, error) {
 
241
                                starts <- struct{}{}
 
242
                                select {
 
243
                                case <-context.Abort():
 
244
                                case <-time.After(coretesting.LongWait):
 
245
                                        c.Errorf("timed out")
 
246
                                }
 
247
                                return nil, errors.New("whatever")
 
248
                        },
 
249
                }
 
250
                err := engine.Install("task", manifold)
 
251
                c.Assert(err, jc.ErrorIsNil)
 
252
 
 
253
                select {
 
254
                case <-starts:
 
255
                case <-time.After(coretesting.LongWait):
 
256
                        c.Fatalf("timed out")
 
257
                }
 
258
                workertest.CleanKill(c, engine)
 
259
 
 
260
                select {
 
261
                case <-starts:
 
262
                        c.Fatalf("unexpected start")
 
263
                default:
 
264
                }
 
265
        })
 
266
}
 
267
 
 
268
func (s *EngineSuite) TestStartAbortOnDependencyChange(c *gc.C) {
 
269
        s.fix.run(c, func(engine *dependency.Engine) {
 
270
                starts := make(chan struct{}, 1000)
 
271
                manifold := dependency.Manifold{
 
272
                        Inputs: []string{"parent"},
 
273
                        Start: func(context dependency.Context) (worker.Worker, error) {
 
274
                                starts <- struct{}{}
 
275
                                select {
 
276
                                case <-context.Abort():
 
277
                                case <-time.After(coretesting.LongWait):
 
278
                                        c.Errorf("timed out")
 
279
                                }
 
280
                                return nil, errors.New("whatever")
 
281
                        },
 
282
                }
 
283
                err := engine.Install("child", manifold)
 
284
                c.Assert(err, jc.ErrorIsNil)
 
285
 
 
286
                select {
 
287
                case <-starts:
 
288
                case <-time.After(coretesting.LongWait):
 
289
                        c.Fatalf("timed out")
 
290
                }
 
291
 
 
292
                mh := newManifoldHarness()
 
293
                err = engine.Install("parent", mh.Manifold())
 
294
                c.Assert(err, jc.ErrorIsNil)
 
295
                mh.AssertOneStart(c)
 
296
 
 
297
                select {
 
298
                case <-starts:
 
299
                case <-time.After(coretesting.LongWait):
 
300
                        c.Fatalf("timed out")
 
301
                }
 
302
                workertest.CleanKill(c, engine)
 
303
 
 
304
                select {
 
305
                case <-starts:
 
306
                        c.Fatalf("unexpected start")
 
307
                default:
 
308
                }
 
309
        })
 
310
}
 
311
 
 
312
func (s *EngineSuite) TestErrorRestartsDependents(c *gc.C) {
 
313
        s.fix.run(c, func(engine *dependency.Engine) {
 
314
 
 
315
                // Start two tasks, one dependent on the other.
 
316
                mh1 := newManifoldHarness()
 
317
                err := engine.Install("error-task", mh1.Manifold())
 
318
                c.Assert(err, jc.ErrorIsNil)
 
319
                mh1.AssertOneStart(c)
 
320
 
 
321
                mh2 := newManifoldHarness("error-task")
 
322
                err = engine.Install("some-task", mh2.Manifold())
 
323
                c.Assert(err, jc.ErrorIsNil)
 
324
                mh2.AssertOneStart(c)
 
325
 
 
326
                // Induce an error in the dependency...
 
327
                mh1.InjectError(c, errors.New("ZAP"))
 
328
 
 
329
                // ...and check that each task restarts once.
 
330
                mh1.AssertOneStart(c)
 
331
                mh2.AssertOneStart(c)
 
332
        })
 
333
}
 
334
 
 
335
func (s *EngineSuite) TestErrorPreservesDependencies(c *gc.C) {
 
336
        s.fix.run(c, func(engine *dependency.Engine) {
 
337
 
 
338
                // Start two tasks, one dependent on the other.
 
339
                mh1 := newManifoldHarness()
 
340
                err := engine.Install("some-task", mh1.Manifold())
 
341
                c.Assert(err, jc.ErrorIsNil)
 
342
                mh1.AssertOneStart(c)
 
343
                mh2 := newManifoldHarness("some-task")
 
344
                err = engine.Install("error-task", mh2.Manifold())
 
345
                c.Assert(err, jc.ErrorIsNil)
 
346
                mh2.AssertOneStart(c)
 
347
 
 
348
                // Induce an error in the dependent...
 
349
                mh2.InjectError(c, errors.New("BLAM"))
 
350
 
 
351
                // ...and check that only the dependent restarts.
 
352
                mh1.AssertNoStart(c)
 
353
                mh2.AssertOneStart(c)
 
354
        })
 
355
}
 
356
 
 
357
func (s *EngineSuite) TestCompletedWorkerNotRestartedOnExit(c *gc.C) {
 
358
        s.fix.run(c, func(engine *dependency.Engine) {
 
359
 
 
360
                // Start a task.
 
361
                mh1 := newManifoldHarness()
 
362
                err := engine.Install("stop-task", mh1.Manifold())
 
363
                c.Assert(err, jc.ErrorIsNil)
 
364
                mh1.AssertOneStart(c)
 
365
 
 
366
                // Stop it without error, and check it doesn't start again.
 
367
                mh1.InjectError(c, nil)
 
368
                mh1.AssertNoStart(c)
 
369
        })
 
370
}
 
371
 
 
372
func (s *EngineSuite) TestCompletedWorkerRestartedByDependencyChange(c *gc.C) {
 
373
        s.fix.run(c, func(engine *dependency.Engine) {
 
374
 
 
375
                // Start a task with a dependency.
 
376
                mh1 := newManifoldHarness()
 
377
                err := engine.Install("some-task", mh1.Manifold())
 
378
                c.Assert(err, jc.ErrorIsNil)
 
379
                mh1.AssertOneStart(c)
 
380
                mh2 := newManifoldHarness("some-task")
 
381
                err = engine.Install("stop-task", mh2.Manifold())
 
382
                c.Assert(err, jc.ErrorIsNil)
 
383
                mh2.AssertOneStart(c)
 
384
 
 
385
                // Complete the dependent task successfully.
 
386
                mh2.InjectError(c, nil)
 
387
                mh2.AssertNoStart(c)
 
388
 
 
389
                // Bounce the dependency, and check the dependent is started again.
 
390
                mh1.InjectError(c, errors.New("CLUNK"))
 
391
                mh1.AssertOneStart(c)
 
392
                mh2.AssertOneStart(c)
 
393
        })
 
394
}
 
395
 
 
396
func (s *EngineSuite) TestRestartRestartsDependents(c *gc.C) {
 
397
        s.fix.run(c, func(engine *dependency.Engine) {
 
398
 
 
399
                // Start a dependency chain of 3 workers.
 
400
                mh1 := newManifoldHarness()
 
401
                err := engine.Install("error-task", mh1.Manifold())
 
402
                c.Assert(err, jc.ErrorIsNil)
 
403
                mh1.AssertOneStart(c)
 
404
                mh2 := newManifoldHarness("error-task")
 
405
                err = engine.Install("restart-task", mh2.Manifold())
 
406
                c.Assert(err, jc.ErrorIsNil)
 
407
                mh2.AssertOneStart(c)
 
408
                mh3 := newManifoldHarness("restart-task")
 
409
                err = engine.Install("consequent-restart-task", mh3.Manifold())
 
410
                c.Assert(err, jc.ErrorIsNil)
 
411
                mh3.AssertOneStart(c)
 
412
 
 
413
                // Once they're all running, induce an error at the top level, which will
 
414
                // cause the next level to be killed cleanly....
 
415
                mh1.InjectError(c, errors.New("ZAP"))
 
416
 
 
417
                // ...but should still cause all 3 workers to bounce.
 
418
                mh1.AssertOneStart(c)
 
419
                mh2.AssertOneStart(c)
 
420
                mh3.AssertOneStart(c)
 
421
        })
 
422
}
 
423
 
 
424
func (s *EngineSuite) TestIsFatal(c *gc.C) {
 
425
        fatalErr := errors.New("KABOOM")
 
426
        s.fix.isFatal = isFatalIf(fatalErr)
 
427
        s.fix.dirty = true
 
428
        s.fix.run(c, func(engine *dependency.Engine) {
 
429
 
 
430
                // Start two independent workers.
 
431
                mh1 := newManifoldHarness()
 
432
                err := engine.Install("some-task", mh1.Manifold())
 
433
                c.Assert(err, jc.ErrorIsNil)
 
434
                mh1.AssertOneStart(c)
 
435
                mh2 := newManifoldHarness()
 
436
                err = engine.Install("other-task", mh2.Manifold())
 
437
                c.Assert(err, jc.ErrorIsNil)
 
438
                mh2.AssertOneStart(c)
 
439
 
 
440
                // Bounce one worker with Just Some Error; check that worker bounces.
 
441
                mh1.InjectError(c, errors.New("splort"))
 
442
                mh1.AssertOneStart(c)
 
443
                mh2.AssertNoStart(c)
 
444
 
 
445
                // Bounce another worker with the fatal error; check the engine exits with
 
446
                // the right error.
 
447
                mh2.InjectError(c, fatalErr)
 
448
                mh1.AssertNoStart(c)
 
449
                mh2.AssertNoStart(c)
 
450
                err = workertest.CheckKilled(c, engine)
 
451
                c.Assert(err, gc.Equals, fatalErr)
 
452
        })
 
453
}
 
454
 
 
455
func (s *EngineSuite) TestConfigFilter(c *gc.C) {
 
456
        fatalErr := errors.New("kerrang")
 
457
        s.fix.isFatal = isFatalIf(fatalErr)
 
458
        reportErr := errors.New("meedly-meedly")
 
459
        s.fix.filter = func(err error) error {
 
460
                c.Check(err, gc.Equals, fatalErr)
 
461
                return reportErr
 
462
        }
 
463
        s.fix.dirty = true
 
464
        s.fix.run(c, func(engine *dependency.Engine) {
 
465
 
 
466
                // Start a task.
 
467
                mh1 := newManifoldHarness()
 
468
                err := engine.Install("stop-task", mh1.Manifold())
 
469
                c.Assert(err, jc.ErrorIsNil)
 
470
                mh1.AssertOneStart(c)
 
471
 
 
472
                // Inject the fatal error, and check what comes out.
 
473
                mh1.InjectError(c, fatalErr)
 
474
                err = workertest.CheckKilled(c, engine)
 
475
                c.Assert(err, gc.Equals, reportErr)
 
476
        })
 
477
}
 
478
 
 
479
func (s *EngineSuite) TestErrMissing(c *gc.C) {
 
480
        s.fix.run(c, func(engine *dependency.Engine) {
 
481
 
 
482
                // ErrMissing is implicitly and indirectly tested by the default
 
483
                // manifoldHarness.start method throughout this suite, but this
 
484
                // test explores its behaviour in pathological cases.
 
485
 
 
486
                // Start a simple dependency.
 
487
                mh1 := newManifoldHarness()
 
488
                err := engine.Install("some-task", mh1.Manifold())
 
489
                c.Assert(err, jc.ErrorIsNil)
 
490
                mh1.AssertOneStart(c)
 
491
 
 
492
                // Start a dependent that always complains ErrMissing.
 
493
                mh2 := newManifoldHarness("some-task")
 
494
                manifold := mh2.Manifold()
 
495
                manifold.Start = func(_ dependency.Context) (worker.Worker, error) {
 
496
                        mh2.starts <- struct{}{}
 
497
                        return nil, errors.Trace(dependency.ErrMissing)
 
498
                }
 
499
                err = engine.Install("unmet-task", manifold)
 
500
                c.Assert(err, jc.ErrorIsNil)
 
501
                mh2.AssertOneStart(c)
 
502
 
 
503
                // Bounce the dependency; check the dependent bounces once or twice (it will
 
504
                // react to both the stop and the start of the dependency, but may be lucky
 
505
                // enough to only restart once).
 
506
                mh1.InjectError(c, errors.New("kerrang"))
 
507
                mh1.AssertOneStart(c)
 
508
                startCount := 0
 
509
                stable := false
 
510
                for !stable {
 
511
                        select {
 
512
                        case <-mh2.starts:
 
513
                                startCount++
 
514
                        case <-time.After(coretesting.ShortWait):
 
515
                                stable = true
 
516
                        }
 
517
                }
 
518
                c.Logf("saw %d starts", startCount)
 
519
                c.Assert(startCount, jc.GreaterThan, 0)
 
520
                c.Assert(startCount, jc.LessThan, 3)
 
521
 
 
522
                // Stop the dependency for good; check the dependent is restarted just once.
 
523
                mh1.InjectError(c, nil)
 
524
                mh1.AssertNoStart(c)
 
525
                mh2.AssertOneStart(c)
 
526
        })
 
527
}
 
528
 
 
529
func (s *EngineSuite) TestErrBounce(c *gc.C) {
 
530
        s.fix.run(c, func(engine *dependency.Engine) {
 
531
 
 
532
                // Start a simple dependency.
 
533
                mh1 := newManifoldHarness()
 
534
                err := engine.Install("some-task", mh1.Manifold())
 
535
                c.Assert(err, jc.ErrorIsNil)
 
536
                mh1.AssertOneStart(c)
 
537
 
 
538
                // Start its dependent.
 
539
                mh2 := newResourceIgnoringManifoldHarness("some-task")
 
540
                err = engine.Install("another-task", mh2.Manifold())
 
541
                c.Assert(err, jc.ErrorIsNil)
 
542
                mh2.AssertOneStart(c)
 
543
 
 
544
                // The parent requests bounce causing both to restart.
 
545
                // Note(mjs): the lack of a restart delay is not specifically
 
546
                // tested as I can't think of a reliable way to do this.
 
547
                // TODO(fwereade): yeah, we need a clock to test this
 
548
                // properly...
 
549
                mh1.InjectError(c, errors.Trace(dependency.ErrBounce))
 
550
                mh1.AssertOneStart(c)
 
551
                mh2.AssertStart(c) // Might restart more than once
 
552
        })
 
553
}
 
554
 
 
555
func (s *EngineSuite) TestErrUninstall(c *gc.C) {
 
556
        s.fix.run(c, func(engine *dependency.Engine) {
 
557
 
 
558
                // Start a simple dependency.
 
559
                mh1 := newManifoldHarness()
 
560
                err := engine.Install("some-task", mh1.Manifold())
 
561
                c.Assert(err, jc.ErrorIsNil)
 
562
                mh1.AssertOneStart(c)
 
563
 
 
564
                // Start its dependent. Note that in this case we want to record all start
 
565
                // attempts, even if there are resource errors.
 
566
                mh2 := newResourceIgnoringManifoldHarness("some-task")
 
567
                err = engine.Install("another-task", mh2.Manifold())
 
568
                c.Assert(err, jc.ErrorIsNil)
 
569
                mh2.AssertOneStart(c)
 
570
 
 
571
                // Uninstall the dependency; it should not be restarted, but its dependent should.
 
572
                mh1.InjectError(c, errors.Trace(dependency.ErrUninstall))
 
573
                mh1.AssertNoStart(c)
 
574
                mh2.AssertOneStart(c)
 
575
 
 
576
                // Installing a new some-task manifold restarts the dependent.
 
577
                mh3 := newManifoldHarness()
 
578
                err = engine.Install("some-task", mh3.Manifold())
 
579
                c.Assert(err, jc.ErrorIsNil)
 
580
                mh3.AssertOneStart(c)
 
581
                mh2.AssertOneStart(c)
 
582
        })
 
583
}
 
584
 
 
585
func (s *EngineSuite) TestFilterStartError(c *gc.C) {
 
586
        s.fix.isFatal = alwaysFatal
 
587
        s.fix.dirty = true
 
588
        s.fix.run(c, func(engine *dependency.Engine) {
 
589
 
 
590
                startErr := errors.New("grr crunch")
 
591
                filterErr := errors.New("mew hiss")
 
592
 
 
593
                err := engine.Install("task", dependency.Manifold{
 
594
                        Start: func(_ dependency.Context) (worker.Worker, error) {
 
595
                                return nil, startErr
 
596
                        },
 
597
                        Filter: func(in error) error {
 
598
                                c.Check(in, gc.Equals, startErr)
 
599
                                return filterErr
 
600
                        },
 
601
                })
 
602
                c.Assert(err, jc.ErrorIsNil)
 
603
 
 
604
                err = workertest.CheckKilled(c, engine)
 
605
                c.Check(err, gc.Equals, filterErr)
 
606
        })
 
607
}
 
608
 
 
609
func (s *EngineSuite) TestFilterWorkerError(c *gc.C) {
 
610
        s.fix.isFatal = alwaysFatal
 
611
        s.fix.dirty = true
 
612
        s.fix.run(c, func(engine *dependency.Engine) {
 
613
 
 
614
                injectErr := errors.New("arg squish")
 
615
                filterErr := errors.New("blam dink")
 
616
 
 
617
                mh := newManifoldHarness()
 
618
                manifold := mh.Manifold()
 
619
                manifold.Filter = func(in error) error {
 
620
                        c.Check(in, gc.Equals, injectErr)
 
621
                        return filterErr
 
622
                }
 
623
                err := engine.Install("task", manifold)
 
624
                c.Assert(err, jc.ErrorIsNil)
 
625
                mh.AssertOneStart(c)
 
626
 
 
627
                mh.InjectError(c, injectErr)
 
628
                err = workertest.CheckKilled(c, engine)
 
629
                c.Check(err, gc.Equals, filterErr)
 
630
        })
 
631
}
 
632
 
 
633
// TestWorstError starts an engine with two manifolds that always error
 
634
// with fatal errors. We test that the most important error is the one
 
635
// returned by the engine.
 
636
//
 
637
// This test uses manifolds whose workers ignore kill requests. We want
 
638
// this (dangerous!) behaviour so that we don't race over which fatal
 
639
// error is seen by the engine first.
 
640
func (s *EngineSuite) TestWorstError(c *gc.C) {
 
641
        worstErr := errors.New("awful error")
 
642
        callCount := 0
 
643
        s.fix.worstError = func(err1, err2 error) error {
 
644
                callCount++
 
645
                return worstErr
 
646
        }
 
647
        s.fix.isFatal = alwaysFatal
 
648
        s.fix.dirty = true
 
649
        s.fix.run(c, func(engine *dependency.Engine) {
 
650
 
 
651
                mh1 := newErrorIgnoringManifoldHarness()
 
652
                err := engine.Install("task", mh1.Manifold())
 
653
                c.Assert(err, jc.ErrorIsNil)
 
654
                mh1.AssertOneStart(c)
 
655
 
 
656
                mh2 := newErrorIgnoringManifoldHarness()
 
657
                err = engine.Install("another task", mh2.Manifold())
 
658
                c.Assert(err, jc.ErrorIsNil)
 
659
                mh2.AssertOneStart(c)
 
660
 
 
661
                mh1.InjectError(c, errors.New("ping"))
 
662
                mh2.InjectError(c, errors.New("pong"))
 
663
 
 
664
                err = workertest.CheckKilled(c, engine)
 
665
                c.Check(errors.Cause(err), gc.Equals, worstErr)
 
666
                c.Check(callCount, gc.Equals, 2)
 
667
        })
 
668
}
 
669
 
 
670
func (s *EngineSuite) TestConfigValidate(c *gc.C) {
 
671
        tests := []struct {
 
672
                breakConfig func(*dependency.EngineConfig)
 
673
                err         string
 
674
        }{{
 
675
                func(config *dependency.EngineConfig) {
 
676
                        config.IsFatal = nil
 
677
                }, "IsFatal not specified",
 
678
        }, {
 
679
                func(config *dependency.EngineConfig) {
 
680
                        config.WorstError = nil
 
681
                }, "WorstError not specified",
 
682
        }, {
 
683
                func(config *dependency.EngineConfig) {
 
684
                        config.ErrorDelay = -time.Second
 
685
                }, "ErrorDelay is negative",
 
686
        }, {
 
687
                func(config *dependency.EngineConfig) {
 
688
                        config.BounceDelay = -time.Second
 
689
                }, "BounceDelay is negative",
 
690
        }}
 
691
 
 
692
        for i, test := range tests {
 
693
                c.Logf("test %d", i)
 
694
                config := dependency.EngineConfig{
 
695
                        IsFatal:     alwaysFatal,
 
696
                        WorstError:  firstError,
 
697
                        ErrorDelay:  time.Second,
 
698
                        BounceDelay: time.Second,
 
699
                }
 
700
                test.breakConfig(&config)
 
701
 
 
702
                c.Logf("config validation...")
 
703
                validateErr := config.Validate()
 
704
                c.Check(validateErr, gc.ErrorMatches, test.err)
 
705
 
 
706
                c.Logf("engine creation...")
 
707
                engine, createErr := dependency.NewEngine(config)
 
708
                c.Check(engine, gc.IsNil)
 
709
                c.Check(createErr, gc.ErrorMatches, "invalid config: "+test.err)
 
710
        }
 
711
}
 
712
 
 
713
func (s *EngineSuite) TestValidateEmptyManifolds(c *gc.C) {
 
714
        err := dependency.Validate(dependency.Manifolds{})
 
715
        c.Check(err, jc.ErrorIsNil)
 
716
}
 
717
 
 
718
func (s *EngineSuite) TestValidateTrivialCycle(c *gc.C) {
 
719
        err := dependency.Validate(dependency.Manifolds{
 
720
                "a": dependency.Manifold{Inputs: []string{"a"}},
 
721
        })
 
722
        c.Check(err.Error(), gc.Equals, `cycle detected at "a" (considering: map[a:true])`)
 
723
}
 
724
 
 
725
func (s *EngineSuite) TestValidateComplexManifolds(c *gc.C) {
 
726
 
 
727
        // Create a bunch of manifolds with tangled but acyclic dependencies; check
 
728
        // that they pass validation.
 
729
        manifolds := dependency.Manifolds{
 
730
                "root1": dependency.Manifold{},
 
731
                "root2": dependency.Manifold{},
 
732
                "mid1":  dependency.Manifold{Inputs: []string{"root1"}},
 
733
                "mid2":  dependency.Manifold{Inputs: []string{"root1", "root2"}},
 
734
                "leaf1": dependency.Manifold{Inputs: []string{"root2", "mid1"}},
 
735
                "leaf2": dependency.Manifold{Inputs: []string{"root1", "mid2"}},
 
736
                "leaf3": dependency.Manifold{Inputs: []string{"root1", "root2", "mid1", "mid2"}},
 
737
        }
 
738
        err := dependency.Validate(manifolds)
 
739
        c.Check(err, jc.ErrorIsNil)
 
740
 
 
741
        // Introduce a cycle; check the manifolds no longer validate.
 
742
        manifolds["root1"] = dependency.Manifold{Inputs: []string{"leaf1"}}
 
743
        err = dependency.Validate(manifolds)
 
744
        c.Check(err, gc.ErrorMatches, "cycle detected at .*")
 
745
}