github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/state/task_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  	"encoding/json"
    24  	"fmt"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	"github.com/snapcore/snapd/overlord/state"
    29  	"github.com/snapcore/snapd/testutil"
    30  	"time"
    31  )
    32  
    33  type taskSuite struct{}
    34  
    35  var _ = Suite(&taskSuite{})
    36  
    37  func (ts *taskSuite) TestNewTask(c *C) {
    38  	st := state.New(nil)
    39  	st.Lock()
    40  	defer st.Unlock()
    41  
    42  	t := st.NewTask("download", "1...")
    43  
    44  	c.Check(t.Kind(), Equals, "download")
    45  	c.Check(t.Summary(), Equals, "1...")
    46  }
    47  
    48  func (cs *taskSuite) TestReadyTime(c *C) {
    49  	st := state.New(nil)
    50  	st.Lock()
    51  	defer st.Unlock()
    52  
    53  	task := st.NewTask("download", "summary...")
    54  
    55  	now := time.Now()
    56  
    57  	t := task.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(task.ReadyTime().IsZero(), Equals, true)
    62  
    63  	task.SetStatus(state.DoneStatus)
    64  
    65  	t = task.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 *taskSuite) TestDoingUndoingTime(c *C) {
    71  	st := state.New(nil)
    72  	st.Lock()
    73  	defer st.Unlock()
    74  
    75  	task := st.NewTask("download", "summary...")
    76  
    77  	task.AccumulateDoingTime(123456)
    78  	c.Assert(task.DoingTime(), Equals, time.Duration(123456))
    79  
    80  	task.AccumulateUndoingTime(654321)
    81  	c.Assert(task.UndoingTime(), Equals, time.Duration(654321))
    82  }
    83  
    84  func (ts *taskSuite) TestGetSet(c *C) {
    85  	st := state.New(nil)
    86  	st.Lock()
    87  	defer st.Unlock()
    88  
    89  	t := st.NewTask("download", "1...")
    90  
    91  	t.Set("a", 1)
    92  
    93  	var v int
    94  	err := t.Get("a", &v)
    95  	c.Assert(err, IsNil)
    96  	c.Check(v, Equals, 1)
    97  }
    98  
    99  func (ts *taskSuite) TestHas(c *C) {
   100  	st := state.New(nil)
   101  	st.Lock()
   102  	defer st.Unlock()
   103  
   104  	t := st.NewTask("download", "1...")
   105  	c.Check(t.Has("a"), Equals, false)
   106  
   107  	t.Set("a", 1)
   108  	c.Check(t.Has("a"), Equals, true)
   109  
   110  	t.Set("a", nil)
   111  	c.Check(t.Has("a"), Equals, false)
   112  }
   113  
   114  func (ts *taskSuite) TestClear(c *C) {
   115  	st := state.New(nil)
   116  	st.Lock()
   117  	defer st.Unlock()
   118  
   119  	t := st.NewTask("download", "1...")
   120  
   121  	t.Set("a", 1)
   122  
   123  	var v int
   124  	err := t.Get("a", &v)
   125  	c.Assert(err, IsNil)
   126  	c.Check(v, Equals, 1)
   127  
   128  	t.Clear("a")
   129  
   130  	c.Check(t.Get("a", &v), Equals, state.ErrNoState)
   131  }
   132  
   133  func (ts *taskSuite) TestStatusAndSetStatus(c *C) {
   134  	st := state.New(nil)
   135  	st.Lock()
   136  	defer st.Unlock()
   137  
   138  	t := st.NewTask("download", "1...")
   139  
   140  	c.Check(t.Status(), Equals, state.DoStatus)
   141  
   142  	t.SetStatus(state.DoneStatus)
   143  
   144  	c.Check(t.Status(), Equals, state.DoneStatus)
   145  }
   146  
   147  func (ts *taskSuite) TestIsCleanAndSetClean(c *C) {
   148  	st := state.New(nil)
   149  	st.Lock()
   150  	defer st.Unlock()
   151  
   152  	t := st.NewTask("download", "1...")
   153  
   154  	c.Check(t.IsClean(), Equals, false)
   155  
   156  	t.SetStatus(state.DoneStatus)
   157  	t.SetClean()
   158  
   159  	c.Check(t.IsClean(), Equals, true)
   160  }
   161  
   162  func jsonStr(m json.Marshaler) string {
   163  	data, err := m.MarshalJSON()
   164  	if err != nil {
   165  		panic(err)
   166  	}
   167  	return string(data)
   168  }
   169  
   170  func (ts *taskSuite) TestProgressAndSetProgress(c *C) {
   171  	st := state.New(nil)
   172  	st.Lock()
   173  	defer st.Unlock()
   174  
   175  	t := st.NewTask("download", "1...")
   176  
   177  	t.SetProgress("snap", 2, 99)
   178  	label, cur, tot := t.Progress()
   179  	c.Check(label, Equals, "snap")
   180  	c.Check(cur, Equals, 2)
   181  	c.Check(tot, Equals, 99)
   182  
   183  	t.SetProgress("", 0, 0)
   184  	label, cur, tot = t.Progress()
   185  	c.Check(label, Equals, "")
   186  	c.Check(cur, Equals, 0)
   187  	c.Check(tot, Equals, 1)
   188  	c.Check(jsonStr(t), Not(testutil.Contains), "progress")
   189  
   190  	t.SetProgress("", 0, -1)
   191  	_, cur, tot = t.Progress()
   192  	c.Check(cur, Equals, 0)
   193  	c.Check(tot, Equals, 1)
   194  	c.Check(jsonStr(t), Not(testutil.Contains), "progress")
   195  
   196  	t.SetProgress("", 0, -1)
   197  	_, cur, tot = t.Progress()
   198  	c.Check(cur, Equals, 0)
   199  	c.Check(tot, Equals, 1)
   200  	c.Check(jsonStr(t), Not(testutil.Contains), "progress")
   201  
   202  	t.SetProgress("", 2, 1)
   203  	_, cur, tot = t.Progress()
   204  	c.Check(cur, Equals, 0)
   205  	c.Check(tot, Equals, 1)
   206  	c.Check(jsonStr(t), Not(testutil.Contains), "progress")
   207  
   208  	t.SetProgress("", 42, 42)
   209  	_, cur, tot = t.Progress()
   210  	c.Check(cur, Equals, 42)
   211  	c.Check(tot, Equals, 42)
   212  }
   213  
   214  func (ts *taskSuite) TestProgressDefaults(c *C) {
   215  	st := state.New(nil)
   216  	st.Lock()
   217  	defer st.Unlock()
   218  
   219  	t := st.NewTask("download", "1...")
   220  
   221  	c.Check(t.Status(), Equals, state.DoStatus)
   222  	_, cur, tot := t.Progress()
   223  	c.Check(cur, Equals, 0)
   224  	c.Check(tot, Equals, 1)
   225  
   226  	t.SetStatus(state.DoStatus)
   227  	_, cur, tot = t.Progress()
   228  	c.Check(cur, Equals, 0)
   229  	c.Check(tot, Equals, 1)
   230  
   231  	t.SetStatus(state.DoneStatus)
   232  	_, cur, tot = t.Progress()
   233  	c.Check(cur, Equals, 1)
   234  	c.Check(tot, Equals, 1)
   235  
   236  	t.SetStatus(state.ErrorStatus)
   237  	_, cur, tot = t.Progress()
   238  	c.Check(cur, Equals, 1)
   239  	c.Check(tot, Equals, 1)
   240  }
   241  
   242  func (ts *taskSuite) TestState(c *C) {
   243  	st := state.New(nil)
   244  	st.Lock()
   245  	t := st.NewTask("download", "1...")
   246  	st.Unlock()
   247  
   248  	c.Assert(t.State(), Equals, st)
   249  }
   250  
   251  func (ts *taskSuite) TestTaskMarshalsWaitFor(c *C) {
   252  	st := state.New(nil)
   253  	st.Lock()
   254  	defer st.Unlock()
   255  
   256  	t1 := st.NewTask("download", "1...")
   257  	t2 := st.NewTask("install", "2...")
   258  	t2.WaitFor(t1)
   259  
   260  	d, err := t2.MarshalJSON()
   261  	c.Assert(err, IsNil)
   262  
   263  	needle := fmt.Sprintf(`"wait-tasks":["%s"`, t1.ID())
   264  	c.Assert(string(d), testutil.Contains, needle)
   265  }
   266  
   267  func (ts *taskSuite) TestTaskMarshalsDoingUndoingTime(c *C) {
   268  	st := state.New(nil)
   269  	st.Lock()
   270  	defer st.Unlock()
   271  
   272  	t := st.NewTask("download", "1...")
   273  	t.AccumulateDoingTime(123456)
   274  	t.AccumulateUndoingTime(654321)
   275  
   276  	d, err := t.MarshalJSON()
   277  	c.Assert(err, IsNil)
   278  
   279  	c.Assert(string(d), testutil.Contains, `"doing-time":123456`)
   280  	c.Assert(string(d), testutil.Contains, `"undoing-time":654321`)
   281  }
   282  
   283  func (ts *taskSuite) TestTaskWaitFor(c *C) {
   284  	st := state.New(nil)
   285  	st.Lock()
   286  	defer st.Unlock()
   287  
   288  	t1 := st.NewTask("download", "1...")
   289  	t2 := st.NewTask("install", "2...")
   290  	t2.WaitFor(t1)
   291  
   292  	c.Assert(t2.WaitTasks(), DeepEquals, []*state.Task{t1})
   293  	c.Assert(t1.HaltTasks(), DeepEquals, []*state.Task{t2})
   294  	c.Assert(t1.NumHaltTasks(), Equals, 1)
   295  	c.Assert(t2.NumHaltTasks(), Equals, 0)
   296  }
   297  
   298  func (ts *taskSuite) TestAt(c *C) {
   299  	b := new(fakeStateBackend)
   300  	b.ensureBefore = time.Hour
   301  	st := state.New(b)
   302  	st.Lock()
   303  	defer st.Unlock()
   304  
   305  	t := st.NewTask("download", "1...")
   306  
   307  	now := time.Now()
   308  	restore := state.MockTime(now)
   309  	defer restore()
   310  	when := now.Add(10 * time.Second)
   311  	t.At(when)
   312  
   313  	c.Check(t.AtTime().Equal(when), Equals, true)
   314  	c.Check(b.ensureBefore, Equals, 10*time.Second)
   315  }
   316  
   317  func (ts *taskSuite) TestAtPast(c *C) {
   318  	b := new(fakeStateBackend)
   319  	b.ensureBefore = time.Hour
   320  	st := state.New(b)
   321  	st.Lock()
   322  	defer st.Unlock()
   323  
   324  	t := st.NewTask("download", "1...")
   325  
   326  	when := time.Now().Add(-10 * time.Second)
   327  	t.At(when)
   328  
   329  	c.Check(t.AtTime().Equal(when), Equals, true)
   330  	c.Check(b.ensureBefore, Equals, time.Duration(0))
   331  }
   332  
   333  func (ts *taskSuite) TestAtReadyNop(c *C) {
   334  	b := new(fakeStateBackend)
   335  	b.ensureBefore = time.Hour
   336  	st := state.New(b)
   337  	st.Lock()
   338  	defer st.Unlock()
   339  
   340  	t := st.NewTask("download", "1...")
   341  	t.SetStatus(state.DoneStatus)
   342  
   343  	when := time.Now().Add(10 * time.Second)
   344  	t.At(when)
   345  
   346  	c.Check(t.AtTime().IsZero(), Equals, true)
   347  	c.Check(b.ensureBefore, Equals, time.Hour)
   348  }
   349  
   350  func (cs *taskSuite) TestLogf(c *C) {
   351  	st := state.New(nil)
   352  	st.Lock()
   353  	defer st.Unlock()
   354  
   355  	t := st.NewTask("download", "1...")
   356  
   357  	for i := 0; i < 20; i++ {
   358  		t.Logf("Message #%d", i)
   359  	}
   360  
   361  	log := t.Log()
   362  	c.Assert(log, HasLen, 10)
   363  	for i := 0; i < 10; i++ {
   364  		c.Assert(log[i], Matches, fmt.Sprintf("....-..-..T.* INFO Message #%d", i+10))
   365  	}
   366  }
   367  
   368  func (cs *taskSuite) TestErrorf(c *C) {
   369  	st := state.New(nil)
   370  	st.Lock()
   371  	defer st.Unlock()
   372  
   373  	t := st.NewTask("download", "1...")
   374  
   375  	t.Errorf("Some %s", "error")
   376  	c.Assert(t.Log()[0], Matches, "....-..-..T.* ERROR Some error")
   377  }
   378  
   379  func (ts *taskSuite) TestTaskMarshalsLog(c *C) {
   380  	st := state.New(nil)
   381  	st.Lock()
   382  	defer st.Unlock()
   383  
   384  	t := st.NewTask("download", "1...")
   385  	t.Logf("foo")
   386  
   387  	d, err := t.MarshalJSON()
   388  	c.Assert(err, IsNil)
   389  
   390  	c.Assert(string(d), Matches, `.*"log":\["....-..-..T.* INFO foo"\].*`)
   391  }
   392  
   393  // TODO: Better testing of full task roundtripping via JSON.
   394  
   395  func (cs *taskSuite) TestMethodEntrance(c *C) {
   396  	st := state.New(&fakeStateBackend{})
   397  	st.Lock()
   398  	t1 := st.NewTask("download", "1...")
   399  	t2 := st.NewTask("install", "2...")
   400  	st.Unlock()
   401  
   402  	writes := []func(){
   403  		func() { t1.SetStatus(state.DoneStatus) },
   404  		func() { t1.SetClean() },
   405  		func() { t1.Set("a", 1) },
   406  		func() { t2.WaitFor(t1) },
   407  		func() { t1.SetProgress("", 2, 2) },
   408  		func() { t1.Logf("") },
   409  		func() { t1.Errorf("") },
   410  		func() { t1.UnmarshalJSON(nil) },
   411  		func() { t1.SetProgress("", 1, 1) },
   412  		func() { t1.JoinLane(1) },
   413  		func() { t1.AccumulateDoingTime(1) },
   414  		func() { t1.AccumulateUndoingTime(2) },
   415  	}
   416  
   417  	reads := []func(){
   418  		func() { t1.Status() },
   419  		func() { t1.IsClean() },
   420  		func() { t1.Get("a", nil) },
   421  		func() { t1.WaitTasks() },
   422  		func() { t1.HaltTasks() },
   423  		func() { t1.Progress() },
   424  		func() { t1.Log() },
   425  		func() { t1.MarshalJSON() },
   426  		func() { t1.Progress() },
   427  		func() { t1.SetProgress("", 0, 1) },
   428  		func() { t1.Lanes() },
   429  		func() { t1.DoingTime() },
   430  		func() { t1.UndoingTime() },
   431  	}
   432  
   433  	for i, f := range reads {
   434  		c.Logf("Testing read function #%d", i)
   435  		c.Assert(f, PanicMatches, "internal error: accessing state without lock")
   436  		c.Assert(st.Modified(), Equals, false)
   437  	}
   438  
   439  	for i, f := range writes {
   440  		st.Lock()
   441  		st.Unlock()
   442  		c.Assert(st.Modified(), Equals, false)
   443  
   444  		c.Logf("Testing write function #%d", i)
   445  		c.Assert(f, PanicMatches, "internal error: accessing state without lock")
   446  		c.Assert(st.Modified(), Equals, true)
   447  	}
   448  }
   449  
   450  func (cs *taskSuite) TestNewTaskSet(c *C) {
   451  	ts0 := state.NewTaskSet()
   452  	c.Check(ts0.Tasks(), HasLen, 0)
   453  
   454  	st := state.New(nil)
   455  	st.Lock()
   456  	t1 := st.NewTask("download", "1...")
   457  	t2 := st.NewTask("install", "2...")
   458  	ts2 := state.NewTaskSet(t1, t2)
   459  	st.Unlock()
   460  
   461  	c.Assert(ts2.Tasks(), DeepEquals, []*state.Task{t1, t2})
   462  }
   463  
   464  func (ts *taskSuite) TestTaskWaitAll(c *C) {
   465  	st := state.New(nil)
   466  	st.Lock()
   467  	defer st.Unlock()
   468  
   469  	t1 := st.NewTask("download", "1...")
   470  	t2 := st.NewTask("install", "2...")
   471  	t3 := st.NewTask("setup", "3...")
   472  	t3.WaitAll(state.NewTaskSet(t1, t2))
   473  
   474  	c.Assert(t3.WaitTasks(), HasLen, 2)
   475  	c.Assert(t1.HaltTasks(), DeepEquals, []*state.Task{t3})
   476  	c.Assert(t2.HaltTasks(), DeepEquals, []*state.Task{t3})
   477  }
   478  
   479  func (ts *taskSuite) TestTaskSetWaitFor(c *C) {
   480  	st := state.New(nil)
   481  	st.Lock()
   482  	defer st.Unlock()
   483  
   484  	t1 := st.NewTask("download", "1...")
   485  	t2 := st.NewTask("install", "2...")
   486  	t3 := st.NewTask("setup", "3...")
   487  	ts23 := state.NewTaskSet(t2, t3)
   488  	ts23.WaitFor(t1)
   489  
   490  	c.Assert(t2.WaitTasks(), DeepEquals, []*state.Task{t1})
   491  	c.Assert(t3.WaitTasks(), DeepEquals, []*state.Task{t1})
   492  	c.Assert(t1.NumHaltTasks(), Equals, 2)
   493  }
   494  
   495  func (ts *taskSuite) TestTaskSetWaitAll(c *C) {
   496  	st := state.New(nil)
   497  	st.Lock()
   498  	defer st.Unlock()
   499  
   500  	t1 := st.NewTask("download", "1...")
   501  	t2 := st.NewTask("check", "2...")
   502  	t3 := st.NewTask("setup", "3...")
   503  	t4 := st.NewTask("link", "4...")
   504  	ts12 := state.NewTaskSet(t1, t2)
   505  	ts34 := state.NewTaskSet(t3, t4)
   506  	ts34.WaitAll(ts12)
   507  
   508  	c.Assert(t3.WaitTasks(), DeepEquals, []*state.Task{t1, t2})
   509  	c.Assert(t4.WaitTasks(), DeepEquals, []*state.Task{t1, t2})
   510  	c.Assert(t1.NumHaltTasks(), Equals, 2)
   511  	c.Assert(t2.NumHaltTasks(), Equals, 2)
   512  }
   513  
   514  func (ts *taskSuite) TestTaskSetAddTaskAndAddAll(c *C) {
   515  	st := state.New(nil)
   516  	st.Lock()
   517  	defer st.Unlock()
   518  
   519  	t1 := st.NewTask("download", "1...")
   520  	t2 := st.NewTask("check", "2...")
   521  	t3 := st.NewTask("setup", "3...")
   522  	t4 := st.NewTask("link", "4...")
   523  
   524  	ts0 := state.NewTaskSet(t1)
   525  
   526  	ts0.AddTask(t2)
   527  	ts0.AddAll(state.NewTaskSet(t3, t4))
   528  
   529  	// these do nothing
   530  	ts0.AddTask(t2)
   531  	ts0.AddAll(state.NewTaskSet(t3, t4))
   532  
   533  	c.Check(ts0.Tasks(), DeepEquals, []*state.Task{t1, t2, t3, t4})
   534  }
   535  
   536  func (ts *taskSuite) TestLanes(c *C) {
   537  	st := state.New(nil)
   538  	st.Lock()
   539  	defer st.Unlock()
   540  
   541  	t := st.NewTask("download", "1...")
   542  
   543  	c.Assert(t.Lanes(), DeepEquals, []int{0})
   544  	t.JoinLane(1)
   545  	c.Assert(t.Lanes(), DeepEquals, []int{1})
   546  	t.JoinLane(2)
   547  	c.Assert(t.Lanes(), DeepEquals, []int{1, 2})
   548  }
   549  
   550  func (cs *taskSuite) TestTaskSetEdge(c *C) {
   551  	st := state.New(nil)
   552  	st.Lock()
   553  	defer st.Unlock()
   554  
   555  	// setup an example taskset
   556  	t1 := st.NewTask("download", "1...")
   557  	t2 := st.NewTask("verify", "2...")
   558  	t3 := st.NewTask("install", "3...")
   559  	ts := state.NewTaskSet(t1, t2, t3)
   560  
   561  	// edges are just typed strings
   562  	edge1 := state.TaskSetEdge("on-edge")
   563  	edge2 := state.TaskSetEdge("eddie")
   564  
   565  	// nil task causes panic
   566  	c.Check(func() { ts.MarkEdge(nil, edge1) }, PanicMatches, `cannot set edge "on-edge" with nil task`)
   567  
   568  	// no edge marked yet
   569  	t, err := ts.Edge(edge1)
   570  	c.Assert(t, IsNil)
   571  	c.Assert(err, ErrorMatches, `internal error: missing "on-edge" edge in task set`)
   572  	t, err = ts.Edge(edge2)
   573  	c.Assert(t, IsNil)
   574  	c.Assert(err, ErrorMatches, `internal error: missing "eddie" edge in task set`)
   575  
   576  	// one edge
   577  	ts.MarkEdge(t1, edge1)
   578  	t, err = ts.Edge(edge1)
   579  	c.Assert(t, Equals, t1)
   580  	c.Assert(err, IsNil)
   581  
   582  	// two edges
   583  	ts.MarkEdge(t2, edge2)
   584  	t, err = ts.Edge(edge1)
   585  	c.Assert(t, Equals, t1)
   586  	c.Assert(err, IsNil)
   587  	t, err = ts.Edge(edge2)
   588  	c.Assert(t, Equals, t2)
   589  	c.Assert(err, IsNil)
   590  
   591  	// edges can be reassigned
   592  	ts.MarkEdge(t3, edge1)
   593  	t, err = ts.Edge(edge1)
   594  	c.Assert(t, Equals, t3)
   595  	c.Assert(err, IsNil)
   596  }
   597  
   598  func (cs *taskSuite) TestTaskAddAllWithEdges(c *C) {
   599  	st := state.New(nil)
   600  	st.Lock()
   601  	defer st.Unlock()
   602  
   603  	edge1 := state.TaskSetEdge("install")
   604  
   605  	t1 := st.NewTask("download", "1...")
   606  	t2 := st.NewTask("verify", "2...")
   607  	t3 := st.NewTask("install", "3...")
   608  	ts := state.NewTaskSet(t1, t2, t3)
   609  
   610  	ts.MarkEdge(t1, edge1)
   611  	t, err := ts.Edge(edge1)
   612  	c.Assert(t, Equals, t1)
   613  	c.Assert(err, IsNil)
   614  
   615  	ts2 := state.NewTaskSet()
   616  	err = ts2.AddAllWithEdges(ts)
   617  	c.Assert(err, IsNil)
   618  	t, err = ts2.Edge(edge1)
   619  	c.Assert(t, Equals, t1)
   620  	c.Assert(err, IsNil)
   621  
   622  	// doing it again is no harm
   623  	err = ts2.AddAllWithEdges(ts)
   624  	c.Assert(err, IsNil)
   625  	t, err = ts2.Edge(edge1)
   626  	c.Assert(t, Equals, t1)
   627  	c.Assert(err, IsNil)
   628  
   629  	// but conflicting edges are an error
   630  	t4 := st.NewTask("another-kind", "4...")
   631  	tsWithDuplicatedEdge := state.NewTaskSet(t4)
   632  	tsWithDuplicatedEdge.MarkEdge(t4, edge1)
   633  	err = ts2.AddAllWithEdges(tsWithDuplicatedEdge)
   634  	c.Assert(err, ErrorMatches, `cannot add taskset: duplicated edge "install"`)
   635  }