github.com/abayer/test-infra@v0.0.5/prow/cmd/splice/main_test.go (about)

     1  /*
     2  Copyright 2016 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 main
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"reflect"
    25  	"testing"
    26  
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  
    29  	"k8s.io/test-infra/prow/config"
    30  	"k8s.io/test-infra/prow/kube"
    31  )
    32  
    33  func expectEqual(t *testing.T, msg string, have interface{}, want interface{}) {
    34  	if !reflect.DeepEqual(have, want) {
    35  		t.Errorf("bad %s: got %v, wanted %v",
    36  			msg, have, want)
    37  	}
    38  }
    39  
    40  type stringHandler string
    41  
    42  func (h stringHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    43  	fmt.Fprintf(w, "%s", h)
    44  }
    45  
    46  func TestGetQueuedPRs(t *testing.T) {
    47  	body := `{"E2EQueue":[
    48  		{"Number":3, "Title": "blah"},
    49  		{"Number":4, "BaseRef": "master"},
    50  		{"Number":1},
    51  		{"Number":5, "BaseRef": "release-1.5"}
    52  	]}`
    53  	serv := httptest.NewServer(stringHandler(body))
    54  	defer serv.Close()
    55  	q, err := getQueuedPRs(serv.URL)
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  	expectEqual(t, "queued PRs", q, []int{3, 4, 1})
    60  }
    61  
    62  // Since the splicer object already has helpers for doing git operations,
    63  // extend it to be useful for producing git repos for testing!
    64  
    65  // branch creates a new branch off of master
    66  func (s *splicer) branch(name string) error {
    67  	return s.gitCall("checkout", "-B", name, "master")
    68  }
    69  
    70  // commit makes a new commit with the provided files added.
    71  func (s *splicer) commit(msg string, contents map[string]string) error {
    72  	for fname, data := range contents {
    73  		err := ioutil.WriteFile(s.dir+"/"+fname, []byte(data), 0644)
    74  		if err != nil {
    75  			return err
    76  		}
    77  		err = s.gitCall("add", fname)
    78  		if err != nil {
    79  			return err
    80  		}
    81  	}
    82  	return s.gitCall("commit", "-m", msg)
    83  }
    84  
    85  // Create a basic commit (so master can be branched off)
    86  func (s *splicer) firstCommit() error {
    87  	return s.commit("first commit", map[string]string{"README": "hi"})
    88  }
    89  
    90  type branchesSpec map[string]map[string]string
    91  
    92  // addBranches does multiple branch/commit calls.
    93  func (s *splicer) addBranches(b branchesSpec) error {
    94  	for name, contents := range b {
    95  		err := s.branch(name)
    96  		if err != nil {
    97  			return err
    98  		}
    99  		err = s.commit("msg", contents)
   100  		if err != nil {
   101  			return err
   102  		}
   103  	}
   104  	return nil
   105  }
   106  
   107  func TestGitOperations(t *testing.T) {
   108  	s, err := makeSplicer()
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  	defer s.cleanup()
   113  	err = s.firstCommit()
   114  	if err != nil {
   115  		t.Fatal(err)
   116  	}
   117  	err = s.addBranches(branchesSpec{
   118  		"pr/123": {"a": "1", "b": "2"},
   119  		"pr/456": {"a": "1", "b": "4", "c": "e"},
   120  	})
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  }
   125  
   126  func TestFindMergeable(t *testing.T) {
   127  	up, _ := makeSplicer()
   128  	defer up.cleanup()
   129  	up.firstCommit()
   130  	err := up.addBranches(branchesSpec{
   131  		"pull/1/head": {"a": "1", "e": "1"},
   132  		"pull/2/head": {"b": "2"},
   133  		"pull/3/head": {"a": "1", "b": "2", "c": "3"},
   134  		"pull/4/head": {"a": "5"},
   135  	})
   136  	if err != nil {
   137  		t.Fatal(err)
   138  	}
   139  
   140  	s, _ := makeSplicer()
   141  	defer s.cleanup()
   142  	mergeable, err := s.findMergeable(up.dir, []int{3, 2, 1, 4})
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  	expectEqual(t, "mergeable PRs", mergeable, []int{3, 2, 1})
   147  
   148  	// findMergeable should work if repeated-- the repo should be
   149  	// reset into a state so it can try to merge again.
   150  	mergeable, err = s.findMergeable(up.dir, []int{3, 2, 1, 4})
   151  	if err != nil {
   152  		t.Fatal(err)
   153  	}
   154  	expectEqual(t, "mergeable PRs", mergeable, []int{3, 2, 1})
   155  
   156  	// PRs that cause merge conflicts should be skipped
   157  	mergeable, err = s.findMergeable(up.dir, []int{1, 4, 2, 3})
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  	expectEqual(t, "mergeable PRs", mergeable, []int{1, 2, 3})
   162  
   163  	// doing a force push should work as well!
   164  	err = up.addBranches(branchesSpec{
   165  		"pull/2/head": {"b": "2", "e": "2"}, // now conflicts with 1
   166  	})
   167  	if err != nil {
   168  		t.Fatal(err)
   169  	}
   170  	mergeable, err = s.findMergeable(up.dir, []int{3, 2, 1, 4})
   171  	if err != nil {
   172  		t.Fatal(err)
   173  	}
   174  	expectEqual(t, "mergeable PRs", mergeable, []int{3, 2})
   175  
   176  }
   177  
   178  func fakeRefs(ref, sha string) kube.Refs {
   179  	return kube.Refs{
   180  		BaseRef: ref,
   181  		BaseSHA: sha,
   182  	}
   183  }
   184  
   185  func fakeProwJob(context string, jobType kube.ProwJobType, completed bool, state kube.ProwJobState, refs kube.Refs) kube.ProwJob {
   186  	pj := kube.ProwJob{
   187  		Status: kube.ProwJobStatus{
   188  			State: state,
   189  		},
   190  		Spec: kube.ProwJobSpec{
   191  			Context: context,
   192  			Refs:    &refs,
   193  			Type:    jobType,
   194  		},
   195  	}
   196  	if completed {
   197  		pj.SetComplete()
   198  	}
   199  	return pj
   200  }
   201  
   202  func TestCompletedJobs(t *testing.T) {
   203  	refs := fakeRefs("ref", "sha")
   204  	other := fakeRefs("otherref", "othersha")
   205  	tests := []struct {
   206  		name      string
   207  		jobs      []kube.ProwJob
   208  		refs      kube.Refs
   209  		completed []string
   210  	}{
   211  		{
   212  			name: "completed when passed",
   213  			jobs: []kube.ProwJob{
   214  				fakeProwJob("passed-a", kube.BatchJob, true, kube.SuccessState, refs),
   215  				fakeProwJob("passed-b", kube.BatchJob, true, kube.SuccessState, refs),
   216  			},
   217  			refs:      refs,
   218  			completed: []string{"passed-a", "passed-b"},
   219  		},
   220  		{
   221  			name: "ignore bad ref",
   222  			jobs: []kube.ProwJob{
   223  				fakeProwJob("passed-a", kube.BatchJob, true, kube.SuccessState, other),
   224  			},
   225  			refs: refs,
   226  		},
   227  		{
   228  			name: "only complete good refs",
   229  			jobs: []kube.ProwJob{
   230  				fakeProwJob("passed-a", kube.BatchJob, true, kube.SuccessState, refs),
   231  				fakeProwJob("passed-b-bad-ref", kube.BatchJob, true, kube.SuccessState, other),
   232  			},
   233  			refs:      refs,
   234  			completed: []string{"passed-a"},
   235  		},
   236  		{
   237  			name: "completed when good and bad ref",
   238  			jobs: []kube.ProwJob{
   239  				fakeProwJob("passed-a", kube.BatchJob, true, kube.SuccessState, refs),
   240  				fakeProwJob("passed-a", kube.BatchJob, true, kube.SuccessState, other),
   241  			},
   242  			refs:      refs,
   243  			completed: []string{"passed-a"},
   244  		},
   245  		{
   246  			name: "ignore incomplete",
   247  			jobs: []kube.ProwJob{
   248  				fakeProwJob("passed-a", kube.BatchJob, true, kube.SuccessState, refs),
   249  				fakeProwJob("pending-b", kube.BatchJob, false, kube.PendingState, refs),
   250  			},
   251  			refs:      refs,
   252  			completed: []string{"passed-a"},
   253  		},
   254  		{
   255  			name: "ignore failed",
   256  			jobs: []kube.ProwJob{
   257  				fakeProwJob("passed-a", kube.BatchJob, true, kube.SuccessState, refs),
   258  				fakeProwJob("failed-b", kube.BatchJob, true, kube.FailureState, refs),
   259  			},
   260  			refs:      refs,
   261  			completed: []string{"passed-a"},
   262  		},
   263  		{
   264  			name: "ignore non-batch",
   265  			jobs: []kube.ProwJob{
   266  				fakeProwJob("passed-a", kube.BatchJob, true, kube.SuccessState, refs),
   267  				fakeProwJob("non-batch-b", kube.PresubmitJob, true, kube.SuccessState, refs),
   268  			},
   269  			refs:      refs,
   270  			completed: []string{"passed-a"},
   271  		},
   272  	}
   273  
   274  	for _, tc := range tests {
   275  		completed := completedJobs(tc.jobs, tc.refs)
   276  		var completedContexts []string
   277  		for _, job := range completed {
   278  			completedContexts = append(completedContexts, job.Spec.Context)
   279  		}
   280  		expectEqual(t, "completed contexts", completedContexts, tc.completed)
   281  	}
   282  }
   283  
   284  func TestRequiredPresubmits(t *testing.T) {
   285  	tests := []struct {
   286  		name       string
   287  		possible   []config.Presubmit
   288  		required   []string
   289  		overridden sets.String
   290  	}{
   291  		{
   292  			name: "basic",
   293  			possible: []config.Presubmit{
   294  				{
   295  					Name:      "always",
   296  					AlwaysRun: true,
   297  				},
   298  				{
   299  					Name:      "optional",
   300  					AlwaysRun: false,
   301  				},
   302  				{
   303  					Name:       "hidden",
   304  					AlwaysRun:  true,
   305  					SkipReport: true,
   306  				},
   307  				{
   308  					Name:      "optional_but_overridden",
   309  					AlwaysRun: false,
   310  				},
   311  			},
   312  			required:   []string{"always", "optional_but_overridden"},
   313  			overridden: sets.NewString("optional_but_overridden"),
   314  		},
   315  	}
   316  
   317  	for _, tc := range tests {
   318  		var names []string
   319  		for _, job := range requiredPresubmits(tc.possible, tc.overridden) {
   320  			names = append(names, job.Name)
   321  		}
   322  		expectEqual(t, tc.name, names, tc.required)
   323  	}
   324  }
   325  
   326  func TestNeededPresubmits(t *testing.T) {
   327  	tests := []struct {
   328  		name     string
   329  		possible []config.Presubmit
   330  		current  []kube.ProwJob
   331  		refs     kube.Refs
   332  		required []string
   333  	}{
   334  		{
   335  			name: "basic",
   336  			possible: []config.Presubmit{
   337  				{
   338  					Name:      "always",
   339  					AlwaysRun: true,
   340  				},
   341  				{
   342  					Name:      "optional",
   343  					AlwaysRun: false,
   344  				},
   345  				{
   346  					Name:       "hidden",
   347  					AlwaysRun:  true,
   348  					SkipReport: true,
   349  				},
   350  			},
   351  			required: []string{"always"},
   352  		},
   353  		{
   354  			name: "skip already passed",
   355  			possible: []config.Presubmit{
   356  				{
   357  					Name:      "new",
   358  					Context:   "brandnew",
   359  					AlwaysRun: true,
   360  				},
   361  				{
   362  					Name:      "passed",
   363  					Context:   "already-ran",
   364  					AlwaysRun: true,
   365  				},
   366  			},
   367  			current: []kube.ProwJob{
   368  				fakeProwJob("already-ran", kube.BatchJob, true, kube.SuccessState, fakeRefs("ref", "sha")),
   369  			},
   370  			refs:     fakeRefs("ref", "sha"),
   371  			required: []string{"new"},
   372  		},
   373  		{
   374  			name: "handle branches/skipbranches specifiers",
   375  			possible: []config.Presubmit{
   376  				{
   377  					Name:      "old",
   378  					Brancher:  config.Brancher{Branches: []string{"release-1.2", "release-1.3"}},
   379  					AlwaysRun: true,
   380  				},
   381  				{
   382  					Name:      "outdated",
   383  					Brancher:  config.Brancher{SkipBranches: []string{"master"}},
   384  					AlwaysRun: true,
   385  				},
   386  				{
   387  					Name:      "latest",
   388  					Brancher:  config.Brancher{Branches: []string{"master"}},
   389  					AlwaysRun: true,
   390  				},
   391  			},
   392  			required: []string{"latest"},
   393  		},
   394  	}
   395  
   396  	for _, tc := range tests {
   397  		if err := config.SetPresubmitRegexes(tc.possible); err != nil {
   398  			t.Fatalf("could not set regexes: %v", err)
   399  		}
   400  		var names []string
   401  		for _, job := range neededPresubmits(tc.possible, tc.current, tc.refs, sets.String{}) {
   402  			names = append(names, job.Name)
   403  		}
   404  		expectEqual(t, tc.name, names, tc.required)
   405  	}
   406  }