github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  }