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