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