Skip to content

Commit 101a9af

Browse files
committed
cmd/gopherbot: improve test fake for GitHub issues
This change adds an interface and a fake implementation of a GitHub service for labeling GitHub issues. This allows us to test more thoroughly, as well as avoid swapping globally scoped function vars out in tests. There are still more places to improve testing of GitHub API calls, but this is a start. Change-Id: I76d007163d65e513d74c98d0211cbb92296bfa0c Reviewed-on: https://go-review.googlesource.com/c/build/+/209717 Run-TryBot: Alexander Rakoczy <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Carlos Amedee <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 6e1808a commit 101a9af

File tree

2 files changed

+95
-51
lines changed

2 files changed

+95
-51
lines changed

cmd/gopherbot/gopherbot.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ func main() {
208208
ghc: ghc,
209209
gerrit: gerrit,
210210
mc: mc,
211+
is: ghc.Issues,
211212
deletedChanges: map[gerritChange]bool{
212213
{"crypto", 35958}: true,
213214
{"scratch", 71730}: true,
@@ -265,6 +266,7 @@ type gopherbot struct {
265266
mc apipb.MaintnerServiceClient
266267
corpus *maintner.Corpus
267268
gorepo *maintner.GitHubRepo
269+
is issuesService
268270

269271
knownContributors map[string]bool
270272

@@ -279,6 +281,7 @@ type gopherbot struct {
279281
}
280282
}
281283

284+
// tasks are executed in order every corpus update.
282285
var tasks = []struct {
283286
name string
284287
fn func(*gopherbot, context.Context) error
@@ -340,6 +343,13 @@ func (b *gopherbot) doTasks(ctx context.Context) []error {
340343
return errs
341344
}
342345

346+
// issuesService represents portions of github.IssuesService that we want to override in tests.
347+
type issuesService interface {
348+
ListLabelsByIssue(ctx context.Context, owner string, repo string, number int, opt *github.ListOptions) ([]*github.Label, *github.Response, error)
349+
AddLabelsToIssue(ctx context.Context, owner string, repo string, number int, labels []string) ([]*github.Label, *github.Response, error)
350+
RemoveLabelForIssue(ctx context.Context, owner string, repo string, number int, label string) (*github.Response, error)
351+
}
352+
343353
func (b *gopherbot) addLabel(ctx context.Context, gi *maintner.GitHubIssue, label string) error {
344354
return b.addLabels(ctx, gi, []string{label})
345355
}
@@ -359,13 +369,7 @@ func (b *gopherbot) addLabels(ctx context.Context, gi *maintner.GitHubIssue, lab
359369
return nil
360370
}
361371

362-
return addLabelsToIssue(ctx, b.ghc.Issues, int(gi.Number), toAdd)
363-
}
364-
365-
// addLabelsToIssue adds labels to the issue in golang/go with the given issueNum.
366-
// TODO: Proper stubs via interfaces.
367-
var addLabelsToIssue = func(ctx context.Context, issues *github.IssuesService, issueNum int, labels []string) error {
368-
_, _, err := issues.AddLabelsToIssue(ctx, "golang", "go", issueNum, labels)
372+
_, _, err := b.is.AddLabelsToIssue(ctx, "golang", "go", int(gi.Number), toAdd)
369373
return err
370374
}
371375

@@ -389,7 +393,7 @@ func (b *gopherbot) removeLabels(ctx context.Context, gi *maintner.GitHubIssue,
389393
return nil
390394
}
391395

392-
ghLabels, err := labelsForIssue(ctx, b.ghc.Issues, int(gi.Number))
396+
ghLabels, err := labelsForIssue(ctx, b.is, int(gi.Number))
393397
if err != nil {
394398
return err
395399
}
@@ -400,7 +404,7 @@ func (b *gopherbot) removeLabels(ctx context.Context, gi *maintner.GitHubIssue,
400404

401405
for _, l := range ghLabels {
402406
if toRemove[l] {
403-
if err := removeLabelFromIssue(ctx, b.ghc.Issues, int(gi.Number), l); err != nil {
407+
if err := removeLabelFromIssue(ctx, b.is, int(gi.Number), l); err != nil {
404408
log.Printf("Could not remove label %q from issue %d: %v", l, gi.Number, err)
405409
continue
406410
}
@@ -410,8 +414,7 @@ func (b *gopherbot) removeLabels(ctx context.Context, gi *maintner.GitHubIssue,
410414
}
411415

412416
// labelsForIssue returns all labels for the given issue in the golang/go repo.
413-
// TODO: Proper stubs via interfaces.
414-
var labelsForIssue = func(ctx context.Context, issues *github.IssuesService, issueNum int) ([]string, error) {
417+
func labelsForIssue(ctx context.Context, issues issuesService, issueNum int) ([]string, error) {
415418
ghLabels, _, err := issues.ListLabelsByIssue(ctx, "golang", "go", issueNum, &github.ListOptions{PerPage: 100})
416419
if err != nil {
417420
return nil, fmt.Errorf("could not list labels for golang/go#%d: %v", issueNum, err)
@@ -425,8 +428,7 @@ var labelsForIssue = func(ctx context.Context, issues *github.IssuesService, iss
425428

426429
// removeLabelForIssue removes the given label from golang/go with the given issueNum.
427430
// If the issue did not have the label already (or the label didn't exist), return nil.
428-
// TODO: Proper stubs via interfaces.
429-
var removeLabelFromIssue = func(ctx context.Context, issues *github.IssuesService, issueNum int, label string) error {
431+
func removeLabelFromIssue(ctx context.Context, issues issuesService, issueNum int, label string) error {
430432
_, err := issues.RemoveLabelForIssue(ctx, "golang", "go", issueNum, label)
431433
if ge, ok := err.(*github.ErrorResponse); ok && ge.Response != nil && ge.Response.StatusCode == http.StatusNotFound {
432434
return nil

cmd/gopherbot/gopherbot_test.go

Lines changed: 80 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/google/go-cmp/cmp"
1414
"github.com/google/go-cmp/cmp/cmpopts"
1515
"github.com/google/go-github/github"
16+
1617
"golang.org/x/build/devapp/owners"
1718
"golang.org/x/build/maintner"
1819
)
@@ -178,16 +179,65 @@ func TestLabelMutations(t *testing.T) {
178179
}
179180
}
180181

181-
func TestAddLabels(t *testing.T) {
182-
oldFunc := addLabelsToIssue
183-
defer func() { addLabelsToIssue = oldFunc }()
182+
type fakeIssuesService struct {
183+
labels map[int][]string
184+
}
184185

185-
var added []string
186-
addLabelsToIssue = func(_ context.Context, _ *github.IssuesService, _ int, labels []string) error {
187-
added = labels
188-
return nil
186+
func (f *fakeIssuesService) ListLabelsByIssue(ctx context.Context, owner string, repo string, number int, opt *github.ListOptions) ([]*github.Label, *github.Response, error) {
187+
var labels []*github.Label
188+
if ls, ok := f.labels[number]; ok {
189+
for _, l := range ls {
190+
name := l
191+
labels = append(labels, &github.Label{Name: &name})
192+
}
189193
}
194+
return labels, nil, nil
195+
}
190196

197+
func (f *fakeIssuesService) AddLabelsToIssue(ctx context.Context, owner string, repo string, number int, labels []string) ([]*github.Label, *github.Response, error) {
198+
if f.labels == nil {
199+
f.labels = map[int][]string{number: labels}
200+
return nil, nil, nil
201+
}
202+
ls, ok := f.labels[number]
203+
if !ok {
204+
f.labels[number] = labels
205+
return nil, nil, nil
206+
}
207+
for _, label := range labels {
208+
var found bool
209+
for _, l := range ls {
210+
if l == label {
211+
found = true
212+
}
213+
}
214+
if found {
215+
continue
216+
}
217+
f.labels[number] = append(f.labels[number], label)
218+
}
219+
return nil, nil, nil
220+
}
221+
222+
func (f *fakeIssuesService) RemoveLabelForIssue(ctx context.Context, owner string, repo string, number int, label string) (*github.Response, error) {
223+
if ls, ok := f.labels[number]; ok {
224+
for i, l := range ls {
225+
if l == label {
226+
f.labels[number] = append(f.labels[number][:i], f.labels[number][i+1:]...)
227+
return nil, nil
228+
}
229+
}
230+
}
231+
// The GitHub API returns a NotFound error if the label did not exist.
232+
return nil, &github.ErrorResponse{
233+
Response: &http.Response{
234+
Status: http.StatusText(http.StatusNotFound),
235+
StatusCode: http.StatusNotFound,
236+
},
237+
}
238+
}
239+
240+
func TestAddLabels(t *testing.T) {
191241
testCases := []struct {
192242
desc string
193243
gi *maintner.GitHubIssue
@@ -222,60 +272,49 @@ func TestAddLabels(t *testing.T) {
222272
},
223273
}
224274

225-
b := &gopherbot{ghc: github.NewClient(http.DefaultClient)}
275+
b := &gopherbot{}
226276
for _, tc := range testCases {
227-
// Clear any previous state from stubbed addLabelsToIssue function above
228-
// since some test cases may skip calls to it.
229-
added = nil
277+
// Clear any previous state from fake addLabelsToIssue since some test cases may skip calls to it.
278+
fis := &fakeIssuesService{}
279+
b.is = fis
230280

231281
if err := b.addLabels(context.Background(), tc.gi, tc.labels); err != nil {
232282
t.Errorf("%s: b.addLabels got unexpected error: %v", tc.desc, err)
233283
continue
234284
}
235-
if diff := cmp.Diff(added, tc.added); diff != "" {
285+
if diff := cmp.Diff(fis.labels[int(tc.gi.ID)], tc.added); diff != "" {
236286
t.Errorf("%s: labels added differ: (-got, +want)\n%s", tc.desc, diff)
237287
}
238288
}
239289
}
240290

241291
func TestRemoveLabels(t *testing.T) {
242-
oldLabelsForIssue := labelsForIssue
243-
defer func() { labelsForIssue = oldLabelsForIssue }()
244-
245-
labelsForIssue = func(_ context.Context, _ *github.IssuesService, _ int) ([]string, error) {
246-
return []string{"help wanted", "NeedsFix"}, nil
247-
}
248-
249-
oldRemoveLabelFromIssue := removeLabelFromIssue
250-
defer func() { removeLabelFromIssue = oldRemoveLabelFromIssue }()
251-
252-
var removed []string
253-
removeLabelFromIssue = func(_ context.Context, _ *github.IssuesService, _ int, label string) error {
254-
removed = append(removed, label)
255-
return nil
256-
}
257-
258292
testCases := []struct {
259293
desc string
260294
gi *maintner.GitHubIssue
295+
ghLabels []string
261296
toRemove []string
262-
removed []string
297+
want []string
263298
}{
264299
{
265300
"basic remove",
266301
&maintner.GitHubIssue{
302+
Number: 123,
267303
Labels: map[int64]*maintner.GitHubLabel{
268304
0: {Name: "NeedsFix"},
305+
1: {Name: "help wanted"},
269306
},
270307
},
308+
[]string{"NeedsFix", "help wanted"},
271309
[]string{"NeedsFix"},
272-
[]string{"NeedsFix"},
310+
[]string{"help wanted"},
273311
},
274312
{
275313
"label not present in maintner",
276314
&maintner.GitHubIssue{},
277315
[]string{"NeedsFix"},
278-
nil,
316+
[]string{"NeedsFix"},
317+
[]string{"NeedsFix"},
279318
},
280319
{
281320
"label not present in GitHub",
@@ -284,23 +323,26 @@ func TestRemoveLabels(t *testing.T) {
284323
0: {Name: "foo"},
285324
},
286325
},
326+
[]string{"NeedsFix"},
287327
[]string{"foo"},
288-
nil,
328+
[]string{"NeedsFix"},
289329
},
290330
}
291331

292-
b := &gopherbot{ghc: github.NewClient(http.DefaultClient)}
332+
b := &gopherbot{}
293333
for _, tc := range testCases {
294-
// Clear any previous state from stubbed removeLabelFromIssue function above
295-
// since some test cases may skip calls to it.
296-
removed = nil
334+
// Clear any previous state from fakeIssuesService since some test cases may skip calls to it.
335+
fis := &fakeIssuesService{map[int][]string{
336+
int(tc.gi.Number): tc.ghLabels,
337+
}}
338+
b.is = fis
297339

298340
if err := b.removeLabels(context.Background(), tc.gi, tc.toRemove); err != nil {
299341
t.Errorf("%s: b.addLabels got unexpected error: %v", tc.desc, err)
300342
continue
301343
}
302-
if diff := cmp.Diff(removed, tc.removed); diff != "" {
303-
t.Errorf("%s: labels removed differ: (-got, +want)\n%s", tc.desc, diff)
344+
if diff := cmp.Diff(fis.labels[int(tc.gi.Number)], tc.want); diff != "" {
345+
t.Errorf("%s: labels differ: (-got, +want)\n%s", tc.desc, diff)
304346
}
305347
}
306348
}

0 commit comments

Comments
 (0)