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  }