Skip to content

Commit a5d5669

Browse files
committed
fix: handle case where multiple reviewers but not all owners
1 parent cf34820 commit a5d5669

File tree

4 files changed

+74
-33
lines changed

4 files changed

+74
-33
lines changed

pkg/plugins/approve/approve_test.go

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package approve
1818

1919
import (
2020
"fmt"
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
2123
"net/url"
2224
"os"
2325
"reflect"
@@ -1197,8 +1199,8 @@ Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a commen
11971199
11981200
This pull-request has been approved by: *[derek](<> "Approved")*
11991201
The changes made require 1 more approval(s).
1200-
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign
1201-
You can assign the PR to them by writing ` + "`/assign `" + ` in a comment when ready.
1202+
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **derek**
1203+
You can assign the PR to them by writing ` + "`/assign @derek`" + ` in a comment when ready.
12021204
12031205
The full list of commands accepted by this bot can be found [here](https://jenkins-x.io/v3/develop/reference/chatops/?repo=org%2Frepo).
12041206
@@ -1210,15 +1212,15 @@ Needs approval from an approver in each of these files:
12101212
Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment
12111213
Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment
12121214
</details>
1213-
<!-- META={"approvers":[]} -->`,
1215+
<!-- META={"approvers":["derek"]} -->`,
12141216
},
12151217
{
12161218
name: "2 minimum reviewers - 2 approval",
12171219
hasLabel: false,
12181220
files: []string{"d/d.go"},
12191221
comments: []*scm.Comment{
12201222
newTestComment("derek", "/approve"),
1221-
newTestComment("alice", "/approve"),
1223+
newTestComment("jerry", "/approve"),
12221224
},
12231225
reviews: []*scm.Review{},
12241226
selfApprove: false,
@@ -1232,7 +1234,7 @@ Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a commen
12321234
expectComment: true,
12331235
expectedComment: `[APPROVALNOTIFIER] This PR is **APPROVED**
12341236
1235-
This pull-request has been approved by: *[alice](<> "Approved")*, *[derek](<> "Approved")*
1237+
This pull-request has been approved by: *[derek](<> "Approved")*, *[jerry](<> "Approved")*
12361238
12371239
The full list of commands accepted by this bot can be found [here](https://jenkins-x.io/v3/develop/reference/chatops/?repo=org%2Frepo).
12381240
@@ -1241,27 +1243,63 @@ The pull request process is described [here](https://git.k8s.io/community/contri
12411243
<details >
12421244
Needs approval from an approver in each of these files:
12431245
1244-
- ~~[d/OWNERS](https://github.com/org/repo/blob/master/d/OWNERS)~~ [derek]
1246+
- ~~[d/OWNERS](https://github.com/org/repo/blob/master/d/OWNERS)~~ [derek,jerry]
12451247
12461248
Approvers can indicate their approval by writing ` + "`/approve` " + `in a comment
12471249
Approvers can cancel approval by writing ` + "`/approve cancel` " + `in a comment
12481250
</details>
12491251
<!-- META={"approvers":[]} -->`,
12501252
},
1253+
{
1254+
name: "2 minimum reviewers - 1 approval, 1 not registered approval",
1255+
hasLabel: false,
1256+
files: []string{"d/d.go"},
1257+
comments: []*scm.Comment{
1258+
newTestComment("derek", "/approve"),
1259+
newTestComment("alice", "/approve"),
1260+
},
1261+
reviews: []*scm.Review{},
1262+
selfApprove: false,
1263+
needsIssue: false,
1264+
lgtmActsAsApprove: false,
1265+
reviewActsAsApprove: false,
1266+
githubLinkURL: &url.URL{Scheme: "https", Host: "github.com"},
1267+
1268+
expectDelete: false,
1269+
expectToggle: false,
1270+
expectComment: true,
1271+
expectedComment: `[APPROVALNOTIFIER] This PR is **NOT APPROVED**
1272+
1273+
This pull-request has been approved by: *[alice](<> "Approved")*, *[derek](<> "Approved")*
1274+
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **derek**
1275+
You can assign the PR to them by writing ` + "`/assign @derek`" + ` in a comment when ready.
1276+
1277+
The full list of commands accepted by this bot can be found [here](https://jenkins-x.io/v3/develop/reference/chatops/?repo=org%2Frepo).
1278+
1279+
<details open>
1280+
Needs approval from an approver in each of these files:
1281+
1282+
- ~~[d/OWNERS](https://github.com/org/repo/blob/master/d/OWNERS)~~ [derek]
1283+
1284+
Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment
1285+
Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment
1286+
</details>
1287+
<!-- META={"approvers":["derek"]} -->`,
1288+
},
12511289
}
12521290

12531291
fr := fakeRepo{
12541292
approvers: map[string]sets.String{
12551293
"a": sets.NewString("alice"),
12561294
"a/b": sets.NewString("alice", "bob"),
12571295
"c": sets.NewString("cblecker", "cjwagner"),
1258-
"d": sets.NewString("derek"),
1296+
"d": sets.NewString("derek", "jerry"),
12591297
},
12601298
leafApprovers: map[string]sets.String{
12611299
"a": sets.NewString("alice"),
12621300
"a/b": sets.NewString("bob"),
12631301
"c": sets.NewString("cblecker", "cjwagner"),
1264-
"d": sets.NewString("derek"),
1302+
"d": sets.NewString("derek", "jerry"),
12651303
},
12661304
approverOwners: map[string]string{
12671305
"a/a.go": "a",
@@ -1271,7 +1309,7 @@ Approvers can cancel approval by writing ` + "`/approve cancel` " + `in a commen
12711309
"d/d.go": "d",
12721310
},
12731311
minimumReviewers: map[string]int{
1274-
"d/d.go": 2,
1312+
"d": 2,
12751313
},
12761314
}
12771315

@@ -1285,7 +1323,7 @@ Approvers can cancel approval by writing ` + "`/approve cancel` " + `in a commen
12851323

12861324
rsa := !test.selfApprove
12871325
irs := !test.reviewActsAsApprove
1288-
if err := handle(
1326+
err := handle(
12891327
logrus.WithField("plugin", "approve"),
12901328
fakeClient,
12911329
fr,
@@ -1306,9 +1344,8 @@ Approvers can cancel approval by writing ` + "`/approve cancel` " + `in a commen
13061344
author: "cjwagner",
13071345
assignees: []scm.User{{Login: "spxtr"}},
13081346
},
1309-
); err != nil {
1310-
t.Errorf("[%s] Unexpected error handling event: %v.", test.name, err)
1311-
}
1347+
)
1348+
require.NoError(t, err)
13121349

13131350
fakeLabel := fmt.Sprintf("org/repo#%v:approved", prNumber)
13141351

@@ -1350,12 +1387,7 @@ Approvers can cancel approval by writing ` + "`/approve cancel` " + `in a commen
13501387
len(fspc.PullRequestCommentsAdded),
13511388
)
13521389
} else if expect, got := fmt.Sprintf("org/repo#%v:", prNumber)+test.expectedComment, fspc.PullRequestCommentsAdded[0]; test.expectedComment != "" && got != expect {
1353-
t.Errorf(
1354-
"[%s] Expected the created notification to be:\n%s\n\nbut got:\n%s\n\n",
1355-
test.name,
1356-
expect,
1357-
got,
1358-
)
1390+
assert.Equal(t, expect, got, "actual notification does not equal expected")
13591391
}
13601392
} else {
13611393
if len(fspc.PullRequestCommentsAdded) != 0 {
@@ -1389,12 +1421,7 @@ Approvers can cancel approval by writing ` + "`/approve cancel` " + `in a commen
13891421
}
13901422
}
13911423
if test.expectToggle != toggled {
1392-
t.Errorf(
1393-
"[%s] Expected 'approved' label toggled: %t, but got %t.",
1394-
test.name,
1395-
test.expectToggle,
1396-
toggled,
1397-
)
1424+
assert.Equal(t, test.expectToggle, toggled, "actual toggle state does not equal expected")
13981425
}
13991426
})
14001427
}

pkg/plugins/approve/approvers/approvers_test.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -400,9 +400,14 @@ func TestIsApproved(t *testing.T) {
400400
"minReviewers/2Required": minApprovers2Required,
401401
}
402402
fakeMinReviewersMap := map[string]int{
403-
"minReviewers/test.go": 1,
404-
"minReviewers/2Required/test.go": 2,
403+
"minReviewers": 1,
404+
"minReviewers/2Required": 2,
405405
}
406+
fakeNoParentsOwnersMap := map[string]bool{
407+
"minReviewers": true,
408+
"minReviewers/2Required": true,
409+
}
410+
406411
tests := []struct {
407412
testName string
408413
filenames []string
@@ -522,11 +527,20 @@ func TestIsApproved(t *testing.T) {
522527
currentlyApproved: sets.NewString("Alice", "Bob"),
523528
isApproved: true,
524529
},
530+
{
531+
testName: "Min Reviewers/2required & root; 1 approval, 1 not registered approver",
532+
filenames: []string{"minReviewers/test.go", "minReviewers/2Required/test.go"},
533+
testSeed: 0,
534+
currentlyApproved: sets.NewString("Alice", "Derek"),
535+
isApproved: false,
536+
},
525537
}
526538

527539
for _, test := range tests {
528540
t.Run(test.testName, func(t *testing.T) {
529-
testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap, fakeMinReviewersMap), seed: test.testSeed, log: logrus.WithField("plugin", "some_plugin")})
541+
fakeRepo := createFakeRepo(FakeRepoMap, fakeMinReviewersMap)
542+
fakeRepo.noParentOwnersMap = fakeNoParentsOwnersMap
543+
testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: fakeRepo, seed: test.testSeed, log: logrus.WithField("plugin", "some_plugin")})
530544
for approver := range test.currentlyApproved {
531545
testApprovers.AddApprover(approver, "REFERENCE", false)
532546
}

pkg/plugins/approve/approvers/owners.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (o Owners) GetApprovers() map[string]sets.String {
7979
// If no minimum reviewers are found then 0 is returned.
8080
func (o Owners) GetRequiredApproversCount() int {
8181
var requiredApprovers int
82-
for _, fn := range o.filenames {
82+
for fn := range o.GetOwnersSet() {
8383
if count := o.repo.MinimumReviewersForFile(fn); count > requiredApprovers {
8484
requiredApprovers = count
8585
}
@@ -176,7 +176,7 @@ func (o Owners) GetSuggestedApprovers(reverseMap map[string]sets.String, potenti
176176
ap := NewApprovers(o)
177177
for !ap.RequirementsMet() {
178178
newApprover := findMostCoveringApprover(potentialApprovers, reverseMap, ap.UnapprovedFiles())
179-
if newApprover == "" {
179+
if newApprover == "" || ap.GetCurrentApproversSet().Has(newApprover) {
180180
o.log.Warnf("Couldn't find/suggest approvers for each files. Unapproved: %q", ap.UnapprovedFiles().List())
181181
return ap.GetCurrentApproversSet()
182182
}
@@ -443,7 +443,7 @@ func (ap Approvers) NoIssueApprovers() map[string]Approval {
443443
func (ap Approvers) UnapprovedFiles() sets.String {
444444
unapproved := sets.NewString()
445445
for fn, approvers := range ap.GetFilesApprovers() {
446-
if len(approvers) == 0 {
446+
if len(approvers) < ap.owners.repo.MinimumReviewersForFile(fn) {
447447
unapproved.Insert(fn)
448448
}
449449
}
@@ -519,7 +519,7 @@ func (ap Approvers) GetCCs() []string {
519519
// the PR are approved. If this returns true, the PR may still not be fully approved depending
520520
// on the associated issue requirement
521521
func (ap Approvers) AreFilesApproved() bool {
522-
return ap.UnapprovedFiles().Len() == 0 && ap.GetRemainingRequiredApprovers() <= 0
522+
return ap.UnapprovedFiles().Len() == 0
523523
}
524524

525525
// RequirementsMet returns a bool indicating whether the PR has met all approval requirements:

pkg/plugins/approve/approvers/owners_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func (f FakeRepo) MinimumReviewersForFile(path string) int {
4242
if out, ok := f.minReviewersMap[path]; ok {
4343
return out
4444
}
45-
return 0
45+
return 1
4646
}
4747

4848
func (f FakeRepo) Approvers(path string) sets.String {

0 commit comments

Comments
 (0)