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