blob: f2dbddcff4d8ea3dea77f4cd0cf4bd96c49d8ae3 [file] [log] [blame]
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package maintner
import (
"context"
"errors"
"fmt"
"reflect"
"strings"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/golang/protobuf/ptypes"
google_protobuf "github.com/golang/protobuf/ptypes/timestamp"
"github.com/google/go-github/v48/github"
"golang.org/x/build/maintner/maintpb"
)
var u1 = &GitHubUser{
Login: "gopherbot",
ID: 100,
}
var u2 = &GitHubUser{
Login: "kevinburke",
ID: 101,
}
type dummyMutationLogger struct {
Mutations []*maintpb.Mutation
}
func (d *dummyMutationLogger) Log(m *maintpb.Mutation) error {
if d.Mutations == nil {
d.Mutations = []*maintpb.Mutation{}
}
d.Mutations = append(d.Mutations, m)
return nil
}
type mutationTest struct {
corpus *Corpus
want *Corpus
}
func (mt mutationTest) test(t *testing.T, muts ...*maintpb.Mutation) {
c := mt.corpus
if c == nil {
c = new(Corpus)
}
for _, m := range muts {
c.processMutationLocked(m)
}
c.github.c = nil
mt.want.github.c = nil
if !reflect.DeepEqual(c.github, mt.want.github) {
t.Errorf("corpus mismatch:\n got: %s\n\nwant: %s\n\ndiff: %v",
spew.Sdump(c.github),
spew.Sdump(mt.want.github),
diffPath(reflect.ValueOf(c.github), reflect.ValueOf(mt.want.github)))
}
}
var t1, t2 time.Time
var tp1, tp2 *google_protobuf.Timestamp
func init() {
t1, _ = time.Parse(time.RFC3339, "2016-01-02T15:04:00Z")
t2, _ = time.Parse(time.RFC3339, "2016-01-02T15:30:00Z")
tp1, _ = ptypes.TimestampProto(t1)
tp2, _ = ptypes.TimestampProto(t2)
}
func singleIssueGitHubCorpus() *Corpus {
c := new(Corpus)
github := &GitHub{c: c}
c.github = github
github.users = map[int64]*GitHubUser{
u1.ID: u1,
}
github.repos = map[GitHubRepoID]*GitHubRepo{
GitHubRepoID{"golang", "go"}: &GitHubRepo{
github: github,
id: GitHubRepoID{"golang", "go"},
issues: map[int32]*GitHubIssue{
3: &GitHubIssue{
Number: 3,
User: u1,
Title: "some title",
Body: "some body",
Created: t1,
Assignees: nil,
},
},
},
}
return c
}
func TestProcessMutation_Github_NewIssue(t *testing.T) {
mutationTest{want: singleIssueGitHubCorpus()}.test(t, &maintpb.Mutation{
GithubIssue: &maintpb.GithubIssueMutation{
Owner: "golang",
Repo: "go",
Number: 3,
User: &maintpb.GithubUser{
Login: "gopherbot",
Id: 100,
},
Title: "some title",
Body: "some body",
Created: tp1,
},
})
}
func TestProcessMutation_Github_RemoveBody(t *testing.T) {
c := singleIssueGitHubCorpus()
want := singleIssueGitHubCorpus()
want.github.repos[GitHubRepoID{"golang", "go"}].issues[3].Body = ""
mutationTest{corpus: c, want: want}.test(t, &maintpb.Mutation{
GithubIssue: &maintpb.GithubIssueMutation{
Owner: "golang",
Repo: "go",
Number: 3,
BodyChange: &maintpb.StringChange{Val: ""},
},
})
// And test that the old mutation field (Body) still works.
want.github.repos[GitHubRepoID{"golang", "go"}].issues[3].Body = "and back"
mutationTest{corpus: c, want: want}.test(t, &maintpb.Mutation{
GithubIssue: &maintpb.GithubIssueMutation{
Owner: "golang",
Repo: "go",
Number: 3,
Body: "and back",
},
})
}
func TestProcessMutation_Github(t *testing.T) {
c := new(Corpus)
github := &GitHub{c: c}
c.github = github
github.repos = map[GitHubRepoID]*GitHubRepo{
GitHubRepoID{"golang", "go"}: &GitHubRepo{
github: github,
id: GitHubRepoID{"golang", "go"},
issues: make(map[int32]*GitHubIssue),
},
}
mutationTest{want: c}.test(t, &maintpb.Mutation{
Github: &maintpb.GithubMutation{
Owner: "golang",
Repo: "go",
},
})
}
func TestNewMutationsFromIssue(t *testing.T) {
gh := &github.Issue{
Number: github.Int(5),
CreatedAt: &t1,
UpdatedAt: &t2,
Body: github.String("body of the issue"),
State: github.String("closed"),
}
gr := &GitHubRepo{
id: GitHubRepoID{"golang", "go"},
}
is := gr.newMutationFromIssue(nil, gh)
want := &maintpb.Mutation{GithubIssue: &maintpb.GithubIssueMutation{
Owner: "golang",
Repo: "go",
Number: 5,
BodyChange: &maintpb.StringChange{Val: "body of the issue"},
Created: tp1,
Updated: tp2,
Assignees: []*maintpb.GithubUser{},
NoMilestone: true,
Closed: &maintpb.BoolChange{Val: true},
}}
if !reflect.DeepEqual(is, want) {
t.Errorf("issue mismatch\n got: %v\nwant: %v\ndiff path: %v", spew.Sdump(is), spew.Sdump(want),
diffPath(reflect.ValueOf(is), reflect.ValueOf(want)))
}
}
func TestNewAssigneesHandlesNil(t *testing.T) {
users := []*github.User{
&github.User{Login: github.String("foo"), ID: github.Int64(3)},
}
got := newAssignees(nil, users)
want := []*maintpb.GithubUser{&maintpb.GithubUser{
Id: 3,
Login: "foo",
}}
if !reflect.DeepEqual(got, want) {
t.Errorf("assignee mismatch\n got: %#v\nwant: %#v", got, want)
}
}
func TestAssigneesDeleted(t *testing.T) {
c := new(Corpus)
assignees := []*GitHubUser{u1, u2}
issue := &GitHubIssue{
Number: 3,
User: u1,
Body: "some body",
Created: t2,
Updated: t2,
Assignees: assignees,
}
gr := &GitHubRepo{
id: GitHubRepoID{"golang", "go"},
issues: map[int32]*GitHubIssue{
3: issue,
},
}
c.github = &GitHub{
users: map[int64]*GitHubUser{
u1.ID: u1,
},
repos: map[GitHubRepoID]*GitHubRepo{
GitHubRepoID{"golang", "go"}: gr,
},
}
mutation := gr.newMutationFromIssue(issue, &github.Issue{
Number: github.Int(3),
Assignees: []*github.User{&github.User{ID: github.Int64(u2.ID)}},
})
c.addMutation(mutation)
gi := gr.issues[3]
if len(gi.Assignees) != 1 || gi.Assignees[0].ID != u2.ID {
t.Errorf("expected u1 to be deleted, got %v", gi.Assignees)
}
}
func TestSync(t *testing.T) {
c := new(Corpus)
assignees := []*GitHubUser{u1, u2}
issue := &GitHubIssue{
Number: 3,
User: u1,
Body: "some body",
Created: t2,
Updated: t2,
Assignees: assignees,
}
gr := &GitHubRepo{
id: GitHubRepoID{"golang", "go"},
issues: map[int32]*GitHubIssue{
3: issue,
},
}
c.github = &GitHub{
users: map[int64]*GitHubUser{
u1.ID: u1,
},
repos: map[GitHubRepoID]*GitHubRepo{
GitHubRepoID{"golang", "go"}: gr,
},
}
mutation := gr.newMutationFromIssue(issue, &github.Issue{
Number: github.Int(3),
Assignees: []*github.User{&github.User{ID: github.Int64(u2.ID)}},
})
c.addMutation(mutation)
ctx := context.Background()
err := c.sync(ctx, false)
if err != nil {
t.Fatal("error: ", err)
}
}
func DeepDiff(got, want interface{}) error {
return diffPath(reflect.ValueOf(got), reflect.ValueOf(want))
}
func diffPath(got, want reflect.Value) error {
if !got.IsValid() {
return errors.New("'got' value invalid")
}
if !want.IsValid() {
return errors.New("'want' value invalid")
}
t := got.Type()
if t != want.Type() {
return fmt.Errorf("got=%s, want=%s", got.Type(), want.Type())
}
switch t.Kind() {
case reflect.Ptr, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice:
if got.IsNil() != want.IsNil() {
if got.IsNil() {
return fmt.Errorf("got = (%s)(nil), want = non-nil", t)
}
return fmt.Errorf("got = (%s)(non-nil), want = nil", t)
}
}
switch t.Kind() {
case reflect.Ptr:
if got.IsNil() {
return nil
}
return diffPath(got.Elem(), want.Elem())
case reflect.Struct:
nf := t.NumField()
for i := 0; i < nf; i++ {
sf := t.Field(i)
if err := diffPath(got.Field(i), want.Field(i)); err != nil {
inner := err.Error()
sep := "."
if strings.HasPrefix(inner, "field ") {
inner = strings.TrimPrefix(inner, "field ")
} else {
sep = ": "
}
return fmt.Errorf("field %s%s%v", sf.Name, sep, inner)
}
}
return nil
case reflect.String:
if got.String() != want.String() {
return fmt.Errorf("got = %q; want = %q", got.String(), want.String())
}
return nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if got.Int() != want.Int() {
return fmt.Errorf("got = %v; want = %v", got.Int(), want.Int())
}
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if got.Uint() != want.Uint() {
return fmt.Errorf("got = %v; want = %v", got.Uint(), want.Uint())
}
return nil
case reflect.Bool:
if got.Bool() != want.Bool() {
return fmt.Errorf("got = %v; want = %v", got.Bool(), want.Bool())
}
return nil
case reflect.Slice:
gl, wl := got.Len(), want.Len()
if gl != wl {
return fmt.Errorf("slice len %v; want %v", gl, wl)
}
for i := 0; i < gl; i++ {
if err := diffPath(got.Index(i), want.Index(i)); err != nil {
return fmt.Errorf("index[%d] differs: %v", i, err)
}
}
return nil
default:
return fmt.Errorf("unhandled kind %v", t.Kind())
}
}