github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/prow/config/config_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  	"bytes"
    21  	"errors"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"reflect"
    25  	"regexp"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/ghodss/yaml"
    30  	"k8s.io/test-infra/prow/kube"
    31  )
    32  
    33  func TestConfigLoads(t *testing.T) {
    34  	_, err := Load("../config.yaml")
    35  	if err != nil {
    36  		t.Fatalf("Could not load config: %v", err)
    37  	}
    38  }
    39  
    40  func Replace(j *Presubmit, ks *Presubmit) error {
    41  	name := strings.Replace(j.Name, "pull-kubernetes", "pull-security-kubernetes", -1)
    42  	if name != ks.Name {
    43  		return fmt.Errorf("%s should match %s", name, ks.Name)
    44  	}
    45  	j.Name = name
    46  	j.RerunCommand = strings.Replace(j.RerunCommand, "pull-kubernetes", "pull-security-kubernetes", -1)
    47  	j.Trigger = strings.Replace(j.Trigger, "pull-kubernetes", "pull-security-kubernetes", -1)
    48  	j.Context = strings.Replace(j.Context, "pull-kubernetes", "pull-security-kubernetes", -1)
    49  	j.re = ks.re
    50  	if len(j.RunAfterSuccess) != len(ks.RunAfterSuccess) {
    51  		return fmt.Errorf("length of RunAfterSuccess should match. - %s", name)
    52  	}
    53  
    54  	for i := range j.RunAfterSuccess {
    55  		if err := Replace(&j.RunAfterSuccess[i], &ks.RunAfterSuccess[i]); err != nil {
    56  			return err
    57  		}
    58  	}
    59  
    60  	return nil
    61  }
    62  
    63  func CheckContext(t *testing.T, repo string, p Presubmit) {
    64  	if p.Name != p.Context {
    65  		t.Errorf("Context does not match job name: %s in %s", p.Name, repo)
    66  	}
    67  	for _, c := range p.RunAfterSuccess {
    68  		CheckContext(t, repo, c)
    69  	}
    70  }
    71  
    72  func TestContextMatches(t *testing.T) {
    73  	c, err := Load("../config.yaml")
    74  	if err != nil {
    75  		t.Fatalf("Could not load config: %v", err)
    76  	}
    77  
    78  	for repo, presubmits := range c.Presubmits {
    79  		for _, p := range presubmits {
    80  			CheckContext(t, repo, p)
    81  		}
    82  	}
    83  }
    84  
    85  func CheckRetest(t *testing.T, repo string, presubmits []Presubmit) {
    86  	for _, p := range presubmits {
    87  		expected := fmt.Sprintf("/test %s", p.Name)
    88  		if p.RerunCommand != expected {
    89  			t.Errorf("%s in %s rerun_command: %s != expected: %s", repo, p.Name, p.RerunCommand, expected)
    90  		}
    91  		CheckRetest(t, repo, p.RunAfterSuccess)
    92  	}
    93  }
    94  
    95  func TestRetestMatchJobsName(t *testing.T) {
    96  	c, err := Load("../config.yaml")
    97  	if err != nil {
    98  		t.Fatalf("Could not load config: %v", err)
    99  	}
   100  	for repo, presubmits := range c.Presubmits {
   101  		CheckRetest(t, repo, presubmits)
   102  	}
   103  }
   104  
   105  type SubmitQueueConfig struct {
   106  	Data map[string]string `json:"data"`
   107  }
   108  
   109  func FindRequired(t *testing.T, presubmits []Presubmit) []string {
   110  	var required []string
   111  	for _, p := range presubmits {
   112  		if !p.AlwaysRun {
   113  			continue
   114  		}
   115  		for _, r := range FindRequired(t, p.RunAfterSuccess) {
   116  			required = append(required, r)
   117  		}
   118  		if p.SkipReport {
   119  			continue
   120  		}
   121  		required = append(required, p.Context)
   122  	}
   123  	return required
   124  }
   125  
   126  func TestRequiredRetestContextsMatch(t *testing.T) {
   127  	c, err := Load("../config.yaml")
   128  	if err != nil {
   129  		t.Fatalf("Could not load config: %v", err)
   130  	}
   131  	b, err := ioutil.ReadFile("../../mungegithub/submit-queue/deployment/kubernetes/configmap.yaml")
   132  	if err != nil {
   133  		t.Fatalf("Could not load submit queue configmap: %v", err)
   134  	}
   135  	sqc := &SubmitQueueConfig{}
   136  	if err = yaml.Unmarshal(b, sqc); err != nil {
   137  		t.Fatalf("Could not parse submit queue configmap: %v", err)
   138  	}
   139  	re := regexp.MustCompile(`"([^"]+)"`)
   140  	var required []string
   141  	for _, g := range re.FindAllStringSubmatch(sqc.Data["test-options.required-retest-contexts"], -1) {
   142  		required = append(required, g[1])
   143  	}
   144  
   145  	running := FindRequired(t, c.Presubmits["kubernetes/kubernetes"])
   146  
   147  	for _, r := range required {
   148  		found := false
   149  		for _, s := range running {
   150  			if s == r {
   151  				found = true
   152  				break
   153  			}
   154  		}
   155  		if !found {
   156  			t.Errorf("Required context: %s does not always run: %s", r, running)
   157  		}
   158  	}
   159  }
   160  
   161  func TestConfigSecurityJobsMatch(t *testing.T) {
   162  	c, err := Load("../config.yaml")
   163  	if err != nil {
   164  		t.Fatalf("Could not load config: %v", err)
   165  	}
   166  	kp := c.Presubmits["kubernetes/kubernetes"]
   167  	sp := c.Presubmits["kubernetes-security/kubernetes"]
   168  	if len(kp) != len(sp) {
   169  		t.Fatalf("length of kubernetes/kubernetes presubmits %d does not equal length of kubernetes-security/kubernetes presubmits %d", len(kp), len(sp))
   170  	}
   171  	for i, j := range kp {
   172  		if err := Replace(&j, &sp[i]); err != nil {
   173  			t.Fatalf("[Replace] : %v", err)
   174  		}
   175  
   176  		if !reflect.DeepEqual(j, sp[i]) {
   177  			t.Fatalf("kubernetes/kubernetes prow config jobs do not match kubernetes-security/kubernetes jobs:\n%#v\nshould match: %#v", j, sp[i])
   178  		}
   179  	}
   180  }
   181  
   182  func CheckBazelPortContainer(c kube.Container, cache bool) error {
   183  	if !cache {
   184  		if len(c.Ports) != 0 {
   185  			return errors.New("job does not use --cache-ssd and so should not set ports in spec")
   186  		}
   187  		return nil
   188  	}
   189  
   190  	if len(c.Ports) != 1 {
   191  		return errors.New("job uses --cache-ssd and so needs to set ports in spec")
   192  	} else if c.Ports[0].ContainerPort != 9999 {
   193  		return errors.New("job uses --cache-ssd and so needs to have ContainerPort 9999")
   194  	} else if c.Ports[0].HostPort != 9999 {
   195  		return errors.New("job uses --cache-ssd and so needs to have HostPort 9999")
   196  	}
   197  	return nil
   198  }
   199  
   200  func CheckBazelPortPresubmit(presubmits []Presubmit) error {
   201  	for _, presubmit := range presubmits {
   202  		if presubmit.Spec == nil {
   203  			continue
   204  		}
   205  		hasCache := false
   206  		for _, volume := range presubmit.Spec.Volumes {
   207  			if volume.Name == "cache-ssd" {
   208  				hasCache = true
   209  			}
   210  		}
   211  
   212  		for _, container := range presubmit.Spec.Containers {
   213  			if err := CheckBazelPortContainer(container, hasCache); err != nil {
   214  				return fmt.Errorf("%s: %v", presubmit.Name, err)
   215  			}
   216  		}
   217  
   218  		if err := CheckBazelPortPresubmit(presubmit.RunAfterSuccess); err != nil {
   219  			return fmt.Errorf("%s: %v", presubmit.Name, err)
   220  		}
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  func CheckBazelPortPostsubmit(postsubmits []Postsubmit) error {
   227  	for _, postsubmit := range postsubmits {
   228  		hasCache := false
   229  		for _, volume := range postsubmit.Spec.Volumes {
   230  			if volume.Name == "cache-ssd" {
   231  				hasCache = true
   232  			}
   233  		}
   234  
   235  		for _, container := range postsubmit.Spec.Containers {
   236  			if err := CheckBazelPortContainer(container, hasCache); err != nil {
   237  				return fmt.Errorf("%s: %v", postsubmit.Name, err)
   238  			}
   239  		}
   240  
   241  		if err := CheckBazelPortPostsubmit(postsubmit.RunAfterSuccess); err != nil {
   242  			return fmt.Errorf("%s: %v", postsubmit.Name, err)
   243  		}
   244  	}
   245  
   246  	return nil
   247  }
   248  
   249  func CheckBazelPortPeriodic(periodics []Periodic) error {
   250  	for _, periodic := range periodics {
   251  		hasCache := false
   252  		for _, volume := range periodic.Spec.Volumes {
   253  			if volume.Name == "cache-ssd" {
   254  				hasCache = true
   255  			}
   256  		}
   257  
   258  		for _, container := range periodic.Spec.Containers {
   259  			if err := CheckBazelPortContainer(container, hasCache); err != nil {
   260  				return fmt.Errorf("%s: %v", periodic.Name, err)
   261  			}
   262  		}
   263  
   264  		if err := CheckBazelPortPeriodic(periodic.RunAfterSuccess); err != nil {
   265  			return fmt.Errorf("%s: %v", periodic.Name, err)
   266  		}
   267  	}
   268  
   269  	return nil
   270  }
   271  
   272  // Set the HostPort to 9999 for all bazel pods so that they are forced
   273  // onto different nodes. Once pod affinity is GA, use that instead.
   274  // Until https://github.com/kubernetes/community/blob/master/contributors/design-proposals/local-storage-overview.md
   275  func TestBazelJobHasContainerPort(t *testing.T) {
   276  	c, err := Load("../config.yaml")
   277  	if err != nil {
   278  		t.Fatalf("Could not load config: %v", err)
   279  	}
   280  
   281  	for _, pres := range c.Presubmits {
   282  		if err := CheckBazelPortPresubmit(pres); err != nil {
   283  			t.Errorf("Error in presubmit: %v", err)
   284  		}
   285  	}
   286  
   287  	for _, posts := range c.Postsubmits {
   288  		if err := CheckBazelPortPostsubmit(posts); err != nil {
   289  			t.Errorf("Error in postsubmit: %v", err)
   290  		}
   291  	}
   292  
   293  	if err := CheckBazelPortPeriodic(c.Periodics); err != nil {
   294  		t.Errorf("Error in periodic: %v", err)
   295  	}
   296  }
   297  
   298  // Load the config and extract all jobs, including any child jobs inside
   299  // RunAfterSuccess fields.
   300  func allJobs() ([]Presubmit, []Postsubmit, []Periodic, error) {
   301  	c, err := Load("../config.yaml")
   302  	if err != nil {
   303  		return nil, nil, nil, err
   304  	}
   305  
   306  	pres := []Presubmit{}
   307  	posts := []Postsubmit{}
   308  	peris := []Periodic{}
   309  
   310  	{ // Find all presubmit jobs, including child jobs.
   311  		q := []Presubmit{}
   312  
   313  		for _, p := range c.Presubmits {
   314  			for _, p2 := range p {
   315  				q = append(q, p2)
   316  			}
   317  		}
   318  
   319  		for len(q) > 0 {
   320  			pres = append(pres, q[0])
   321  			for _, p := range q[0].RunAfterSuccess {
   322  				q = append(q, p)
   323  			}
   324  			q = q[1:]
   325  		}
   326  	}
   327  
   328  	{ // Find all postsubmit jobs, including child jobs.
   329  		q := []Postsubmit{}
   330  
   331  		for _, p := range c.Postsubmits {
   332  			for _, p2 := range p {
   333  				q = append(q, p2)
   334  			}
   335  		}
   336  
   337  		for len(q) > 0 {
   338  			posts = append(posts, q[0])
   339  			for _, p := range q[0].RunAfterSuccess {
   340  				q = append(q, p)
   341  			}
   342  			q = q[1:]
   343  		}
   344  	}
   345  
   346  	{ // Find all periodic jobs, including child jobs.
   347  		q := []Periodic{}
   348  		for _, p := range c.Periodics {
   349  			q = append(q, p)
   350  		}
   351  
   352  		for len(q) > 0 {
   353  			peris = append(peris, q[0])
   354  			for _, p := range q[0].RunAfterSuccess {
   355  				q = append(q, p)
   356  			}
   357  			q = q[1:]
   358  		}
   359  	}
   360  
   361  	return pres, posts, peris, nil
   362  }
   363  
   364  // Validate any containers using a bazelbuild image, returning which bazelbuild tags are used.
   365  // In particular ensure that:
   366  //   * Presubmit, postsubmit jobs specify at least one --repo flag, the first of which uses PULL_REFS and REPO_NAME vars
   367  //   * Prow injected vars like REPO_NAME, PULL_REFS, etc are only used on non-periodic jobs
   368  //   * Deprecated --branch, --pull flags are not used
   369  //   * Required --service-account, --upload, --git-cache, --job, --clean flags are present
   370  func CheckBazelbuildSpec(t *testing.T, name string, spec *kube.PodSpec, periodic bool) map[string]int {
   371  	img := "gcr.io/k8s-testimages/bazelbuild"
   372  	tags := map[string]int{}
   373  	if spec == nil {
   374  		return tags
   375  	}
   376  	// Tags look something like vDATE-SHA or vDATE-SHA-BAZELVERSION.
   377  	// We want to match only on the date + sha
   378  	tagRE := regexp.MustCompile(`^([^-]+-[^-]+)(-[^-]+)?$`)
   379  	for _, c := range spec.Containers {
   380  		parts := strings.SplitN(c.Image, ":", 2)
   381  		var i, tag string // image:tag
   382  		i = parts[0]
   383  		if i != img {
   384  			continue
   385  		}
   386  		if len(parts) == 1 {
   387  			tag = "latest"
   388  		} else {
   389  			submatches := tagRE.FindStringSubmatch(parts[1])
   390  			if submatches != nil {
   391  				tag = submatches[1]
   392  			} else {
   393  				t.Errorf("bazelbuild tag '%s' doesn't match expected format", parts[1])
   394  			}
   395  		}
   396  		tags[tag]++
   397  
   398  		found := map[string][]string{}
   399  		for _, a := range c.Args {
   400  			parts := strings.SplitN(a, "=", 2)
   401  			k := parts[0]
   402  			v := "true"
   403  			if len(parts) == 2 {
   404  				v = parts[1]
   405  			}
   406  			found[k] = append(found[k], v)
   407  
   408  			// Require --flag=FOO for easier processing
   409  			if k == "--repo" && len(parts) == 1 {
   410  				t.Errorf("%s: use --repo=FOO not --repo foo", name)
   411  			}
   412  		}
   413  
   414  		if _, ok := found["--pull"]; ok {
   415  			t.Errorf("%s: uses deprecated --pull arg, use --repo=org/repo=$(PULL_REFS) instead", name)
   416  		}
   417  		if _, ok := found["--branch"]; ok {
   418  			t.Errorf("%s: uses deprecated --branch arg, use --repo=org/repo=$(PULL_REFS) instead", name)
   419  		}
   420  
   421  		for _, f := range []string{
   422  			"--service-account",
   423  			"--upload",
   424  			"--git-cache",
   425  			"--job",
   426  			"--clean",
   427  		} {
   428  			if _, ok := found[f]; !ok {
   429  				t.Errorf("%s: missing %s flag", name, f)
   430  			}
   431  		}
   432  
   433  		if v, ok := found["--repo"]; !ok {
   434  			t.Errorf("%s: missing %s flag", name, "--repo")
   435  		} else {
   436  			firstRepo := true
   437  			hasRefs := false
   438  			hasName := false
   439  			for _, r := range v {
   440  				hasRefs = hasRefs || strings.Contains(r, "$(PULL_REFS)")
   441  				hasName = hasName || strings.Contains(r, "$(REPO_NAME)")
   442  				if !firstRepo {
   443  					t.Errorf("%s: has too many --repo. REMOVE THIS CHECK BEFORE MERGE", name)
   444  				}
   445  				for _, d := range []string{
   446  					"$(REPO_NAME)",
   447  					"$(REPO_OWNER)",
   448  					"$(PULL_BASE_REF)",
   449  					"$(PULL_BASE_SHA)",
   450  					"$(PULL_REFS)",
   451  					"$(PULL_NUMBER)",
   452  					"$(PULL_PULL_SHA)",
   453  				} {
   454  					has := strings.Contains(r, d)
   455  					if periodic && has {
   456  						t.Errorf("%s: %s are not available to periodic jobs, please use a static --repo=org/repo=branch", name, d)
   457  					} else if !firstRepo && has {
   458  						t.Errorf("%s: %s are only relevant to the first --repo flag, remove from --repo=%s", name, d, r)
   459  					}
   460  				}
   461  				firstRepo = false
   462  			}
   463  			if !periodic && !hasRefs {
   464  				t.Errorf("%s: non-periodic jobs need a --repo=org/branch=$(PULL_REFS) somewhere", name)
   465  			}
   466  			if !periodic && !hasName {
   467  				t.Errorf("%s: non-periodic jobs need a --repo=org/$(REPO_NAME) somewhere", name)
   468  			}
   469  		}
   470  	}
   471  	return tags
   472  }
   473  
   474  // Unit test jobs that use a bazelbuild image do so correctly.
   475  func TestBazelbuildArgs(t *testing.T) {
   476  	pres, posts, peris, err := allJobs()
   477  	if err != nil {
   478  		t.Fatalf("Could not load config: %v", err)
   479  	}
   480  
   481  	tags := map[string][]string{} // tag -> jobs map
   482  	for _, p := range pres {
   483  		for t := range CheckBazelbuildSpec(t, p.Name, p.Spec, false) {
   484  			tags[t] = append(tags[t], p.Name)
   485  		}
   486  	}
   487  	for _, p := range posts {
   488  		for t := range CheckBazelbuildSpec(t, p.Name, p.Spec, false) {
   489  			tags[t] = append(tags[t], p.Name)
   490  		}
   491  	}
   492  	for _, p := range peris {
   493  		for t := range CheckBazelbuildSpec(t, p.Name, p.Spec, true) {
   494  			tags[t] = append(tags[t], p.Name)
   495  		}
   496  	}
   497  	pinnedJobs := map[string]string{
   498  		//job: reason for pinning
   499  		"ci-kubernetes-bazel-build-1-6":        "https://github.com/kubernetes/kubernetes/issues/51571",
   500  		"ci-kubernetes-bazel-test-1-6":         "https://github.com/kubernetes/kubernetes/issues/51571",
   501  		"periodic-kubernetes-bazel-build-1-6":  "https://github.com/kubernetes/kubernetes/issues/51571",
   502  		"periodic-kubernetes-bazel-test-1-6":   "https://github.com/kubernetes/kubernetes/issues/51571",
   503  		"pull-test-infra-bazel":                "canary testing the latest bazel on test-infra",
   504  		"ci-test-infra-bazel":                  "canary testing the latest bazel on test-infra",
   505  		"pull-kubernetes-bazel-build":          "need different versions of bazel for release branches",
   506  		"pull-kubernetes-bazel-test":           "need different versions of bazel for release branches",
   507  		"pull-security-kubernetes-bazel-build": "need different versions of bazel for release branches",
   508  		"pull-security-kubernetes-bazel-test":  "need different versions of bazel for release branches",
   509  	}
   510  	maxTag := ""
   511  	maxN := 0
   512  	for t, js := range tags {
   513  		n := len(js)
   514  		if n > maxN {
   515  			maxTag = t
   516  			maxN = n
   517  		}
   518  	}
   519  	for tag, js := range tags {
   520  		current := tag == maxTag
   521  		for _, j := range js {
   522  			if v, pinned := pinnedJobs[j]; !pinned && !current {
   523  				t.Errorf("%s: please add to the pinnedJobs list or else update tag to %s", j, maxTag)
   524  			} else if current && pinned {
   525  				t.Errorf("%s: please remove from the pinnedJobs list", j)
   526  			} else if !current && v == "" {
   527  				t.Errorf("%s: pinning to a non-default version requires a non-empty reason for doing so", j)
   528  			}
   529  		}
   530  	}
   531  }
   532  
   533  func TestURLTemplate(t *testing.T) {
   534  	c, err := Load("../config.yaml")
   535  	if err != nil {
   536  		t.Fatalf("Could not load config: %v", err)
   537  	}
   538  	testcases := []struct {
   539  		name    string
   540  		jobType kube.ProwJobType
   541  		org     string
   542  		repo    string
   543  		job     string
   544  		build   string
   545  		expect  string
   546  	}{
   547  		{
   548  			name:    "k8s presubmit",
   549  			jobType: kube.PresubmitJob,
   550  			org:     "kubernetes",
   551  			repo:    "kubernetes",
   552  			job:     "k8s-pre-1",
   553  			build:   "1",
   554  			expect:  "https://k8s-gubernator.appspot.com/build/kubernetes-jenkins/pr-logs/pull/0/k8s-pre-1/1/",
   555  		},
   556  		{
   557  			name:    "k8s/test-infra presubmit",
   558  			jobType: kube.PresubmitJob,
   559  			org:     "kubernetes",
   560  			repo:    "test-infra",
   561  			job:     "ti-pre-1",
   562  			build:   "1",
   563  			expect:  "https://k8s-gubernator.appspot.com/build/kubernetes-jenkins/pr-logs/pull/test-infra/0/ti-pre-1/1/",
   564  		},
   565  		{
   566  			name:    "foo/k8s presubmit",
   567  			jobType: kube.PresubmitJob,
   568  			org:     "foo",
   569  			repo:    "kubernetes",
   570  			job:     "k8s-pre-1",
   571  			build:   "1",
   572  			expect:  "https://k8s-gubernator.appspot.com/build/kubernetes-jenkins/pr-logs/pull/foo_kubernetes/0/k8s-pre-1/1/",
   573  		},
   574  		{
   575  			name:    "foo-bar presubmit",
   576  			jobType: kube.PresubmitJob,
   577  			org:     "foo",
   578  			repo:    "bar",
   579  			job:     "foo-pre-1",
   580  			build:   "1",
   581  			expect:  "https://k8s-gubernator.appspot.com/build/kubernetes-jenkins/pr-logs/pull/foo_bar/0/foo-pre-1/1/",
   582  		},
   583  		{
   584  			name:    "k8s postsubmit",
   585  			jobType: kube.PostsubmitJob,
   586  			org:     "kubernetes",
   587  			repo:    "kubernetes",
   588  			job:     "k8s-post-1",
   589  			build:   "1",
   590  			expect:  "https://k8s-gubernator.appspot.com/build/kubernetes-jenkins/logs/k8s-post-1/1/",
   591  		},
   592  		{
   593  			name:    "k8s periodic",
   594  			jobType: kube.PeriodicJob,
   595  			org:     "kubernetes",
   596  			repo:    "kubernetes",
   597  			job:     "k8s-peri-1",
   598  			build:   "1",
   599  			expect:  "https://k8s-gubernator.appspot.com/build/kubernetes-jenkins/logs/k8s-peri-1/1/",
   600  		},
   601  		{
   602  			name:    "empty periodic",
   603  			jobType: kube.PeriodicJob,
   604  			org:     "",
   605  			repo:    "",
   606  			job:     "nan-peri-1",
   607  			build:   "1",
   608  			expect:  "https://k8s-gubernator.appspot.com/build/kubernetes-jenkins/logs/nan-peri-1/1/",
   609  		},
   610  		{
   611  			name:    "k8s batch",
   612  			jobType: kube.BatchJob,
   613  			org:     "kubernetes",
   614  			repo:    "kubernetes",
   615  			job:     "k8s-batch-1",
   616  			build:   "1",
   617  			expect:  "https://k8s-gubernator.appspot.com/build/kubernetes-jenkins/pr-logs/pull/batch/k8s-batch-1/1/",
   618  		},
   619  	}
   620  
   621  	for _, tc := range testcases {
   622  		var pj = kube.ProwJob{
   623  			Metadata: kube.ObjectMeta{Name: tc.name},
   624  			Spec: kube.ProwJobSpec{
   625  				Type: tc.jobType,
   626  				Job:  tc.job,
   627  				Refs: kube.Refs{
   628  					Pulls: []kube.Pull{{}},
   629  					Org:   tc.org,
   630  					Repo:  tc.repo,
   631  				},
   632  			},
   633  			Status: kube.ProwJobStatus{
   634  				BuildID: tc.build,
   635  			},
   636  		}
   637  
   638  		var b bytes.Buffer
   639  		if err := c.Plank.JobURLTemplate.Execute(&b, &pj); err != nil {
   640  			t.Fatalf("Error executing template: %v", err)
   641  		}
   642  		res := b.String()
   643  		if res != tc.expect {
   644  			t.Errorf("tc: %s, Expect URL: %s, got %s", tc.name, tc.expect, res)
   645  		}
   646  	}
   647  }
   648  
   649  func TestReportTemplate(t *testing.T) {
   650  	c, err := Load("../config.yaml")
   651  	if err != nil {
   652  		t.Fatalf("Could not load config: %v", err)
   653  	}
   654  	var testcases = []struct {
   655  		org    string
   656  		repo   string
   657  		number int
   658  		suffix string
   659  	}{
   660  		{
   661  			org:    "o",
   662  			repo:   "r",
   663  			number: 4,
   664  			suffix: "o_r/4",
   665  		},
   666  		{
   667  			org:    "kubernetes",
   668  			repo:   "test-infra",
   669  			number: 123,
   670  			suffix: "test-infra/123",
   671  		},
   672  		{
   673  			org:    "kubernetes",
   674  			repo:   "kubernetes",
   675  			number: 123,
   676  			suffix: "123",
   677  		},
   678  		{
   679  			org:    "o",
   680  			repo:   "kubernetes",
   681  			number: 456,
   682  			suffix: "o_kubernetes/456",
   683  		},
   684  	}
   685  	for _, tc := range testcases {
   686  		var b bytes.Buffer
   687  		if err := c.Plank.ReportTemplate.Execute(&b, &kube.ProwJob{
   688  			Spec: kube.ProwJobSpec{
   689  				Refs: kube.Refs{
   690  					Org:  tc.org,
   691  					Repo: tc.repo,
   692  					Pulls: []kube.Pull{
   693  						{
   694  							Number: tc.number,
   695  						},
   696  					},
   697  				},
   698  			},
   699  		}); err != nil {
   700  			t.Errorf("Error executing template: %v", err)
   701  			continue
   702  		}
   703  		expectedPath := "https://k8s-gubernator.appspot.com/pr/" + tc.suffix
   704  		if !strings.Contains(b.String(), expectedPath) {
   705  			t.Errorf("Expected template to contain %s, but it didn't: %s", expectedPath, b.String())
   706  		}
   707  	}
   708  }
   709  
   710  func TestPullKubernetesCross(t *testing.T) {
   711  	crossBuildJob := "pull-kubernetes-cross"
   712  	c, err := Load("../config.yaml")
   713  	if err != nil {
   714  		t.Fatalf("Could not load config: %v", err)
   715  	}
   716  	tests := []struct {
   717  		changedFile string
   718  		expected    bool
   719  	}{
   720  		{
   721  			changedFile: "pkg/kubelet/cadvisor/cadvisor_unsupported.go",
   722  			expected:    true,
   723  		},
   724  		{
   725  			changedFile: "pkg/kubelet/cadvisor/util.go",
   726  			expected:    false,
   727  		},
   728  		{
   729  			changedFile: "Makefile",
   730  			expected:    true,
   731  		},
   732  		{
   733  			changedFile: "hack/lib/etcd.sh",
   734  			expected:    true,
   735  		},
   736  		{
   737  			changedFile: "build/debs/kubelet.service",
   738  			expected:    true,
   739  		},
   740  		{
   741  			changedFile: "federation/README.md",
   742  			expected:    false,
   743  		},
   744  	}
   745  	kkPresumits := c.Presubmits["kubernetes/kubernetes"]
   746  	var cross *Presubmit
   747  	for i := range kkPresumits {
   748  		ps := kkPresumits[i]
   749  		if ps.Name == crossBuildJob {
   750  			cross = &ps
   751  			break
   752  		}
   753  	}
   754  	if cross == nil {
   755  		t.Fatalf("expected %q in the presubmit section of the prow config", crossBuildJob)
   756  	}
   757  
   758  	for i, test := range tests {
   759  		t.Logf("test run #%d", i)
   760  		got := cross.RunsAgainstChanges([]string{test.changedFile})
   761  		if got != test.expected {
   762  			t.Errorf("expected changes (%s) to run cross job: %t, got: %t",
   763  				test.changedFile, test.expected, got)
   764  		}
   765  	}
   766  }