golang.org/x/build@v0.0.0-20240506185731-218518f32b70/maintner/maintner_test.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package maintner
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"reflect"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/davecgh/go-spew/spew"
    17  	"github.com/golang/protobuf/ptypes"
    18  	google_protobuf "github.com/golang/protobuf/ptypes/timestamp"
    19  	"github.com/google/go-github/github"
    20  	"golang.org/x/build/maintner/maintpb"
    21  )
    22  
    23  var u1 = &GitHubUser{
    24  	Login: "gopherbot",
    25  	ID:    100,
    26  }
    27  var u2 = &GitHubUser{
    28  	Login: "kevinburke",
    29  	ID:    101,
    30  }
    31  
    32  type dummyMutationLogger struct {
    33  	Mutations []*maintpb.Mutation
    34  }
    35  
    36  func (d *dummyMutationLogger) Log(m *maintpb.Mutation) error {
    37  	if d.Mutations == nil {
    38  		d.Mutations = []*maintpb.Mutation{}
    39  	}
    40  	d.Mutations = append(d.Mutations, m)
    41  	return nil
    42  }
    43  
    44  type mutationTest struct {
    45  	corpus *Corpus
    46  	want   *Corpus
    47  }
    48  
    49  func (mt mutationTest) test(t *testing.T, muts ...*maintpb.Mutation) {
    50  	c := mt.corpus
    51  	if c == nil {
    52  		c = new(Corpus)
    53  	}
    54  	for _, m := range muts {
    55  		c.processMutationLocked(m)
    56  	}
    57  	c.github.c = nil
    58  	mt.want.github.c = nil
    59  	if !reflect.DeepEqual(c.github, mt.want.github) {
    60  		t.Errorf("corpus mismatch:\n got: %s\n\nwant: %s\n\ndiff: %v",
    61  			spew.Sdump(c.github),
    62  			spew.Sdump(mt.want.github),
    63  			diffPath(reflect.ValueOf(c.github), reflect.ValueOf(mt.want.github)))
    64  	}
    65  }
    66  
    67  var t1, t2 time.Time
    68  var tp1, tp2 *google_protobuf.Timestamp
    69  
    70  func init() {
    71  	t1, _ = time.Parse(time.RFC3339, "2016-01-02T15:04:00Z")
    72  	t2, _ = time.Parse(time.RFC3339, "2016-01-02T15:30:00Z")
    73  	tp1, _ = ptypes.TimestampProto(t1)
    74  	tp2, _ = ptypes.TimestampProto(t2)
    75  }
    76  
    77  func singleIssueGitHubCorpus() *Corpus {
    78  	c := new(Corpus)
    79  	github := &GitHub{c: c}
    80  	c.github = github
    81  	github.users = map[int64]*GitHubUser{
    82  		u1.ID: u1,
    83  	}
    84  	github.repos = map[GitHubRepoID]*GitHubRepo{
    85  		GitHubRepoID{"golang", "go"}: &GitHubRepo{
    86  			github: github,
    87  			id:     GitHubRepoID{"golang", "go"},
    88  			issues: map[int32]*GitHubIssue{
    89  				3: &GitHubIssue{
    90  					Number:    3,
    91  					User:      u1,
    92  					Title:     "some title",
    93  					Body:      "some body",
    94  					Created:   t1,
    95  					Assignees: nil,
    96  				},
    97  			},
    98  		},
    99  	}
   100  	return c
   101  }
   102  
   103  func TestProcessMutation_Github_NewIssue(t *testing.T) {
   104  	mutationTest{want: singleIssueGitHubCorpus()}.test(t, &maintpb.Mutation{
   105  		GithubIssue: &maintpb.GithubIssueMutation{
   106  			Owner:  "golang",
   107  			Repo:   "go",
   108  			Number: 3,
   109  			User: &maintpb.GithubUser{
   110  				Login: "gopherbot",
   111  				Id:    100,
   112  			},
   113  			Title:   "some title",
   114  			Body:    "some body",
   115  			Created: tp1,
   116  		},
   117  	})
   118  }
   119  
   120  func TestProcessMutation_Github_RemoveBody(t *testing.T) {
   121  	c := singleIssueGitHubCorpus()
   122  	want := singleIssueGitHubCorpus()
   123  	want.github.repos[GitHubRepoID{"golang", "go"}].issues[3].Body = ""
   124  	mutationTest{corpus: c, want: want}.test(t, &maintpb.Mutation{
   125  		GithubIssue: &maintpb.GithubIssueMutation{
   126  			Owner:      "golang",
   127  			Repo:       "go",
   128  			Number:     3,
   129  			BodyChange: &maintpb.StringChange{Val: ""},
   130  		},
   131  	})
   132  
   133  	// And test that the old mutation field (Body) still works.
   134  	want.github.repos[GitHubRepoID{"golang", "go"}].issues[3].Body = "and back"
   135  	mutationTest{corpus: c, want: want}.test(t, &maintpb.Mutation{
   136  		GithubIssue: &maintpb.GithubIssueMutation{
   137  			Owner:  "golang",
   138  			Repo:   "go",
   139  			Number: 3,
   140  			Body:   "and back",
   141  		},
   142  	})
   143  }
   144  
   145  func TestProcessMutation_Github(t *testing.T) {
   146  	c := new(Corpus)
   147  	github := &GitHub{c: c}
   148  	c.github = github
   149  	github.repos = map[GitHubRepoID]*GitHubRepo{
   150  		GitHubRepoID{"golang", "go"}: &GitHubRepo{
   151  			github: github,
   152  			id:     GitHubRepoID{"golang", "go"},
   153  			issues: make(map[int32]*GitHubIssue),
   154  		},
   155  	}
   156  	mutationTest{want: c}.test(t, &maintpb.Mutation{
   157  		Github: &maintpb.GithubMutation{
   158  			Owner: "golang",
   159  			Repo:  "go",
   160  		},
   161  	})
   162  }
   163  
   164  func TestNewMutationsFromIssue(t *testing.T) {
   165  	gh := &github.Issue{
   166  		Number:    github.Int(5),
   167  		CreatedAt: &t1,
   168  		UpdatedAt: &t2,
   169  		Body:      github.String("body of the issue"),
   170  		State:     github.String("closed"),
   171  	}
   172  	gr := &GitHubRepo{
   173  		id: GitHubRepoID{"golang", "go"},
   174  	}
   175  	is := gr.newMutationFromIssue(nil, gh)
   176  	want := &maintpb.Mutation{GithubIssue: &maintpb.GithubIssueMutation{
   177  		Owner:       "golang",
   178  		Repo:        "go",
   179  		Number:      5,
   180  		BodyChange:  &maintpb.StringChange{Val: "body of the issue"},
   181  		Created:     tp1,
   182  		Updated:     tp2,
   183  		Assignees:   []*maintpb.GithubUser{},
   184  		NoMilestone: true,
   185  		Closed:      &maintpb.BoolChange{Val: true},
   186  	}}
   187  	if !reflect.DeepEqual(is, want) {
   188  		t.Errorf("issue mismatch\n got: %v\nwant: %v\ndiff path: %v", spew.Sdump(is), spew.Sdump(want),
   189  			diffPath(reflect.ValueOf(is), reflect.ValueOf(want)))
   190  	}
   191  }
   192  
   193  func TestNewAssigneesHandlesNil(t *testing.T) {
   194  	users := []*github.User{
   195  		&github.User{Login: github.String("foo"), ID: github.Int64(3)},
   196  	}
   197  	got := newAssignees(nil, users)
   198  	want := []*maintpb.GithubUser{&maintpb.GithubUser{
   199  		Id:    3,
   200  		Login: "foo",
   201  	}}
   202  	if !reflect.DeepEqual(got, want) {
   203  		t.Errorf("assignee mismatch\n got: %#v\nwant: %#v", got, want)
   204  	}
   205  }
   206  
   207  func TestAssigneesDeleted(t *testing.T) {
   208  	c := new(Corpus)
   209  	assignees := []*GitHubUser{u1, u2}
   210  	issue := &GitHubIssue{
   211  		Number:    3,
   212  		User:      u1,
   213  		Body:      "some body",
   214  		Created:   t2,
   215  		Updated:   t2,
   216  		Assignees: assignees,
   217  	}
   218  	gr := &GitHubRepo{
   219  		id: GitHubRepoID{"golang", "go"},
   220  		issues: map[int32]*GitHubIssue{
   221  			3: issue,
   222  		},
   223  	}
   224  	c.github = &GitHub{
   225  		users: map[int64]*GitHubUser{
   226  			u1.ID: u1,
   227  		},
   228  		repos: map[GitHubRepoID]*GitHubRepo{
   229  			GitHubRepoID{"golang", "go"}: gr,
   230  		},
   231  	}
   232  
   233  	mutation := gr.newMutationFromIssue(issue, &github.Issue{
   234  		Number:    github.Int(3),
   235  		Assignees: []*github.User{&github.User{ID: github.Int64(u2.ID)}},
   236  	})
   237  	c.addMutation(mutation)
   238  	gi := gr.issues[3]
   239  	if len(gi.Assignees) != 1 || gi.Assignees[0].ID != u2.ID {
   240  		t.Errorf("expected u1 to be deleted, got %v", gi.Assignees)
   241  	}
   242  }
   243  
   244  func TestSync(t *testing.T) {
   245  	c := new(Corpus)
   246  	assignees := []*GitHubUser{u1, u2}
   247  	issue := &GitHubIssue{
   248  		Number:    3,
   249  		User:      u1,
   250  		Body:      "some body",
   251  		Created:   t2,
   252  		Updated:   t2,
   253  		Assignees: assignees,
   254  	}
   255  	gr := &GitHubRepo{
   256  		id: GitHubRepoID{"golang", "go"},
   257  		issues: map[int32]*GitHubIssue{
   258  			3: issue,
   259  		},
   260  	}
   261  	c.github = &GitHub{
   262  		users: map[int64]*GitHubUser{
   263  			u1.ID: u1,
   264  		},
   265  		repos: map[GitHubRepoID]*GitHubRepo{
   266  			GitHubRepoID{"golang", "go"}: gr,
   267  		},
   268  	}
   269  
   270  	mutation := gr.newMutationFromIssue(issue, &github.Issue{
   271  		Number:    github.Int(3),
   272  		Assignees: []*github.User{&github.User{ID: github.Int64(u2.ID)}},
   273  	})
   274  	c.addMutation(mutation)
   275  	ctx := context.Background()
   276  	err := c.sync(ctx, false)
   277  	if err != nil {
   278  		t.Fatal("error: ", err)
   279  	}
   280  }
   281  
   282  func DeepDiff(got, want interface{}) error {
   283  	return diffPath(reflect.ValueOf(got), reflect.ValueOf(want))
   284  }
   285  
   286  func diffPath(got, want reflect.Value) error {
   287  	if !got.IsValid() {
   288  		return errors.New("'got' value invalid")
   289  	}
   290  	if !want.IsValid() {
   291  		return errors.New("'want' value invalid")
   292  	}
   293  
   294  	t := got.Type()
   295  	if t != want.Type() {
   296  		return fmt.Errorf("got=%s, want=%s", got.Type(), want.Type())
   297  	}
   298  
   299  	switch t.Kind() {
   300  	case reflect.Ptr, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice:
   301  		if got.IsNil() != want.IsNil() {
   302  			if got.IsNil() {
   303  				return fmt.Errorf("got = (%s)(nil), want = non-nil", t)
   304  			}
   305  			return fmt.Errorf("got = (%s)(non-nil), want = nil", t)
   306  		}
   307  	}
   308  
   309  	switch t.Kind() {
   310  	case reflect.Ptr:
   311  		if got.IsNil() {
   312  			return nil
   313  		}
   314  		return diffPath(got.Elem(), want.Elem())
   315  
   316  	case reflect.Struct:
   317  		nf := t.NumField()
   318  		for i := 0; i < nf; i++ {
   319  			sf := t.Field(i)
   320  			if err := diffPath(got.Field(i), want.Field(i)); err != nil {
   321  				inner := err.Error()
   322  				sep := "."
   323  				if strings.HasPrefix(inner, "field ") {
   324  					inner = strings.TrimPrefix(inner, "field ")
   325  				} else {
   326  					sep = ": "
   327  				}
   328  				return fmt.Errorf("field %s%s%v", sf.Name, sep, inner)
   329  			}
   330  		}
   331  		return nil
   332  	case reflect.String:
   333  		if got.String() != want.String() {
   334  			return fmt.Errorf("got = %q; want = %q", got.String(), want.String())
   335  		}
   336  		return nil
   337  
   338  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   339  		if got.Int() != want.Int() {
   340  			return fmt.Errorf("got = %v; want = %v", got.Int(), want.Int())
   341  		}
   342  		return nil
   343  
   344  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   345  		if got.Uint() != want.Uint() {
   346  			return fmt.Errorf("got = %v; want = %v", got.Uint(), want.Uint())
   347  		}
   348  		return nil
   349  
   350  	case reflect.Bool:
   351  		if got.Bool() != want.Bool() {
   352  			return fmt.Errorf("got = %v; want = %v", got.Bool(), want.Bool())
   353  		}
   354  		return nil
   355  
   356  	case reflect.Slice:
   357  		gl, wl := got.Len(), want.Len()
   358  		if gl != wl {
   359  			return fmt.Errorf("slice len %v; want %v", gl, wl)
   360  		}
   361  		for i := 0; i < gl; i++ {
   362  			if err := diffPath(got.Index(i), want.Index(i)); err != nil {
   363  				return fmt.Errorf("index[%d] differs: %v", i, err)
   364  			}
   365  		}
   366  		return nil
   367  
   368  	default:
   369  		return fmt.Errorf("unhandled kind %v", t.Kind())
   370  	}
   371  }