github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/blunderbuss/blunderbuss_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 blunderbuss
    18  
    19  import (
    20  	"errors"
    21  	"reflect"
    22  	"sort"
    23  	"testing"
    24  
    25  	"github.com/sirupsen/logrus"
    26  
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  	"k8s.io/test-infra/prow/github"
    29  )
    30  
    31  type fakeGithubClient struct {
    32  	changes   []github.PullRequestChange
    33  	requested []string
    34  }
    35  
    36  func newFakeGithubClient(filesChanged []string) *fakeGithubClient {
    37  	changes := make([]github.PullRequestChange, 0, len(filesChanged))
    38  	for _, name := range filesChanged {
    39  		changes = append(changes, github.PullRequestChange{Filename: name})
    40  	}
    41  	return &fakeGithubClient{changes: changes}
    42  }
    43  
    44  func (c *fakeGithubClient) RequestReview(org, repo string, number int, logins []string) error {
    45  	if org != "org" {
    46  		return errors.New("org should be 'org'")
    47  	}
    48  	if repo != "repo" {
    49  		return errors.New("repo should be 'repo'")
    50  	}
    51  	if number != 5 {
    52  		return errors.New("number should be 5")
    53  	}
    54  	c.requested = append(c.requested, logins...)
    55  	return nil
    56  }
    57  
    58  func (c *fakeGithubClient) GetPullRequestChanges(org, repo string, num int) ([]github.PullRequestChange, error) {
    59  	if org != "org" {
    60  		return nil, errors.New("org should be 'org'")
    61  	}
    62  	if repo != "repo" {
    63  		return nil, errors.New("repo should be 'repo'")
    64  	}
    65  	if num != 5 {
    66  		return nil, errors.New("number should be 5")
    67  	}
    68  	return c.changes, nil
    69  }
    70  
    71  type fakeOwnersClient struct {
    72  	owners            map[string]string
    73  	approvers         map[string]sets.String
    74  	leafApprovers     map[string]sets.String
    75  	reviewers         map[string]sets.String
    76  	requiredReviewers map[string]sets.String
    77  	leafReviewers     map[string]sets.String
    78  }
    79  
    80  func (foc *fakeOwnersClient) Approvers(path string) sets.String {
    81  	return foc.approvers[path]
    82  }
    83  
    84  func (foc *fakeOwnersClient) LeafApprovers(path string) sets.String {
    85  	return foc.leafApprovers[path]
    86  }
    87  
    88  func (foc *fakeOwnersClient) FindApproverOwnersForFile(path string) string {
    89  	return foc.owners[path]
    90  }
    91  
    92  func (foc *fakeOwnersClient) Reviewers(path string) sets.String {
    93  	return foc.reviewers[path]
    94  }
    95  
    96  func (foc *fakeOwnersClient) RequiredReviewers(path string) sets.String {
    97  	return foc.requiredReviewers[path]
    98  }
    99  
   100  func (foc *fakeOwnersClient) LeafReviewers(path string) sets.String {
   101  	return foc.leafReviewers[path]
   102  }
   103  
   104  func (foc *fakeOwnersClient) FindReviewersOwnersForFile(path string) string {
   105  	return foc.owners[path]
   106  }
   107  
   108  var (
   109  	owners = map[string]string{
   110  		"a.go":  "1",
   111  		"b.go":  "2",
   112  		"bb.go": "3",
   113  		"c.go":  "4",
   114  
   115  		"e.go":  "5",
   116  		"ee.go": "5",
   117  	}
   118  	reviewers = map[string]sets.String{
   119  		"a.go": sets.NewString("al"),
   120  		"b.go": sets.NewString("al"),
   121  		"c.go": sets.NewString("charles"),
   122  
   123  		"e.go":  sets.NewString("erick", "evan"),
   124  		"ee.go": sets.NewString("erick", "evan"),
   125  		"f.go":  sets.NewString("author", "non-author"),
   126  	}
   127  	requiredReviewers = map[string]sets.String{
   128  		"a.go": sets.NewString("ben"),
   129  
   130  		"ee.go": sets.NewString("chris", "charles"),
   131  	}
   132  	leafReviewers = map[string]sets.String{
   133  		"a.go":  sets.NewString("alice"),
   134  		"b.go":  sets.NewString("bob"),
   135  		"bb.go": sets.NewString("bob", "ben"),
   136  		"c.go":  sets.NewString("cole", "carl", "chad"),
   137  
   138  		"e.go":  sets.NewString("erick", "ellen"),
   139  		"ee.go": sets.NewString("erick", "ellen"),
   140  		"f.go":  sets.NewString("author"),
   141  	}
   142  	testcases = []struct {
   143  		name                       string
   144  		filesChanged               []string
   145  		reviewerCount              int
   146  		maxReviewerCount           int
   147  		expectedRequested          []string
   148  		alternateExpectedRequested []string
   149  	}{
   150  		{
   151  			name:              "one file, 3 leaf reviewers, 1 parent, request 3",
   152  			filesChanged:      []string{"c.go"},
   153  			reviewerCount:     3,
   154  			expectedRequested: []string{"cole", "carl", "chad"},
   155  		},
   156  		{
   157  			name:              "one file, 3 leaf reviewers, 1 parent reviewer, request 4",
   158  			filesChanged:      []string{"c.go"},
   159  			reviewerCount:     4,
   160  			expectedRequested: []string{"cole", "carl", "chad", "charles"},
   161  		},
   162  		{
   163  			name:              "two files, 2 leaf reviewers, 1 common parent, request 2",
   164  			filesChanged:      []string{"a.go", "b.go"},
   165  			reviewerCount:     2,
   166  			expectedRequested: []string{"alice", "ben", "bob"},
   167  		},
   168  		{
   169  			name:              "two files, 2 leaf reviewers, 1 common parent, request 3",
   170  			filesChanged:      []string{"a.go", "b.go"},
   171  			reviewerCount:     3,
   172  			expectedRequested: []string{"alice", "ben", "bob", "al"},
   173  		},
   174  		{
   175  			name:              "one files, 1 leaf reviewers, request 1",
   176  			filesChanged:      []string{"a.go"},
   177  			reviewerCount:     1,
   178  			maxReviewerCount:  1,
   179  			expectedRequested: []string{"alice", "ben"},
   180  		},
   181  		{
   182  			name:              "one file, 2 leaf reviewer, 2 parent reviewers (1 dup), request 3",
   183  			filesChanged:      []string{"e.go"},
   184  			reviewerCount:     3,
   185  			expectedRequested: []string{"erick", "ellen", "evan"},
   186  		},
   187  		{
   188  			name:                       "two files, 2 leaf reviewer, 2 parent reviewers (1 dup), request 1",
   189  			filesChanged:               []string{"e.go"},
   190  			reviewerCount:              1,
   191  			expectedRequested:          []string{"erick"},
   192  			alternateExpectedRequested: []string{"ellen"},
   193  		},
   194  		{
   195  			name:              "two files, 1 common leaf reviewer, one additional leaf, one parent, request 1",
   196  			filesChanged:      []string{"b.go", "bb.go"},
   197  			reviewerCount:     1,
   198  			expectedRequested: []string{"bob", "ben"},
   199  		},
   200  		{
   201  			name:              "two files, 2 leaf reviewers, 1 common parent, request 1",
   202  			filesChanged:      []string{"a.go", "b.go"},
   203  			reviewerCount:     1,
   204  			expectedRequested: []string{"alice", "ben", "bob"},
   205  		},
   206  		{
   207  			name:                       "two files, 2 leaf reviewers, 1 common parent, request 1, limit 2",
   208  			filesChanged:               []string{"a.go", "b.go"},
   209  			reviewerCount:              1,
   210  			maxReviewerCount:           1,
   211  			expectedRequested:          []string{"alice", "ben"},
   212  			alternateExpectedRequested: []string{"ben", "bob"},
   213  		},
   214  		{
   215  			name:              "exclude author",
   216  			filesChanged:      []string{"f.go"},
   217  			reviewerCount:     1,
   218  			expectedRequested: []string{"non-author"},
   219  		},
   220  	}
   221  )
   222  
   223  // TestHandleWithExcludeApprovers tests that the handle function requests
   224  // reviews from the correct number of unique users when ExcludeApprovers is
   225  // true.
   226  func TestHandleWithExcludeApproversOnlyReviewers(t *testing.T) {
   227  	foc := &fakeOwnersClient{
   228  		owners:            owners,
   229  		reviewers:         reviewers,
   230  		requiredReviewers: requiredReviewers,
   231  		leafReviewers:     leafReviewers,
   232  	}
   233  
   234  	for _, tc := range testcases {
   235  		fghc := newFakeGithubClient(tc.filesChanged)
   236  		pre := &github.PullRequestEvent{
   237  			Number:      5,
   238  			PullRequest: github.PullRequest{User: github.User{Login: "AUTHOR"}},
   239  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   240  		}
   241  		if err := handle(fghc, foc, logrus.WithField("plugin", PluginName), &tc.reviewerCount, nil, tc.maxReviewerCount, true, pre); err != nil {
   242  			t.Errorf("[%s] unexpected error from handle: %v", tc.name, err)
   243  			continue
   244  		}
   245  
   246  		sort.Strings(fghc.requested)
   247  		sort.Strings(tc.expectedRequested)
   248  		sort.Strings(tc.alternateExpectedRequested)
   249  		if !reflect.DeepEqual(fghc.requested, tc.expectedRequested) {
   250  			if len(tc.alternateExpectedRequested) > 0 {
   251  				if !reflect.DeepEqual(fghc.requested, tc.alternateExpectedRequested) {
   252  					t.Errorf("[%s] expected the requested reviewers to be %q or %q, but got %q.", tc.name, tc.expectedRequested, tc.alternateExpectedRequested, fghc.requested)
   253  				}
   254  				continue
   255  			}
   256  			t.Errorf("[%s] expected the requested reviewers to be %q, but got %q.", tc.name, tc.expectedRequested, fghc.requested)
   257  		}
   258  	}
   259  }
   260  
   261  // TestHandleWithoutExcludeApprovers verifies that behavior is the same
   262  // when ExcludeApprovers is false and only approvers exist in the OWNERS files.
   263  // The owners fixture and test cases should always be the same as the ones in
   264  // TestHandleWithExcludeApprovers.
   265  func TestHandleWithoutExcludeApproversNoReviewers(t *testing.T) {
   266  	foc := &fakeOwnersClient{
   267  		owners:            owners,
   268  		approvers:         reviewers,
   269  		leafApprovers:     leafReviewers,
   270  		requiredReviewers: requiredReviewers,
   271  	}
   272  
   273  	for _, tc := range testcases {
   274  		fghc := newFakeGithubClient(tc.filesChanged)
   275  		pre := &github.PullRequestEvent{
   276  			Number:      5,
   277  			PullRequest: github.PullRequest{User: github.User{Login: "AUTHOR"}},
   278  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   279  		}
   280  		if err := handle(fghc, foc, logrus.WithField("plugin", PluginName), &tc.reviewerCount, nil, tc.maxReviewerCount, false, pre); err != nil {
   281  			t.Errorf("[%s] unexpected error from handle: %v", tc.name, err)
   282  			continue
   283  		}
   284  
   285  		sort.Strings(fghc.requested)
   286  		sort.Strings(tc.expectedRequested)
   287  		sort.Strings(tc.alternateExpectedRequested)
   288  		if !reflect.DeepEqual(fghc.requested, tc.expectedRequested) {
   289  			if len(tc.alternateExpectedRequested) > 0 {
   290  				if !reflect.DeepEqual(fghc.requested, tc.alternateExpectedRequested) {
   291  					t.Errorf("[%s] expected the requested reviewers to be %q or %q, but got %q.", tc.name, tc.expectedRequested, tc.alternateExpectedRequested, fghc.requested)
   292  				}
   293  				continue
   294  			}
   295  			t.Errorf("[%s] expected the requested reviewers to be %q, but got %q.", tc.name, tc.expectedRequested, fghc.requested)
   296  		}
   297  	}
   298  }
   299  
   300  func TestHandleWithoutExcludeApproversMixed(t *testing.T) {
   301  	foc := &fakeOwnersClient{
   302  		owners: map[string]string{
   303  			"a.go":  "1",
   304  			"b.go":  "2",
   305  			"bb.go": "3",
   306  			"c.go":  "4",
   307  
   308  			"e.go":  "5",
   309  			"ee.go": "5",
   310  		},
   311  		approvers: map[string]sets.String{
   312  			"a.go": sets.NewString("al"),
   313  			"b.go": sets.NewString("jeff"),
   314  			"c.go": sets.NewString("jeff"),
   315  
   316  			"e.go":  sets.NewString(),
   317  			"ee.go": sets.NewString("larry"),
   318  		},
   319  		leafApprovers: map[string]sets.String{
   320  			"a.go": sets.NewString("alice"),
   321  			"b.go": sets.NewString("brad"),
   322  			"c.go": sets.NewString("evan"),
   323  
   324  			"e.go":  sets.NewString("erick", "evan"),
   325  			"ee.go": sets.NewString("erick", "evan"),
   326  		},
   327  		reviewers: map[string]sets.String{
   328  			"a.go": sets.NewString("al"),
   329  			"b.go": sets.NewString(),
   330  			"c.go": sets.NewString("charles"),
   331  
   332  			"e.go":  sets.NewString("erick", "evan"),
   333  			"ee.go": sets.NewString("erick", "evan"),
   334  		},
   335  		leafReviewers: map[string]sets.String{
   336  			"a.go":  sets.NewString("alice"),
   337  			"b.go":  sets.NewString("bob"),
   338  			"bb.go": sets.NewString("bob", "ben"),
   339  			"c.go":  sets.NewString("cole", "carl", "chad"),
   340  
   341  			"e.go":  sets.NewString("erick", "ellen"),
   342  			"ee.go": sets.NewString("erick", "ellen"),
   343  		},
   344  	}
   345  
   346  	var testcases = []struct {
   347  		name                       string
   348  		filesChanged               []string
   349  		reviewerCount              int
   350  		maxReviewerCount           int
   351  		expectedRequested          []string
   352  		alternateExpectedRequested []string
   353  	}{
   354  		{
   355  			name:              "1 file, 1 leaf reviewer, 1 leaf approver, 1 approver, request 3",
   356  			filesChanged:      []string{"b.go"},
   357  			reviewerCount:     3,
   358  			expectedRequested: []string{"bob", "brad", "jeff"},
   359  		},
   360  		{
   361  			name:              "1 file, 1 leaf reviewer, 1 leaf approver, 1 approver, request 1, limit 1",
   362  			filesChanged:      []string{"b.go"},
   363  			reviewerCount:     1,
   364  			expectedRequested: []string{"bob"},
   365  		},
   366  		{
   367  			name:              "2 file, 2 leaf reviewers, 1 parent reviewers, 1 leaf approver, 1 approver, request 5",
   368  			filesChanged:      []string{"a.go", "b.go"},
   369  			reviewerCount:     5,
   370  			expectedRequested: []string{"alice", "bob", "al", "brad", "jeff"},
   371  		},
   372  		{
   373  			name:              "1 file, 1 leaf reviewer+approver, 1 reviewer+approver, request 3",
   374  			filesChanged:      []string{"a.go"},
   375  			reviewerCount:     3,
   376  			expectedRequested: []string{"alice", "al"},
   377  		},
   378  		{
   379  			name:              "1 file, 2 leaf reviewers, request 2",
   380  			filesChanged:      []string{"e.go"},
   381  			reviewerCount:     2,
   382  			expectedRequested: []string{"erick", "ellen"},
   383  		},
   384  		{
   385  			name:              "2 files, 2 leaf+parent reviewers, 1 parent reviewer, 1 parent approver, request 4",
   386  			filesChanged:      []string{"e.go", "ee.go"},
   387  			reviewerCount:     4,
   388  			expectedRequested: []string{"erick", "ellen", "evan", "larry"},
   389  		},
   390  	}
   391  	for _, tc := range testcases {
   392  		fghc := newFakeGithubClient(tc.filesChanged)
   393  		pre := &github.PullRequestEvent{
   394  			Number:      5,
   395  			PullRequest: github.PullRequest{User: github.User{Login: "author"}},
   396  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   397  		}
   398  		if err := handle(fghc, foc, logrus.WithField("plugin", PluginName), &tc.reviewerCount, nil, tc.maxReviewerCount, false, pre); err != nil {
   399  			t.Errorf("[%s] unexpected error from handle: %v", tc.name, err)
   400  			continue
   401  		}
   402  
   403  		sort.Strings(fghc.requested)
   404  		sort.Strings(tc.expectedRequested)
   405  		sort.Strings(tc.alternateExpectedRequested)
   406  		if !reflect.DeepEqual(fghc.requested, tc.expectedRequested) {
   407  			if len(tc.alternateExpectedRequested) > 0 {
   408  				if !reflect.DeepEqual(fghc.requested, tc.alternateExpectedRequested) {
   409  					t.Errorf("[%s] expected the requested reviewers to be %q or %q, but got %q.", tc.name, tc.expectedRequested, tc.alternateExpectedRequested, fghc.requested)
   410  				}
   411  				continue
   412  			}
   413  			t.Errorf("[%s] expected the requested reviewers to be %q, but got %q.", tc.name, tc.expectedRequested, fghc.requested)
   414  		}
   415  	}
   416  }
   417  
   418  func TestHandleOld(t *testing.T) {
   419  	foc := &fakeOwnersClient{
   420  		reviewers: map[string]sets.String{
   421  			"c.go": sets.NewString("charles"),
   422  			"d.go": sets.NewString("dan"),
   423  			"e.go": sets.NewString("erick", "evan"),
   424  			"f.go": sets.NewString("author", "non-author"),
   425  		},
   426  		leafReviewers: map[string]sets.String{
   427  			"a.go": sets.NewString("alice"),
   428  			"b.go": sets.NewString("bob"),
   429  			"c.go": sets.NewString("cole", "carl", "chad"),
   430  			"e.go": sets.NewString("erick"),
   431  			"f.go": sets.NewString("author"),
   432  		},
   433  	}
   434  
   435  	var testcases = []struct {
   436  		name              string
   437  		filesChanged      []string
   438  		reviewerCount     int
   439  		expectedRequested []string
   440  	}{
   441  		{
   442  			name:              "one file, 3 leaf reviewers, request 3",
   443  			filesChanged:      []string{"c.go"},
   444  			reviewerCount:     3,
   445  			expectedRequested: []string{"cole", "carl", "chad"},
   446  		},
   447  		{
   448  			name:              "one file, 3 leaf reviewers, 1 parent reviewer, request 4",
   449  			filesChanged:      []string{"c.go"},
   450  			reviewerCount:     4,
   451  			expectedRequested: []string{"cole", "carl", "chad", "charles"},
   452  		},
   453  		{
   454  			name:              "two files, 2 leaf reviewers, request 2",
   455  			filesChanged:      []string{"a.go", "b.go"},
   456  			reviewerCount:     2,
   457  			expectedRequested: []string{"alice", "bob"},
   458  		},
   459  		{
   460  			name:              "one files, 1 leaf reviewers, request 1",
   461  			filesChanged:      []string{"a.go"},
   462  			reviewerCount:     1,
   463  			expectedRequested: []string{"alice"},
   464  		},
   465  		{
   466  			name:              "one file, 0 leaf reviewers, 1 parent reviewer, request 1",
   467  			filesChanged:      []string{"d.go"},
   468  			reviewerCount:     1,
   469  			expectedRequested: []string{"dan"},
   470  		},
   471  		{
   472  			name:              "one file, 0 leaf reviewers, 1 parent reviewer, request 2",
   473  			filesChanged:      []string{"d.go"},
   474  			reviewerCount:     2,
   475  			expectedRequested: []string{"dan"},
   476  		},
   477  		{
   478  			name:              "one file, 1 leaf reviewers, 2 parent reviewers (1 dup), request 2",
   479  			filesChanged:      []string{"e.go"},
   480  			reviewerCount:     2,
   481  			expectedRequested: []string{"erick", "evan"},
   482  		},
   483  		{
   484  			name:              "exclude author",
   485  			filesChanged:      []string{"f.go"},
   486  			reviewerCount:     1,
   487  			expectedRequested: []string{"non-author"},
   488  		},
   489  	}
   490  	for _, tc := range testcases {
   491  		fghc := newFakeGithubClient(tc.filesChanged)
   492  		pre := &github.PullRequestEvent{
   493  			Number:      5,
   494  			PullRequest: github.PullRequest{User: github.User{Login: "AUTHOR"}},
   495  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   496  		}
   497  		if err := handle(fghc, foc, logrus.WithField("plugin", PluginName), nil, &tc.reviewerCount, 0, false, pre); err != nil {
   498  			t.Errorf("[%s] unexpected error from handle: %v", tc.name, err)
   499  			continue
   500  		}
   501  
   502  		sort.Strings(fghc.requested)
   503  		sort.Strings(tc.expectedRequested)
   504  		if !reflect.DeepEqual(fghc.requested, tc.expectedRequested) {
   505  			t.Errorf("[%s] expected the requested reviewers to be %q, but got %q.", tc.name, tc.expectedRequested, fghc.requested)
   506  		}
   507  	}
   508  }