github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/prow/tide/tide_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package tide
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"github.com/shurcooL/githubql"
    25  	"github.com/sirupsen/logrus"
    26  
    27  	"k8s.io/test-infra/prow/config"
    28  	"k8s.io/test-infra/prow/git/localgit"
    29  	"k8s.io/test-infra/prow/github"
    30  	"k8s.io/test-infra/prow/kube"
    31  )
    32  
    33  func testPullsMatchList(t *testing.T, test string, actual []pullRequest, expected []int) {
    34  	if len(actual) != len(expected) {
    35  		t.Errorf("Wrong size for case %s. Got PRs %+v, wanted numbers %v.", test, actual, expected)
    36  		return
    37  	}
    38  	for _, pr := range actual {
    39  		var found bool
    40  		n1 := int(pr.Number)
    41  		for _, n2 := range expected {
    42  			if n1 == n2 {
    43  				found = true
    44  			}
    45  		}
    46  		if !found {
    47  			t.Errorf("For case %s, found PR %d but shouldn't have.", test, n1)
    48  		}
    49  	}
    50  }
    51  
    52  func TestAccumulateBatch(t *testing.T) {
    53  	type pull struct {
    54  		number int
    55  		sha    string
    56  	}
    57  	type prowjob struct {
    58  		prs   []pull
    59  		job   string
    60  		state kube.ProwJobState
    61  	}
    62  	tests := []struct {
    63  		name       string
    64  		presubmits []string
    65  		pulls      []pull
    66  		prowJobs   []prowjob
    67  
    68  		merges  []int
    69  		pending bool
    70  	}{
    71  		{
    72  			name: "no batches running",
    73  		},
    74  		{
    75  			name:       "batch pending",
    76  			presubmits: []string{"foo", "bar", "baz"},
    77  			pulls:      []pull{{1, "a"}, {2, "b"}},
    78  			prowJobs:   []prowjob{{job: "foo", state: kube.PendingState, prs: []pull{{1, "a"}}}},
    79  			pending:    true,
    80  		},
    81  		{
    82  			name:       "batch pending, successful previous run",
    83  			presubmits: []string{"foo", "bar", "baz"},
    84  			pulls:      []pull{{1, "a"}, {2, "b"}},
    85  			prowJobs: []prowjob{
    86  				{job: "foo", state: kube.PendingState, prs: []pull{{1, "a"}}},
    87  				{job: "foo", state: kube.SuccessState, prs: []pull{{2, "b"}}},
    88  				{job: "bar", state: kube.SuccessState, prs: []pull{{2, "b"}}},
    89  				{job: "baz", state: kube.SuccessState, prs: []pull{{2, "b"}}},
    90  			},
    91  			pending: true,
    92  		},
    93  		{
    94  			name:       "successful run",
    95  			presubmits: []string{"foo", "bar", "baz"},
    96  			pulls:      []pull{{1, "a"}, {2, "b"}},
    97  			prowJobs: []prowjob{
    98  				{job: "foo", state: kube.SuccessState, prs: []pull{{2, "b"}}},
    99  				{job: "bar", state: kube.SuccessState, prs: []pull{{2, "b"}}},
   100  				{job: "baz", state: kube.SuccessState, prs: []pull{{2, "b"}}},
   101  			},
   102  			merges: []int{2},
   103  		},
   104  		{
   105  			name:       "successful run, multiple PRs",
   106  			presubmits: []string{"foo", "bar", "baz"},
   107  			pulls:      []pull{{1, "a"}, {2, "b"}},
   108  			prowJobs: []prowjob{
   109  				{job: "foo", state: kube.SuccessState, prs: []pull{{1, "a"}, {2, "b"}}},
   110  				{job: "bar", state: kube.SuccessState, prs: []pull{{1, "a"}, {2, "b"}}},
   111  				{job: "baz", state: kube.SuccessState, prs: []pull{{1, "a"}, {2, "b"}}},
   112  			},
   113  			merges: []int{1, 2},
   114  		},
   115  		{
   116  			name:       "successful run, failures in past",
   117  			presubmits: []string{"foo", "bar", "baz"},
   118  			pulls:      []pull{{1, "a"}, {2, "b"}},
   119  			prowJobs: []prowjob{
   120  				{job: "foo", state: kube.SuccessState, prs: []pull{{1, "a"}, {2, "b"}}},
   121  				{job: "bar", state: kube.SuccessState, prs: []pull{{1, "a"}, {2, "b"}}},
   122  				{job: "baz", state: kube.SuccessState, prs: []pull{{1, "a"}, {2, "b"}}},
   123  				{job: "foo", state: kube.FailureState, prs: []pull{{1, "a"}, {2, "b"}}},
   124  				{job: "baz", state: kube.FailureState, prs: []pull{{1, "a"}, {2, "b"}}},
   125  				{job: "foo", state: kube.FailureState, prs: []pull{{1, "c"}, {2, "b"}}},
   126  			},
   127  			merges: []int{1, 2},
   128  		},
   129  		{
   130  			name:       "failures",
   131  			presubmits: []string{"foo", "bar", "baz"},
   132  			pulls:      []pull{{1, "a"}, {2, "b"}},
   133  			prowJobs: []prowjob{
   134  				{job: "foo", state: kube.FailureState, prs: []pull{{1, "a"}, {2, "b"}}},
   135  				{job: "bar", state: kube.SuccessState, prs: []pull{{1, "a"}, {2, "b"}}},
   136  				{job: "baz", state: kube.FailureState, prs: []pull{{1, "a"}, {2, "b"}}},
   137  				{job: "foo", state: kube.FailureState, prs: []pull{{1, "c"}, {2, "b"}}},
   138  			},
   139  		},
   140  	}
   141  	for _, test := range tests {
   142  		var pulls []pullRequest
   143  		for _, p := range test.pulls {
   144  			pr := pullRequest{Number: githubql.Int(p.number)}
   145  			pr.HeadRef.Target.OID = githubql.String(p.sha)
   146  			pulls = append(pulls, pr)
   147  		}
   148  		var pjs []kube.ProwJob
   149  		for _, pj := range test.prowJobs {
   150  			npj := kube.ProwJob{
   151  				Spec: kube.ProwJobSpec{
   152  					Job:  pj.job,
   153  					Type: kube.BatchJob,
   154  				},
   155  				Status: kube.ProwJobStatus{State: pj.state},
   156  			}
   157  			for _, pr := range pj.prs {
   158  				npj.Spec.Refs.Pulls = append(npj.Spec.Refs.Pulls, kube.Pull{
   159  					Number: pr.number,
   160  					SHA:    pr.sha,
   161  				})
   162  			}
   163  			pjs = append(pjs, npj)
   164  		}
   165  		merges, pending := accumulateBatch(test.presubmits, pulls, pjs)
   166  		if pending != test.pending {
   167  			t.Errorf("For case \"%s\", got wrong pending.", test.name)
   168  		}
   169  		testPullsMatchList(t, test.name, merges, test.merges)
   170  	}
   171  }
   172  
   173  func TestAccumulate(t *testing.T) {
   174  	type prowjob struct {
   175  		prNumber int
   176  		job      string
   177  		state    kube.ProwJobState
   178  	}
   179  	tests := []struct {
   180  		presubmits   []string
   181  		pullRequests []int
   182  		prowJobs     []prowjob
   183  
   184  		successes []int
   185  		pendings  []int
   186  		none      []int
   187  	}{
   188  		{
   189  			presubmits:   []string{"job1", "job2"},
   190  			pullRequests: []int{1, 2, 3, 4, 5, 6, 7},
   191  			prowJobs: []prowjob{
   192  				{2, "job1", kube.PendingState},
   193  				{3, "job1", kube.PendingState},
   194  				{3, "job2", kube.TriggeredState},
   195  				{4, "job1", kube.FailureState},
   196  				{4, "job2", kube.PendingState},
   197  				{5, "job1", kube.PendingState},
   198  				{5, "job2", kube.FailureState},
   199  				{5, "job2", kube.PendingState},
   200  				{6, "job1", kube.SuccessState},
   201  				{6, "job2", kube.PendingState},
   202  				{7, "job1", kube.SuccessState},
   203  				{7, "job2", kube.SuccessState},
   204  				{7, "job1", kube.FailureState},
   205  			},
   206  
   207  			successes: []int{7},
   208  			pendings:  []int{3, 5, 6},
   209  			none:      []int{1, 2, 4},
   210  		},
   211  		{
   212  			presubmits:   []string{"job1", "job2", "job3", "job4"},
   213  			pullRequests: []int{7},
   214  			prowJobs: []prowjob{
   215  				{7, "job1", kube.SuccessState},
   216  				{7, "job2", kube.FailureState},
   217  				{7, "job3", kube.FailureState},
   218  				{7, "job4", kube.FailureState},
   219  				{7, "job3", kube.FailureState},
   220  				{7, "job4", kube.FailureState},
   221  				{7, "job2", kube.SuccessState},
   222  				{7, "job3", kube.SuccessState},
   223  				{7, "job4", kube.FailureState},
   224  			},
   225  
   226  			successes: []int{},
   227  			pendings:  []int{},
   228  			none:      []int{7},
   229  		},
   230  		{
   231  			presubmits:   []string{"job1", "job2", "job3", "job4"},
   232  			pullRequests: []int{7},
   233  			prowJobs: []prowjob{
   234  				{7, "job1", kube.FailureState},
   235  				{7, "job2", kube.FailureState},
   236  				{7, "job3", kube.FailureState},
   237  				{7, "job4", kube.FailureState},
   238  				{7, "job3", kube.FailureState},
   239  				{7, "job4", kube.FailureState},
   240  				{7, "job2", kube.FailureState},
   241  				{7, "job3", kube.FailureState},
   242  				{7, "job4", kube.FailureState},
   243  			},
   244  
   245  			successes: []int{},
   246  			pendings:  []int{},
   247  			none:      []int{7},
   248  		},
   249  		{
   250  			presubmits:   []string{"job1", "job2", "job3", "job4"},
   251  			pullRequests: []int{7},
   252  			prowJobs: []prowjob{
   253  				{7, "job1", kube.SuccessState},
   254  				{7, "job2", kube.FailureState},
   255  				{7, "job3", kube.FailureState},
   256  				{7, "job4", kube.FailureState},
   257  				{7, "job3", kube.FailureState},
   258  				{7, "job4", kube.FailureState},
   259  				{7, "job2", kube.SuccessState},
   260  				{7, "job3", kube.SuccessState},
   261  				{7, "job4", kube.SuccessState},
   262  				{7, "job1", kube.FailureState},
   263  			},
   264  
   265  			successes: []int{7},
   266  			pendings:  []int{},
   267  			none:      []int{},
   268  		},
   269  		{
   270  			presubmits:   []string{"job1", "job2", "job3", "job4"},
   271  			pullRequests: []int{7},
   272  			prowJobs: []prowjob{
   273  				{7, "job1", kube.SuccessState},
   274  				{7, "job2", kube.FailureState},
   275  				{7, "job3", kube.FailureState},
   276  				{7, "job4", kube.FailureState},
   277  				{7, "job3", kube.FailureState},
   278  				{7, "job4", kube.FailureState},
   279  				{7, "job2", kube.SuccessState},
   280  				{7, "job3", kube.SuccessState},
   281  				{7, "job4", kube.PendingState},
   282  				{7, "job1", kube.FailureState},
   283  			},
   284  
   285  			successes: []int{},
   286  			pendings:  []int{7},
   287  			none:      []int{},
   288  		},
   289  	}
   290  
   291  	for i, test := range tests {
   292  		var pulls []pullRequest
   293  		for _, p := range test.pullRequests {
   294  			pulls = append(pulls, pullRequest{Number: githubql.Int(p)})
   295  		}
   296  		var pjs []kube.ProwJob
   297  		for _, pj := range test.prowJobs {
   298  			pjs = append(pjs, kube.ProwJob{
   299  				Spec: kube.ProwJobSpec{
   300  					Job:  pj.job,
   301  					Type: kube.PresubmitJob,
   302  					Refs: kube.Refs{Pulls: []kube.Pull{{Number: pj.prNumber}}},
   303  				},
   304  				Status: kube.ProwJobStatus{State: pj.state},
   305  			})
   306  		}
   307  
   308  		successes, pendings, nones := accumulate(test.presubmits, pulls, pjs)
   309  
   310  		t.Logf("test run %d", i)
   311  		testPullsMatchList(t, "successes", successes, test.successes)
   312  		testPullsMatchList(t, "pendings", pendings, test.pendings)
   313  		testPullsMatchList(t, "nones", nones, test.none)
   314  	}
   315  }
   316  
   317  type fgc struct {
   318  	refs   map[string]string
   319  	merged int
   320  }
   321  
   322  func (f *fgc) GetRef(o, r, ref string) (string, error) {
   323  	return f.refs[o+"/"+r+" "+ref], nil
   324  }
   325  
   326  func (f *fgc) Query(ctx context.Context, q interface{}, vars map[string]interface{}) error {
   327  	return nil
   328  }
   329  
   330  func (f *fgc) Merge(org, repo string, number int, details github.MergeDetails) error {
   331  	f.merged++
   332  	return nil
   333  }
   334  
   335  // TestDividePool ensures that subpools returned by dividePool satisfy a few
   336  // important invariants.
   337  func TestDividePool(t *testing.T) {
   338  	testPulls := []struct {
   339  		org    string
   340  		repo   string
   341  		number int
   342  		branch string
   343  	}{
   344  		{
   345  			org:    "k",
   346  			repo:   "t-i",
   347  			number: 5,
   348  			branch: "master",
   349  		},
   350  		{
   351  			org:    "k",
   352  			repo:   "t-i",
   353  			number: 6,
   354  			branch: "master",
   355  		},
   356  		{
   357  			org:    "k",
   358  			repo:   "k",
   359  			number: 123,
   360  			branch: "master",
   361  		},
   362  		{
   363  			org:    "k",
   364  			repo:   "k",
   365  			number: 1000,
   366  			branch: "release-1.6",
   367  		},
   368  	}
   369  	testPJs := []struct {
   370  		jobType kube.ProwJobType
   371  		org     string
   372  		repo    string
   373  		baseRef string
   374  		baseSHA string
   375  	}{
   376  		{
   377  			jobType: kube.PresubmitJob,
   378  			org:     "k",
   379  			repo:    "t-i",
   380  			baseRef: "master",
   381  			baseSHA: "123",
   382  		},
   383  		{
   384  			jobType: kube.BatchJob,
   385  			org:     "k",
   386  			repo:    "t-i",
   387  			baseRef: "master",
   388  			baseSHA: "123",
   389  		},
   390  		{
   391  			jobType: kube.PeriodicJob,
   392  		},
   393  		{
   394  			jobType: kube.PresubmitJob,
   395  			org:     "k",
   396  			repo:    "t-i",
   397  			baseRef: "patch",
   398  			baseSHA: "123",
   399  		},
   400  		{
   401  			jobType: kube.PresubmitJob,
   402  			org:     "k",
   403  			repo:    "t-i",
   404  			baseRef: "master",
   405  			baseSHA: "abc",
   406  		},
   407  		{
   408  			jobType: kube.PresubmitJob,
   409  			org:     "o",
   410  			repo:    "t-i",
   411  			baseRef: "master",
   412  			baseSHA: "123",
   413  		},
   414  		{
   415  			jobType: kube.PresubmitJob,
   416  			org:     "k",
   417  			repo:    "other",
   418  			baseRef: "master",
   419  			baseSHA: "123",
   420  		},
   421  	}
   422  	fc := &fgc{
   423  		refs: map[string]string{"k/t-i heads/master": "123"},
   424  	}
   425  	c := &Controller{
   426  		ghc: fc,
   427  	}
   428  	var pulls []pullRequest
   429  	for _, p := range testPulls {
   430  		npr := pullRequest{Number: githubql.Int(p.number)}
   431  		npr.BaseRef.Name = githubql.String(p.branch)
   432  		npr.BaseRef.Prefix = "refs/heads/"
   433  		npr.Repository.Name = githubql.String(p.repo)
   434  		npr.Repository.Owner.Login = githubql.String(p.org)
   435  		pulls = append(pulls, npr)
   436  	}
   437  	var pjs []kube.ProwJob
   438  	for _, pj := range testPJs {
   439  		pjs = append(pjs, kube.ProwJob{
   440  			Spec: kube.ProwJobSpec{
   441  				Type: pj.jobType,
   442  				Refs: kube.Refs{
   443  					Org:     pj.org,
   444  					Repo:    pj.repo,
   445  					BaseRef: pj.baseRef,
   446  					BaseSHA: pj.baseSHA,
   447  				},
   448  			},
   449  		})
   450  	}
   451  	sps, err := c.dividePool(pulls, pjs)
   452  	if err != nil {
   453  		t.Fatalf("Error dividing pool: %v", err)
   454  	}
   455  	if len(sps) == 0 {
   456  		t.Error("No subpools.")
   457  	}
   458  	for _, sp := range sps {
   459  		name := fmt.Sprintf("%s/%s %s", sp.org, sp.repo, sp.branch)
   460  		sha := fc.refs[sp.org+"/"+sp.repo+" heads/"+sp.branch]
   461  		if sp.sha != sha {
   462  			t.Errorf("For subpool %s, got sha %s, expected %s.", name, sp.sha, sha)
   463  		}
   464  		if len(sp.prs) == 0 {
   465  			t.Errorf("Subpool %s has no PRs.", name)
   466  		}
   467  		for _, pr := range sp.prs {
   468  			if string(pr.Repository.Owner.Login) != sp.org || string(pr.Repository.Name) != sp.repo || string(pr.BaseRef.Name) != sp.branch {
   469  				t.Errorf("PR in wrong subpool. Got PR %+v in subpool %s.", pr, name)
   470  			}
   471  		}
   472  		for _, pj := range sp.pjs {
   473  			if pj.Spec.Type != kube.PresubmitJob && pj.Spec.Type != kube.BatchJob {
   474  				t.Errorf("PJ with bad type in subpool %s: %+v", name, pj)
   475  			}
   476  			if pj.Spec.Refs.Org != sp.org || pj.Spec.Refs.Repo != sp.repo || pj.Spec.Refs.BaseRef != sp.branch || pj.Spec.Refs.BaseSHA != sp.sha {
   477  				t.Errorf("PJ in wrong subpool. Got PJ %+v in subpool %s.", pj, name)
   478  			}
   479  		}
   480  	}
   481  }
   482  
   483  func TestPickBatch(t *testing.T) {
   484  	lg, gc, err := localgit.New()
   485  	if err != nil {
   486  		t.Fatalf("Error making local git: %v", err)
   487  	}
   488  	defer gc.Clean()
   489  	defer lg.Clean()
   490  	if err := lg.MakeFakeRepo("o", "r"); err != nil {
   491  		t.Fatalf("Error making fake repo: %v", err)
   492  	}
   493  	if err := lg.AddCommit("o", "r", map[string][]byte{"foo": []byte("foo")}); err != nil {
   494  		t.Fatalf("Adding initial commit: %v", err)
   495  	}
   496  	testprs := []struct {
   497  		files   map[string][]byte
   498  		success bool
   499  
   500  		included bool
   501  	}{
   502  		{
   503  			files:    map[string][]byte{"bar": []byte("ok")},
   504  			success:  true,
   505  			included: true,
   506  		},
   507  		{
   508  			files:    map[string][]byte{"foo": []byte("ok")},
   509  			success:  true,
   510  			included: true,
   511  		},
   512  		{
   513  			files:    map[string][]byte{"bar": []byte("conflicts with 0")},
   514  			success:  true,
   515  			included: false,
   516  		},
   517  		{
   518  			files:    map[string][]byte{"qux": []byte("ok")},
   519  			success:  false,
   520  			included: false,
   521  		},
   522  		{
   523  			files:    map[string][]byte{"bazel": []byte("ok")},
   524  			success:  true,
   525  			included: true,
   526  		},
   527  	}
   528  	sp := subpool{
   529  		org:    "o",
   530  		repo:   "r",
   531  		branch: "master",
   532  		sha:    "master",
   533  	}
   534  	for i, testpr := range testprs {
   535  		if err := lg.CheckoutNewBranch("o", "r", fmt.Sprintf("pr-%d", i)); err != nil {
   536  			t.Fatalf("Error checking out new branch: %v", err)
   537  		}
   538  		if err := lg.AddCommit("o", "r", testpr.files); err != nil {
   539  			t.Fatalf("Error adding commit: %v", err)
   540  		}
   541  		if err := lg.Checkout("o", "r", "master"); err != nil {
   542  			t.Fatalf("Error checking out master: %v", err)
   543  		}
   544  		var pr pullRequest
   545  		pr.Number = githubql.Int(i)
   546  		pr.Commits.Nodes = []struct {
   547  			Commit struct {
   548  				Status struct{ State githubql.String }
   549  			}
   550  		}{{}}
   551  		if testpr.success {
   552  			pr.Commits.Nodes[0].Commit.Status.State = githubql.String("SUCCESS")
   553  		}
   554  		pr.HeadRef.Target.OID = githubql.String(fmt.Sprintf("origin/pr-%d", i))
   555  		sp.prs = append(sp.prs, pr)
   556  	}
   557  	c := &Controller{
   558  		gc: gc,
   559  	}
   560  	prs, err := c.pickBatch(sp)
   561  	if err != nil {
   562  		t.Fatalf("Error from pickBatch: %v", err)
   563  	}
   564  	for i, testpr := range testprs {
   565  		var found bool
   566  		for _, pr := range prs {
   567  			if int(pr.Number) == i {
   568  				found = true
   569  				break
   570  			}
   571  		}
   572  		if found != testpr.included {
   573  			t.Errorf("PR %d should not be picked.", i)
   574  		}
   575  	}
   576  }
   577  
   578  type fkc struct {
   579  	createdJobs []kube.ProwJob
   580  }
   581  
   582  func (c *fkc) ListProwJobs(map[string]string) ([]kube.ProwJob, error) {
   583  	return nil, nil
   584  }
   585  
   586  func (c *fkc) CreateProwJob(pj kube.ProwJob) (kube.ProwJob, error) {
   587  	c.createdJobs = append(c.createdJobs, pj)
   588  	return pj, nil
   589  }
   590  
   591  func TestTakeAction(t *testing.T) {
   592  	// PRs 0-9 exist. All are mergable, and all are passing tests.
   593  	testcases := []struct {
   594  		name string
   595  
   596  		batchPending bool
   597  		successes    []int
   598  		pendings     []int
   599  		nones        []int
   600  		batchMerges  []int
   601  
   602  		merged            int
   603  		triggered         int
   604  		triggered_batches int
   605  	}{
   606  		{
   607  			name: "no prs to test, should do nothing",
   608  
   609  			batchPending: true,
   610  			successes:    []int{},
   611  			pendings:     []int{},
   612  			nones:        []int{},
   613  			batchMerges:  []int{},
   614  
   615  			merged:    0,
   616  			triggered: 0,
   617  		},
   618  		{
   619  			name: "pending batch, pending serial, nothing to do",
   620  
   621  			batchPending: true,
   622  			successes:    []int{},
   623  			pendings:     []int{1},
   624  			nones:        []int{0, 2},
   625  			batchMerges:  []int{},
   626  
   627  			merged:    0,
   628  			triggered: 0,
   629  		},
   630  		{
   631  			name: "pending batch, successful serial, nothing to do",
   632  
   633  			batchPending: true,
   634  			successes:    []int{1},
   635  			pendings:     []int{},
   636  			nones:        []int{0, 2},
   637  			batchMerges:  []int{},
   638  
   639  			merged:    0,
   640  			triggered: 0,
   641  		},
   642  		{
   643  			name: "pending batch, should trigger serial",
   644  
   645  			batchPending: true,
   646  			successes:    []int{},
   647  			pendings:     []int{},
   648  			nones:        []int{0, 1, 2},
   649  			batchMerges:  []int{},
   650  
   651  			merged:    0,
   652  			triggered: 1,
   653  		},
   654  		{
   655  			name: "no pending batch, should trigger serial and batch",
   656  
   657  			batchPending: false,
   658  			successes:    []int{},
   659  			pendings:     []int{},
   660  			nones:        []int{0, 1, 2, 3},
   661  			batchMerges:  []int{},
   662  
   663  			merged:            0,
   664  			triggered:         2,
   665  			triggered_batches: 1,
   666  		},
   667  		{
   668  			name: "one PR, should not trigger batch",
   669  
   670  			batchPending: false,
   671  			successes:    []int{},
   672  			pendings:     []int{},
   673  			nones:        []int{0},
   674  			batchMerges:  []int{},
   675  
   676  			merged:    0,
   677  			triggered: 1,
   678  		},
   679  		{
   680  			name: "successful PR, should merge",
   681  
   682  			batchPending: false,
   683  			successes:    []int{0},
   684  			pendings:     []int{},
   685  			nones:        []int{1, 2, 3},
   686  			batchMerges:  []int{},
   687  
   688  			merged:    1,
   689  			triggered: 0,
   690  		},
   691  		{
   692  			name: "successful batch, should merge",
   693  
   694  			batchPending: false,
   695  			successes:    []int{0, 1},
   696  			pendings:     []int{2, 3},
   697  			nones:        []int{4, 5},
   698  			batchMerges:  []int{6, 7, 8},
   699  
   700  			merged:    3,
   701  			triggered: 0,
   702  		},
   703  	}
   704  
   705  	for _, tc := range testcases {
   706  		ca := &config.Agent{}
   707  		ca.Set(&config.Config{
   708  			Presubmits: map[string][]config.Presubmit{
   709  				"o/r": {
   710  					{
   711  						Name:      "foo",
   712  						AlwaysRun: true,
   713  					},
   714  				},
   715  			},
   716  		})
   717  		lg, gc, err := localgit.New()
   718  		if err != nil {
   719  			t.Fatalf("Error making local git: %v", err)
   720  		}
   721  		defer gc.Clean()
   722  		defer lg.Clean()
   723  		if err := lg.MakeFakeRepo("o", "r"); err != nil {
   724  			t.Fatalf("Error making fake repo: %v", err)
   725  		}
   726  		if err := lg.AddCommit("o", "r", map[string][]byte{"foo": []byte("foo")}); err != nil {
   727  			t.Fatalf("Adding initial commit: %v", err)
   728  		}
   729  
   730  		sp := subpool{
   731  			org:    "o",
   732  			repo:   "r",
   733  			branch: "master",
   734  			sha:    "master",
   735  		}
   736  		genPulls := func(nums []int) []pullRequest {
   737  			var prs []pullRequest
   738  			for _, i := range nums {
   739  				if err := lg.CheckoutNewBranch("o", "r", fmt.Sprintf("pr-%d", i)); err != nil {
   740  					t.Fatalf("Error checking out new branch: %v", err)
   741  				}
   742  				if err := lg.AddCommit("o", "r", map[string][]byte{fmt.Sprintf("%d", i): []byte("WOW")}); err != nil {
   743  					t.Fatalf("Error adding commit: %v", err)
   744  				}
   745  				if err := lg.Checkout("o", "r", "master"); err != nil {
   746  					t.Fatalf("Error checking out master: %v", err)
   747  				}
   748  				var pr pullRequest
   749  				pr.Number = githubql.Int(i)
   750  				pr.Commits.Nodes = []struct {
   751  					Commit struct {
   752  						Status struct{ State githubql.String }
   753  					}
   754  				}{{}}
   755  				pr.Commits.Nodes[0].Commit.Status.State = githubql.String("SUCCESS")
   756  				pr.HeadRef.Target.OID = githubql.String(fmt.Sprintf("origin/pr-%d", i))
   757  				sp.prs = append(sp.prs, pr)
   758  				prs = append(prs, pr)
   759  			}
   760  			return prs
   761  		}
   762  		var fkc fkc
   763  		var fgc fgc
   764  		c := &Controller{
   765  			Logger: logrus.StandardLogger().WithField("controller", "tide"),
   766  			gc:     gc,
   767  			ghc:    &fgc,
   768  			ca:     ca,
   769  			kc:     &fkc,
   770  		}
   771  		t.Logf("Test case: %s", tc.name)
   772  		if err := c.takeAction(sp, tc.batchPending, genPulls(tc.successes), genPulls(tc.pendings), genPulls(tc.nones), genPulls(tc.batchMerges)); err != nil {
   773  			t.Errorf("Error in takeAction: %v", err)
   774  			continue
   775  		}
   776  		if tc.triggered != len(fkc.createdJobs) {
   777  			t.Errorf("Wrong number of jobs triggered. Got %d, expected %d.", len(fkc.createdJobs), tc.triggered)
   778  		}
   779  		if tc.merged != fgc.merged {
   780  			t.Errorf("Wrong number of merges. Got %d, expected %d.", fgc.merged, tc.merged)
   781  		}
   782  		// Ensure that the correct number of batch jobs were triggered
   783  		batches := 0
   784  		for _, job := range fkc.createdJobs {
   785  			if (len(job.Spec.Refs.Pulls) > 1) != (job.Spec.Type == kube.BatchJob) {
   786  				t.Error("Found a batch job that doesn't contain multiple pull refs!")
   787  			}
   788  			if len(job.Spec.Refs.Pulls) > 1 {
   789  				batches++
   790  			}
   791  		}
   792  		if tc.triggered_batches != batches {
   793  			t.Errorf("Wrong number of batches triggered. Got %d, expected %d.", batches, tc.triggered_batches)
   794  		}
   795  	}
   796  }