github.com/abayer/test-infra@v0.0.5/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  	}
   126  	requiredReviewers = map[string]sets.String{
   127  		"a.go": sets.NewString("ben"),
   128  
   129  		"ee.go": sets.NewString("chris", "charles"),
   130  	}
   131  	leafReviewers = map[string]sets.String{
   132  		"a.go":  sets.NewString("alice"),
   133  		"b.go":  sets.NewString("bob"),
   134  		"bb.go": sets.NewString("bob", "ben"),
   135  		"c.go":  sets.NewString("cole", "carl", "chad"),
   136  
   137  		"e.go":  sets.NewString("erick", "ellen"),
   138  		"ee.go": sets.NewString("erick", "ellen"),
   139  	}
   140  	testcases = []struct {
   141  		name                       string
   142  		filesChanged               []string
   143  		reviewerCount              int
   144  		maxReviewerCount           int
   145  		expectedRequested          []string
   146  		alternateExpectedRequested []string
   147  	}{
   148  		{
   149  			name:              "one file, 3 leaf reviewers, 1 parent, request 3",
   150  			filesChanged:      []string{"c.go"},
   151  			reviewerCount:     3,
   152  			expectedRequested: []string{"cole", "carl", "chad"},
   153  		},
   154  		{
   155  			name:              "one file, 3 leaf reviewers, 1 parent reviewer, request 4",
   156  			filesChanged:      []string{"c.go"},
   157  			reviewerCount:     4,
   158  			expectedRequested: []string{"cole", "carl", "chad", "charles"},
   159  		},
   160  		{
   161  			name:              "two files, 2 leaf reviewers, 1 common parent, request 2",
   162  			filesChanged:      []string{"a.go", "b.go"},
   163  			reviewerCount:     2,
   164  			expectedRequested: []string{"alice", "ben", "bob"},
   165  		},
   166  		{
   167  			name:              "two files, 2 leaf reviewers, 1 common parent, request 3",
   168  			filesChanged:      []string{"a.go", "b.go"},
   169  			reviewerCount:     3,
   170  			expectedRequested: []string{"alice", "ben", "bob", "al"},
   171  		},
   172  		{
   173  			name:              "one files, 1 leaf reviewers, request 1",
   174  			filesChanged:      []string{"a.go"},
   175  			reviewerCount:     1,
   176  			maxReviewerCount:  1,
   177  			expectedRequested: []string{"alice", "ben"},
   178  		},
   179  		{
   180  			name:              "one file, 2 leaf reviewer, 2 parent reviewers (1 dup), request 3",
   181  			filesChanged:      []string{"e.go"},
   182  			reviewerCount:     3,
   183  			expectedRequested: []string{"erick", "ellen", "evan"},
   184  		},
   185  		{
   186  			name:                       "two files, 2 leaf reviewer, 2 parent reviewers (1 dup), request 1",
   187  			filesChanged:               []string{"e.go"},
   188  			reviewerCount:              1,
   189  			expectedRequested:          []string{"erick"},
   190  			alternateExpectedRequested: []string{"ellen"},
   191  		},
   192  		{
   193  			name:              "two files, 1 common leaf reviewer, one additional leaf, one parent, request 1",
   194  			filesChanged:      []string{"b.go", "bb.go"},
   195  			reviewerCount:     1,
   196  			expectedRequested: []string{"bob", "ben"},
   197  		},
   198  		{
   199  			name:              "two files, 2 leaf reviewers, 1 common parent, request 1",
   200  			filesChanged:      []string{"a.go", "b.go"},
   201  			reviewerCount:     1,
   202  			expectedRequested: []string{"alice", "ben", "bob"},
   203  		},
   204  		{
   205  			name:                       "two files, 2 leaf reviewers, 1 common parent, request 1, limit 2",
   206  			filesChanged:               []string{"a.go", "b.go"},
   207  			reviewerCount:              1,
   208  			maxReviewerCount:           1,
   209  			expectedRequested:          []string{"alice", "ben"},
   210  			alternateExpectedRequested: []string{"ben", "bob"},
   211  		},
   212  	}
   213  )
   214  
   215  // TestHandleWithExcludeApprovers tests that the handle function requests
   216  // reviews from the correct number of unique users when ExcludeApprovers is
   217  // true.
   218  func TestHandleWithExcludeApproversOnlyReviewers(t *testing.T) {
   219  	foc := &fakeOwnersClient{
   220  		owners:            owners,
   221  		reviewers:         reviewers,
   222  		requiredReviewers: requiredReviewers,
   223  		leafReviewers:     leafReviewers,
   224  	}
   225  
   226  	for _, tc := range testcases {
   227  		fghc := newFakeGithubClient(tc.filesChanged)
   228  		pre := &github.PullRequestEvent{
   229  			Number:      5,
   230  			PullRequest: github.PullRequest{User: github.User{Login: "author"}},
   231  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   232  		}
   233  		if err := handle(fghc, foc, logrus.WithField("plugin", pluginName), &tc.reviewerCount, nil, tc.maxReviewerCount, true, pre); err != nil {
   234  			t.Errorf("[%s] unexpected error from handle: %v", tc.name, err)
   235  			continue
   236  		}
   237  
   238  		sort.Strings(fghc.requested)
   239  		sort.Strings(tc.expectedRequested)
   240  		sort.Strings(tc.alternateExpectedRequested)
   241  		if !reflect.DeepEqual(fghc.requested, tc.expectedRequested) {
   242  			if len(tc.alternateExpectedRequested) > 0 {
   243  				if !reflect.DeepEqual(fghc.requested, tc.alternateExpectedRequested) {
   244  					t.Errorf("[%s] expected the requested reviewers to be %q or %q, but got %q.", tc.name, tc.expectedRequested, tc.alternateExpectedRequested, fghc.requested)
   245  				}
   246  				continue
   247  			}
   248  			t.Errorf("[%s] expected the requested reviewers to be %q, but got %q.", tc.name, tc.expectedRequested, fghc.requested)
   249  		}
   250  	}
   251  }
   252  
   253  // TestHandleWithoutExcludeApprovers verifies that behavior is the same
   254  // when ExcludeApprovers is false and only approvers exist in the OWNERS files.
   255  // The owners fixture and test cases should always be the same as the ones in
   256  // TestHandleWithExcludeApprovers.
   257  func TestHandleWithoutExcludeApproversNoReviewers(t *testing.T) {
   258  	foc := &fakeOwnersClient{
   259  		owners:            owners,
   260  		approvers:         reviewers,
   261  		leafApprovers:     leafReviewers,
   262  		requiredReviewers: requiredReviewers,
   263  	}
   264  
   265  	for _, tc := range testcases {
   266  		fghc := newFakeGithubClient(tc.filesChanged)
   267  		pre := &github.PullRequestEvent{
   268  			Number:      5,
   269  			PullRequest: github.PullRequest{User: github.User{Login: "author"}},
   270  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   271  		}
   272  		if err := handle(fghc, foc, logrus.WithField("plugin", pluginName), &tc.reviewerCount, nil, tc.maxReviewerCount, false, pre); err != nil {
   273  			t.Errorf("[%s] unexpected error from handle: %v", tc.name, err)
   274  			continue
   275  		}
   276  
   277  		sort.Strings(fghc.requested)
   278  		sort.Strings(tc.expectedRequested)
   279  		sort.Strings(tc.alternateExpectedRequested)
   280  		if !reflect.DeepEqual(fghc.requested, tc.expectedRequested) {
   281  			if len(tc.alternateExpectedRequested) > 0 {
   282  				if !reflect.DeepEqual(fghc.requested, tc.alternateExpectedRequested) {
   283  					t.Errorf("[%s] expected the requested reviewers to be %q or %q, but got %q.", tc.name, tc.expectedRequested, tc.alternateExpectedRequested, fghc.requested)
   284  				}
   285  				continue
   286  			}
   287  			t.Errorf("[%s] expected the requested reviewers to be %q, but got %q.", tc.name, tc.expectedRequested, fghc.requested)
   288  		}
   289  	}
   290  }
   291  
   292  func TestHandleWithoutExcludeApproversMixed(t *testing.T) {
   293  	foc := &fakeOwnersClient{
   294  		owners: map[string]string{
   295  			"a.go":  "1",
   296  			"b.go":  "2",
   297  			"bb.go": "3",
   298  			"c.go":  "4",
   299  
   300  			"e.go":  "5",
   301  			"ee.go": "5",
   302  		},
   303  		approvers: map[string]sets.String{
   304  			"a.go": sets.NewString("al"),
   305  			"b.go": sets.NewString("jeff"),
   306  			"c.go": sets.NewString("jeff"),
   307  
   308  			"e.go":  sets.NewString(),
   309  			"ee.go": sets.NewString("larry"),
   310  		},
   311  		leafApprovers: map[string]sets.String{
   312  			"a.go": sets.NewString("alice"),
   313  			"b.go": sets.NewString("brad"),
   314  			"c.go": sets.NewString("evan"),
   315  
   316  			"e.go":  sets.NewString("erick", "evan"),
   317  			"ee.go": sets.NewString("erick", "evan"),
   318  		},
   319  		reviewers: map[string]sets.String{
   320  			"a.go": sets.NewString("al"),
   321  			"b.go": sets.NewString(),
   322  			"c.go": sets.NewString("charles"),
   323  
   324  			"e.go":  sets.NewString("erick", "evan"),
   325  			"ee.go": sets.NewString("erick", "evan"),
   326  		},
   327  		leafReviewers: map[string]sets.String{
   328  			"a.go":  sets.NewString("alice"),
   329  			"b.go":  sets.NewString("bob"),
   330  			"bb.go": sets.NewString("bob", "ben"),
   331  			"c.go":  sets.NewString("cole", "carl", "chad"),
   332  
   333  			"e.go":  sets.NewString("erick", "ellen"),
   334  			"ee.go": sets.NewString("erick", "ellen"),
   335  		},
   336  	}
   337  
   338  	var testcases = []struct {
   339  		name                       string
   340  		filesChanged               []string
   341  		reviewerCount              int
   342  		maxReviewerCount           int
   343  		expectedRequested          []string
   344  		alternateExpectedRequested []string
   345  	}{
   346  		{
   347  			name:              "1 file, 1 leaf reviewer, 1 leaf approver, 1 approver, request 3",
   348  			filesChanged:      []string{"b.go"},
   349  			reviewerCount:     3,
   350  			expectedRequested: []string{"bob", "brad", "jeff"},
   351  		},
   352  		{
   353  			name:              "1 file, 1 leaf reviewer, 1 leaf approver, 1 approver, request 1, limit 1",
   354  			filesChanged:      []string{"b.go"},
   355  			reviewerCount:     1,
   356  			expectedRequested: []string{"bob"},
   357  		},
   358  		{
   359  			name:              "2 file, 2 leaf reviewers, 1 parent reviewers, 1 leaf approver, 1 approver, request 5",
   360  			filesChanged:      []string{"a.go", "b.go"},
   361  			reviewerCount:     5,
   362  			expectedRequested: []string{"alice", "bob", "al", "brad", "jeff"},
   363  		},
   364  		{
   365  			name:              "1 file, 1 leaf reviewer+approver, 1 reviewer+approver, request 3",
   366  			filesChanged:      []string{"a.go"},
   367  			reviewerCount:     3,
   368  			expectedRequested: []string{"alice", "al"},
   369  		},
   370  		{
   371  			name:              "1 file, 2 leaf reviewers, request 2",
   372  			filesChanged:      []string{"e.go"},
   373  			reviewerCount:     2,
   374  			expectedRequested: []string{"erick", "ellen"},
   375  		},
   376  		{
   377  			name:              "2 files, 2 leaf+parent reviewers, 1 parent reviewer, 1 parent approver, request 4",
   378  			filesChanged:      []string{"e.go", "ee.go"},
   379  			reviewerCount:     4,
   380  			expectedRequested: []string{"erick", "ellen", "evan", "larry"},
   381  		},
   382  	}
   383  	for _, tc := range testcases {
   384  		fghc := newFakeGithubClient(tc.filesChanged)
   385  		pre := &github.PullRequestEvent{
   386  			Number:      5,
   387  			PullRequest: github.PullRequest{User: github.User{Login: "author"}},
   388  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   389  		}
   390  		if err := handle(fghc, foc, logrus.WithField("plugin", pluginName), &tc.reviewerCount, nil, tc.maxReviewerCount, false, pre); err != nil {
   391  			t.Errorf("[%s] unexpected error from handle: %v", tc.name, err)
   392  			continue
   393  		}
   394  
   395  		sort.Strings(fghc.requested)
   396  		sort.Strings(tc.expectedRequested)
   397  		sort.Strings(tc.alternateExpectedRequested)
   398  		if !reflect.DeepEqual(fghc.requested, tc.expectedRequested) {
   399  			if len(tc.alternateExpectedRequested) > 0 {
   400  				if !reflect.DeepEqual(fghc.requested, tc.alternateExpectedRequested) {
   401  					t.Errorf("[%s] expected the requested reviewers to be %q or %q, but got %q.", tc.name, tc.expectedRequested, tc.alternateExpectedRequested, fghc.requested)
   402  				}
   403  				continue
   404  			}
   405  			t.Errorf("[%s] expected the requested reviewers to be %q, but got %q.", tc.name, tc.expectedRequested, fghc.requested)
   406  		}
   407  	}
   408  }
   409  
   410  func TestHandleOld(t *testing.T) {
   411  	foc := &fakeOwnersClient{
   412  		reviewers: map[string]sets.String{
   413  			"c.go": sets.NewString("charles"),
   414  			"d.go": sets.NewString("dan"),
   415  			"e.go": sets.NewString("erick", "evan"),
   416  		},
   417  		leafReviewers: map[string]sets.String{
   418  			"a.go": sets.NewString("alice"),
   419  			"b.go": sets.NewString("bob"),
   420  			"c.go": sets.NewString("cole", "carl", "chad"),
   421  			"e.go": sets.NewString("erick"),
   422  		},
   423  	}
   424  
   425  	var testcases = []struct {
   426  		name              string
   427  		filesChanged      []string
   428  		reviewerCount     int
   429  		expectedRequested []string
   430  	}{
   431  		{
   432  			name:              "one file, 3 leaf reviewers, request 3",
   433  			filesChanged:      []string{"c.go"},
   434  			reviewerCount:     3,
   435  			expectedRequested: []string{"cole", "carl", "chad"},
   436  		},
   437  		{
   438  			name:              "one file, 3 leaf reviewers, 1 parent reviewer, request 4",
   439  			filesChanged:      []string{"c.go"},
   440  			reviewerCount:     4,
   441  			expectedRequested: []string{"cole", "carl", "chad", "charles"},
   442  		},
   443  		{
   444  			name:              "two files, 2 leaf reviewers, request 2",
   445  			filesChanged:      []string{"a.go", "b.go"},
   446  			reviewerCount:     2,
   447  			expectedRequested: []string{"alice", "bob"},
   448  		},
   449  		{
   450  			name:              "one files, 1 leaf reviewers, request 1",
   451  			filesChanged:      []string{"a.go"},
   452  			reviewerCount:     1,
   453  			expectedRequested: []string{"alice"},
   454  		},
   455  		{
   456  			name:              "one file, 0 leaf reviewers, 1 parent reviewer, request 1",
   457  			filesChanged:      []string{"d.go"},
   458  			reviewerCount:     1,
   459  			expectedRequested: []string{"dan"},
   460  		},
   461  		{
   462  			name:              "one file, 0 leaf reviewers, 1 parent reviewer, request 2",
   463  			filesChanged:      []string{"d.go"},
   464  			reviewerCount:     2,
   465  			expectedRequested: []string{"dan"},
   466  		},
   467  		{
   468  			name:              "one file, 1 leaf reviewers, 2 parent reviewers (1 dup), request 2",
   469  			filesChanged:      []string{"e.go"},
   470  			reviewerCount:     2,
   471  			expectedRequested: []string{"erick", "evan"},
   472  		},
   473  	}
   474  	for _, tc := range testcases {
   475  		fghc := newFakeGithubClient(tc.filesChanged)
   476  		pre := &github.PullRequestEvent{
   477  			Number:      5,
   478  			PullRequest: github.PullRequest{User: github.User{Login: "author"}},
   479  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   480  		}
   481  		if err := handle(fghc, foc, logrus.WithField("plugin", pluginName), nil, &tc.reviewerCount, 0, false, pre); err != nil {
   482  			t.Errorf("[%s] unexpected error from handle: %v", tc.name, err)
   483  			continue
   484  		}
   485  
   486  		sort.Strings(fghc.requested)
   487  		sort.Strings(tc.expectedRequested)
   488  		if !reflect.DeepEqual(fghc.requested, tc.expectedRequested) {
   489  			t.Errorf("[%s] expected the requested reviewers to be %q, but got %q.", tc.name, tc.expectedRequested, fghc.requested)
   490  		}
   491  	}
   492  }