github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/dependency/engine_test.go (about) 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 }