gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/state/change_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  	"fmt"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/overlord/state"
    32  )
    33  
    34  type changeSuite struct{}
    35  
    36  var _ = Suite(&changeSuite{})
    37  
    38  func (cs *changeSuite) TestNewChange(c *C) {
    39  	st := state.New(nil)
    40  	st.Lock()
    41  	defer st.Unlock()
    42  
    43  	chg := st.NewChange("install", "summary...")
    44  	c.Check(chg.Kind(), Equals, "install")
    45  	c.Check(chg.Summary(), Equals, "summary...")
    46  }
    47  
    48  func (cs *changeSuite) TestReadyTime(c *C) {
    49  	st := state.New(nil)
    50  	st.Lock()
    51  	defer st.Unlock()
    52  
    53  	chg := st.NewChange("install", "summary...")
    54  
    55  	now := time.Now()
    56  
    57  	t := chg.SpawnTime()
    58  	c.Check(t.After(now.Add(-5*time.Second)), Equals, true)
    59  	c.Check(t.Before(now.Add(5*time.Second)), Equals, true)
    60  
    61  	c.Check(chg.ReadyTime().IsZero(), Equals, true)
    62  
    63  	chg.SetStatus(state.DoneStatus)
    64  
    65  	t = chg.ReadyTime()
    66  	c.Check(t.After(now.Add(-5*time.Second)), Equals, true)
    67  	c.Check(t.Before(now.Add(5*time.Second)), Equals, true)
    68  }
    69  
    70  func (cs *changeSuite) TestStatusString(c *C) {
    71  	for s := state.Status(0); s < state.ErrorStatus+1; s++ {
    72  		c.Assert(s.String(), Matches, ".+")
    73  	}
    74  }
    75  
    76  func (cs *changeSuite) TestGetSet(c *C) {
    77  	st := state.New(nil)
    78  	st.Lock()
    79  	defer st.Unlock()
    80  
    81  	chg := st.NewChange("install", "...")
    82  
    83  	chg.Set("a", 1)
    84  
    85  	var v int
    86  	err := chg.Get("a", &v)
    87  	c.Assert(err, IsNil)
    88  	c.Check(v, Equals, 1)
    89  }
    90  
    91  // TODO Better testing of full change roundtripping via JSON.
    92  
    93  func (cs *changeSuite) TestNewTaskAddTaskAndTasks(c *C) {
    94  	st := state.New(nil)
    95  	st.Lock()
    96  	defer st.Unlock()
    97  
    98  	chg := st.NewChange("install", "...")
    99  
   100  	t1 := st.NewTask("download", "1...")
   101  	chg.AddTask(t1)
   102  	t2 := st.NewTask("verify", "2...")
   103  	chg.AddTask(t2)
   104  
   105  	tasks := chg.Tasks()
   106  	// Tasks must return tasks in the order they were added (first)!
   107  	c.Check(tasks, DeepEquals, []*state.Task{t1, t2})
   108  	c.Check(t1.Change(), Equals, chg)
   109  	c.Check(t2.Change(), Equals, chg)
   110  
   111  	chg2 := st.NewChange("install", "...")
   112  	c.Check(func() { chg2.AddTask(t1) }, PanicMatches, `internal error: cannot add one "download" task to multiple changes`)
   113  }
   114  
   115  func (cs *changeSuite) TestAddAll(c *C) {
   116  	st := state.New(nil)
   117  	st.Lock()
   118  	defer st.Unlock()
   119  
   120  	chg := st.NewChange("install", "...")
   121  
   122  	t1 := st.NewTask("download", "1...")
   123  	t2 := st.NewTask("verify", "2...")
   124  	chg.AddAll(state.NewTaskSet(t1, t2))
   125  
   126  	tasks := chg.Tasks()
   127  	c.Check(tasks, DeepEquals, []*state.Task{t1, t2})
   128  	c.Check(t1.Change(), Equals, chg)
   129  	c.Check(t2.Change(), Equals, chg)
   130  }
   131  
   132  func (cs *changeSuite) TestStatusExplicitlyDefined(c *C) {
   133  	st := state.New(nil)
   134  	st.Lock()
   135  	defer st.Unlock()
   136  
   137  	chg := st.NewChange("install", "...")
   138  	c.Assert(chg.Status(), Equals, state.HoldStatus)
   139  
   140  	t := st.NewTask("download", "...")
   141  	chg.AddTask(t)
   142  
   143  	t.SetStatus(state.DoingStatus)
   144  	c.Assert(chg.Status(), Equals, state.DoingStatus)
   145  	chg.SetStatus(state.ErrorStatus)
   146  	c.Assert(chg.Status(), Equals, state.ErrorStatus)
   147  }
   148  
   149  func (cs *changeSuite) TestLaneTasks(c *C) {
   150  	st := state.New(nil)
   151  	st.Lock()
   152  	defer st.Unlock()
   153  
   154  	chg := st.NewChange("change", "...")
   155  
   156  	lane1 := st.NewLane()
   157  	lane2 := st.NewLane()
   158  
   159  	t1 := st.NewTask("task1", "...")
   160  	t2 := st.NewTask("task2", "...")
   161  	t3 := st.NewTask("task3", "...")
   162  	t4 := st.NewTask("task4", "...")
   163  	t5 := st.NewTask("task5", "...")
   164  	t6 := st.NewTask("task6", "...")
   165  
   166  	// lane1: task1, task2, task4
   167  	// lane2: task3, task4
   168  	t1.JoinLane(lane1)
   169  	t2.JoinLane(lane1)
   170  	t3.JoinLane(lane2)
   171  	t4.JoinLane(lane1)
   172  	t4.JoinLane(lane2)
   173  
   174  	chg.AddTask(t1)
   175  	chg.AddTask(t2)
   176  	chg.AddTask(t3)
   177  	chg.AddTask(t4)
   178  	chg.AddTask(t5)
   179  	chg.AddTask(t6)
   180  
   181  	checkTasks := func(obtained, expected []*state.Task) {
   182  		c.Assert(obtained, HasLen, len(expected))
   183  
   184  		tasks1 := make([]string, len(obtained))
   185  		tasks2 := make([]string, len(expected))
   186  
   187  		for i, t := range obtained {
   188  			tasks1[i] = t.ID()
   189  		}
   190  		for i, t := range expected {
   191  			tasks2[i] = t.ID()
   192  		}
   193  
   194  		sort.Strings(tasks1)
   195  		sort.Strings(tasks2)
   196  
   197  		c.Assert(tasks1, DeepEquals, tasks2)
   198  	}
   199  
   200  	c.Assert(chg.LaneTasks(), HasLen, 0)
   201  
   202  	tasks := chg.LaneTasks(0)
   203  	checkTasks(tasks, []*state.Task{t5, t6})
   204  
   205  	tasks = chg.LaneTasks(0, lane2)
   206  	checkTasks(tasks, []*state.Task{t3, t4, t5, t6})
   207  
   208  	tasks = chg.LaneTasks(lane1)
   209  	checkTasks(tasks, []*state.Task{t1, t2, t4})
   210  
   211  	tasks = chg.LaneTasks(lane2)
   212  	checkTasks(tasks, []*state.Task{t3, t4})
   213  
   214  	tasks = chg.LaneTasks(lane1, lane2)
   215  	checkTasks(tasks, []*state.Task{t1, t2, t3, t4})
   216  }
   217  
   218  func (cs *changeSuite) TestStatusDerivedFromTasks(c *C) {
   219  	st := state.New(nil)
   220  	st.Lock()
   221  	defer st.Unlock()
   222  
   223  	chg := st.NewChange("install", "...")
   224  
   225  	// Nothing to do with it if there are no tasks.
   226  	c.Assert(chg.Status(), Equals, state.HoldStatus)
   227  
   228  	tasks := make(map[state.Status]*state.Task)
   229  
   230  	for s := state.DefaultStatus + 1; s < state.ErrorStatus+1; s++ {
   231  		t := st.NewTask("download", s.String())
   232  		t.SetStatus(s)
   233  		chg.AddTask(t)
   234  		tasks[s] = t
   235  	}
   236  
   237  	order := []state.Status{
   238  		state.AbortStatus,
   239  		state.UndoingStatus,
   240  		state.UndoStatus,
   241  		state.DoingStatus,
   242  		state.DoStatus,
   243  		state.ErrorStatus,
   244  		state.UndoneStatus,
   245  		state.DoneStatus,
   246  		state.HoldStatus,
   247  	}
   248  
   249  	for _, s := range order {
   250  		// Set all tasks with previous statuses to s as well.
   251  		for _, s2 := range order {
   252  			if s == s2 {
   253  				break
   254  			}
   255  			tasks[s2].SetStatus(s)
   256  		}
   257  		c.Assert(chg.Status(), Equals, s)
   258  	}
   259  }
   260  
   261  func (cs *changeSuite) TestCloseReadyOnExplicitStatus(c *C) {
   262  	st := state.New(nil)
   263  	st.Lock()
   264  	defer st.Unlock()
   265  
   266  	chg := st.NewChange("install", "...")
   267  
   268  	select {
   269  	case <-chg.Ready():
   270  		c.Fatalf("Change should not be ready")
   271  	default:
   272  	}
   273  	c.Assert(chg.IsReady(), Equals, false)
   274  
   275  	chg.SetStatus(state.ErrorStatus)
   276  
   277  	select {
   278  	case <-chg.Ready():
   279  	default:
   280  		c.Fatalf("Change should be ready")
   281  	}
   282  	c.Assert(chg.IsReady(), Equals, true)
   283  }
   284  
   285  func (cs *changeSuite) TestCloseReadyWhenTasksReady(c *C) {
   286  	st := state.New(nil)
   287  	st.Lock()
   288  	defer st.Unlock()
   289  
   290  	chg := st.NewChange("install", "...")
   291  	t1 := st.NewTask("download", "...")
   292  	t2 := st.NewTask("download", "...")
   293  	chg.AddTask(t1)
   294  	chg.AddTask(t2)
   295  
   296  	select {
   297  	case <-chg.Ready():
   298  		c.Fatalf("Change should not be ready")
   299  	default:
   300  	}
   301  	c.Assert(chg.IsReady(), Equals, false)
   302  
   303  	t1.SetStatus(state.DoneStatus)
   304  
   305  	select {
   306  	case <-chg.Ready():
   307  		c.Fatalf("Change should not be ready")
   308  	default:
   309  	}
   310  	c.Assert(chg.IsReady(), Equals, false)
   311  
   312  	t2.SetStatus(state.DoneStatus)
   313  
   314  	select {
   315  	case <-chg.Ready():
   316  	default:
   317  		c.Fatalf("Change should be ready")
   318  	}
   319  	c.Assert(chg.IsReady(), Equals, true)
   320  }
   321  
   322  func (cs *changeSuite) TestIsClean(c *C) {
   323  	st := state.New(nil)
   324  	st.Lock()
   325  	defer st.Unlock()
   326  
   327  	chg := st.NewChange("install", "...")
   328  
   329  	t1 := st.NewTask("download", "1...")
   330  	t2 := st.NewTask("verify", "2...")
   331  	chg.AddAll(state.NewTaskSet(t1, t2))
   332  
   333  	t1.SetStatus(state.DoneStatus)
   334  	c.Assert(t1.SetClean, PanicMatches, ".*while change not ready")
   335  	t2.SetStatus(state.DoneStatus)
   336  
   337  	t1.SetClean()
   338  	c.Assert(chg.IsClean(), Equals, false)
   339  	t2.SetClean()
   340  	c.Assert(chg.IsClean(), Equals, true)
   341  }
   342  
   343  func (cs *changeSuite) TestState(c *C) {
   344  	st := state.New(nil)
   345  	st.Lock()
   346  	chg := st.NewChange("install", "...")
   347  	st.Unlock()
   348  
   349  	c.Assert(chg.State(), Equals, st)
   350  }
   351  
   352  func (cs *changeSuite) TestErr(c *C) {
   353  	st := state.New(nil)
   354  	st.Lock()
   355  	defer st.Unlock()
   356  
   357  	chg := st.NewChange("install", "...")
   358  
   359  	t1 := st.NewTask("download", "Download")
   360  	t2 := st.NewTask("activate", "Activate")
   361  
   362  	chg.AddTask(t1)
   363  	chg.AddTask(t2)
   364  
   365  	c.Assert(chg.Err(), IsNil)
   366  
   367  	// t2 still running so change not yet in ErrorStatus
   368  	t1.SetStatus(state.ErrorStatus)
   369  	c.Assert(chg.Err(), IsNil)
   370  
   371  	t2.SetStatus(state.ErrorStatus)
   372  	c.Assert(chg.Err(), ErrorMatches, `internal inconsistency: change "install" in ErrorStatus with no task errors logged`)
   373  
   374  	t1.Errorf("Download error")
   375  	c.Assert(chg.Err(), ErrorMatches, ""+
   376  		"cannot perform the following tasks:\n"+
   377  		"- Download \\(Download error\\)")
   378  
   379  	t2.Errorf("Activate error")
   380  	c.Assert(chg.Err(), ErrorMatches, ""+
   381  		"cannot perform the following tasks:\n"+
   382  		"- Download \\(Download error\\)\n"+
   383  		"- Activate \\(Activate error\\)")
   384  }
   385  
   386  func (cs *changeSuite) TestMethodEntrance(c *C) {
   387  	st := state.New(&fakeStateBackend{})
   388  	st.Lock()
   389  	chg := st.NewChange("install", "...")
   390  	st.Unlock()
   391  
   392  	writes := []func(){
   393  		func() { chg.Set("a", 1) },
   394  		func() { chg.SetStatus(state.DoStatus) },
   395  		func() { chg.AddTask(nil) },
   396  		func() { chg.AddAll(nil) },
   397  		func() { chg.UnmarshalJSON(nil) },
   398  	}
   399  
   400  	reads := []func(){
   401  		func() { chg.Get("a", nil) },
   402  		func() { chg.Status() },
   403  		func() { chg.IsClean() },
   404  		func() { chg.Tasks() },
   405  		func() { chg.Err() },
   406  		func() { chg.MarshalJSON() },
   407  		func() { chg.SpawnTime() },
   408  		func() { chg.ReadyTime() },
   409  	}
   410  
   411  	for i, f := range reads {
   412  		c.Logf("Testing read function #%d", i)
   413  		c.Assert(f, PanicMatches, "internal error: accessing state without lock")
   414  		c.Assert(st.Modified(), Equals, false)
   415  	}
   416  
   417  	for i, f := range writes {
   418  		st.Lock()
   419  		st.Unlock()
   420  		c.Assert(st.Modified(), Equals, false)
   421  
   422  		c.Logf("Testing write function #%d", i)
   423  		c.Assert(f, PanicMatches, "internal error: accessing state without lock")
   424  		c.Assert(st.Modified(), Equals, true)
   425  	}
   426  }
   427  
   428  func (cs *changeSuite) TestAbort(c *C) {
   429  	st := state.New(nil)
   430  	st.Lock()
   431  	defer st.Unlock()
   432  
   433  	chg := st.NewChange("install", "...")
   434  
   435  	for s := state.DefaultStatus + 1; s < state.ErrorStatus+1; s++ {
   436  		t := st.NewTask("download", s.String())
   437  		t.SetStatus(s)
   438  		t.Set("old-status", s)
   439  		chg.AddTask(t)
   440  	}
   441  
   442  	chg.Abort()
   443  
   444  	tasks := chg.Tasks()
   445  	for _, t := range tasks {
   446  		var s state.Status
   447  		err := t.Get("old-status", &s)
   448  		c.Assert(err, IsNil)
   449  
   450  		c.Logf("Checking %s task after abort", t.Summary())
   451  		switch s {
   452  		case state.DoStatus:
   453  			c.Assert(t.Status(), Equals, state.HoldStatus)
   454  		case state.DoneStatus:
   455  			c.Assert(t.Status(), Equals, state.UndoStatus)
   456  		case state.DoingStatus:
   457  			c.Assert(t.Status(), Equals, state.AbortStatus)
   458  		default:
   459  			c.Assert(t.Status(), Equals, s)
   460  		}
   461  	}
   462  }
   463  
   464  func (cs *changeSuite) TestAbortCircular(c *C) {
   465  	st := state.New(nil)
   466  	st.Lock()
   467  	defer st.Unlock()
   468  
   469  	chg := st.NewChange("circular", "...")
   470  
   471  	t1 := st.NewTask("one", "one")
   472  	t2 := st.NewTask("two", "two")
   473  	t1.WaitFor(t2)
   474  	t2.WaitFor(t1)
   475  	chg.AddTask(t1)
   476  	chg.AddTask(t2)
   477  
   478  	chg.Abort()
   479  
   480  	tasks := chg.Tasks()
   481  	for _, t := range tasks {
   482  		c.Assert(t.Status(), Equals, state.HoldStatus)
   483  	}
   484  }
   485  
   486  func (cs *changeSuite) TestAbortKⁿ(c *C) {
   487  	st := state.New(nil)
   488  	st.Lock()
   489  	defer st.Unlock()
   490  
   491  	chg := st.NewChange("Kⁿ", "...")
   492  
   493  	var prev *state.TaskSet
   494  	N := 22 // ∛10,000
   495  	for i := 0; i < N; i++ {
   496  		ts := make([]*state.Task, N)
   497  		for j := range ts {
   498  			name := fmt.Sprintf("task-%d", j)
   499  			ts[j] = st.NewTask(name, name)
   500  		}
   501  		t := state.NewTaskSet(ts...)
   502  		if prev != nil {
   503  			t.WaitAll(prev)
   504  		}
   505  		prev = t
   506  		chg.AddAll(t)
   507  
   508  		for j := 0; j < N; j++ {
   509  			lid := st.NewLane()
   510  			for k := range ts {
   511  				name := fmt.Sprintf("task-%d-%d", lid, k)
   512  				ts[k] = st.NewTask(name, name)
   513  			}
   514  			t := state.NewTaskSet(ts...)
   515  			t.WaitAll(prev)
   516  			chg.AddAll(t)
   517  		}
   518  	}
   519  	chg.Abort()
   520  
   521  	tasks := chg.Tasks()
   522  	for _, t := range tasks {
   523  		c.Assert(t.Status(), Equals, state.HoldStatus)
   524  	}
   525  }
   526  
   527  // Task wait order:
   528  //
   529  //             => t21 => t22
   530  //           /               \
   531  // t11 => t12                 => t41 => t42
   532  //           \               /
   533  //             => t31 => t32
   534  //
   535  // setup and result lines are <task>:<status>[:<lane>,...]
   536  //
   537  // "*" as task name means "all remaining".
   538  //
   539  var abortLanesTests = []struct {
   540  	setup  string
   541  	abort  []int
   542  	result string
   543  }{
   544  
   545  	// Some basics.
   546  	{
   547  		setup:  "*:do",
   548  		abort:  []int{},
   549  		result: "*:do",
   550  	}, {
   551  		setup:  "*:do",
   552  		abort:  []int{1},
   553  		result: "*:do",
   554  	}, {
   555  		setup:  "*:do",
   556  		abort:  []int{0},
   557  		result: "*:hold",
   558  	}, {
   559  		setup:  "t11:done t12:doing t22:do",
   560  		abort:  []int{0},
   561  		result: "t11:undo t12:abort t22:hold",
   562  	},
   563  
   564  	//                      => t21 (2) => t22 (2)
   565  	//                    /                       \
   566  	// t11 (1) => t12 (1)                           => t41 (4) => t42 (4)
   567  	//                    \                       /
   568  	//                      => t31 (3) => t32 (3)
   569  	{
   570  		setup:  "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
   571  		abort:  []int{0},
   572  		result: "*:do",
   573  	}, {
   574  		setup:  "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
   575  		abort:  []int{1},
   576  		result: "*:hold",
   577  	}, {
   578  		setup:  "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
   579  		abort:  []int{2},
   580  		result: "t21:hold t22:hold t41:hold t42:hold *:do",
   581  	}, {
   582  		setup:  "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
   583  		abort:  []int{3},
   584  		result: "t31:hold t32:hold t41:hold t42:hold *:do",
   585  	}, {
   586  		setup:  "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
   587  		abort:  []int{2, 3},
   588  		result: "t21:hold t22:hold t31:hold t32:hold t41:hold t42:hold *:do",
   589  	}, {
   590  		setup:  "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
   591  		abort:  []int{4},
   592  		result: "t41:hold t42:hold *:do",
   593  	}, {
   594  		setup:  "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
   595  		abort:  []int{5},
   596  		result: "*:do",
   597  	},
   598  
   599  	//                          => t21 (2) => t22 (2)
   600  	//                        /                       \
   601  	// t11 (2,3) => t12 (2,3)                           => t41 (4) => t42 (4)
   602  	//                        \                       /
   603  	//                          => t31 (3) => t32 (3)
   604  	{
   605  		setup:  "t11:do:2,3 t12:do:2,3 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
   606  		abort:  []int{2},
   607  		result: "t21:hold t22:hold t41:hold t42:hold *:do",
   608  	}, {
   609  		setup:  "t11:do:2,3 t12:do:2,3 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
   610  		abort:  []int{3},
   611  		result: "t31:hold t32:hold t41:hold t42:hold *:do",
   612  	}, {
   613  		setup:  "t11:do:2,3 t12:do:2,3 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
   614  		abort:  []int{2, 3},
   615  		result: "*:hold",
   616  	},
   617  
   618  	//                      => t21 (1) => t22 (1)
   619  	//                    /                       \
   620  	// t11 (1) => t12 (1)                           => t41 (4) => t42 (4)
   621  	//                    \                       /
   622  	//                      => t31 (1) => t32 (1)
   623  	{
   624  		setup:  "t41:error:4 t42:do:4 *:do:1",
   625  		abort:  []int{1},
   626  		result: "t41:error *:hold",
   627  	},
   628  }
   629  
   630  func (ts *taskRunnerSuite) TestAbortLanes(c *C) {
   631  
   632  	names := strings.Fields("t11 t12 t21 t22 t31 t32 t41 t42")
   633  
   634  	for _, test := range abortLanesTests {
   635  		sb := &stateBackend{}
   636  		st := state.New(sb)
   637  		r := state.NewTaskRunner(st)
   638  		defer r.Stop()
   639  
   640  		st.Lock()
   641  		defer st.Unlock()
   642  
   643  		c.Assert(len(st.Tasks()), Equals, 0)
   644  
   645  		chg := st.NewChange("install", "...")
   646  		tasks := make(map[string]*state.Task)
   647  		for _, name := range names {
   648  			tasks[name] = st.NewTask("do", name)
   649  			chg.AddTask(tasks[name])
   650  		}
   651  		tasks["t12"].WaitFor(tasks["t11"])
   652  		tasks["t21"].WaitFor(tasks["t12"])
   653  		tasks["t22"].WaitFor(tasks["t21"])
   654  		tasks["t31"].WaitFor(tasks["t12"])
   655  		tasks["t32"].WaitFor(tasks["t31"])
   656  		tasks["t41"].WaitFor(tasks["t22"])
   657  		tasks["t41"].WaitFor(tasks["t32"])
   658  		tasks["t42"].WaitFor(tasks["t41"])
   659  
   660  		c.Logf("-----")
   661  		c.Logf("Testing setup: %s", test.setup)
   662  
   663  		statuses := make(map[string]state.Status)
   664  		for s := state.DefaultStatus; s <= state.ErrorStatus; s++ {
   665  			statuses[strings.ToLower(s.String())] = s
   666  		}
   667  
   668  		items := strings.Fields(test.setup)
   669  		seen := make(map[string]bool)
   670  		for i := 0; i < len(items); i++ {
   671  			item := items[i]
   672  			parts := strings.Split(item, ":")
   673  			if parts[0] == "*" {
   674  				for _, name := range names {
   675  					if !seen[name] {
   676  						parts[0] = name
   677  						items = append(items, strings.Join(parts, ":"))
   678  					}
   679  				}
   680  				continue
   681  			}
   682  			seen[parts[0]] = true
   683  			task := tasks[parts[0]]
   684  			task.SetStatus(statuses[parts[1]])
   685  			if len(parts) > 2 {
   686  				lanes := strings.Split(parts[2], ",")
   687  				for _, lane := range lanes {
   688  					n, err := strconv.Atoi(lane)
   689  					c.Assert(err, IsNil)
   690  					task.JoinLane(n)
   691  				}
   692  			}
   693  		}
   694  
   695  		c.Logf("Aborting with: %v", test.abort)
   696  
   697  		chg.AbortLanes(test.abort)
   698  
   699  		c.Logf("Expected result: %s", test.result)
   700  
   701  		seen = make(map[string]bool)
   702  		var expected = strings.Fields(test.result)
   703  		var obtained []string
   704  		for i := 0; i < len(expected); i++ {
   705  			item := expected[i]
   706  			parts := strings.Split(item, ":")
   707  			if parts[0] == "*" {
   708  				var expanded []string
   709  				for _, name := range names {
   710  					if !seen[name] {
   711  						parts[0] = name
   712  						expanded = append(expanded, strings.Join(parts, ":"))
   713  					}
   714  				}
   715  				expected = append(expected[:i], append(expanded, expected[i+1:]...)...)
   716  				i--
   717  				continue
   718  			}
   719  			name := parts[0]
   720  			seen[parts[0]] = true
   721  			obtained = append(obtained, name+":"+strings.ToLower(tasks[name].Status().String()))
   722  		}
   723  
   724  		c.Assert(strings.Join(obtained, " "), Equals, strings.Join(expected, " "), Commentf("setup: %s", test.setup))
   725  	}
   726  }