github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/scheduler/util_test.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"reflect"
     8  	"testing"
     9  
    10  	"github.com/hashicorp/nomad/nomad/mock"
    11  	"github.com/hashicorp/nomad/nomad/state"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  )
    14  
    15  func TestMaterializeTaskGroups(t *testing.T) {
    16  	job := mock.Job()
    17  	index := materializeTaskGroups(job)
    18  	if len(index) != 10 {
    19  		t.Fatalf("Bad: %#v", index)
    20  	}
    21  
    22  	for i := 0; i < 10; i++ {
    23  		name := fmt.Sprintf("my-job.web[%d]", i)
    24  		tg, ok := index[name]
    25  		if !ok {
    26  			t.Fatalf("bad")
    27  		}
    28  		if tg != job.TaskGroups[0] {
    29  			t.Fatalf("bad")
    30  		}
    31  	}
    32  }
    33  
    34  func TestDiffAllocs(t *testing.T) {
    35  	job := mock.Job()
    36  	required := materializeTaskGroups(job)
    37  
    38  	// The "old" job has a previous modify index
    39  	oldJob := new(structs.Job)
    40  	*oldJob = *job
    41  	oldJob.ModifyIndex -= 1
    42  
    43  	tainted := map[string]bool{
    44  		"dead": true,
    45  		"zip":  false,
    46  	}
    47  
    48  	allocs := []*structs.Allocation{
    49  		// Update the 1st
    50  		&structs.Allocation{
    51  			ID:     structs.GenerateUUID(),
    52  			NodeID: "zip",
    53  			Name:   "my-job.web[0]",
    54  			Job:    oldJob,
    55  		},
    56  
    57  		// Ignore the 2rd
    58  		&structs.Allocation{
    59  			ID:     structs.GenerateUUID(),
    60  			NodeID: "zip",
    61  			Name:   "my-job.web[1]",
    62  			Job:    job,
    63  		},
    64  
    65  		// Evict 11th
    66  		&structs.Allocation{
    67  			ID:     structs.GenerateUUID(),
    68  			NodeID: "zip",
    69  			Name:   "my-job.web[10]",
    70  		},
    71  
    72  		// Migrate the 3rd
    73  		&structs.Allocation{
    74  			ID:     structs.GenerateUUID(),
    75  			NodeID: "dead",
    76  			Name:   "my-job.web[2]",
    77  		},
    78  	}
    79  
    80  	diff := diffAllocs(job, tainted, required, allocs)
    81  	place := diff.place
    82  	update := diff.update
    83  	migrate := diff.migrate
    84  	stop := diff.stop
    85  	ignore := diff.ignore
    86  
    87  	// We should update the first alloc
    88  	if len(update) != 1 || update[0].Alloc != allocs[0] {
    89  		t.Fatalf("bad: %#v", update)
    90  	}
    91  
    92  	// We should ignore the second alloc
    93  	if len(ignore) != 1 || ignore[0].Alloc != allocs[1] {
    94  		t.Fatalf("bad: %#v", ignore)
    95  	}
    96  
    97  	// We should stop the 3rd alloc
    98  	if len(stop) != 1 || stop[0].Alloc != allocs[2] {
    99  		t.Fatalf("bad: %#v", stop)
   100  	}
   101  
   102  	// We should migrate the 4rd alloc
   103  	if len(migrate) != 1 || migrate[0].Alloc != allocs[3] {
   104  		t.Fatalf("bad: %#v", migrate)
   105  	}
   106  
   107  	// We should place 7
   108  	if len(place) != 7 {
   109  		t.Fatalf("bad: %#v", place)
   110  	}
   111  }
   112  
   113  func TestDiffSystemAllocs(t *testing.T) {
   114  	job := mock.SystemJob()
   115  
   116  	// Create three alive nodes.
   117  	nodes := []*structs.Node{{ID: "foo"}, {ID: "bar"}, {ID: "baz"}}
   118  
   119  	// The "old" job has a previous modify index
   120  	oldJob := new(structs.Job)
   121  	*oldJob = *job
   122  	oldJob.ModifyIndex -= 1
   123  
   124  	tainted := map[string]bool{
   125  		"dead": true,
   126  		"baz":  false,
   127  	}
   128  
   129  	allocs := []*structs.Allocation{
   130  		// Update allocation on baz
   131  		&structs.Allocation{
   132  			ID:     structs.GenerateUUID(),
   133  			NodeID: "baz",
   134  			Name:   "my-job.web[0]",
   135  			Job:    oldJob,
   136  		},
   137  
   138  		// Ignore allocation on bar
   139  		&structs.Allocation{
   140  			ID:     structs.GenerateUUID(),
   141  			NodeID: "bar",
   142  			Name:   "my-job.web[0]",
   143  			Job:    job,
   144  		},
   145  
   146  		// Stop allocation on dead.
   147  		&structs.Allocation{
   148  			ID:     structs.GenerateUUID(),
   149  			NodeID: "dead",
   150  			Name:   "my-job.web[0]",
   151  		},
   152  	}
   153  
   154  	diff := diffSystemAllocs(job, nodes, tainted, allocs)
   155  	place := diff.place
   156  	update := diff.update
   157  	migrate := diff.migrate
   158  	stop := diff.stop
   159  	ignore := diff.ignore
   160  
   161  	// We should update the first alloc
   162  	if len(update) != 1 || update[0].Alloc != allocs[0] {
   163  		t.Fatalf("bad: %#v", update)
   164  	}
   165  
   166  	// We should ignore the second alloc
   167  	if len(ignore) != 1 || ignore[0].Alloc != allocs[1] {
   168  		t.Fatalf("bad: %#v", ignore)
   169  	}
   170  
   171  	// We should stop the third alloc
   172  	if len(stop) != 1 || stop[0].Alloc != allocs[2] {
   173  		t.Fatalf("bad: %#v", stop)
   174  	}
   175  
   176  	// There should be no migrates.
   177  	if len(migrate) != 0 {
   178  		t.Fatalf("bad: %#v", migrate)
   179  	}
   180  
   181  	// We should place 1
   182  	if len(place) != 1 {
   183  		t.Fatalf("bad: %#v", place)
   184  	}
   185  }
   186  
   187  func TestReadyNodesInDCs(t *testing.T) {
   188  	state, err := state.NewStateStore(os.Stderr)
   189  	if err != nil {
   190  		t.Fatalf("err: %v", err)
   191  	}
   192  
   193  	node1 := mock.Node()
   194  	node2 := mock.Node()
   195  	node2.Datacenter = "dc2"
   196  	node3 := mock.Node()
   197  	node3.Datacenter = "dc2"
   198  	node3.Status = structs.NodeStatusDown
   199  	node4 := mock.Node()
   200  	node4.Drain = true
   201  
   202  	noErr(t, state.UpsertNode(1000, node1))
   203  	noErr(t, state.UpsertNode(1001, node2))
   204  	noErr(t, state.UpsertNode(1002, node3))
   205  	noErr(t, state.UpsertNode(1003, node4))
   206  
   207  	nodes, err := readyNodesInDCs(state, []string{"dc1", "dc2"})
   208  	if err != nil {
   209  		t.Fatalf("err: %v", err)
   210  	}
   211  
   212  	if len(nodes) != 2 {
   213  		t.Fatalf("bad: %v", nodes)
   214  	}
   215  	if nodes[0].ID == node3.ID || nodes[1].ID == node3.ID {
   216  		t.Fatalf("Bad: %#v", nodes)
   217  	}
   218  }
   219  
   220  func TestRetryMax(t *testing.T) {
   221  	calls := 0
   222  	bad := func() (bool, error) {
   223  		calls += 1
   224  		return false, nil
   225  	}
   226  	err := retryMax(3, bad)
   227  	if err == nil {
   228  		t.Fatalf("should fail")
   229  	}
   230  	if calls != 3 {
   231  		t.Fatalf("mis match")
   232  	}
   233  
   234  	calls = 0
   235  	good := func() (bool, error) {
   236  		calls += 1
   237  		return true, nil
   238  	}
   239  	err = retryMax(3, good)
   240  	if err != nil {
   241  		t.Fatalf("err: %v", err)
   242  	}
   243  	if calls != 1 {
   244  		t.Fatalf("mis match")
   245  	}
   246  }
   247  
   248  func TestTaintedNodes(t *testing.T) {
   249  	state, err := state.NewStateStore(os.Stderr)
   250  	if err != nil {
   251  		t.Fatalf("err: %v", err)
   252  	}
   253  
   254  	node1 := mock.Node()
   255  	node2 := mock.Node()
   256  	node2.Datacenter = "dc2"
   257  	node3 := mock.Node()
   258  	node3.Datacenter = "dc2"
   259  	node3.Status = structs.NodeStatusDown
   260  	node4 := mock.Node()
   261  	node4.Drain = true
   262  	noErr(t, state.UpsertNode(1000, node1))
   263  	noErr(t, state.UpsertNode(1001, node2))
   264  	noErr(t, state.UpsertNode(1002, node3))
   265  	noErr(t, state.UpsertNode(1003, node4))
   266  
   267  	allocs := []*structs.Allocation{
   268  		&structs.Allocation{NodeID: node1.ID},
   269  		&structs.Allocation{NodeID: node2.ID},
   270  		&structs.Allocation{NodeID: node3.ID},
   271  		&structs.Allocation{NodeID: node4.ID},
   272  		&structs.Allocation{NodeID: "blah"},
   273  	}
   274  	tainted, err := taintedNodes(state, allocs)
   275  	if err != nil {
   276  		t.Fatalf("err: %v", err)
   277  	}
   278  
   279  	if len(tainted) != 5 {
   280  		t.Fatalf("bad: %v", tainted)
   281  	}
   282  	if tainted[node1.ID] || tainted[node2.ID] {
   283  		t.Fatalf("Bad: %v", tainted)
   284  	}
   285  	if !tainted[node3.ID] || !tainted[node4.ID] || !tainted["blah"] {
   286  		t.Fatalf("Bad: %v", tainted)
   287  	}
   288  }
   289  
   290  func TestShuffleNodes(t *testing.T) {
   291  	// Use a large number of nodes to make the probability of shuffling to the
   292  	// original order very low.
   293  	nodes := []*structs.Node{
   294  		mock.Node(),
   295  		mock.Node(),
   296  		mock.Node(),
   297  		mock.Node(),
   298  		mock.Node(),
   299  		mock.Node(),
   300  		mock.Node(),
   301  		mock.Node(),
   302  		mock.Node(),
   303  		mock.Node(),
   304  	}
   305  	orig := make([]*structs.Node, len(nodes))
   306  	copy(orig, nodes)
   307  	shuffleNodes(nodes)
   308  	if reflect.DeepEqual(nodes, orig) {
   309  		t.Fatalf("should not match")
   310  	}
   311  }
   312  
   313  func TestTasksUpdated(t *testing.T) {
   314  	j1 := mock.Job()
   315  	j2 := mock.Job()
   316  
   317  	if tasksUpdated(j1.TaskGroups[0], j2.TaskGroups[0]) {
   318  		t.Fatalf("bad")
   319  	}
   320  
   321  	j2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other"
   322  	if !tasksUpdated(j1.TaskGroups[0], j2.TaskGroups[0]) {
   323  		t.Fatalf("bad")
   324  	}
   325  
   326  	j3 := mock.Job()
   327  	j3.TaskGroups[0].Tasks[0].Name = "foo"
   328  	if !tasksUpdated(j1.TaskGroups[0], j3.TaskGroups[0]) {
   329  		t.Fatalf("bad")
   330  	}
   331  
   332  	j4 := mock.Job()
   333  	j4.TaskGroups[0].Tasks[0].Driver = "foo"
   334  	if !tasksUpdated(j1.TaskGroups[0], j4.TaskGroups[0]) {
   335  		t.Fatalf("bad")
   336  	}
   337  
   338  	j5 := mock.Job()
   339  	j5.TaskGroups[0].Tasks = append(j5.TaskGroups[0].Tasks,
   340  		j5.TaskGroups[0].Tasks[0])
   341  	if !tasksUpdated(j1.TaskGroups[0], j5.TaskGroups[0]) {
   342  		t.Fatalf("bad")
   343  	}
   344  
   345  	j6 := mock.Job()
   346  	j6.TaskGroups[0].Tasks[0].Resources.Networks[0].DynamicPorts = []string{"http", "https", "admin"}
   347  	if !tasksUpdated(j1.TaskGroups[0], j6.TaskGroups[0]) {
   348  		t.Fatalf("bad")
   349  	}
   350  }
   351  
   352  func TestEvictAndPlace_LimitLessThanAllocs(t *testing.T) {
   353  	_, ctx := testContext(t)
   354  	allocs := []allocTuple{
   355  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   356  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   357  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   358  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   359  	}
   360  	diff := &diffResult{}
   361  
   362  	limit := 2
   363  	if !evictAndPlace(ctx, diff, allocs, "", &limit) {
   364  		t.Fatal("evictAndReplace() should have returned true")
   365  	}
   366  
   367  	if limit != 0 {
   368  		t.Fatalf("evictAndReplace() should decremented limit; got %v; want 0", limit)
   369  	}
   370  
   371  	if len(diff.place) != 2 {
   372  		t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place)
   373  	}
   374  }
   375  
   376  func TestEvictAndPlace_LimitEqualToAllocs(t *testing.T) {
   377  	_, ctx := testContext(t)
   378  	allocs := []allocTuple{
   379  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   380  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   381  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   382  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   383  	}
   384  	diff := &diffResult{}
   385  
   386  	limit := 4
   387  	if evictAndPlace(ctx, diff, allocs, "", &limit) {
   388  		t.Fatal("evictAndReplace() should have returned false")
   389  	}
   390  
   391  	if limit != 0 {
   392  		t.Fatalf("evictAndReplace() should decremented limit; got %v; want 0", limit)
   393  	}
   394  
   395  	if len(diff.place) != 4 {
   396  		t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place)
   397  	}
   398  }
   399  
   400  func TestSetStatus(t *testing.T) {
   401  	h := NewHarness(t)
   402  	logger := log.New(os.Stderr, "", log.LstdFlags)
   403  	eval := mock.Eval()
   404  	status := "a"
   405  	desc := "b"
   406  	if err := setStatus(logger, h, eval, nil, status, desc); err != nil {
   407  		t.Fatalf("setStatus() failed: %v", err)
   408  	}
   409  
   410  	if len(h.Evals) != 1 {
   411  		t.Fatalf("setStatus() didn't update plan: %v", h.Evals)
   412  	}
   413  
   414  	newEval := h.Evals[0]
   415  	if newEval.ID != eval.ID || newEval.Status != status || newEval.StatusDescription != desc {
   416  		t.Fatalf("setStatus() submited invalid eval: %v", newEval)
   417  	}
   418  
   419  	h = NewHarness(t)
   420  	next := mock.Eval()
   421  	if err := setStatus(logger, h, eval, next, status, desc); err != nil {
   422  		t.Fatalf("setStatus() failed: %v", err)
   423  	}
   424  
   425  	if len(h.Evals) != 1 {
   426  		t.Fatalf("setStatus() didn't update plan: %v", h.Evals)
   427  	}
   428  
   429  	newEval = h.Evals[0]
   430  	if newEval.NextEval != next.ID {
   431  		t.Fatalf("setStatus() didn't set nextEval correctly: %v", newEval)
   432  	}
   433  }
   434  
   435  func TestInplaceUpdate_ChangedTaskGroup(t *testing.T) {
   436  	state, ctx := testContext(t)
   437  	eval := mock.Eval()
   438  	job := mock.Job()
   439  
   440  	node := mock.Node()
   441  	noErr(t, state.UpsertNode(1000, node))
   442  
   443  	// Register an alloc
   444  	alloc := &structs.Allocation{
   445  		ID:     structs.GenerateUUID(),
   446  		EvalID: eval.ID,
   447  		NodeID: node.ID,
   448  		JobID:  job.ID,
   449  		Job:    job,
   450  		Resources: &structs.Resources{
   451  			CPU:      2048,
   452  			MemoryMB: 2048,
   453  		},
   454  		DesiredStatus: structs.AllocDesiredStatusRun,
   455  	}
   456  	alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources}
   457  	noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc}))
   458  
   459  	// Create a new task group that prevents in-place updates.
   460  	tg := &structs.TaskGroup{}
   461  	*tg = *job.TaskGroups[0]
   462  	task := &structs.Task{Name: "FOO"}
   463  	tg.Tasks = nil
   464  	tg.Tasks = append(tg.Tasks, task)
   465  
   466  	updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}}
   467  	stack := NewGenericStack(false, ctx)
   468  
   469  	// Do the inplace update.
   470  	unplaced := inplaceUpdate(ctx, eval, job, stack, updates)
   471  
   472  	if len(unplaced) != 1 {
   473  		t.Fatal("inplaceUpdate incorrectly did an inplace update")
   474  	}
   475  
   476  	if len(ctx.plan.NodeAllocation) != 0 {
   477  		t.Fatal("inplaceUpdate incorrectly did an inplace update")
   478  	}
   479  }
   480  
   481  func TestInplaceUpdate_NoMatch(t *testing.T) {
   482  	state, ctx := testContext(t)
   483  	eval := mock.Eval()
   484  	job := mock.Job()
   485  
   486  	node := mock.Node()
   487  	noErr(t, state.UpsertNode(1000, node))
   488  
   489  	// Register an alloc
   490  	alloc := &structs.Allocation{
   491  		ID:     structs.GenerateUUID(),
   492  		EvalID: eval.ID,
   493  		NodeID: node.ID,
   494  		JobID:  job.ID,
   495  		Job:    job,
   496  		Resources: &structs.Resources{
   497  			CPU:      2048,
   498  			MemoryMB: 2048,
   499  		},
   500  		DesiredStatus: structs.AllocDesiredStatusRun,
   501  	}
   502  	alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources}
   503  	noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc}))
   504  
   505  	// Create a new task group that requires too much resources.
   506  	tg := &structs.TaskGroup{}
   507  	*tg = *job.TaskGroups[0]
   508  	resource := &structs.Resources{CPU: 9999}
   509  	tg.Tasks[0].Resources = resource
   510  
   511  	updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}}
   512  	stack := NewGenericStack(false, ctx)
   513  
   514  	// Do the inplace update.
   515  	unplaced := inplaceUpdate(ctx, eval, job, stack, updates)
   516  
   517  	if len(unplaced) != 1 {
   518  		t.Fatal("inplaceUpdate incorrectly did an inplace update")
   519  	}
   520  
   521  	if len(ctx.plan.NodeAllocation) != 0 {
   522  		t.Fatal("inplaceUpdate incorrectly did an inplace update")
   523  	}
   524  }
   525  
   526  func TestInplaceUpdate_Success(t *testing.T) {
   527  	state, ctx := testContext(t)
   528  	eval := mock.Eval()
   529  	job := mock.Job()
   530  
   531  	node := mock.Node()
   532  	noErr(t, state.UpsertNode(1000, node))
   533  
   534  	// Register an alloc
   535  	alloc := &structs.Allocation{
   536  		ID:     structs.GenerateUUID(),
   537  		EvalID: eval.ID,
   538  		NodeID: node.ID,
   539  		JobID:  job.ID,
   540  		Job:    job,
   541  		Resources: &structs.Resources{
   542  			CPU:      2048,
   543  			MemoryMB: 2048,
   544  		},
   545  		DesiredStatus: structs.AllocDesiredStatusRun,
   546  	}
   547  	alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources}
   548  	noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc}))
   549  
   550  	// Create a new task group that updates the resources.
   551  	tg := &structs.TaskGroup{}
   552  	*tg = *job.TaskGroups[0]
   553  	resource := &structs.Resources{CPU: 737}
   554  	tg.Tasks[0].Resources = resource
   555  
   556  	updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}}
   557  	stack := NewGenericStack(false, ctx)
   558  
   559  	// Do the inplace update.
   560  	unplaced := inplaceUpdate(ctx, eval, job, stack, updates)
   561  
   562  	if len(unplaced) != 0 {
   563  		t.Fatal("inplaceUpdate did not do an inplace update")
   564  	}
   565  
   566  	if len(ctx.plan.NodeAllocation) != 1 {
   567  		t.Fatal("inplaceUpdate did not do an inplace update")
   568  	}
   569  }
   570  
   571  func TestEvictAndPlace_LimitGreaterThanAllocs(t *testing.T) {
   572  	_, ctx := testContext(t)
   573  	allocs := []allocTuple{
   574  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   575  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   576  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   577  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   578  	}
   579  	diff := &diffResult{}
   580  
   581  	limit := 6
   582  	if evictAndPlace(ctx, diff, allocs, "", &limit) {
   583  		t.Fatal("evictAndReplace() should have returned false")
   584  	}
   585  
   586  	if limit != 2 {
   587  		t.Fatalf("evictAndReplace() should decremented limit; got %v; want 2", limit)
   588  	}
   589  
   590  	if len(diff.place) != 4 {
   591  		t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place)
   592  	}
   593  }
   594  
   595  func TestTaskGroupConstraints(t *testing.T) {
   596  	constr := &structs.Constraint{Hard: true}
   597  	constr2 := &structs.Constraint{LTarget: "foo"}
   598  	constr3 := &structs.Constraint{Weight: 10}
   599  
   600  	tg := &structs.TaskGroup{
   601  		Name:        "web",
   602  		Count:       10,
   603  		Constraints: []*structs.Constraint{constr},
   604  		Tasks: []*structs.Task{
   605  			&structs.Task{
   606  				Driver: "exec",
   607  				Resources: &structs.Resources{
   608  					CPU:      500,
   609  					MemoryMB: 256,
   610  				},
   611  				Constraints: []*structs.Constraint{constr2},
   612  			},
   613  			&structs.Task{
   614  				Driver: "docker",
   615  				Resources: &structs.Resources{
   616  					CPU:      500,
   617  					MemoryMB: 256,
   618  				},
   619  				Constraints: []*structs.Constraint{constr3},
   620  			},
   621  		},
   622  	}
   623  
   624  	// Build the expected values.
   625  	expConstr := []*structs.Constraint{constr, constr2, constr3}
   626  	expDrivers := map[string]struct{}{"exec": struct{}{}, "docker": struct{}{}}
   627  	expSize := &structs.Resources{
   628  		CPU:      1000,
   629  		MemoryMB: 512,
   630  	}
   631  
   632  	actConstrains := taskGroupConstraints(tg)
   633  	if !reflect.DeepEqual(actConstrains.constraints, expConstr) {
   634  		t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.constraints, expConstr)
   635  	}
   636  	if !reflect.DeepEqual(actConstrains.drivers, expDrivers) {
   637  		t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.drivers, expDrivers)
   638  	}
   639  	if !reflect.DeepEqual(actConstrains.size, expSize) {
   640  		t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.size, expSize)
   641  	}
   642  
   643  }