github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/external-plugins/cherrypicker/server_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 main
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"testing"
    23  
    24  	"github.com/sirupsen/logrus"
    25  
    26  	"k8s.io/test-infra/prow/git/localgit"
    27  	"k8s.io/test-infra/prow/github"
    28  )
    29  
    30  type fghc struct {
    31  	sync.Mutex
    32  	pr       *github.PullRequest
    33  	isMember bool
    34  
    35  	patch      []byte
    36  	comments   []string
    37  	prs        []string
    38  	prComments []github.IssueComment
    39  	createdNum int
    40  	orgMembers []github.TeamMember
    41  }
    42  
    43  func (f *fghc) AssignIssue(org, repo string, number int, logins []string) error {
    44  	f.Lock()
    45  	defer f.Unlock()
    46  	return nil
    47  }
    48  
    49  func (f *fghc) GetPullRequest(org, repo string, number int) (*github.PullRequest, error) {
    50  	f.Lock()
    51  	defer f.Unlock()
    52  	return f.pr, nil
    53  }
    54  
    55  func (f *fghc) GetPullRequestPatch(org, repo string, number int) ([]byte, error) {
    56  	f.Lock()
    57  	defer f.Unlock()
    58  	return f.patch, nil
    59  }
    60  
    61  func (f *fghc) CreateComment(org, repo string, number int, comment string) error {
    62  	f.Lock()
    63  	defer f.Unlock()
    64  	f.comments = append(f.comments, fmt.Sprintf("%s/%s#%d %s", org, repo, number, comment))
    65  	return nil
    66  }
    67  
    68  func (f *fghc) IsMember(org, user string) (bool, error) {
    69  	f.Lock()
    70  	defer f.Unlock()
    71  	return f.isMember, nil
    72  }
    73  
    74  func (f *fghc) GetRepo(owner, name string) (github.Repo, error) {
    75  	f.Lock()
    76  	defer f.Unlock()
    77  	return github.Repo{}, nil
    78  }
    79  
    80  var expectedFmt = `repo=%s title=%q body=%q head=%s base=%s maintainer_can_modify=%t`
    81  
    82  func (f *fghc) CreatePullRequest(org, repo, title, body, head, base string, canModify bool) (int, error) {
    83  	f.Lock()
    84  	defer f.Unlock()
    85  	f.prs = append(f.prs, fmt.Sprintf(expectedFmt, org+"/"+repo, title, body, head, base, canModify))
    86  	return f.createdNum, nil
    87  }
    88  
    89  func (f *fghc) ListIssueComments(org, repo string, number int) ([]github.IssueComment, error) {
    90  	f.Lock()
    91  	defer f.Unlock()
    92  	return f.prComments, nil
    93  }
    94  
    95  func (f *fghc) ListOrgMembers(org, role string) ([]github.TeamMember, error) {
    96  	f.Lock()
    97  	defer f.Unlock()
    98  	if role != "all" {
    99  		return nil, fmt.Errorf("all is only supported role, not: %s", role)
   100  	}
   101  	return f.orgMembers, nil
   102  }
   103  
   104  func (f *fghc) CreateFork(org, repo string) error {
   105  	return nil
   106  }
   107  
   108  var initialFiles = map[string][]byte{
   109  	"bar.go": []byte(`// Package bar does an interesting thing.
   110  package bar
   111  
   112  // Foo does a thing.
   113  func Foo(wow int) int {
   114  	return 42 + wow
   115  }
   116  `),
   117  }
   118  
   119  var patch = []byte(`From af468c9e69dfdf39db591f1e3e8de5b64b0e62a2 Mon Sep 17 00:00:00 2001
   120  From: Wise Guy <wise@guy.com>
   121  Date: Thu, 19 Oct 2017 15:14:36 +0200
   122  Subject: [PATCH] Update magic number
   123  
   124  ---
   125   bar.go | 3 ++-
   126   1 file changed, 2 insertions(+), 1 deletion(-)
   127  
   128  diff --git a/bar.go b/bar.go
   129  index 1ea52dc..5bd70a9 100644
   130  --- a/bar.go
   131  +++ b/bar.go
   132  @@ -3,5 +3,6 @@ package bar
   133  
   134   // Foo does a thing.
   135   func Foo(wow int) int {
   136  -	return 42 + wow
   137  +	// Needs to be 49 because of a reason.
   138  +	return 49 + wow
   139   }
   140  `)
   141  
   142  var body = "This PR updates the magic number.\n\n```release-note\nUpdate the magic number from 42 to 49\n```"
   143  
   144  func TestCherryPickIC(t *testing.T) {
   145  	lg, c, err := localgit.New()
   146  	if err != nil {
   147  		t.Fatalf("Making localgit: %v", err)
   148  	}
   149  	defer func() {
   150  		if err := lg.Clean(); err != nil {
   151  			t.Errorf("Cleaning up localgit: %v", err)
   152  		}
   153  		if err := c.Clean(); err != nil {
   154  			t.Errorf("Cleaning up client: %v", err)
   155  		}
   156  	}()
   157  	if err := lg.MakeFakeRepo("foo", "bar"); err != nil {
   158  		t.Fatalf("Making fake repo: %v", err)
   159  	}
   160  	if err := lg.AddCommit("foo", "bar", initialFiles); err != nil {
   161  		t.Fatalf("Adding initial commit: %v", err)
   162  	}
   163  	if err := lg.CheckoutNewBranch("foo", "bar", "stage"); err != nil {
   164  		t.Fatalf("Checking out pull branch: %v", err)
   165  	}
   166  
   167  	ghc := &fghc{
   168  		pr: &github.PullRequest{
   169  			Base: github.PullRequestBranch{
   170  				Ref: "master",
   171  			},
   172  			Merged: true,
   173  			Title:  "This is a fix for X",
   174  			Body:   body,
   175  		},
   176  		isMember:   true,
   177  		createdNum: 3,
   178  		patch:      patch,
   179  	}
   180  	ic := github.IssueCommentEvent{
   181  		Action: github.IssueCommentActionCreated,
   182  		Repo: github.Repo{
   183  			Owner: github.User{
   184  				Login: "foo",
   185  			},
   186  			Name:     "bar",
   187  			FullName: "foo/bar",
   188  		},
   189  		Issue: github.Issue{
   190  			Number:      2,
   191  			State:       "closed",
   192  			PullRequest: &struct{}{},
   193  		},
   194  		Comment: github.IssueComment{
   195  			User: github.User{
   196  				Login: "wiseguy",
   197  			},
   198  			Body: "/cherrypick stage",
   199  		},
   200  	}
   201  
   202  	botName := "ci-robot"
   203  	expectedRepo := "foo/bar"
   204  	expectedTitle := "[stage] This is a fix for X"
   205  	expectedBody := "This is an automated cherry-pick of #2\n\n/assign wiseguy\n\n```release-note\nUpdate the magic number from 42 to 49\n```"
   206  	expectedBase := "stage"
   207  	expectedHead := fmt.Sprintf(botName+":"+cherryPickBranchFmt, 2, expectedBase)
   208  	expected := fmt.Sprintf(expectedFmt, expectedRepo, expectedTitle, expectedBody, expectedHead, expectedBase, true)
   209  
   210  	getSecret := func() []byte {
   211  		return []byte("sha=abcdefg")
   212  	}
   213  
   214  	s := &Server{
   215  		botName:        botName,
   216  		gc:             c,
   217  		push:           func(repo, newBranch string) error { return nil },
   218  		ghc:            ghc,
   219  		tokenGenerator: getSecret,
   220  		log:            logrus.StandardLogger().WithField("client", "cherrypicker"),
   221  		repos:          []github.Repo{{Fork: true, FullName: "ci-robot/bar"}},
   222  
   223  		prowAssignments: true,
   224  	}
   225  
   226  	if err := s.handleIssueComment(logrus.NewEntry(logrus.StandardLogger()), ic); err != nil {
   227  		t.Errorf("unexpected error: %v", err)
   228  	}
   229  	if ghc.prs[0] != expected {
   230  		t.Errorf("Expected (%d):\n%s\nGot (%d):\n%+v\n", len(expected), expected, len(ghc.prs[0]), ghc.prs[0])
   231  	}
   232  }
   233  
   234  func TestCherryPickPR(t *testing.T) {
   235  	lg, c, err := localgit.New()
   236  	if err != nil {
   237  		t.Fatalf("Making localgit: %v", err)
   238  	}
   239  	defer func() {
   240  		if err := lg.Clean(); err != nil {
   241  			t.Errorf("Cleaning up localgit: %v", err)
   242  		}
   243  		if err := c.Clean(); err != nil {
   244  			t.Errorf("Cleaning up client: %v", err)
   245  		}
   246  	}()
   247  	if err := lg.MakeFakeRepo("foo", "bar"); err != nil {
   248  		t.Fatalf("Making fake repo: %v", err)
   249  	}
   250  	if err := lg.AddCommit("foo", "bar", initialFiles); err != nil {
   251  		t.Fatalf("Adding initial commit: %v", err)
   252  	}
   253  	if err := lg.CheckoutNewBranch("foo", "bar", "release-1.5"); err != nil {
   254  		t.Fatalf("Checking out pull branch: %v", err)
   255  	}
   256  	if err := lg.CheckoutNewBranch("foo", "bar", "release-1.6"); err != nil {
   257  		t.Fatalf("Checking out pull branch: %v", err)
   258  	}
   259  
   260  	ghc := &fghc{
   261  		orgMembers: []github.TeamMember{
   262  			{
   263  				Login: "approver",
   264  			},
   265  			{
   266  				Login: "merge-bot",
   267  			},
   268  		},
   269  		prComments: []github.IssueComment{
   270  			{
   271  				User: github.User{
   272  					Login: "developer",
   273  				},
   274  				Body: "a review comment",
   275  			},
   276  			{
   277  				User: github.User{
   278  					Login: "approver",
   279  				},
   280  				Body: "/cherrypick release-1.5\r",
   281  			},
   282  			{
   283  				User: github.User{
   284  					Login: "approver",
   285  				},
   286  				Body: "/cherrypick release-1.6",
   287  			},
   288  			{
   289  				User: github.User{
   290  					Login: "fan",
   291  				},
   292  				Body: "/cherrypick release-1.7",
   293  			},
   294  			{
   295  				User: github.User{
   296  					Login: "approver",
   297  				},
   298  				Body: "/approve",
   299  			},
   300  			{
   301  				User: github.User{
   302  					Login: "merge-bot",
   303  				},
   304  				Body: "Automatic merge from submit-queue.",
   305  			},
   306  		},
   307  		isMember:   true,
   308  		createdNum: 3,
   309  		patch:      patch,
   310  	}
   311  	pr := github.PullRequestEvent{
   312  		Action: github.PullRequestActionClosed,
   313  		PullRequest: github.PullRequest{
   314  			Base: github.PullRequestBranch{
   315  				Ref: "master",
   316  				Repo: github.Repo{
   317  					Owner: github.User{
   318  						Login: "foo",
   319  					},
   320  					Name: "bar",
   321  				},
   322  			},
   323  			Number:   2,
   324  			Merged:   true,
   325  			MergeSHA: new(string),
   326  			Title:    "This is a fix for Y",
   327  		},
   328  	}
   329  
   330  	botName := "ci-robot"
   331  
   332  	getSecret := func() []byte {
   333  		return []byte("sha=abcdefg")
   334  	}
   335  
   336  	s := &Server{
   337  		botName:        botName,
   338  		gc:             c,
   339  		push:           func(repo, newBranch string) error { return nil },
   340  		ghc:            ghc,
   341  		tokenGenerator: getSecret,
   342  		log:            logrus.StandardLogger().WithField("client", "cherrypicker"),
   343  		repos:          []github.Repo{{Fork: true, FullName: "ci-robot/bar"}},
   344  
   345  		prowAssignments: false,
   346  	}
   347  
   348  	if err := s.handlePullRequest(logrus.NewEntry(logrus.StandardLogger()), pr); err != nil {
   349  		t.Errorf("unexpected error: %v", err)
   350  	}
   351  
   352  	var expectedFn = func(branch string) string {
   353  		expectedRepo := "foo/bar"
   354  		expectedTitle := fmt.Sprintf("[%s] This is a fix for Y", branch)
   355  		expectedBody := "This is an automated cherry-pick of #2"
   356  		expectedHead := fmt.Sprintf(botName+":"+cherryPickBranchFmt, 2, branch)
   357  		return fmt.Sprintf(expectedFmt, expectedRepo, expectedTitle, expectedBody, expectedHead, branch, true)
   358  	}
   359  
   360  	if len(ghc.prs) != 2 {
   361  		t.Fatalf("Expected %d PRs, got %d", 2, len(ghc.prs))
   362  	}
   363  
   364  	expectedBranches := []string{"release-1.5", "release-1.6"}
   365  	seenBranches := make(map[string]struct{})
   366  	for _, pr := range ghc.prs {
   367  		if pr != expectedFn("release-1.5") && pr != expectedFn("release-1.6") {
   368  			t.Errorf("Unexpected PR:\n%s\nExpected to target one of the following branches: %v", pr, expectedBranches)
   369  		}
   370  		if pr == expectedFn("release-1.5") {
   371  			seenBranches["release-1.5"] = struct{}{}
   372  		}
   373  		if pr == expectedFn("release-1.6") {
   374  			seenBranches["release-1.6"] = struct{}{}
   375  		}
   376  	}
   377  	if len(seenBranches) != 2 {
   378  		t.Fatalf("Expected to see PRs for %d branches, got %d (%v)", 2, len(seenBranches), seenBranches)
   379  	}
   380  }