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 }