github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/prow/jenkins/controller_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 jenkins
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"sync"
    23  	"testing"
    24  	"text/template"
    25  	"time"
    26  
    27  	"k8s.io/test-infra/prow/config"
    28  	"k8s.io/test-infra/prow/github"
    29  	"k8s.io/test-infra/prow/kube"
    30  	"k8s.io/test-infra/prow/pjutil"
    31  )
    32  
    33  type fca struct {
    34  	sync.Mutex
    35  	c *config.Config
    36  }
    37  
    38  func newFakeConfigAgent(t *testing.T, maxConcurrency int) *fca {
    39  	presubmits := []config.Presubmit{
    40  		{
    41  			Name: "test-bazel-build",
    42  			RunAfterSuccess: []config.Presubmit{
    43  				{
    44  					Name:         "test-kubeadm-cloud",
    45  					RunIfChanged: "^(cmd/kubeadm|build/debs).*$",
    46  				},
    47  			},
    48  		},
    49  		{
    50  			Name: "test-e2e",
    51  			RunAfterSuccess: []config.Presubmit{
    52  				{
    53  					Name: "push-image",
    54  				},
    55  			},
    56  		},
    57  		{
    58  			Name: "test-bazel-test",
    59  		},
    60  	}
    61  	if err := config.SetRegexes(presubmits); err != nil {
    62  		t.Fatal(err)
    63  	}
    64  	presubmitMap := map[string][]config.Presubmit{
    65  		"kubernetes/kubernetes": presubmits,
    66  	}
    67  
    68  	return &fca{
    69  		c: &config.Config{
    70  			JenkinsOperator: config.JenkinsOperator{
    71  				JobURLTemplate: template.Must(template.New("test").Parse("{{.Status.PodName}}/{{.Status.State}}")),
    72  				MaxConcurrency: maxConcurrency,
    73  			},
    74  			Presubmits: presubmitMap,
    75  		},
    76  	}
    77  
    78  }
    79  
    80  func (f *fca) Config() *config.Config {
    81  	f.Lock()
    82  	defer f.Unlock()
    83  	return f.c
    84  }
    85  
    86  type fkc struct {
    87  	sync.Mutex
    88  	prowjobs []kube.ProwJob
    89  }
    90  
    91  func (f *fkc) CreateProwJob(pj kube.ProwJob) (kube.ProwJob, error) {
    92  	f.Lock()
    93  	defer f.Unlock()
    94  	f.prowjobs = append(f.prowjobs, pj)
    95  	return pj, nil
    96  }
    97  
    98  func (f *fkc) ListProwJobs(map[string]string) ([]kube.ProwJob, error) {
    99  	f.Lock()
   100  	defer f.Unlock()
   101  	return f.prowjobs, nil
   102  }
   103  
   104  func (f *fkc) ReplaceProwJob(name string, job kube.ProwJob) (kube.ProwJob, error) {
   105  	f.Lock()
   106  	defer f.Unlock()
   107  	for i := range f.prowjobs {
   108  		if f.prowjobs[i].Metadata.Name == name {
   109  			f.prowjobs[i] = job
   110  			return job, nil
   111  		}
   112  	}
   113  	return kube.ProwJob{}, fmt.Errorf("did not find prowjob %s", name)
   114  }
   115  
   116  type fjc struct {
   117  	sync.Mutex
   118  	built  bool
   119  	pjs    []kube.ProwJob
   120  	err    error
   121  	builds map[string]JenkinsBuild
   122  }
   123  
   124  func (f *fjc) Build(pj *kube.ProwJob) error {
   125  	f.Lock()
   126  	defer f.Unlock()
   127  	if f.err != nil {
   128  		return f.err
   129  	}
   130  	f.built = true
   131  	f.pjs = append(f.pjs, *pj)
   132  	return nil
   133  }
   134  
   135  func (f *fjc) ListJenkinsBuilds(jobs map[string]struct{}) (map[string]JenkinsBuild, error) {
   136  	f.Lock()
   137  	defer f.Unlock()
   138  	if f.err != nil {
   139  		return nil, f.err
   140  	}
   141  	return f.builds, nil
   142  }
   143  
   144  func (f *fjc) Abort(job string, build *JenkinsBuild) error {
   145  	f.Lock()
   146  	defer f.Unlock()
   147  	return nil
   148  }
   149  
   150  type fghc struct {
   151  	sync.Mutex
   152  	changes []github.PullRequestChange
   153  	err     error
   154  }
   155  
   156  func (f *fghc) GetPullRequestChanges(org, repo string, number int) ([]github.PullRequestChange, error) {
   157  	f.Lock()
   158  	defer f.Unlock()
   159  	return f.changes, f.err
   160  }
   161  
   162  func (f *fghc) BotName() (string, error) {
   163  	f.Lock()
   164  	defer f.Unlock()
   165  	return "bot", nil
   166  }
   167  func (f *fghc) CreateStatus(org, repo, ref string, s github.Status) error {
   168  	f.Lock()
   169  	defer f.Unlock()
   170  	return nil
   171  }
   172  func (f *fghc) ListIssueComments(org, repo string, number int) ([]github.IssueComment, error) {
   173  	f.Lock()
   174  	defer f.Unlock()
   175  	return nil, nil
   176  }
   177  func (f *fghc) CreateComment(org, repo string, number int, comment string) error {
   178  	f.Lock()
   179  	defer f.Unlock()
   180  	return nil
   181  }
   182  func (f *fghc) DeleteComment(org, repo string, ID int) error {
   183  	f.Lock()
   184  	defer f.Unlock()
   185  	return nil
   186  }
   187  func (f *fghc) EditComment(org, repo string, ID int, comment string) error {
   188  	f.Lock()
   189  	defer f.Unlock()
   190  	return nil
   191  }
   192  
   193  func TestSyncNonPendingJobs(t *testing.T) {
   194  	var testcases = []struct {
   195  		name           string
   196  		pj             kube.ProwJob
   197  		pendingJobs    map[string]int
   198  		maxConcurrency int
   199  		builds         map[string]JenkinsBuild
   200  		err            error
   201  
   202  		expectedState    kube.ProwJobState
   203  		expectedBuild    bool
   204  		expectedComplete bool
   205  		expectedReport   bool
   206  		expectedEnqueued bool
   207  		expectedError    bool
   208  	}{
   209  		{
   210  			name: "complete pj",
   211  			pj: kube.ProwJob{
   212  				Status: kube.ProwJobStatus{
   213  					CompletionTime: time.Now(),
   214  				},
   215  			},
   216  			expectedComplete: true,
   217  		},
   218  		{
   219  			name: "start new job",
   220  			pj: kube.ProwJob{
   221  				Spec: kube.ProwJobSpec{
   222  					Type: kube.PostsubmitJob,
   223  				},
   224  				Status: kube.ProwJobStatus{
   225  					State: kube.TriggeredState,
   226  				},
   227  			},
   228  			expectedBuild:    true,
   229  			expectedReport:   true,
   230  			expectedState:    kube.PendingState,
   231  			expectedEnqueued: true,
   232  		},
   233  		{
   234  			name: "start new job, error",
   235  			pj: kube.ProwJob{
   236  				Spec: kube.ProwJobSpec{
   237  					Type: kube.PresubmitJob,
   238  					Refs: kube.Refs{
   239  						Pulls: []kube.Pull{{
   240  							Number: 1,
   241  							SHA:    "fake-sha",
   242  						}},
   243  					},
   244  				},
   245  				Status: kube.ProwJobStatus{
   246  					State: kube.TriggeredState,
   247  				},
   248  			},
   249  			err:              errors.New("oh no"),
   250  			expectedReport:   true,
   251  			expectedState:    kube.ErrorState,
   252  			expectedComplete: true,
   253  			expectedError:    true,
   254  		},
   255  		{
   256  			name: "block running new job",
   257  			pj: kube.ProwJob{
   258  				Spec: kube.ProwJobSpec{
   259  					Type: kube.PostsubmitJob,
   260  				},
   261  				Status: kube.ProwJobStatus{
   262  					State: kube.TriggeredState,
   263  				},
   264  			},
   265  			pendingJobs:      map[string]int{"motherearth": 10, "allagash": 8, "krusovice": 2},
   266  			maxConcurrency:   20,
   267  			expectedBuild:    false,
   268  			expectedReport:   false,
   269  			expectedState:    kube.TriggeredState,
   270  			expectedEnqueued: false,
   271  		},
   272  		{
   273  			name: "allow running new job",
   274  			pj: kube.ProwJob{
   275  				Spec: kube.ProwJobSpec{
   276  					Type: kube.PostsubmitJob,
   277  				},
   278  				Status: kube.ProwJobStatus{
   279  					State: kube.TriggeredState,
   280  				},
   281  			},
   282  			pendingJobs:      map[string]int{"motherearth": 10, "allagash": 8, "krusovice": 2},
   283  			maxConcurrency:   21,
   284  			expectedBuild:    true,
   285  			expectedReport:   true,
   286  			expectedState:    kube.PendingState,
   287  			expectedEnqueued: true,
   288  		},
   289  	}
   290  	for _, tc := range testcases {
   291  		t.Logf("scenario %q", tc.name)
   292  		fjc := &fjc{
   293  			err: tc.err,
   294  		}
   295  		fkc := &fkc{
   296  			prowjobs: []kube.ProwJob{tc.pj},
   297  		}
   298  
   299  		c := Controller{
   300  			kc:          fkc,
   301  			jc:          fjc,
   302  			ca:          newFakeConfigAgent(t, tc.maxConcurrency),
   303  			lock:        sync.RWMutex{},
   304  			pendingJobs: make(map[string]int),
   305  		}
   306  		if tc.pendingJobs != nil {
   307  			c.pendingJobs = tc.pendingJobs
   308  		}
   309  
   310  		reports := make(chan kube.ProwJob, 100)
   311  		if err := c.syncNonPendingJob(tc.pj, reports, tc.builds); err != nil {
   312  			t.Errorf("unexpected error: %v", err)
   313  			continue
   314  		}
   315  		close(reports)
   316  
   317  		actual := fkc.prowjobs[0]
   318  		if tc.expectedError && actual.Status.Description != "Error starting Jenkins job." {
   319  			t.Errorf("expected description %q, got %q", "Error starting Jenkins job.", actual.Status.Description)
   320  			continue
   321  		}
   322  		if actual.Status.State != tc.expectedState {
   323  			t.Errorf("expected state %q, got %q", tc.expectedState, actual.Status.State)
   324  			continue
   325  		}
   326  		if actual.Complete() != tc.expectedComplete {
   327  			t.Errorf("expected complete prowjob, got %v", actual)
   328  			continue
   329  		}
   330  		if tc.expectedReport && len(reports) != 1 {
   331  			t.Errorf("wanted one report but got %d", len(reports))
   332  			continue
   333  		}
   334  		if !tc.expectedReport && len(reports) != 0 {
   335  			t.Errorf("did not wany any reports but got %d", len(reports))
   336  			continue
   337  		}
   338  		if fjc.built != tc.expectedBuild {
   339  			t.Errorf("expected build: %t, got: %t", tc.expectedBuild, fjc.built)
   340  			continue
   341  		}
   342  		if tc.expectedEnqueued && actual.Status.Description != "Jenkins job enqueued." {
   343  			t.Errorf("expected enqueued prowjob, got %v", actual)
   344  		}
   345  	}
   346  }
   347  
   348  func TestSyncPendingJobs(t *testing.T) {
   349  	var testcases = []struct {
   350  		name        string
   351  		pj          kube.ProwJob
   352  		pendingJobs map[string]int
   353  		builds      map[string]JenkinsBuild
   354  		err         error
   355  
   356  		// TODO: Change to pass a ProwJobStatus
   357  		expectedState    kube.ProwJobState
   358  		expectedBuild    bool
   359  		expectedURL      string
   360  		expectedComplete bool
   361  		expectedReport   bool
   362  		expectedEnqueued bool
   363  		expectedError    bool
   364  	}{
   365  		{
   366  			name: "enqueued",
   367  			pj: kube.ProwJob{
   368  				Metadata: kube.ObjectMeta{
   369  					Name: "foofoo",
   370  				},
   371  				Spec: kube.ProwJobSpec{
   372  					Job: "test-job",
   373  				},
   374  				Status: kube.ProwJobStatus{
   375  					State:       kube.PendingState,
   376  					Description: "Jenkins job enqueued.",
   377  				},
   378  			},
   379  			builds: map[string]JenkinsBuild{
   380  				"foofoo": {enqueued: true, Number: 10},
   381  			},
   382  			expectedState:    kube.PendingState,
   383  			expectedEnqueued: true,
   384  		},
   385  		{
   386  			name: "finished queue",
   387  			pj: kube.ProwJob{
   388  				Metadata: kube.ObjectMeta{
   389  					Name: "boing",
   390  				},
   391  				Spec: kube.ProwJobSpec{
   392  					Job: "test-job",
   393  				},
   394  				Status: kube.ProwJobStatus{
   395  					State:       kube.PendingState,
   396  					Description: "Jenkins job enqueued.",
   397  				},
   398  			},
   399  			builds: map[string]JenkinsBuild{
   400  				"boing": {enqueued: false, Number: 10},
   401  			},
   402  			expectedURL:      "test-job-10/pending",
   403  			expectedState:    kube.PendingState,
   404  			expectedEnqueued: false,
   405  			expectedReport:   true,
   406  		},
   407  		{
   408  			name: "building",
   409  			pj: kube.ProwJob{
   410  				Metadata: kube.ObjectMeta{
   411  					Name: "firstoutthetrenches",
   412  				},
   413  				Spec: kube.ProwJobSpec{
   414  					Job: "test-job",
   415  				},
   416  				Status: kube.ProwJobStatus{
   417  					State: kube.PendingState,
   418  				},
   419  			},
   420  			builds: map[string]JenkinsBuild{
   421  				"firstoutthetrenches": {enqueued: false, Number: 10},
   422  			},
   423  			expectedURL:    "test-job-10/pending",
   424  			expectedState:  kube.PendingState,
   425  			expectedReport: true,
   426  		},
   427  		{
   428  			name: "missing build",
   429  			pj: kube.ProwJob{
   430  				Metadata: kube.ObjectMeta{
   431  					Name: "blabla",
   432  				},
   433  				Spec: kube.ProwJobSpec{
   434  					Type: kube.PresubmitJob,
   435  					Job:  "test-job",
   436  					Refs: kube.Refs{
   437  						Pulls: []kube.Pull{{
   438  							Number: 1,
   439  							SHA:    "fake-sha",
   440  						}},
   441  					},
   442  				},
   443  				Status: kube.ProwJobStatus{
   444  					State: kube.PendingState,
   445  				},
   446  			},
   447  			// missing build
   448  			builds: map[string]JenkinsBuild{
   449  				"other": {enqueued: false, Number: 10},
   450  			},
   451  			expectedURL:      "https://github.com/kubernetes/test-infra/issues",
   452  			expectedState:    kube.ErrorState,
   453  			expectedError:    true,
   454  			expectedComplete: true,
   455  			expectedReport:   true,
   456  		},
   457  		{
   458  			name: "finished, success",
   459  			pj: kube.ProwJob{
   460  				Metadata: kube.ObjectMeta{
   461  					Name: "winwin",
   462  				},
   463  				Spec: kube.ProwJobSpec{
   464  					Job: "test-job",
   465  				},
   466  				Status: kube.ProwJobStatus{
   467  					State: kube.PendingState,
   468  				},
   469  			},
   470  			builds: map[string]JenkinsBuild{
   471  				"winwin": {Result: pState(Succeess), Number: 11},
   472  			},
   473  			expectedURL:      "test-job-11/success",
   474  			expectedState:    kube.SuccessState,
   475  			expectedComplete: true,
   476  			expectedReport:   true,
   477  		},
   478  		{
   479  			name: "finished, failed",
   480  			pj: kube.ProwJob{
   481  				Metadata: kube.ObjectMeta{
   482  					Name: "whatapity",
   483  				},
   484  				Spec: kube.ProwJobSpec{
   485  					Job: "test-job",
   486  				},
   487  				Status: kube.ProwJobStatus{
   488  					State: kube.PendingState,
   489  				},
   490  			},
   491  			builds: map[string]JenkinsBuild{
   492  				"whatapity": {Result: pState(Failure), Number: 12},
   493  			},
   494  			expectedURL:      "test-job-12/failure",
   495  			expectedState:    kube.FailureState,
   496  			expectedComplete: true,
   497  			expectedReport:   true,
   498  		},
   499  	}
   500  	for _, tc := range testcases {
   501  		t.Logf("scenario %q", tc.name)
   502  		fjc := &fjc{
   503  			err: tc.err,
   504  		}
   505  		fkc := &fkc{
   506  			prowjobs: []kube.ProwJob{tc.pj},
   507  		}
   508  
   509  		c := Controller{
   510  			kc:          fkc,
   511  			jc:          fjc,
   512  			ca:          newFakeConfigAgent(t, 0),
   513  			lock:        sync.RWMutex{},
   514  			pendingJobs: make(map[string]int),
   515  		}
   516  
   517  		reports := make(chan kube.ProwJob, 100)
   518  		if err := c.syncPendingJob(tc.pj, reports, tc.builds); err != nil {
   519  			t.Errorf("unexpected error: %v", err)
   520  			continue
   521  		}
   522  		close(reports)
   523  
   524  		actual := fkc.prowjobs[0]
   525  		if tc.expectedError && actual.Status.Description != "Error finding Jenkins job." {
   526  			t.Errorf("expected description %q, got %q", "Error finding Jenkins job.", actual.Status.Description)
   527  			continue
   528  		}
   529  		if actual.Status.State != tc.expectedState {
   530  			t.Errorf("expected state %q, got %q", tc.expectedState, actual.Status.State)
   531  			continue
   532  		}
   533  		if actual.Complete() != tc.expectedComplete {
   534  			t.Errorf("expected complete prowjob, got %v", actual)
   535  			continue
   536  		}
   537  		if tc.expectedReport && len(reports) != 1 {
   538  			t.Errorf("wanted one report but got %d", len(reports))
   539  			continue
   540  		}
   541  		if !tc.expectedReport && len(reports) != 0 {
   542  			t.Errorf("did not wany any reports but got %d", len(reports))
   543  			continue
   544  		}
   545  		if fjc.built != tc.expectedBuild {
   546  			t.Errorf("expected build: %t, got: %t", tc.expectedBuild, fjc.built)
   547  			continue
   548  		}
   549  		if tc.expectedEnqueued && actual.Status.Description != "Jenkins job enqueued." {
   550  			t.Errorf("expected enqueued prowjob, got %v", actual)
   551  		}
   552  		if tc.expectedURL != actual.Status.URL {
   553  			t.Errorf("expected status URL: %s, got: %s", tc.expectedURL, actual.Status.URL)
   554  		}
   555  	}
   556  }
   557  
   558  func pState(state string) *string {
   559  	s := state
   560  	return &s
   561  }
   562  
   563  // TestBatch walks through the happy path of a batch job on Jenkins.
   564  func TestBatch(t *testing.T) {
   565  	pre := config.Presubmit{
   566  		Name:    "pr-some-job",
   567  		Agent:   "jenkins",
   568  		Context: "Some Job Context",
   569  	}
   570  	pj := pjutil.NewProwJob(pjutil.BatchSpec(pre, kube.Refs{
   571  		Org:     "o",
   572  		Repo:    "r",
   573  		BaseRef: "master",
   574  		BaseSHA: "123",
   575  		Pulls: []kube.Pull{
   576  			{
   577  				Number: 1,
   578  				SHA:    "abc",
   579  			},
   580  			{
   581  				Number: 2,
   582  				SHA:    "qwe",
   583  			},
   584  		},
   585  	}))
   586  	pj.Metadata.Name = "known_name"
   587  	fc := &fkc{
   588  		prowjobs: []kube.ProwJob{pj},
   589  	}
   590  	jc := &fjc{
   591  		builds: map[string]JenkinsBuild{
   592  			"known_name": { /* Running */ },
   593  		},
   594  	}
   595  	c := Controller{
   596  		kc:          fc,
   597  		jc:          jc,
   598  		ca:          newFakeConfigAgent(t, 0),
   599  		pendingJobs: make(map[string]int),
   600  		lock:        sync.RWMutex{},
   601  	}
   602  
   603  	if err := c.Sync(); err != nil {
   604  		t.Fatalf("Error on first sync: %v", err)
   605  	}
   606  	if fc.prowjobs[0].Status.State != kube.PendingState {
   607  		t.Fatalf("Wrong state: %v", fc.prowjobs[0].Status.State)
   608  	}
   609  	if fc.prowjobs[0].Status.Description != "Jenkins job enqueued." {
   610  		t.Fatalf("Expected description %q, got %q.", "Jenkins job enqueued.", fc.prowjobs[0].Status.Description)
   611  	}
   612  	jc.builds["known_name"] = JenkinsBuild{Number: 42}
   613  	if err := c.Sync(); err != nil {
   614  		t.Fatalf("Error on second sync: %v", err)
   615  	}
   616  	if fc.prowjobs[0].Status.Description != "Jenkins job running." {
   617  		t.Fatalf("Expected description %q, got %q.", "Jenkins job running.", fc.prowjobs[0].Status.Description)
   618  	}
   619  	if fc.prowjobs[0].Status.PodName != "pr-some-job-42" {
   620  		t.Fatalf("Wrong PodName: %s", fc.prowjobs[0].Status.PodName)
   621  	}
   622  	jc.builds["known_name"] = JenkinsBuild{Result: pState(Succeess)}
   623  	if err := c.Sync(); err != nil {
   624  		t.Fatalf("Error on third sync: %v", err)
   625  	}
   626  	if fc.prowjobs[0].Status.Description != "Jenkins job succeeded." {
   627  		t.Fatalf("Expected description %q, got %q.", "Jenkins job succeeded.", fc.prowjobs[0].Status.Description)
   628  	}
   629  	if fc.prowjobs[0].Status.State != kube.SuccessState {
   630  		t.Fatalf("Wrong state: %v", fc.prowjobs[0].Status.State)
   631  	}
   632  	// This is what the SQ reads.
   633  	if fc.prowjobs[0].Spec.Context != "Some Job Context" {
   634  		t.Fatalf("Wrong context: %v", fc.prowjobs[0].Spec.Context)
   635  	}
   636  }
   637  
   638  func TestRunAfterSuccessCanRun(t *testing.T) {
   639  	tests := []struct {
   640  		name string
   641  
   642  		parent *kube.ProwJob
   643  		child  *kube.ProwJob
   644  
   645  		changes []github.PullRequestChange
   646  		err     error
   647  
   648  		expected bool
   649  	}{
   650  		{
   651  			name: "child does not require specific changes",
   652  			parent: &kube.ProwJob{
   653  				Spec: kube.ProwJobSpec{
   654  					Job:  "test-e2e",
   655  					Type: kube.PresubmitJob,
   656  					Refs: kube.Refs{
   657  						Org:  "kubernetes",
   658  						Repo: "kubernetes",
   659  						Pulls: []kube.Pull{
   660  							{Number: 123},
   661  						},
   662  					},
   663  				},
   664  			},
   665  			child: &kube.ProwJob{
   666  				Spec: kube.ProwJobSpec{
   667  					Job: "push-image",
   668  				},
   669  			},
   670  			expected: true,
   671  		},
   672  		{
   673  			name: "child requires specific changes that are done",
   674  			parent: &kube.ProwJob{
   675  				Spec: kube.ProwJobSpec{
   676  					Job:  "test-bazel-build",
   677  					Type: kube.PresubmitJob,
   678  					Refs: kube.Refs{
   679  						Org:  "kubernetes",
   680  						Repo: "kubernetes",
   681  						Pulls: []kube.Pull{
   682  							{Number: 123},
   683  						},
   684  					},
   685  				},
   686  			},
   687  			child: &kube.ProwJob{
   688  				Spec: kube.ProwJobSpec{
   689  					Job: "test-kubeadm-cloud",
   690  				},
   691  			},
   692  			changes: []github.PullRequestChange{
   693  				{Filename: "cmd/kubeadm/kubeadm.go"},
   694  				{Filename: "vendor/BUILD"},
   695  				{Filename: ".gitatrributes"},
   696  			},
   697  			expected: true,
   698  		},
   699  		{
   700  			name: "child requires specific changes that are not done",
   701  			parent: &kube.ProwJob{
   702  				Spec: kube.ProwJobSpec{
   703  					Job:  "test-bazel-build",
   704  					Type: kube.PresubmitJob,
   705  					Refs: kube.Refs{
   706  						Org:  "kubernetes",
   707  						Repo: "kubernetes",
   708  						Pulls: []kube.Pull{
   709  							{Number: 123},
   710  						},
   711  					},
   712  				},
   713  			},
   714  			child: &kube.ProwJob{
   715  				Spec: kube.ProwJobSpec{
   716  					Job: "test-kubeadm-cloud",
   717  				},
   718  			},
   719  			changes: []github.PullRequestChange{
   720  				{Filename: "vendor/BUILD"},
   721  				{Filename: ".gitatrributes"},
   722  			},
   723  			expected: false,
   724  		},
   725  	}
   726  
   727  	for _, test := range tests {
   728  		t.Logf("scenario %q", test.name)
   729  
   730  		fakeGH := &fghc{
   731  			changes: test.changes,
   732  			err:     test.err,
   733  		}
   734  
   735  		got := RunAfterSuccessCanRun(test.parent, test.child, newFakeConfigAgent(t, 0), fakeGH)
   736  		if got != test.expected {
   737  			t.Errorf("expected to run: %t, got: %t", test.expected, got)
   738  		}
   739  	}
   740  }
   741  
   742  func TestMaxConcurrencyWithNewlyTriggeredJobs(t *testing.T) {
   743  	tests := []struct {
   744  		name           string
   745  		pjs            []kube.ProwJob
   746  		pendingJobs    map[string]int
   747  		expectedBuilds int
   748  	}{
   749  		{
   750  			name: "avoid starting a triggered job",
   751  			pjs: []kube.ProwJob{
   752  				{
   753  					Spec: kube.ProwJobSpec{
   754  						Job:            "test-bazel-build",
   755  						Type:           kube.PostsubmitJob,
   756  						MaxConcurrency: 1,
   757  					},
   758  					Status: kube.ProwJobStatus{
   759  						State: kube.TriggeredState,
   760  					},
   761  				},
   762  				{
   763  					Spec: kube.ProwJobSpec{
   764  						Job:            "test-bazel-build",
   765  						Type:           kube.PostsubmitJob,
   766  						MaxConcurrency: 1,
   767  					},
   768  					Status: kube.ProwJobStatus{
   769  						State: kube.TriggeredState,
   770  					},
   771  				},
   772  			},
   773  			pendingJobs:    make(map[string]int),
   774  			expectedBuilds: 1,
   775  		},
   776  		{
   777  			name: "both triggered jobs can start",
   778  			pjs: []kube.ProwJob{
   779  				{
   780  					Spec: kube.ProwJobSpec{
   781  						Job:            "test-bazel-build",
   782  						Type:           kube.PostsubmitJob,
   783  						MaxConcurrency: 2,
   784  					},
   785  					Status: kube.ProwJobStatus{
   786  						State: kube.TriggeredState,
   787  					},
   788  				},
   789  				{
   790  					Spec: kube.ProwJobSpec{
   791  						Job:            "test-bazel-build",
   792  						Type:           kube.PostsubmitJob,
   793  						MaxConcurrency: 2,
   794  					},
   795  					Status: kube.ProwJobStatus{
   796  						State: kube.TriggeredState,
   797  					},
   798  				},
   799  			},
   800  			pendingJobs:    make(map[string]int),
   801  			expectedBuilds: 2,
   802  		},
   803  		{
   804  			name: "no triggered job can start",
   805  			pjs: []kube.ProwJob{
   806  				{
   807  					Spec: kube.ProwJobSpec{
   808  						Job:            "test-bazel-build",
   809  						Type:           kube.PostsubmitJob,
   810  						MaxConcurrency: 5,
   811  					},
   812  					Status: kube.ProwJobStatus{
   813  						State: kube.TriggeredState,
   814  					},
   815  				},
   816  				{
   817  					Spec: kube.ProwJobSpec{
   818  						Job:            "test-bazel-build",
   819  						Type:           kube.PostsubmitJob,
   820  						MaxConcurrency: 5,
   821  					},
   822  					Status: kube.ProwJobStatus{
   823  						State: kube.TriggeredState,
   824  					},
   825  				},
   826  			},
   827  			pendingJobs:    map[string]int{"test-bazel-build": 5},
   828  			expectedBuilds: 0,
   829  		},
   830  	}
   831  
   832  	for _, test := range tests {
   833  		jobs := make(chan kube.ProwJob, len(test.pjs))
   834  		for _, pj := range test.pjs {
   835  			jobs <- pj
   836  		}
   837  		close(jobs)
   838  
   839  		fc := &fkc{
   840  			prowjobs: test.pjs,
   841  		}
   842  		fjc := &fjc{}
   843  		c := Controller{
   844  			kc:          fc,
   845  			jc:          fjc,
   846  			ca:          newFakeConfigAgent(t, 0),
   847  			pendingJobs: test.pendingJobs,
   848  		}
   849  
   850  		reports := make(chan<- kube.ProwJob, len(test.pjs))
   851  		errors := make(chan<- error, len(test.pjs))
   852  
   853  		syncProwJobs(c.syncNonPendingJob, jobs, reports, errors, nil)
   854  		if len(fjc.pjs) != test.expectedBuilds {
   855  			t.Errorf("expected builds: %d, got: %d", test.expectedBuilds, len(fjc.pjs))
   856  		}
   857  	}
   858  }
   859  
   860  func TestGetJenkinsJobs(t *testing.T) {
   861  	tests := []struct {
   862  		name     string
   863  		pjs      []kube.ProwJob
   864  		expected map[string]struct{}
   865  	}{
   866  		{
   867  			name: "both complete and running",
   868  			pjs: []kube.ProwJob{
   869  				{
   870  					Spec: kube.ProwJobSpec{
   871  						Job: "coolio",
   872  					},
   873  					Status: kube.ProwJobStatus{
   874  						CompletionTime: time.Now(),
   875  					},
   876  				},
   877  				{
   878  					Spec: kube.ProwJobSpec{
   879  						Job: "maradona",
   880  					},
   881  					Status: kube.ProwJobStatus{},
   882  				},
   883  			},
   884  			expected: map[string]struct{}{"maradona": {}},
   885  		},
   886  		{
   887  			name: "only complete",
   888  			pjs: []kube.ProwJob{
   889  				{
   890  					Spec: kube.ProwJobSpec{
   891  						Job: "coolio",
   892  					},
   893  					Status: kube.ProwJobStatus{
   894  						CompletionTime: time.Now(),
   895  					},
   896  				},
   897  				{
   898  					Spec: kube.ProwJobSpec{
   899  						Job: "maradona",
   900  					},
   901  					Status: kube.ProwJobStatus{
   902  						CompletionTime: time.Now(),
   903  					},
   904  				},
   905  			},
   906  			expected: map[string]struct{}{},
   907  		},
   908  		{
   909  			name: "only running",
   910  			pjs: []kube.ProwJob{
   911  				{
   912  					Spec: kube.ProwJobSpec{
   913  						Job: "coolio",
   914  					},
   915  					Status: kube.ProwJobStatus{},
   916  				},
   917  				{
   918  					Spec: kube.ProwJobSpec{
   919  						Job: "maradona",
   920  					},
   921  					Status: kube.ProwJobStatus{},
   922  				},
   923  			},
   924  			expected: map[string]struct{}{"maradona": {}, "coolio": {}},
   925  		},
   926  	}
   927  
   928  	for _, test := range tests {
   929  		t.Logf("scenario %q", test.name)
   930  		got := getJenkinsJobs(test.pjs)
   931  		if len(got) != len(test.expected) {
   932  			t.Errorf("unexpected job amount: %d (%v), expected: %d (%v)",
   933  				len(got), got, len(test.expected), test.expected)
   934  		}
   935  		for job := range test.expected {
   936  			if _, ok := got[job]; !ok {
   937  				t.Errorf("expected job %q was not found, got %v", job, got)
   938  			}
   939  		}
   940  	}
   941  }