github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/prow/config/jobs_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 config
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"regexp"
    25  	"strings"
    26  	"testing"
    27  )
    28  
    29  var podRe = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`)
    30  
    31  const (
    32  	testThis   = "/test all"
    33  	retestBody = "/test all [submit-queue is verifying that this PR is safe to merge]"
    34  )
    35  
    36  type JSONJob struct {
    37  	Scenario string   `json:"scenario"`
    38  	Args     []string `json:"args"`
    39  }
    40  
    41  // Consistent but meaningless order.
    42  func flattenJobs(jobs []Presubmit) []Presubmit {
    43  	ret := jobs
    44  	for _, job := range jobs {
    45  		if len(job.RunAfterSuccess) > 0 {
    46  			ret = append(ret, flattenJobs(job.RunAfterSuccess)...)
    47  		}
    48  	}
    49  	return ret
    50  }
    51  
    52  // Returns if two brancher has overlapping branches
    53  func CheckOverlapBrancher(b1, b2 Brancher) bool {
    54  	if b1.RunsAgainstAllBranch() || b2.RunsAgainstAllBranch() {
    55  		return true
    56  	}
    57  
    58  	for _, run1 := range b1.Branches {
    59  		if b2.RunsAgainstBranch(run1) {
    60  			return true
    61  		}
    62  	}
    63  
    64  	for _, run2 := range b2.Branches {
    65  		if b1.RunsAgainstBranch(run2) {
    66  			return true
    67  		}
    68  	}
    69  
    70  	return false
    71  }
    72  
    73  // TODO(spxtr): Some of this is generic prowjob stuff and some of this is k8s-
    74  // specific. Figure out which is which and split this up.
    75  func TestPresubmits(t *testing.T) {
    76  	c, err := Load("../config.yaml")
    77  	if err != nil {
    78  		t.Fatalf("Could not load config: %v", err)
    79  	}
    80  	if len(c.Presubmits) == 0 {
    81  		t.Fatalf("No jobs found in presubmit.yaml.")
    82  	}
    83  	b, err := ioutil.ReadFile("../../jobs/config.json")
    84  	if err != nil {
    85  		t.Fatalf("Could not load jobs/config.json: %v", err)
    86  	}
    87  	var bootstrapConfig map[string]JSONJob
    88  	json.Unmarshal(b, &bootstrapConfig)
    89  	for _, rootJobs := range c.Presubmits {
    90  		jobs := flattenJobs(rootJobs)
    91  		for i, job := range jobs {
    92  			if job.Name == "" {
    93  				t.Errorf("Job %v needs a name.", job)
    94  				continue
    95  			}
    96  			if job.Context == "" {
    97  				t.Errorf("Job %s needs a context.", job.Name)
    98  			}
    99  			if job.RerunCommand == "" || job.Trigger == "" {
   100  				t.Errorf("Job %s needs a trigger and a rerun command.", job.Name)
   101  				continue
   102  			}
   103  			// Check that the merge bot will run AlwaysRun jobs, otherwise it
   104  			// will attempt to rerun forever.
   105  			if job.AlwaysRun && !job.re.MatchString(testThis) {
   106  				t.Errorf("AlwaysRun job %s: \"%s\" does not match regex \"%v\".", job.Name, testThis, job.Trigger)
   107  			}
   108  			if job.AlwaysRun && !job.re.MatchString(retestBody) {
   109  				t.Errorf("AlwaysRun job %s: \"%s\" does not match regex \"%v\".", job.Name, retestBody, job.Trigger)
   110  			}
   111  			// Check that the merge bot will not run Non-AlwaysRun jobs
   112  			if !job.AlwaysRun && job.re.MatchString(testThis) {
   113  				t.Errorf("Non-AlwaysRun job %s: \"%s\" matches regex \"%v\".", job.Name, testThis, job.Trigger)
   114  			}
   115  			if !job.AlwaysRun && job.re.MatchString(retestBody) {
   116  				t.Errorf("Non-AlwaysRun job %s: \"%s\" matches regex \"%v\".", job.Name, retestBody, job.Trigger)
   117  			}
   118  
   119  			if len(job.Brancher.Branches) > 0 && len(job.Brancher.SkipBranches) > 0 {
   120  				t.Errorf("Job %s : Cannot have both branches and skip_branches set", job.Name)
   121  			}
   122  			// Next check that the rerun command doesn't run any other jobs.
   123  			for j, job2 := range jobs[i+1:] {
   124  				if job.Name == job2.Name {
   125  					// Make sure max_concurrency are the same
   126  					if job.MaxConcurrency != job2.MaxConcurrency {
   127  						t.Errorf("Jobs %s share same name but has different max_concurrency", job.Name)
   128  					}
   129  					// Make sure branches are not overlapping
   130  					if CheckOverlapBrancher(job.Brancher, job2.Brancher) {
   131  						t.Errorf("Two jobs have the same name: %s, and has conflicted branches", job.Name)
   132  					}
   133  				} else {
   134  					if job.Context == job2.Context {
   135  						t.Errorf("Jobs %s and %s have the same context: %s", job.Name, job2.Name, job.Context)
   136  					}
   137  					if job2.re.MatchString(job.RerunCommand) {
   138  						t.Errorf("%d, %d, RerunCommand \"%s\" from job %s matches \"%v\" from job %s but shouldn't.", i, j, job.RerunCommand, job.Name, job2.Trigger, job2.Name)
   139  					}
   140  				}
   141  			}
   142  			var scenario string
   143  			job.Name = strings.Replace(job.Name, "pull-security-kubernetes", "pull-kubernetes", 1)
   144  			if j, present := bootstrapConfig[job.Name]; present {
   145  				scenario = fmt.Sprintf("scenarios/%s.py", j.Scenario)
   146  			} else {
   147  				scenario = fmt.Sprintf("jobs/%s.sh", job.Name)
   148  			}
   149  
   150  			// Ensure that jobs have a shell script of the same name.
   151  			if s, err := os.Stat(fmt.Sprintf("../../%s", scenario)); err != nil {
   152  				t.Errorf("Cannot find test-infra/%s for %s", scenario, job.Name)
   153  			} else {
   154  				if s.Mode()&0111 == 0 {
   155  					t.Errorf("Not executable: test-infra/%s (%o)", scenario, s.Mode()&0777)
   156  				}
   157  				if s.Mode()&0444 == 0 {
   158  					t.Errorf("Not readable: test-infra/%s (%o)", scenario, s.Mode()&0777)
   159  				}
   160  			}
   161  		}
   162  	}
   163  }
   164  
   165  func TestCommentBodyMatches(t *testing.T) {
   166  	var testcases = []struct {
   167  		repo         string
   168  		body         string
   169  		expectedJobs []string
   170  	}{
   171  		{
   172  			"org/repo",
   173  			"this is a random comment",
   174  			[]string{},
   175  		},
   176  		{
   177  			"org/repo",
   178  			"/ok-to-test",
   179  			[]string{"gce", "unit"},
   180  		},
   181  		{
   182  			"org/repo",
   183  			"/test all",
   184  			[]string{"gce", "unit", "gke"},
   185  		},
   186  		{
   187  			"org/repo",
   188  			"/test unit",
   189  			[]string{"unit"},
   190  		},
   191  		{
   192  			"org/repo",
   193  			"/test federation",
   194  			[]string{"federation"},
   195  		},
   196  		{
   197  			"org/repo2",
   198  			"/test all",
   199  			[]string{"cadveapster", "after-cadveapster", "after-after-cadveapster"},
   200  		},
   201  		{
   202  			"org/repo2",
   203  			"/test really",
   204  			[]string{"after-cadveapster"},
   205  		},
   206  		{
   207  			"org/repo2",
   208  			"/test again really",
   209  			[]string{"after-after-cadveapster"},
   210  		},
   211  		{
   212  			"org/repo3",
   213  			"/test all",
   214  			[]string{},
   215  		},
   216  	}
   217  	c := &Config{
   218  		Presubmits: map[string][]Presubmit{
   219  			"org/repo": {
   220  				{
   221  					Name:      "gce",
   222  					re:        regexp.MustCompile(`/test (gce|all)`),
   223  					AlwaysRun: true,
   224  				},
   225  				{
   226  					Name:      "unit",
   227  					re:        regexp.MustCompile(`/test (unit|all)`),
   228  					AlwaysRun: true,
   229  				},
   230  				{
   231  					Name:      "gke",
   232  					re:        regexp.MustCompile(`/test (gke|all)`),
   233  					AlwaysRun: false,
   234  				},
   235  				{
   236  					Name:      "federation",
   237  					re:        regexp.MustCompile(`/test federation`),
   238  					AlwaysRun: false,
   239  				},
   240  			},
   241  			"org/repo2": {
   242  				{
   243  					Name:      "cadveapster",
   244  					re:        regexp.MustCompile(`/test all`),
   245  					AlwaysRun: true,
   246  					RunAfterSuccess: []Presubmit{
   247  						{
   248  							Name:      "after-cadveapster",
   249  							re:        regexp.MustCompile(`/test (really|all)`),
   250  							AlwaysRun: true,
   251  							RunAfterSuccess: []Presubmit{
   252  								{
   253  									Name:      "after-after-cadveapster",
   254  									re:        regexp.MustCompile(`/test (again really|all)`),
   255  									AlwaysRun: true,
   256  								},
   257  							},
   258  						},
   259  						{
   260  							Name:      "another-after-cadveapster",
   261  							re:        regexp.MustCompile(`@k8s-bot dont test this`),
   262  							AlwaysRun: true,
   263  						},
   264  					},
   265  				},
   266  			},
   267  		},
   268  	}
   269  	for _, tc := range testcases {
   270  		actualJobs := c.MatchingPresubmits(tc.repo, tc.body, regexp.MustCompile(`/ok-to-test`))
   271  		match := true
   272  		if len(actualJobs) != len(tc.expectedJobs) {
   273  			match = false
   274  		} else {
   275  			for _, actualJob := range actualJobs {
   276  				found := false
   277  				for _, expectedJob := range tc.expectedJobs {
   278  					if expectedJob == actualJob.Name {
   279  						found = true
   280  						break
   281  					}
   282  				}
   283  				if !found {
   284  					match = false
   285  					break
   286  				}
   287  			}
   288  		}
   289  		if !match {
   290  			t.Errorf("Wrong jobs for body %s. Got %v, expected %v.", tc.body, actualJobs, tc.expectedJobs)
   291  		}
   292  	}
   293  }
   294  
   295  func TestRetestPresubmits(t *testing.T) {
   296  	var testcases = []struct {
   297  		skipContexts     map[string]bool
   298  		runContexts      map[string]bool
   299  		expectedContexts []string
   300  	}{
   301  		{
   302  			map[string]bool{},
   303  			map[string]bool{},
   304  			[]string{"gce", "unit"},
   305  		},
   306  		{
   307  			map[string]bool{"gce": true},
   308  			map[string]bool{},
   309  			[]string{"unit"},
   310  		},
   311  		{
   312  			map[string]bool{},
   313  			map[string]bool{"federation": true, "nonexistent": true},
   314  			[]string{"gce", "unit", "federation"},
   315  		},
   316  		{
   317  			map[string]bool{},
   318  			map[string]bool{"gke": true},
   319  			[]string{"gce", "unit", "gke"},
   320  		},
   321  		{
   322  			map[string]bool{"gce": true},
   323  			map[string]bool{"gce": true}, // should never happen
   324  			[]string{"unit"},
   325  		},
   326  	}
   327  	c := &Config{
   328  		Presubmits: map[string][]Presubmit{
   329  			"org/repo": {
   330  				{
   331  					Context:   "gce",
   332  					AlwaysRun: true,
   333  				},
   334  				{
   335  					Context:   "unit",
   336  					AlwaysRun: true,
   337  				},
   338  				{
   339  					Context:   "gke",
   340  					AlwaysRun: false,
   341  				},
   342  				{
   343  					Context:   "federation",
   344  					AlwaysRun: false,
   345  				},
   346  			},
   347  			"org/repo2": {
   348  				{
   349  					Context:   "shouldneverrun",
   350  					AlwaysRun: true,
   351  				},
   352  			},
   353  		},
   354  	}
   355  	for _, tc := range testcases {
   356  		actualContexts := c.RetestPresubmits("org/repo", tc.skipContexts, tc.runContexts)
   357  		match := true
   358  		if len(actualContexts) != len(tc.expectedContexts) {
   359  			match = false
   360  		} else {
   361  			for _, actualJob := range actualContexts {
   362  				found := false
   363  				for _, expectedContext := range tc.expectedContexts {
   364  					if expectedContext == actualJob.Context {
   365  						found = true
   366  						break
   367  					}
   368  				}
   369  				if !found {
   370  					match = false
   371  					break
   372  				}
   373  			}
   374  		}
   375  		if !match {
   376  			t.Errorf("Wrong contexts for skip %v run %v. Got %v, expected %v.", tc.runContexts, tc.skipContexts, actualContexts, tc.expectedContexts)
   377  		}
   378  	}
   379  
   380  }
   381  
   382  func TestConditionalPresubmits(t *testing.T) {
   383  	presubmits := []Presubmit{
   384  		{
   385  			Name:         "cross build",
   386  			RunIfChanged: `(Makefile|\.sh|_(windows|linux|osx|unknown)(_test)?\.go)$`,
   387  		},
   388  	}
   389  	SetRegexes(presubmits)
   390  	ps := presubmits[0]
   391  	var testcases = []struct {
   392  		changes  []string
   393  		expected bool
   394  	}{
   395  		{[]string{"some random file"}, false},
   396  		{[]string{"./pkg/util/rlimit/rlimit_linux.go"}, true},
   397  		{[]string{"./pkg/util/rlimit/rlimit_unknown_test.go"}, true},
   398  		{[]string{"build.sh"}, true},
   399  		{[]string{"build.shoo"}, false},
   400  		{[]string{"Makefile"}, true},
   401  	}
   402  	for _, tc := range testcases {
   403  		actual := ps.RunsAgainstChanges(tc.changes)
   404  		if actual != tc.expected {
   405  			t.Errorf("wrong RunsAgainstChanges(%#v) result. Got %v, expected %v", tc.changes, actual, tc.expected)
   406  		}
   407  	}
   408  }
   409  
   410  func TestListPresubmit(t *testing.T) {
   411  	c := &Config{
   412  		Presubmits: map[string][]Presubmit{
   413  			"r1": {
   414  				{
   415  					Name: "a",
   416  					RunAfterSuccess: []Presubmit{
   417  						{Name: "aa"},
   418  						{Name: "ab"},
   419  					},
   420  				},
   421  				{Name: "b"},
   422  			},
   423  			"r2": {
   424  				{
   425  					Name: "c",
   426  					RunAfterSuccess: []Presubmit{
   427  						{Name: "ca"},
   428  						{Name: "cb"},
   429  					},
   430  				},
   431  				{Name: "d"},
   432  			},
   433  		},
   434  		Postsubmits: map[string][]Postsubmit{
   435  			"r1": {{Name: "e"}},
   436  		},
   437  		Periodics: []Periodic{
   438  			{Name: "f"},
   439  		},
   440  	}
   441  
   442  	var testcases = []struct {
   443  		name     string
   444  		expected []string
   445  		repos    []string
   446  	}{
   447  		{
   448  			"all presubmits",
   449  			[]string{"a", "aa", "ab", "b", "c", "ca", "cb", "d"},
   450  			[]string{},
   451  		},
   452  		{
   453  			"r2 presubmits",
   454  			[]string{"c", "ca", "cb", "d"},
   455  			[]string{"r2"},
   456  		},
   457  	}
   458  
   459  	for _, tc := range testcases {
   460  		actual := c.AllPresubmits(tc.repos)
   461  		if len(actual) != len(tc.expected) {
   462  			t.Fatalf("test %s - Wrong number of jobs. Got %v, expected %v", tc.name, actual, tc.expected)
   463  		}
   464  		for _, j1 := range tc.expected {
   465  			found := false
   466  			for _, j2 := range actual {
   467  				if j1 == j2.Name {
   468  					found = true
   469  					break
   470  				}
   471  			}
   472  			if !found {
   473  				t.Errorf("test %s - Did not find job %s in output", tc.name, j1)
   474  			}
   475  		}
   476  	}
   477  }
   478  
   479  func TestListPostsubmit(t *testing.T) {
   480  	c := &Config{
   481  		Presubmits: map[string][]Presubmit{
   482  			"r1": {{Name: "a"}},
   483  		},
   484  		Postsubmits: map[string][]Postsubmit{
   485  			"r1": {
   486  				{
   487  					Name: "c",
   488  					RunAfterSuccess: []Postsubmit{
   489  						{Name: "ca"},
   490  						{Name: "cb"},
   491  					},
   492  				},
   493  				{Name: "d"},
   494  			},
   495  			"r2": {{Name: "e"}},
   496  		},
   497  		Periodics: []Periodic{
   498  			{Name: "f"},
   499  		},
   500  	}
   501  
   502  	var testcases = []struct {
   503  		name     string
   504  		expected []string
   505  		repos    []string
   506  	}{
   507  		{
   508  			"all postsubmits",
   509  			[]string{"c", "ca", "cb", "d", "e"},
   510  			[]string{},
   511  		},
   512  		{
   513  			"r2 presubmits",
   514  			[]string{"e"},
   515  			[]string{"r2"},
   516  		},
   517  	}
   518  
   519  	for _, tc := range testcases {
   520  		actual := c.AllPostsubmits(tc.repos)
   521  		if len(actual) != len(tc.expected) {
   522  			t.Fatalf("%s - Wrong number of jobs. Got %v, expected %v", tc.name, actual, tc.expected)
   523  		}
   524  		for _, j1 := range tc.expected {
   525  			found := false
   526  			for _, j2 := range actual {
   527  				if j1 == j2.Name {
   528  					found = true
   529  					break
   530  				}
   531  			}
   532  			if !found {
   533  				t.Errorf("Did not find job %s in output", j1)
   534  			}
   535  		}
   536  	}
   537  }
   538  
   539  func TestListPeriodic(t *testing.T) {
   540  	c := &Config{
   541  		Presubmits: map[string][]Presubmit{
   542  			"r1": {{Name: "a"}},
   543  		},
   544  		Postsubmits: map[string][]Postsubmit{
   545  			"r1": {{Name: "b"}},
   546  		},
   547  		Periodics: []Periodic{
   548  			{
   549  				Name: "c",
   550  				RunAfterSuccess: []Periodic{
   551  					{Name: "ca"},
   552  					{Name: "cb"},
   553  				},
   554  			},
   555  			{Name: "d"},
   556  		},
   557  	}
   558  
   559  	expected := []string{"c", "ca", "cb", "d"}
   560  	actual := c.AllPeriodics()
   561  	if len(actual) != len(expected) {
   562  		t.Fatalf("Wrong number of jobs. Got %v, expected %v", actual, expected)
   563  	}
   564  	for _, j1 := range expected {
   565  		found := false
   566  		for _, j2 := range actual {
   567  			if j1 == j2.Name {
   568  				found = true
   569  				break
   570  			}
   571  		}
   572  		if !found {
   573  			t.Errorf("Did not find job %s in output", j1)
   574  		}
   575  	}
   576  }
   577  
   578  func TestRunAgainstBranch(t *testing.T) {
   579  	jobs := []Presubmit{
   580  		{
   581  			Name:     "a",
   582  			Brancher: Brancher{SkipBranches: []string{"s"}},
   583  		},
   584  		{
   585  			Name:     "b",
   586  			Brancher: Brancher{Branches: []string{"r"}},
   587  		},
   588  		{
   589  			Name: "c",
   590  			Brancher: Brancher{
   591  				SkipBranches: []string{"s"},
   592  				Branches:     []string{"r"},
   593  			},
   594  		},
   595  		{
   596  			Name: "d",
   597  			Brancher: Brancher{
   598  				SkipBranches: []string{"s"},
   599  				Branches:     []string{"s", "r"},
   600  			},
   601  		},
   602  		{
   603  			Name: "default",
   604  		},
   605  	}
   606  
   607  	for _, job := range jobs {
   608  		if job.Name == "default" {
   609  			if !job.RunsAgainstBranch("s") {
   610  				t.Errorf("Job %s should run branch s", job.Name)
   611  			}
   612  		} else if job.RunsAgainstBranch("s") {
   613  			t.Errorf("Job %s should not run branch s", job.Name)
   614  		}
   615  
   616  		if !job.RunsAgainstBranch("r") {
   617  			t.Errorf("Job %s should run branch r", job.Name)
   618  		}
   619  	}
   620  }
   621  
   622  func TestValidPodNames(t *testing.T) {
   623  	c, err := Load("../config.yaml")
   624  	if err != nil {
   625  		t.Fatalf("Could not load config: %v", err)
   626  	}
   627  	for _, j := range c.AllPresubmits([]string{}) {
   628  		if !podRe.MatchString(j.Name) {
   629  			t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String())
   630  		}
   631  	}
   632  	for _, j := range c.AllPostsubmits([]string{}) {
   633  		if !podRe.MatchString(j.Name) {
   634  			t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String())
   635  		}
   636  	}
   637  	for _, j := range c.AllPeriodics() {
   638  		if !podRe.MatchString(j.Name) {
   639  			t.Errorf("Job \"%s\" must match regex \"%s\".", j.Name, podRe.String())
   640  		}
   641  	}
   642  }
   643  
   644  func TestNoDuplicateJobs(t *testing.T) {
   645  	c, err := Load("../config.yaml")
   646  	if err != nil {
   647  		t.Fatalf("Could not load config: %v", err)
   648  	}
   649  
   650  	// Presubmit test is covered under TestPresubmits() above
   651  
   652  	allJobs := make(map[string]bool)
   653  	for _, j := range c.AllPostsubmits([]string{}) {
   654  		if allJobs[j.Name] {
   655  			t.Errorf("Found duplicate job in postsubmit: %s.", j.Name)
   656  		}
   657  		allJobs[j.Name] = true
   658  	}
   659  
   660  	allJobs = make(map[string]bool)
   661  	for _, j := range c.AllPeriodics() {
   662  		if allJobs[j.Name] {
   663  			t.Errorf("Found duplicate job in periodic %s.", j.Name)
   664  		}
   665  		allJobs[j.Name] = true
   666  	}
   667  }