github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/state/taskrunner_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package state_test
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	. "gopkg.in/check.v1"
    32  	"gopkg.in/tomb.v2"
    33  
    34  	"github.com/snapcore/snapd/overlord/state"
    35  )
    36  
    37  type taskRunnerSuite struct{}
    38  
    39  var _ = Suite(&taskRunnerSuite{})
    40  
    41  type stateBackend struct {
    42  	mu               sync.Mutex
    43  	ensureBefore     time.Duration
    44  	ensureBeforeSeen chan<- bool
    45  }
    46  
    47  func (b *stateBackend) Checkpoint([]byte) error { return nil }
    48  
    49  func (b *stateBackend) EnsureBefore(d time.Duration) {
    50  	b.mu.Lock()
    51  	if d < b.ensureBefore {
    52  		b.ensureBefore = d
    53  	}
    54  	b.mu.Unlock()
    55  	if b.ensureBeforeSeen != nil {
    56  		b.ensureBeforeSeen <- true
    57  	}
    58  }
    59  
    60  func (b *stateBackend) RequestRestart(t state.RestartType) {}
    61  
    62  func ensureChange(c *C, r *state.TaskRunner, sb *stateBackend, chg *state.Change) {
    63  	for i := 0; i < 20; i++ {
    64  		sb.ensureBefore = time.Hour
    65  		r.Ensure()
    66  		r.Wait()
    67  		chg.State().Lock()
    68  		s := chg.Status()
    69  		chg.State().Unlock()
    70  		if s.Ready() {
    71  			return
    72  		}
    73  		if sb.ensureBefore > 0 {
    74  			break
    75  		}
    76  	}
    77  	var statuses []string
    78  	chg.State().Lock()
    79  	for _, t := range chg.Tasks() {
    80  		statuses = append(statuses, t.Summary()+":"+t.Status().String())
    81  	}
    82  	chg.State().Unlock()
    83  	c.Fatalf("Change didn't reach final state without blocking: %s", strings.Join(statuses, " "))
    84  }
    85  
    86  // The result field encodes the expected order in which the task
    87  // handlers will be called, assuming the provided setup is in place.
    88  //
    89  // Setup options:
    90  //     <task>:was-<status>    - set task status before calling ensure (must be sensible)
    91  //     <task>:(do|undo)-block - block handler until task tomb dies
    92  //     <task>:(do|undo)-retry - return from handler with with state.Retry
    93  //     <task>:(do|undo)-error - return from handler with an error
    94  //     <task>:...:1,2         - one of the above, and add task to lanes 1 and 2
    95  //     chg:abort              - call abort on the change
    96  //
    97  // Task wait order: ( t11 | t12 ) => ( t21 ) => ( t31 | t32 )
    98  //
    99  // Task t12 has no undo.
   100  //
   101  // Final task statuses are tested based on the resulting events list.
   102  //
   103  var sequenceTests = []struct{ setup, result string }{{
   104  	setup:  "",
   105  	result: "t11:do t12:do t21:do t31:do t32:do",
   106  }, {
   107  	setup:  "t11:was-done t12:was-doing",
   108  	result: "t12:do t21:do t31:do t32:do",
   109  }, {
   110  	setup:  "t11:was-done t12:was-doing chg:abort",
   111  	result: "t11:undo",
   112  }, {
   113  	setup:  "t12:do-retry",
   114  	result: "t11:do t12:do t12:do-retry t12:do t21:do t31:do t32:do",
   115  }, {
   116  	setup:  "t11:do-block t12:do-error",
   117  	result: "t11:do t11:do-block t12:do t12:do-error t11:do-unblock t11:undo",
   118  }, {
   119  	setup:  "t11:do-error t12:do-block",
   120  	result: "t11:do t11:do-error t12:do t12:do-block t12:do-unblock",
   121  }, {
   122  	setup:  "t11:do-block t11:do-retry t12:do-error",
   123  	result: "t11:do t11:do-block t12:do t12:do-error t11:do-unblock t11:do-retry t11:undo",
   124  }, {
   125  	setup:  "t11:do-error t12:do-block t12:do-retry",
   126  	result: "t11:do t11:do-error t12:do t12:do-block t12:do-unblock t12:do-retry",
   127  }, {
   128  	setup:  "t31:do-error t21:undo-error",
   129  	result: "t11:do t12:do t21:do t31:do t31:do-error t32:do t32:undo t21:undo t21:undo-error t11:undo",
   130  }, {
   131  	setup:  "t21:do-set-ready",
   132  	result: "t11:do t12:do t21:do t31:do t32:do",
   133  }, {
   134  	setup:  "t31:do-error t21:undo-set-ready",
   135  	result: "t11:do t12:do t21:do t31:do t31:do-error t32:do t32:undo t21:undo t11:undo",
   136  }, {
   137  	setup:  "t11:was-done:1 t12:was-done:2 t21:was-done:1,2 t31:was-done:1 t32:do-error:2",
   138  	result: "t31:undo t32:do t32:do-error t21:undo t11:undo",
   139  }, {
   140  	setup:  "t11:was-done:1 t12:was-done:2 t21:was-done:2 t31:was-done:2 t32:do-error:2",
   141  	result: "t31:undo t32:do t32:do-error t21:undo",
   142  }}
   143  
   144  func (ts *taskRunnerSuite) TestSequenceTests(c *C) {
   145  	sb := &stateBackend{}
   146  	st := state.New(sb)
   147  	r := state.NewTaskRunner(st)
   148  	defer r.Stop()
   149  
   150  	ch := make(chan string, 256)
   151  	fn := func(label string) state.HandlerFunc {
   152  		return func(task *state.Task, tomb *tomb.Tomb) error {
   153  			st.Lock()
   154  			defer st.Unlock()
   155  			ch <- task.Summary() + ":" + label
   156  			var isSet bool
   157  			if task.Get(label+"-block", &isSet) == nil && isSet {
   158  				ch <- task.Summary() + ":" + label + "-block"
   159  				st.Unlock()
   160  				<-tomb.Dying()
   161  				st.Lock()
   162  				ch <- task.Summary() + ":" + label + "-unblock"
   163  			}
   164  			if task.Get(label+"-retry", &isSet) == nil && isSet {
   165  				task.Set(label+"-retry", false)
   166  				ch <- task.Summary() + ":" + label + "-retry"
   167  				return &state.Retry{}
   168  			}
   169  			if task.Get(label+"-error", &isSet) == nil && isSet {
   170  				ch <- task.Summary() + ":" + label + "-error"
   171  				return errors.New("boom")
   172  			}
   173  			if task.Get(label+"-set-ready", &isSet) == nil && isSet {
   174  				switch task.Status() {
   175  				case state.DoingStatus:
   176  					task.SetStatus(state.DoneStatus)
   177  				case state.UndoingStatus:
   178  					task.SetStatus(state.UndoneStatus)
   179  				}
   180  			}
   181  			return nil
   182  		}
   183  	}
   184  	r.AddHandler("do", fn("do"), nil)
   185  	r.AddHandler("do-undo", fn("do"), fn("undo"))
   186  
   187  	for _, test := range sequenceTests {
   188  		st.Lock()
   189  
   190  		// Delete previous changes.
   191  		st.Prune(1, 1, 1)
   192  
   193  		chg := st.NewChange("install", "...")
   194  		tasks := make(map[string]*state.Task)
   195  		for _, name := range strings.Fields("t11 t12 t21 t31 t32") {
   196  			if name == "t12" {
   197  				tasks[name] = st.NewTask("do", name)
   198  			} else {
   199  				tasks[name] = st.NewTask("do-undo", name)
   200  			}
   201  			chg.AddTask(tasks[name])
   202  		}
   203  		tasks["t21"].WaitFor(tasks["t11"])
   204  		tasks["t21"].WaitFor(tasks["t12"])
   205  		tasks["t31"].WaitFor(tasks["t21"])
   206  		tasks["t32"].WaitFor(tasks["t21"])
   207  		st.Unlock()
   208  
   209  		c.Logf("-----")
   210  		c.Logf("Testing setup: %s", test.setup)
   211  
   212  		statuses := make(map[string]state.Status)
   213  		for s := state.DefaultStatus; s <= state.ErrorStatus; s++ {
   214  			statuses[strings.ToLower(s.String())] = s
   215  		}
   216  
   217  		// Reset and prepare initial task state.
   218  		st.Lock()
   219  		for _, t := range chg.Tasks() {
   220  			t.SetStatus(state.DefaultStatus)
   221  			t.Set("do-error", false)
   222  			t.Set("do-block", false)
   223  			t.Set("undo-error", false)
   224  			t.Set("undo-block", false)
   225  		}
   226  		for _, item := range strings.Fields(test.setup) {
   227  			parts := strings.Split(item, ":")
   228  			if parts[0] == "chg" && parts[1] == "abort" {
   229  				chg.Abort()
   230  			} else {
   231  				if strings.HasPrefix(parts[1], "was-") {
   232  					tasks[parts[0]].SetStatus(statuses[parts[1][4:]])
   233  				} else {
   234  					tasks[parts[0]].Set(parts[1], true)
   235  				}
   236  			}
   237  			if len(parts) > 2 {
   238  				lanes := strings.Split(parts[2], ",")
   239  				for _, lane := range lanes {
   240  					n, err := strconv.Atoi(lane)
   241  					c.Assert(err, IsNil)
   242  					tasks[parts[0]].JoinLane(n)
   243  				}
   244  			}
   245  		}
   246  		st.Unlock()
   247  
   248  		// Run change until final.
   249  		ensureChange(c, r, sb, chg)
   250  
   251  		// Compute order of events observed.
   252  		var events []string
   253  		var done bool
   254  		for !done {
   255  			select {
   256  			case ev := <-ch:
   257  				events = append(events, ev)
   258  				// Make t11/t12 and t31/t32 always show up in the
   259  				// same order if they're next to each other.
   260  				for i := len(events) - 2; i >= 0; i-- {
   261  					prev := events[i]
   262  					next := events[i+1]
   263  					switch strings.Split(next, ":")[1] {
   264  					case "do-unblock", "undo-unblock":
   265  					default:
   266  						if prev[1] == next[1] && prev[2] > next[2] {
   267  							events[i], events[i+1] = next, prev
   268  							continue
   269  						}
   270  					}
   271  					break
   272  				}
   273  			default:
   274  				done = true
   275  			}
   276  		}
   277  
   278  		c.Logf("Expected result: %s", test.result)
   279  		c.Assert(strings.Join(events, " "), Equals, test.result, Commentf("setup: %s", test.setup))
   280  
   281  		// Compute final expected status for tasks.
   282  		finalStatus := make(map[string]state.Status)
   283  		// ... default when no handler is called
   284  		for tname := range tasks {
   285  			finalStatus[tname] = state.HoldStatus
   286  		}
   287  		// ... overwrite based on relevant setup
   288  		for _, item := range strings.Fields(test.setup) {
   289  			parts := strings.Split(item, ":")
   290  			if parts[0] == "chg" && parts[1] == "abort" && strings.Contains(test.setup, "t12:was-doing") {
   291  				// t12 has no undo so must hold if asked to abort when was doing.
   292  				finalStatus["t12"] = state.HoldStatus
   293  			}
   294  			if !strings.HasPrefix(parts[1], "was-") {
   295  				continue
   296  			}
   297  			switch strings.TrimPrefix(parts[1], "was-") {
   298  			case "do", "doing", "done":
   299  				finalStatus[parts[0]] = state.DoneStatus
   300  			case "abort", "undo", "undoing", "undone":
   301  				if parts[0] == "t12" {
   302  					finalStatus[parts[0]] = state.DoneStatus // no undo for t12
   303  				} else {
   304  					finalStatus[parts[0]] = state.UndoneStatus
   305  				}
   306  			case "was-error":
   307  				finalStatus[parts[0]] = state.ErrorStatus
   308  			case "was-hold":
   309  				finalStatus[parts[0]] = state.ErrorStatus
   310  			}
   311  		}
   312  		// ... and overwrite based on events observed.
   313  		for _, ev := range events {
   314  			parts := strings.Split(ev, ":")
   315  			switch parts[1] {
   316  			case "do":
   317  				finalStatus[parts[0]] = state.DoneStatus
   318  			case "undo":
   319  				finalStatus[parts[0]] = state.UndoneStatus
   320  			case "do-error", "undo-error":
   321  				finalStatus[parts[0]] = state.ErrorStatus
   322  			case "do-retry":
   323  				if parts[0] == "t12" && finalStatus["t11"] == state.ErrorStatus {
   324  					// t12 has no undo so must hold if asked to abort on retry.
   325  					finalStatus["t12"] = state.HoldStatus
   326  				}
   327  			}
   328  		}
   329  
   330  		st.Lock()
   331  		var gotStatus, wantStatus []string
   332  		for _, task := range chg.Tasks() {
   333  			gotStatus = append(gotStatus, task.Summary()+":"+task.Status().String())
   334  			wantStatus = append(wantStatus, task.Summary()+":"+finalStatus[task.Summary()].String())
   335  		}
   336  		st.Unlock()
   337  
   338  		c.Logf("Expected statuses: %s", strings.Join(wantStatus, " "))
   339  		comment := Commentf("calls: %s", test.result)
   340  		c.Assert(strings.Join(gotStatus, " "), Equals, strings.Join(wantStatus, " "), comment)
   341  	}
   342  }
   343  
   344  func (ts *taskRunnerSuite) TestExternalAbort(c *C) {
   345  	sb := &stateBackend{}
   346  	st := state.New(sb)
   347  	r := state.NewTaskRunner(st)
   348  	defer r.Stop()
   349  
   350  	ch := make(chan bool)
   351  	r.AddHandler("blocking", func(t *state.Task, tb *tomb.Tomb) error {
   352  		ch <- true
   353  		<-tb.Dying()
   354  		return nil
   355  	}, nil)
   356  
   357  	st.Lock()
   358  	chg := st.NewChange("install", "...")
   359  	t := st.NewTask("blocking", "...")
   360  	chg.AddTask(t)
   361  	st.Unlock()
   362  
   363  	r.Ensure()
   364  	<-ch
   365  
   366  	st.Lock()
   367  	chg.Abort()
   368  	st.Unlock()
   369  
   370  	// The Abort above must make Ensure kill the task, or this will never end.
   371  	ensureChange(c, r, sb, chg)
   372  }
   373  
   374  func (ts *taskRunnerSuite) TestStopHandlerJustFinishing(c *C) {
   375  	sb := &stateBackend{}
   376  	st := state.New(sb)
   377  	r := state.NewTaskRunner(st)
   378  	defer r.Stop()
   379  
   380  	ch := make(chan bool)
   381  	r.AddHandler("just-finish", func(t *state.Task, tb *tomb.Tomb) error {
   382  		ch <- true
   383  		<-tb.Dying()
   384  		// just ignore and actually finishes
   385  		return nil
   386  	}, nil)
   387  
   388  	st.Lock()
   389  	chg := st.NewChange("install", "...")
   390  	t := st.NewTask("just-finish", "...")
   391  	chg.AddTask(t)
   392  	st.Unlock()
   393  
   394  	r.Ensure()
   395  	<-ch
   396  	r.Stop()
   397  
   398  	st.Lock()
   399  	defer st.Unlock()
   400  	c.Check(t.Status(), Equals, state.DoneStatus)
   401  	c.Check(t.DoingTime(), Not(Equals), 0)
   402  	c.Check(t.UndoingTime(), Equals, time.Duration(0))
   403  }
   404  
   405  func (ts *taskRunnerSuite) TestStopKinds(c *C) {
   406  	sb := &stateBackend{}
   407  	st := state.New(sb)
   408  	r := state.NewTaskRunner(st)
   409  	defer r.Stop()
   410  
   411  	ch1 := make(chan bool)
   412  	ch2 := make(chan bool)
   413  	r.AddHandler("just-finish1", func(t *state.Task, tb *tomb.Tomb) error {
   414  		ch1 <- true
   415  		<-tb.Dying()
   416  		// just ignore and actually finishes
   417  		return nil
   418  	}, nil)
   419  	r.AddHandler("just-finish2", func(t *state.Task, tb *tomb.Tomb) error {
   420  		ch2 <- true
   421  		<-tb.Dying()
   422  		// just ignore and actually finishes
   423  		return nil
   424  	}, nil)
   425  
   426  	st.Lock()
   427  	chg := st.NewChange("install", "...")
   428  	t1 := st.NewTask("just-finish1", "...")
   429  	t2 := st.NewTask("just-finish2", "...")
   430  	chg.AddTask(t1)
   431  	chg.AddTask(t2)
   432  	st.Unlock()
   433  
   434  	r.Ensure()
   435  	<-ch1
   436  	<-ch2
   437  	r.StopKinds("just-finish1")
   438  
   439  	st.Lock()
   440  	defer st.Unlock()
   441  	c.Check(t1.Status(), Equals, state.DoneStatus)
   442  	c.Check(t2.Status(), Equals, state.DoingStatus)
   443  
   444  	st.Unlock()
   445  	r.Stop()
   446  	st.Lock()
   447  	c.Check(t2.Status(), Equals, state.DoneStatus)
   448  }
   449  
   450  func (ts *taskRunnerSuite) TestErrorsOnStopAreRetried(c *C) {
   451  	sb := &stateBackend{}
   452  	st := state.New(sb)
   453  	r := state.NewTaskRunner(st)
   454  	defer r.Stop()
   455  
   456  	ch := make(chan bool)
   457  	r.AddHandler("error-on-stop", func(t *state.Task, tb *tomb.Tomb) error {
   458  		ch <- true
   459  		<-tb.Dying()
   460  		// error here could be due to cancellation, task will be retried
   461  		return errors.New("error at stop")
   462  	}, nil)
   463  
   464  	st.Lock()
   465  	chg := st.NewChange("install", "...")
   466  	t := st.NewTask("error-on-stop", "...")
   467  	chg.AddTask(t)
   468  	st.Unlock()
   469  
   470  	r.Ensure()
   471  	<-ch
   472  	r.Stop()
   473  
   474  	st.Lock()
   475  	defer st.Unlock()
   476  	// still Doing, will be retried
   477  	c.Check(t.Status(), Equals, state.DoingStatus)
   478  }
   479  
   480  func (ts *taskRunnerSuite) TestStopAskForRetry(c *C) {
   481  	sb := &stateBackend{}
   482  	st := state.New(sb)
   483  	r := state.NewTaskRunner(st)
   484  	defer r.Stop()
   485  
   486  	ch := make(chan bool)
   487  	r.AddHandler("ask-for-retry", func(t *state.Task, tb *tomb.Tomb) error {
   488  		ch <- true
   489  		<-tb.Dying()
   490  		// ask for retry
   491  		return &state.Retry{After: 2 * time.Minute}
   492  	}, nil)
   493  
   494  	st.Lock()
   495  	chg := st.NewChange("install", "...")
   496  	t := st.NewTask("ask-for-retry", "...")
   497  	chg.AddTask(t)
   498  	st.Unlock()
   499  
   500  	r.Ensure()
   501  	<-ch
   502  	r.Stop()
   503  
   504  	st.Lock()
   505  	defer st.Unlock()
   506  	c.Check(t.Status(), Equals, state.DoingStatus)
   507  	c.Check(t.AtTime().IsZero(), Equals, false)
   508  }
   509  
   510  func (ts *taskRunnerSuite) TestRetryAfterDuration(c *C) {
   511  	ensureBeforeTick := make(chan bool, 1)
   512  	sb := &stateBackend{
   513  		ensureBefore:     time.Hour,
   514  		ensureBeforeSeen: ensureBeforeTick,
   515  	}
   516  	st := state.New(sb)
   517  	r := state.NewTaskRunner(st)
   518  	defer r.Stop()
   519  
   520  	ch := make(chan bool)
   521  	ask := 0
   522  	r.AddHandler("ask-for-retry", func(t *state.Task, _ *tomb.Tomb) error {
   523  		ask++
   524  		if ask == 1 {
   525  			return &state.Retry{After: time.Minute}
   526  		}
   527  		ch <- true
   528  		return nil
   529  	}, nil)
   530  
   531  	st.Lock()
   532  	chg := st.NewChange("install", "...")
   533  	t := st.NewTask("ask-for-retry", "...")
   534  	chg.AddTask(t)
   535  	st.Unlock()
   536  
   537  	tock := time.Now()
   538  	restore := state.MockTime(tock)
   539  	defer restore()
   540  	r.Ensure() // will run and be rescheduled in a minute
   541  	select {
   542  	case <-ensureBeforeTick:
   543  	case <-time.After(2 * time.Second):
   544  		c.Fatal("EnsureBefore wasn't called")
   545  	}
   546  
   547  	st.Lock()
   548  	defer st.Unlock()
   549  	c.Check(t.Status(), Equals, state.DoingStatus)
   550  
   551  	c.Check(ask, Equals, 1)
   552  	c.Check(sb.ensureBefore, Equals, 1*time.Minute)
   553  	schedule := t.AtTime()
   554  	c.Check(schedule.IsZero(), Equals, false)
   555  
   556  	state.MockTime(tock.Add(5 * time.Second))
   557  	sb.ensureBefore = time.Hour
   558  	st.Unlock()
   559  	r.Ensure() // too soon
   560  	st.Lock()
   561  
   562  	c.Check(t.Status(), Equals, state.DoingStatus)
   563  	c.Check(ask, Equals, 1)
   564  	c.Check(sb.ensureBefore, Equals, 55*time.Second)
   565  	c.Check(t.AtTime().Equal(schedule), Equals, true)
   566  
   567  	state.MockTime(schedule)
   568  	sb.ensureBefore = time.Hour
   569  	st.Unlock()
   570  	r.Ensure() // time to run again
   571  	select {
   572  	case <-ch:
   573  	case <-time.After(2 * time.Second):
   574  		c.Fatal("handler wasn't called")
   575  	}
   576  
   577  	// wait for handler to finish
   578  	r.Wait()
   579  
   580  	st.Lock()
   581  	c.Check(t.Status(), Equals, state.DoneStatus)
   582  	c.Check(ask, Equals, 2)
   583  	c.Check(sb.ensureBefore, Equals, time.Hour)
   584  	c.Check(t.AtTime().IsZero(), Equals, true)
   585  }
   586  
   587  func (ts *taskRunnerSuite) testTaskSerialization(c *C, setupBlocked func(r *state.TaskRunner)) {
   588  	ensureBeforeTick := make(chan bool, 1)
   589  	sb := &stateBackend{
   590  		ensureBefore:     time.Hour,
   591  		ensureBeforeSeen: ensureBeforeTick,
   592  	}
   593  	st := state.New(sb)
   594  	r := state.NewTaskRunner(st)
   595  	defer r.Stop()
   596  
   597  	ch1 := make(chan bool)
   598  	ch2 := make(chan bool)
   599  	r.AddHandler("do1", func(t *state.Task, _ *tomb.Tomb) error {
   600  		ch1 <- true
   601  		ch1 <- true
   602  		return nil
   603  	}, nil)
   604  	r.AddHandler("do2", func(t *state.Task, _ *tomb.Tomb) error {
   605  		ch2 <- true
   606  		return nil
   607  	}, nil)
   608  
   609  	// setup blocked predicates
   610  	setupBlocked(r)
   611  
   612  	st.Lock()
   613  	chg := st.NewChange("install", "...")
   614  	t1 := st.NewTask("do1", "...")
   615  	chg.AddTask(t1)
   616  	t2 := st.NewTask("do2", "...")
   617  	chg.AddTask(t2)
   618  	st.Unlock()
   619  
   620  	r.Ensure() // will start only one, do1
   621  
   622  	select {
   623  	case <-ch1:
   624  	case <-time.After(2 * time.Second):
   625  		c.Fatal("do1 wasn't called")
   626  	}
   627  
   628  	c.Check(ensureBeforeTick, HasLen, 0)
   629  	c.Check(ch2, HasLen, 0)
   630  
   631  	r.Ensure() // won't yet start anything new
   632  
   633  	c.Check(ensureBeforeTick, HasLen, 0)
   634  	c.Check(ch2, HasLen, 0)
   635  
   636  	// finish do1
   637  	select {
   638  	case <-ch1:
   639  	case <-time.After(2 * time.Second):
   640  		c.Fatal("do1 wasn't continued")
   641  	}
   642  
   643  	// getting an EnsureBefore 0 call
   644  	select {
   645  	case <-ensureBeforeTick:
   646  	case <-time.After(2 * time.Second):
   647  		c.Fatal("EnsureBefore wasn't called")
   648  	}
   649  	c.Check(sb.ensureBefore, Equals, time.Duration(0))
   650  
   651  	r.Ensure() // will start do2
   652  
   653  	select {
   654  	case <-ch2:
   655  	case <-time.After(2 * time.Second):
   656  		c.Fatal("do2 wasn't called")
   657  	}
   658  
   659  	// no more EnsureBefore calls
   660  	c.Check(ensureBeforeTick, HasLen, 0)
   661  }
   662  
   663  func (ts *taskRunnerSuite) TestTaskSerializationSetBlocked(c *C) {
   664  	// start first do1, and then do2 when nothing else is running
   665  	startedDo1 := false
   666  	ts.testTaskSerialization(c, func(r *state.TaskRunner) {
   667  		r.SetBlocked(func(t *state.Task, running []*state.Task) bool {
   668  			if t.Kind() == "do2" && (len(running) != 0 || !startedDo1) {
   669  				return true
   670  			}
   671  			if t.Kind() == "do1" {
   672  				startedDo1 = true
   673  			}
   674  			return false
   675  		})
   676  	})
   677  }
   678  
   679  func (ts *taskRunnerSuite) TestTaskSerializationAddBlocked(c *C) {
   680  	// start first do1, and then do2 when nothing else is running
   681  	startedDo1 := false
   682  	ts.testTaskSerialization(c, func(r *state.TaskRunner) {
   683  		r.AddBlocked(func(t *state.Task, running []*state.Task) bool {
   684  			if t.Kind() == "do2" && (len(running) != 0 || !startedDo1) {
   685  				return true
   686  			}
   687  			return false
   688  		})
   689  		r.AddBlocked(func(t *state.Task, running []*state.Task) bool {
   690  			if t.Kind() == "do1" {
   691  				startedDo1 = true
   692  			}
   693  			return false
   694  		})
   695  	})
   696  }
   697  
   698  func (ts *taskRunnerSuite) TestPrematureChangeReady(c *C) {
   699  	sb := &stateBackend{}
   700  	st := state.New(sb)
   701  	r := state.NewTaskRunner(st)
   702  	defer r.Stop()
   703  
   704  	ch := make(chan bool)
   705  	r.AddHandler("block-undo", func(t *state.Task, tb *tomb.Tomb) error { return nil },
   706  		func(t *state.Task, tb *tomb.Tomb) error {
   707  			ch <- true
   708  			<-ch
   709  			return nil
   710  		})
   711  	r.AddHandler("fail", func(t *state.Task, tb *tomb.Tomb) error {
   712  		return errors.New("BAM")
   713  	}, nil)
   714  
   715  	st.Lock()
   716  	chg := st.NewChange("install", "...")
   717  	t1 := st.NewTask("block-undo", "...")
   718  	t2 := st.NewTask("fail", "...")
   719  	chg.AddTask(t1)
   720  	chg.AddTask(t2)
   721  	st.Unlock()
   722  
   723  	r.Ensure() // Error
   724  	r.Wait()
   725  	r.Ensure() // Block on undo
   726  	<-ch
   727  
   728  	defer func() {
   729  		ch <- true
   730  		r.Wait()
   731  	}()
   732  
   733  	st.Lock()
   734  	defer st.Unlock()
   735  
   736  	if chg.IsReady() || chg.Status().Ready() {
   737  		c.Errorf("Change considered ready prematurely")
   738  	}
   739  
   740  	c.Assert(chg.Err(), IsNil)
   741  }
   742  
   743  func (ts *taskRunnerSuite) TestOptionalHandler(c *C) {
   744  	sb := &stateBackend{}
   745  	st := state.New(sb)
   746  	r := state.NewTaskRunner(st)
   747  
   748  	r.AddOptionalHandler(func(t *state.Task) bool { return true },
   749  		func(t *state.Task, tomb *tomb.Tomb) error {
   750  			return fmt.Errorf("optional handler error for %q", t.Kind())
   751  		}, nil)
   752  
   753  	st.Lock()
   754  	chg := st.NewChange("install", "...")
   755  	t1 := st.NewTask("an unknown task", "...")
   756  	chg.AddTask(t1)
   757  	st.Unlock()
   758  
   759  	// Mark tasks as done.
   760  	ensureChange(c, r, sb, chg)
   761  	r.Stop()
   762  
   763  	st.Lock()
   764  	defer st.Unlock()
   765  	c.Assert(t1.Status(), Equals, state.ErrorStatus)
   766  	c.Assert(strings.Join(t1.Log(), ""), Matches, `.*optional handler error for "an unknown task"`)
   767  }
   768  
   769  func (ts *taskRunnerSuite) TestUndoSequence(c *C) {
   770  	sb := &stateBackend{}
   771  	st := state.New(sb)
   772  	r := state.NewTaskRunner(st)
   773  
   774  	var events []string
   775  
   776  	r.AddHandler("do-with-undo",
   777  		func(t *state.Task, tb *tomb.Tomb) error {
   778  			events = append(events, fmt.Sprintf("do-with-undo:%s", t.ID()))
   779  			return nil
   780  		}, func(t *state.Task, tb *tomb.Tomb) error {
   781  			events = append(events, fmt.Sprintf("undo:%s", t.ID()))
   782  			return nil
   783  		})
   784  	r.AddHandler("do-no-undo",
   785  		func(t *state.Task, tb *tomb.Tomb) error {
   786  			events = append(events, fmt.Sprintf("do-no-undo:%s", t.ID()))
   787  			return nil
   788  		}, nil)
   789  
   790  	r.AddHandler("error-trigger",
   791  		func(t *state.Task, tb *tomb.Tomb) error {
   792  			events = append(events, fmt.Sprintf("do-with-error:%s", t.ID()))
   793  			return fmt.Errorf("error")
   794  		}, nil)
   795  
   796  	st.Lock()
   797  	chg := st.NewChange("install", "...")
   798  
   799  	var prev *state.Task
   800  
   801  	// create a sequence of tasks: 3 tasks with undo handlers, a task with
   802  	// no undo handler, 3 tasks with undo handler, a task with no undo
   803  	// handler, finally a task that errors out. Every task waits for previous
   804  	// taske.
   805  	for i := 0; i < 2; i++ {
   806  		for j := 0; j < 3; j++ {
   807  			t := st.NewTask("do-with-undo", "...")
   808  			if prev != nil {
   809  				t.WaitFor(prev)
   810  			}
   811  			chg.AddTask(t)
   812  			prev = t
   813  		}
   814  
   815  		t := st.NewTask("do-no-undo", "...")
   816  		t.WaitFor(prev)
   817  		chg.AddTask(t)
   818  		prev = t
   819  	}
   820  
   821  	terr := st.NewTask("error-trigger", "provoking undo")
   822  	terr.WaitFor(prev)
   823  	chg.AddTask(terr)
   824  
   825  	c.Check(chg.Tasks(), HasLen, 9) // sanity check
   826  
   827  	st.Unlock()
   828  
   829  	ensureChange(c, r, sb, chg)
   830  	r.Stop()
   831  
   832  	c.Assert(events, DeepEquals, []string{
   833  		"do-with-undo:1",
   834  		"do-with-undo:2",
   835  		"do-with-undo:3",
   836  		"do-no-undo:4",
   837  		"do-with-undo:5",
   838  		"do-with-undo:6",
   839  		"do-with-undo:7",
   840  		"do-no-undo:8",
   841  		"do-with-error:9",
   842  		"undo:7",
   843  		"undo:6",
   844  		"undo:5",
   845  		"undo:3",
   846  		"undo:2",
   847  		"undo:1"})
   848  }
   849  
   850  func (ts *taskRunnerSuite) TestKnownTaskKinds(c *C) {
   851  	st := state.New(nil)
   852  	r := state.NewTaskRunner(st)
   853  	r.AddHandler("task-kind-1", func(t *state.Task, tb *tomb.Tomb) error { return nil }, nil)
   854  	r.AddHandler("task-kind-2", func(t *state.Task, tb *tomb.Tomb) error { return nil }, nil)
   855  
   856  	kinds := r.KnownTaskKinds()
   857  	sort.Strings(kinds)
   858  	c.Assert(kinds, DeepEquals, []string{"task-kind-1", "task-kind-2"})
   859  }
   860  
   861  func (ts *taskRunnerSuite) TestCleanup(c *C) {
   862  	sb := &stateBackend{}
   863  	st := state.New(sb)
   864  	r := state.NewTaskRunner(st)
   865  	defer r.Stop()
   866  
   867  	r.AddHandler("clean-it", func(t *state.Task, tb *tomb.Tomb) error { return nil }, nil)
   868  	r.AddHandler("other", func(t *state.Task, tb *tomb.Tomb) error { return nil }, nil)
   869  
   870  	called := 0
   871  	r.AddCleanup("clean-it", func(t *state.Task, tb *tomb.Tomb) error {
   872  		called++
   873  		if called == 1 {
   874  			return fmt.Errorf("retry me")
   875  		}
   876  		return nil
   877  	})
   878  
   879  	st.Lock()
   880  	chg := st.NewChange("install", "...")
   881  	t1 := st.NewTask("clean-it", "...")
   882  	t2 := st.NewTask("other", "...")
   883  	chg.AddTask(t1)
   884  	chg.AddTask(t2)
   885  	st.Unlock()
   886  
   887  	chgIsClean := func() bool {
   888  		st.Lock()
   889  		defer st.Unlock()
   890  		return chg.IsClean()
   891  	}
   892  
   893  	// Mark tasks as done.
   894  	ensureChange(c, r, sb, chg)
   895  
   896  	// First time it errors, then it works, then it's ignored.
   897  	c.Assert(chgIsClean(), Equals, false)
   898  	c.Assert(called, Equals, 0)
   899  	r.Ensure()
   900  	r.Wait()
   901  	c.Assert(chgIsClean(), Equals, false)
   902  	c.Assert(called, Equals, 1)
   903  	r.Ensure()
   904  	r.Wait()
   905  	c.Assert(chgIsClean(), Equals, true)
   906  	c.Assert(called, Equals, 2)
   907  	r.Ensure()
   908  	r.Wait()
   909  	c.Assert(chgIsClean(), Equals, true)
   910  	c.Assert(called, Equals, 2)
   911  }