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 }