github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/gerrit/client/client_test.go (about)

     1  /*
     2  Copyright 2018 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 client
    18  
    19  import (
    20  	"context"
    21  	"path/filepath"
    22  	"reflect"
    23  	"strings"
    24  	"sync"
    25  	"testing"
    26  	"time"
    27  
    28  	gerrit "github.com/andygrunwald/go-gerrit"
    29  	"github.com/google/go-cmp/cmp"
    30  	"github.com/sirupsen/logrus"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  	"sigs.k8s.io/prow/pkg/config"
    33  	"sigs.k8s.io/prow/pkg/io"
    34  )
    35  
    36  type fgc struct {
    37  	instance string
    38  	changes  map[string][]gerrit.ChangeInfo
    39  	comments map[string]map[string][]gerrit.CommentInfo
    40  }
    41  
    42  func (f *fgc) GetRelatedChanges(changeID string, revisionID string) (*gerrit.RelatedChangesInfo, *gerrit.Response, error) {
    43  	return &gerrit.RelatedChangesInfo{}, nil, nil
    44  }
    45  
    46  func (f *fgc) ListChangeComments(id string) (*map[string][]gerrit.CommentInfo, *gerrit.Response, error) {
    47  	comments := map[string][]gerrit.CommentInfo{}
    48  
    49  	val, ok := f.comments[id]
    50  	if !ok {
    51  		return &comments, nil, nil
    52  	}
    53  
    54  	for path, retComments := range val {
    55  		comments[path] = append(comments[path], retComments...)
    56  	}
    57  
    58  	return &comments, nil, nil
    59  }
    60  
    61  func (f *fgc) SubmitChange(changeID string, input *gerrit.SubmitInput) (*ChangeInfo, *gerrit.Response, error) {
    62  	return nil, nil, nil
    63  }
    64  
    65  func TestApplyGlobalConfigOnce(t *testing.T) {
    66  	dir := t.TempDir()
    67  	path := filepath.Join(dir, "value.txt")
    68  	// Empty opener so *syncTime won't panic.
    69  	opener, err := io.NewOpener(context.Background(), "", "")
    70  	if err != nil {
    71  		t.Fatalf("Failed to create opener: %v", err)
    72  	}
    73  
    74  	// Fixed org/repo, as this test doesn't check the output.
    75  	cfg := config.Config{
    76  		ProwConfig: config.ProwConfig{
    77  			Gerrit: config.Gerrit{
    78  				OrgReposConfig: &config.GerritOrgRepoConfigs{
    79  					{
    80  						Org:   "foo1",
    81  						Repos: []string{"bar1"},
    82  					},
    83  				},
    84  			},
    85  		},
    86  	}
    87  
    88  	// A thread safe map for checking additionalFunc.
    89  	var mux sync.RWMutex
    90  	records := make(map[string]string)
    91  	setRecond := func(key, val string) {
    92  		mux.Lock()
    93  		defer mux.Unlock()
    94  		records[key] = val
    95  	}
    96  	getRecord := func(key string) string {
    97  		mux.RLock()
    98  		defer mux.RUnlock()
    99  		return records[key]
   100  	}
   101  
   102  	tests := []struct {
   103  		name                string
   104  		orgRepoConfigGetter func() *config.GerritOrgRepoConfigs
   105  		lastSyncTracker     *SyncTime
   106  		additionalFunc      func()
   107  		expect              func(t *testing.T)
   108  	}{
   109  		{
   110  			name: "base",
   111  			orgRepoConfigGetter: func() *config.GerritOrgRepoConfigs {
   112  				return cfg.Gerrit.OrgReposConfig
   113  			},
   114  			lastSyncTracker: NewSyncTime(path, opener, context.Background()),
   115  			additionalFunc: func() {
   116  				setRecond("base", "base")
   117  			},
   118  			expect: func(t *testing.T) {
   119  				if got, want := getRecord("base"), "base"; got != want {
   120  					t.Fatalf("Output mismatch. Want: %s, got: %s", want, got)
   121  				}
   122  			},
   123  		},
   124  		{
   125  			name: "nil-lastsynctracker",
   126  			orgRepoConfigGetter: func() *config.GerritOrgRepoConfigs {
   127  				return cfg.Gerrit.OrgReposConfig
   128  			},
   129  			additionalFunc: func() {
   130  				setRecond("nil-lastsynctracker", "nil-lastsynctracker")
   131  			},
   132  			expect: func(t *testing.T) {
   133  				if got, want := getRecord("nil-lastsynctracker"), "nil-lastsynctracker"; got != want {
   134  					t.Fatalf("Output mismatch. Want: %s, got: %s", want, got)
   135  				}
   136  			},
   137  		},
   138  		{
   139  			name: "empty-addtionalfunc",
   140  			orgRepoConfigGetter: func() *config.GerritOrgRepoConfigs {
   141  				return cfg.Gerrit.OrgReposConfig
   142  			},
   143  			additionalFunc: func() {},
   144  			expect: func(t *testing.T) {
   145  				// additionalFunc is nil, there is nothing expected
   146  			},
   147  		},
   148  		{
   149  			name: "nil-addtionalfunc",
   150  			orgRepoConfigGetter: func() *config.GerritOrgRepoConfigs {
   151  				return cfg.Gerrit.OrgReposConfig
   152  			},
   153  			additionalFunc: nil,
   154  			expect: func(t *testing.T) {
   155  				// additionalFunc is nil, there is nothing expected
   156  			},
   157  		},
   158  	}
   159  
   160  	for _, tc := range tests {
   161  		tc := tc
   162  		t.Run(tc.name, func(t *testing.T) {
   163  			fc := &Client{}
   164  			fc.applyGlobalConfigOnce(tc.orgRepoConfigGetter, tc.lastSyncTracker, "", "", tc.additionalFunc)
   165  			if tc.expect != nil {
   166  				tc.expect(t)
   167  			}
   168  		})
   169  	}
   170  }
   171  
   172  func TestQueryStringsFromQueryFilter(t *testing.T) {
   173  	t.Parallel()
   174  	tests := []struct {
   175  		name     string
   176  		filters  *config.GerritQueryFilter
   177  		expected []string
   178  	}{
   179  		{
   180  			name: "nil",
   181  		},
   182  		{
   183  			name: "single-branch",
   184  			filters: &config.GerritQueryFilter{
   185  				Branches: []string{"foo"},
   186  			},
   187  			expected: []string{"(branch:foo)"},
   188  		},
   189  		{
   190  			name: "multiple-branches",
   191  			filters: &config.GerritQueryFilter{
   192  				Branches: []string{"foo1", "foo2", "foo3"},
   193  			},
   194  			expected: []string{"(branch:foo1+OR+branch:foo2+OR+branch:foo3)"},
   195  		},
   196  		{
   197  			name: "branches-and-excluded",
   198  			filters: &config.GerritQueryFilter{
   199  				Branches:         []string{"foo1", "foo2", "foo3"},
   200  				ExcludedBranches: []string{"bar1", "bar2", "bar3"},
   201  			},
   202  			expected: []string{
   203  				"(branch:foo1+OR+branch:foo2+OR+branch:foo3)",
   204  				"(-branch:bar1+AND+-branch:bar2+AND+-branch:bar3)",
   205  			},
   206  		},
   207  	}
   208  
   209  	for _, tc := range tests {
   210  		tc := tc
   211  		t.Run(tc.name, func(t *testing.T) {
   212  			t.Parallel()
   213  			if diff := cmp.Diff(tc.expected, queryStringsFromQueryFilter(tc.filters)); diff != "" {
   214  				t.Fatalf("Output mismatch. Want(-), got(+):\n%s", diff)
   215  			}
   216  		})
   217  	}
   218  }
   219  
   220  func (f *fgc) QueryChanges(opt *gerrit.QueryChangeOptions) (*[]gerrit.ChangeInfo, *gerrit.Response, error) {
   221  	changes := []gerrit.ChangeInfo{}
   222  
   223  	changeInfos, ok := f.changes[f.instance]
   224  	if !ok {
   225  		return &changes, nil, nil
   226  	}
   227  
   228  	project := ""
   229  	for _, query := range opt.Query {
   230  		for _, q := range strings.Split(query, "+") {
   231  			if strings.HasPrefix(q, "project:") {
   232  				project = q[8:]
   233  			}
   234  		}
   235  	}
   236  
   237  	for idx, change := range changeInfos {
   238  		if idx >= opt.Start && len(changes) <= opt.Limit {
   239  			if project == change.Project {
   240  				changes = append(changes, change)
   241  			}
   242  		}
   243  	}
   244  
   245  	return &changes, nil, nil
   246  }
   247  
   248  func (f *fgc) SetReview(changeID, revisionID string, input *gerrit.ReviewInput) (*gerrit.ReviewResult, *gerrit.Response, error) {
   249  	return nil, nil, nil
   250  }
   251  
   252  func (f *fgc) GetChange(changeId string, opt *gerrit.ChangeOptions) (*ChangeInfo, *gerrit.Response, error) {
   253  	return nil, nil, nil
   254  }
   255  
   256  func makeStamp(t time.Time) gerrit.Timestamp {
   257  	return gerrit.Timestamp{Time: t}
   258  }
   259  
   260  func newStamp(t time.Time) *gerrit.Timestamp {
   261  	gt := makeStamp(t)
   262  	return &gt
   263  }
   264  
   265  func TestUpdateClients(t *testing.T) {
   266  	tests := []struct {
   267  		name              string
   268  		existingInstances map[string]map[string]*config.GerritQueryFilter
   269  		newInstances      map[string]map[string]*config.GerritQueryFilter
   270  		wantInstances     map[string]map[string]*config.GerritQueryFilter
   271  	}{
   272  		{
   273  			name:              "normal",
   274  			existingInstances: map[string]map[string]*config.GerritQueryFilter{"foo1": {"bar1": nil}},
   275  			newInstances:      map[string]map[string]*config.GerritQueryFilter{"foo2": {"bar2": nil}},
   276  			wantInstances:     map[string]map[string]*config.GerritQueryFilter{"foo2": {"bar2": nil}},
   277  		},
   278  		{
   279  			name:              "same instance",
   280  			existingInstances: map[string]map[string]*config.GerritQueryFilter{"foo1": {"bar1": nil}},
   281  			newInstances:      map[string]map[string]*config.GerritQueryFilter{"foo1": {"bar2": nil}},
   282  			wantInstances:     map[string]map[string]*config.GerritQueryFilter{"foo1": {"bar2": nil}},
   283  		},
   284  		{
   285  			name:              "delete",
   286  			existingInstances: map[string]map[string]*config.GerritQueryFilter{"foo1": {"bar1": nil}, "foo2": {"bar2": nil}},
   287  			newInstances:      map[string]map[string]*config.GerritQueryFilter{"foo1": {"bar1": nil}},
   288  			wantInstances:     map[string]map[string]*config.GerritQueryFilter{"foo1": {"bar1": nil}},
   289  		},
   290  	}
   291  
   292  	for _, tc := range tests {
   293  		t.Run(tc.name, func(t *testing.T) {
   294  			client := &Client{
   295  				handlers: make(map[string]*gerritInstanceHandler),
   296  			}
   297  			for instance, projects := range tc.existingInstances {
   298  				client.handlers[instance] = &gerritInstanceHandler{
   299  					instance: instance,
   300  					projects: projects,
   301  				}
   302  			}
   303  
   304  			if err := client.UpdateClients(tc.newInstances); err != nil {
   305  				t.Fatal(err)
   306  			}
   307  			gotInstances := make(map[string]map[string]*config.GerritQueryFilter)
   308  			for instance, handler := range client.handlers {
   309  				gotInstances[instance] = handler.projects
   310  			}
   311  			if diff := cmp.Diff(tc.wantInstances, gotInstances); diff != "" {
   312  				t.Fatalf("mismatch. got(+), want(-):\n%s", diff)
   313  			}
   314  		})
   315  	}
   316  }
   317  
   318  func TestDedupeIntoResult(t *testing.T) {
   319  	var testcases = []struct {
   320  		name  string
   321  		input []gerrit.ChangeInfo
   322  		want  []gerrit.ChangeInfo
   323  	}{
   324  		{
   325  			name:  "no changes",
   326  			input: []gerrit.ChangeInfo{},
   327  			want:  []gerrit.ChangeInfo{},
   328  		},
   329  		{
   330  			name: "no dupes",
   331  			input: []gerrit.ChangeInfo{
   332  				{
   333  					Number:          1,
   334  					CurrentRevision: "1-1",
   335  				},
   336  				{
   337  					Number:          2,
   338  					CurrentRevision: "2-1",
   339  				},
   340  			},
   341  			want: []gerrit.ChangeInfo{
   342  				{
   343  					Number:          1,
   344  					CurrentRevision: "1-1",
   345  				},
   346  				{
   347  					Number:          2,
   348  					CurrentRevision: "2-1",
   349  				},
   350  			},
   351  		},
   352  		{
   353  			name: "single dupe",
   354  			input: []gerrit.ChangeInfo{
   355  				{
   356  					Number:          1,
   357  					CurrentRevision: "1-1",
   358  				},
   359  				{
   360  					Number:          2,
   361  					CurrentRevision: "2-1",
   362  				},
   363  				{
   364  					Number:          1,
   365  					CurrentRevision: "1-2",
   366  				},
   367  			},
   368  			want: []gerrit.ChangeInfo{
   369  				{
   370  					Number:          2,
   371  					CurrentRevision: "2-1",
   372  				},
   373  				{
   374  					Number:          1,
   375  					CurrentRevision: "1-2",
   376  				},
   377  			},
   378  		},
   379  		{
   380  			name: "many dupes",
   381  			input: []gerrit.ChangeInfo{
   382  				{
   383  					Number:          1,
   384  					CurrentRevision: "1-1",
   385  				},
   386  				{
   387  					Number:          2,
   388  					CurrentRevision: "2-1",
   389  				},
   390  				{
   391  					Number:          1,
   392  					CurrentRevision: "1-2",
   393  				},
   394  				{
   395  					Number:          2,
   396  					CurrentRevision: "2-2",
   397  				},
   398  				{
   399  					Number:          1,
   400  					CurrentRevision: "1-3",
   401  				},
   402  				{
   403  					Number:          1,
   404  					CurrentRevision: "1-4",
   405  				},
   406  				{
   407  					Number:          3,
   408  					CurrentRevision: "3-1",
   409  				},
   410  			},
   411  			want: []gerrit.ChangeInfo{
   412  				{
   413  					Number:          2,
   414  					CurrentRevision: "2-2",
   415  				},
   416  				{
   417  					Number:          1,
   418  					CurrentRevision: "1-4",
   419  				},
   420  				{
   421  					Number:          3,
   422  					CurrentRevision: "3-1",
   423  				},
   424  			},
   425  		},
   426  	}
   427  
   428  	for _, tc := range testcases {
   429  		deduper := &deduper{
   430  			result:  []gerrit.ChangeInfo{},
   431  			seenPos: make(map[int]int),
   432  		}
   433  
   434  		for _, ci := range tc.input {
   435  			deduper.dedupeIntoResult(ci)
   436  		}
   437  
   438  		if diff := cmp.Diff(tc.want, deduper.result); diff != "" {
   439  			t.Fatalf("Output mismatch. Want(-), got(+):\n%s", diff)
   440  		}
   441  	}
   442  }
   443  
   444  func TestQueryChange(t *testing.T) {
   445  	now := time.Now().UTC()
   446  
   447  	var testcases = []struct {
   448  		name       string
   449  		lastUpdate map[string]time.Time
   450  		changes    map[string][]gerrit.ChangeInfo
   451  		comments   map[string]map[string][]gerrit.CommentInfo
   452  		// expected
   453  		revisions map[string][]string
   454  		messages  map[string][]gerrit.ChangeMessageInfo
   455  	}{
   456  		{
   457  			name: "no changes",
   458  			lastUpdate: map[string]time.Time{
   459  				"bar": now.Add(-time.Minute),
   460  			},
   461  			revisions: map[string][]string{},
   462  		},
   463  		{
   464  			name: "one outdated change",
   465  			lastUpdate: map[string]time.Time{
   466  				"bar": now.Add(-time.Minute),
   467  			},
   468  			changes: map[string][]gerrit.ChangeInfo{
   469  				"foo": {
   470  					{
   471  						Project:         "bar",
   472  						ID:              "1",
   473  						Number:          1,
   474  						CurrentRevision: "1-1",
   475  						Updated:         makeStamp(now.Add(-time.Hour)),
   476  						Revisions: map[string]gerrit.RevisionInfo{
   477  							"1-1": {
   478  								Created: makeStamp(now.Add(-time.Hour)),
   479  							},
   480  						},
   481  						Status: "NEW",
   482  					},
   483  				},
   484  			},
   485  			revisions: map[string][]string{},
   486  		},
   487  		{
   488  			name: "find comments in special patchset file",
   489  			lastUpdate: map[string]time.Time{
   490  				"bar": now.Add(-time.Minute),
   491  			},
   492  			changes: map[string][]gerrit.ChangeInfo{
   493  				"foo": {
   494  					{
   495  						Project:         "bar",
   496  						ID:              "bar~branch~random-string",
   497  						Number:          1,
   498  						ChangeID:        "random-string",
   499  						CurrentRevision: "1-1",
   500  						Updated:         makeStamp(now),
   501  						Revisions: map[string]gerrit.RevisionInfo{
   502  							"1-1": {
   503  								Created: makeStamp(now.Add(-time.Hour)),
   504  								Number:  1,
   505  							},
   506  						},
   507  						Status: "NEW",
   508  						Messages: []gerrit.ChangeMessageInfo{
   509  							{
   510  								Date:           makeStamp(now),
   511  								Message:        "first",
   512  								RevisionNumber: 1,
   513  							},
   514  							{
   515  								Date:           makeStamp(now.Add(2 * time.Second)),
   516  								Message:        "second",
   517  								RevisionNumber: 1,
   518  							},
   519  						},
   520  					},
   521  				},
   522  			},
   523  			comments: map[string]map[string][]gerrit.CommentInfo{
   524  				"bar~branch~random-string": {
   525  					"/PATCHSET_LEVEL": {
   526  						{
   527  							Message:  "before",
   528  							Updated:  newStamp(now.Add(-time.Second)),
   529  							PatchSet: 1,
   530  						},
   531  						{
   532  							Message:  "after",
   533  							Updated:  newStamp(now.Add(time.Second)),
   534  							PatchSet: 1,
   535  						},
   536  					},
   537  					"random.yaml": {
   538  						{
   539  							Message:  "ignore this file",
   540  							Updated:  newStamp(now.Add(-time.Second)),
   541  							PatchSet: 1,
   542  						},
   543  					},
   544  				},
   545  			},
   546  			revisions: map[string][]string{"foo": {"1-1"}},
   547  			messages: map[string][]gerrit.ChangeMessageInfo{
   548  				"random-string": {
   549  					{
   550  						Date:           makeStamp(now.Add(-time.Second)),
   551  						Message:        "before",
   552  						RevisionNumber: 1,
   553  					},
   554  					{
   555  						Date:           makeStamp(now),
   556  						Message:        "first",
   557  						RevisionNumber: 1,
   558  					},
   559  					{
   560  						Date:           makeStamp(now.Add(time.Second)),
   561  						Message:        "after",
   562  						RevisionNumber: 1,
   563  					},
   564  					{
   565  						Date:           makeStamp(now.Add(2 * time.Second)),
   566  						Message:        "second",
   567  						RevisionNumber: 1,
   568  					},
   569  				},
   570  			},
   571  		},
   572  		{
   573  			name: "one outdated change, but there's a new message",
   574  			lastUpdate: map[string]time.Time{
   575  				"bar": now.Add(-time.Minute),
   576  			},
   577  			changes: map[string][]gerrit.ChangeInfo{
   578  				"foo": {
   579  					{
   580  						Project:         "bar",
   581  						ID:              "100",
   582  						Number:          100,
   583  						CurrentRevision: "1-1",
   584  						Updated:         makeStamp(now),
   585  						Revisions: map[string]gerrit.RevisionInfo{
   586  							"1-1": {
   587  								Created: makeStamp(now.Add(-time.Hour)),
   588  								Number:  1,
   589  							},
   590  						},
   591  						Status: "NEW",
   592  						Messages: []gerrit.ChangeMessageInfo{
   593  							{
   594  								Date:           makeStamp(now),
   595  								Message:        "some message",
   596  								RevisionNumber: 1,
   597  							},
   598  						},
   599  					},
   600  				},
   601  			},
   602  			revisions: map[string][]string{"foo": {"1-1"}},
   603  		},
   604  		{
   605  			name: "one up-to-date change",
   606  			lastUpdate: map[string]time.Time{
   607  				"bar": now.Add(-time.Minute),
   608  			},
   609  			changes: map[string][]gerrit.ChangeInfo{
   610  				"foo": {
   611  					{
   612  						Project:         "bar",
   613  						ID:              "1",
   614  						Number:          1,
   615  						CurrentRevision: "1-1",
   616  						Updated:         makeStamp(now),
   617  						Revisions: map[string]gerrit.RevisionInfo{
   618  							"1-1": {
   619  								Created: makeStamp(now),
   620  							},
   621  						},
   622  						Status: "NEW",
   623  					},
   624  				},
   625  			},
   626  			revisions: map[string][]string{
   627  				"foo": {"1-1"},
   628  			},
   629  		},
   630  		{
   631  			name: "one up-to-date change, same timestamp",
   632  			lastUpdate: map[string]time.Time{
   633  				"bar": now.Add(-time.Minute),
   634  			},
   635  			changes: map[string][]gerrit.ChangeInfo{
   636  				"foo": {
   637  					{
   638  						Project:         "bar",
   639  						ID:              "1",
   640  						Number:          1,
   641  						CurrentRevision: "1-1",
   642  						Updated:         makeStamp(now),
   643  						Revisions: map[string]gerrit.RevisionInfo{
   644  							"1-1": {
   645  								Created: makeStamp(now),
   646  							},
   647  						},
   648  						Status: "NEW",
   649  					},
   650  				},
   651  			},
   652  			revisions: map[string][]string{
   653  				"foo": {"1-1"},
   654  			},
   655  		},
   656  		{
   657  			name: "one up-to-date change but stale commit",
   658  			lastUpdate: map[string]time.Time{
   659  				"bar": now.Add(-time.Minute),
   660  			},
   661  			changes: map[string][]gerrit.ChangeInfo{
   662  				"foo": {
   663  					{
   664  						Project:         "bar",
   665  						ID:              "1",
   666  						Number:          1,
   667  						CurrentRevision: "1-1",
   668  						Updated:         makeStamp(now),
   669  						Revisions: map[string]gerrit.RevisionInfo{
   670  							"1-1": {
   671  								Created: makeStamp(now.Add(-time.Hour)),
   672  							},
   673  						},
   674  						Status: "NEW",
   675  					},
   676  				},
   677  			},
   678  			revisions: map[string][]string{},
   679  		},
   680  		{
   681  			name: "one up-to-date change, wrong instance",
   682  			lastUpdate: map[string]time.Time{
   683  				"bar": now.Add(-time.Minute),
   684  			},
   685  			changes: map[string][]gerrit.ChangeInfo{
   686  				"evil": {
   687  					{
   688  						Project:         "bar",
   689  						ID:              "1",
   690  						Number:          1,
   691  						CurrentRevision: "1-1",
   692  						Updated:         makeStamp(now),
   693  						Revisions: map[string]gerrit.RevisionInfo{
   694  							"1-1": {
   695  								Created: makeStamp(now),
   696  							},
   697  						},
   698  						Status: "NEW",
   699  					},
   700  				},
   701  			},
   702  			revisions: map[string][]string{},
   703  		},
   704  		{
   705  			name: "one up-to-date change, wrong project",
   706  			lastUpdate: map[string]time.Time{
   707  				"bar": now.Add(-time.Minute),
   708  			},
   709  			changes: map[string][]gerrit.ChangeInfo{
   710  				"foo": {
   711  					{
   712  						Project:         "evil",
   713  						ID:              "1",
   714  						Number:          1,
   715  						CurrentRevision: "1-1",
   716  						Updated:         makeStamp(now),
   717  						Revisions: map[string]gerrit.RevisionInfo{
   718  							"1-1": {
   719  								Created: makeStamp(now),
   720  							},
   721  						},
   722  						Status: "NEW",
   723  					},
   724  				},
   725  			},
   726  			revisions: map[string][]string{},
   727  		},
   728  		{
   729  			name: "two up-to-date changes, two projects",
   730  			lastUpdate: map[string]time.Time{
   731  				"bar": now.Add(-time.Minute),
   732  			},
   733  			changes: map[string][]gerrit.ChangeInfo{
   734  				"foo": {
   735  					{
   736  						Project:         "bar",
   737  						ID:              "1",
   738  						Number:          1,
   739  						CurrentRevision: "1-1",
   740  						Updated:         makeStamp(now),
   741  						Revisions: map[string]gerrit.RevisionInfo{
   742  							"1-1": {
   743  								Created: makeStamp(now),
   744  							},
   745  						},
   746  						Status: "NEW",
   747  					},
   748  					{
   749  						Project:         "bar",
   750  						ID:              "2",
   751  						Number:          2,
   752  						CurrentRevision: "2-1",
   753  						Updated:         makeStamp(now),
   754  						Revisions: map[string]gerrit.RevisionInfo{
   755  							"2-1": {
   756  								Created: makeStamp(now),
   757  							},
   758  						},
   759  						Status: "NEW",
   760  					},
   761  				},
   762  			},
   763  			revisions: map[string][]string{
   764  				"foo": {"1-1", "2-1"},
   765  			},
   766  		},
   767  		{
   768  			name: "one good one bad",
   769  			lastUpdate: map[string]time.Time{
   770  				"bar": now.Add(-time.Minute),
   771  			},
   772  			changes: map[string][]gerrit.ChangeInfo{
   773  				"foo": {
   774  					{
   775  						Project:         "bar",
   776  						ID:              "1",
   777  						Number:          1,
   778  						CurrentRevision: "1-1",
   779  						Updated:         makeStamp(now),
   780  						Revisions: map[string]gerrit.RevisionInfo{
   781  							"1-1": {
   782  								Created: makeStamp(now),
   783  							},
   784  						},
   785  						Status: "NEW",
   786  					},
   787  					{
   788  						Project:         "bar",
   789  						ID:              "2",
   790  						Number:          2,
   791  						CurrentRevision: "2-1",
   792  						Updated:         makeStamp(now.Add(-time.Hour)),
   793  						Revisions: map[string]gerrit.RevisionInfo{
   794  							"2-1": {
   795  								Created: makeStamp(now.Add(-time.Hour)),
   796  							},
   797  						},
   798  						Status: "NEW",
   799  					},
   800  				},
   801  			},
   802  			revisions: map[string][]string{
   803  				"foo": {"1-1"},
   804  			},
   805  		},
   806  		{
   807  			name: "multiple up-to-date changes",
   808  			lastUpdate: map[string]time.Time{
   809  				"bar": now.Add(-time.Minute),
   810  				"boo": now.Add(-time.Minute),
   811  			},
   812  			changes: map[string][]gerrit.ChangeInfo{
   813  				"foo": {
   814  					{
   815  						Project:         "bar",
   816  						ID:              "1",
   817  						Number:          1,
   818  						CurrentRevision: "1-1",
   819  						Updated:         makeStamp(now),
   820  						Revisions: map[string]gerrit.RevisionInfo{
   821  							"1-1": {
   822  								Created: makeStamp(now),
   823  							},
   824  						},
   825  						Status: "NEW",
   826  					},
   827  					{
   828  						Project:         "bar",
   829  						ID:              "2",
   830  						Number:          2,
   831  						CurrentRevision: "2-1",
   832  						Updated:         makeStamp(now),
   833  						Revisions: map[string]gerrit.RevisionInfo{
   834  							"2-1": {
   835  								Created: makeStamp(now),
   836  							},
   837  						},
   838  						Status: "NEW",
   839  					},
   840  				},
   841  				"baz": {
   842  					{
   843  						Project:         "boo",
   844  						ID:              "3",
   845  						Number:          3,
   846  						CurrentRevision: "3-2",
   847  						Updated:         makeStamp(now),
   848  						Revisions: map[string]gerrit.RevisionInfo{
   849  							"3-2": {
   850  								Created: makeStamp(now),
   851  							},
   852  							"3-1": {
   853  								Created: makeStamp(now),
   854  							},
   855  						},
   856  						Status: "NEW",
   857  					},
   858  					{
   859  						Project:         "evil",
   860  						ID:              "4",
   861  						Number:          4,
   862  						CurrentRevision: "4-1",
   863  						Updated:         makeStamp(now.Add(-time.Hour)),
   864  						Revisions: map[string]gerrit.RevisionInfo{
   865  							"4-1": {
   866  								Created: makeStamp(now.Add(-time.Hour)),
   867  							},
   868  						},
   869  						Status: "NEW",
   870  					},
   871  				},
   872  			},
   873  			revisions: map[string][]string{
   874  				"foo": {"1-1", "2-1"},
   875  				"baz": {"3-2"},
   876  			},
   877  		},
   878  		{
   879  			name: "one up-to-date merged change",
   880  			lastUpdate: map[string]time.Time{
   881  				"bar": now.Add(-time.Minute),
   882  			},
   883  			changes: map[string][]gerrit.ChangeInfo{
   884  				"foo": {
   885  					{
   886  						Project:         "bar",
   887  						ID:              "1",
   888  						Number:          1,
   889  						CurrentRevision: "1-1",
   890  						Updated:         makeStamp(now),
   891  						Submitted:       newStamp(now),
   892  						Status:          "MERGED",
   893  					},
   894  				},
   895  			},
   896  			revisions: map[string][]string{
   897  				"foo": {"1-1"},
   898  			},
   899  		},
   900  		{
   901  			name: "one up-to-date abandoned change",
   902  			lastUpdate: map[string]time.Time{
   903  				"bar": now.Add(-time.Minute),
   904  			},
   905  			changes: map[string][]gerrit.ChangeInfo{
   906  				"foo": {
   907  					{
   908  						Project:         "bar",
   909  						ID:              "1",
   910  						Number:          1,
   911  						CurrentRevision: "1-1",
   912  						Updated:         makeStamp(now),
   913  						Submitted:       newStamp(now),
   914  						Status:          "ABANDONED",
   915  					},
   916  				},
   917  			},
   918  			revisions: map[string][]string{},
   919  		},
   920  		{
   921  			name: "merged change recently updated but submitted before last update",
   922  			lastUpdate: map[string]time.Time{
   923  				"bar": now.Add(-time.Minute),
   924  			},
   925  			changes: map[string][]gerrit.ChangeInfo{
   926  				"foo": {
   927  					{
   928  						Project:         "bar",
   929  						ID:              "1",
   930  						Number:          1,
   931  						CurrentRevision: "1-1",
   932  						Updated:         makeStamp(now),
   933  						Submitted:       newStamp(now.Add(-2 * time.Minute)),
   934  						Status:          "MERGED",
   935  					},
   936  				},
   937  			},
   938  			revisions: map[string][]string{},
   939  		},
   940  		{
   941  			name: "one abandoned, one merged",
   942  			lastUpdate: map[string]time.Time{
   943  				"bar": now.Add(-time.Minute),
   944  			},
   945  			changes: map[string][]gerrit.ChangeInfo{
   946  				"foo": {
   947  					{
   948  						Project:         "bar",
   949  						ID:              "1",
   950  						Number:          1,
   951  						CurrentRevision: "1-1",
   952  						Updated:         makeStamp(now),
   953  						Status:          "ABANDONED",
   954  					},
   955  					{
   956  						Project:         "bar",
   957  						ID:              "2",
   958  						CurrentRevision: "2-1",
   959  						Updated:         makeStamp(now),
   960  						Submitted:       newStamp(now),
   961  						Status:          "MERGED",
   962  					},
   963  				},
   964  			},
   965  			revisions: map[string][]string{
   966  				"foo": {"2-1"},
   967  			},
   968  		},
   969  		{
   970  			name: "merged change with new message, should ignore",
   971  			lastUpdate: map[string]time.Time{
   972  				"bar": now.Add(-time.Minute),
   973  			},
   974  			changes: map[string][]gerrit.ChangeInfo{
   975  				"foo": {
   976  					{
   977  						Project:         "bar",
   978  						ID:              "2",
   979  						Number:          2,
   980  						CurrentRevision: "2-1",
   981  						Updated:         makeStamp(now),
   982  						Submitted:       newStamp(now.Add(-time.Hour)),
   983  						Status:          "MERGED",
   984  						Messages: []gerrit.ChangeMessageInfo{
   985  							{
   986  								Date:           makeStamp(now),
   987  								Message:        "some message",
   988  								RevisionNumber: 1,
   989  							},
   990  						},
   991  					},
   992  				},
   993  			},
   994  			revisions: map[string][]string{},
   995  		},
   996  		{
   997  			name: "one up-to-date change found twice due to pagination. Duplicate should be removed",
   998  			lastUpdate: map[string]time.Time{
   999  				"bar": now.Add(-time.Hour),
  1000  			},
  1001  			changes: map[string][]gerrit.ChangeInfo{
  1002  				"foo": {
  1003  					{
  1004  						Project:         "bar",
  1005  						ID:              "1",
  1006  						Number:          1,
  1007  						CurrentRevision: "1-1",
  1008  						Updated:         makeStamp(now.Add(-time.Minute)),
  1009  						Revisions: map[string]gerrit.RevisionInfo{
  1010  							"1-1": {
  1011  								Created: makeStamp(now.Add(-time.Minute)),
  1012  							},
  1013  						},
  1014  						Status: "NEW",
  1015  					},
  1016  					{
  1017  						Project:         "bar",
  1018  						ID:              "2",
  1019  						Number:          2,
  1020  						CurrentRevision: "2-1",
  1021  						Updated:         makeStamp(now.Add(-time.Minute)),
  1022  						Revisions: map[string]gerrit.RevisionInfo{
  1023  							"2-1": {
  1024  								Created: makeStamp(now.Add(-time.Minute)),
  1025  							},
  1026  						},
  1027  						Status: "NEW",
  1028  					},
  1029  					{
  1030  						Project:         "bar",
  1031  						ID:              "1",
  1032  						Number:          1,
  1033  						CurrentRevision: "1-2",
  1034  						Updated:         makeStamp(now),
  1035  						Revisions: map[string]gerrit.RevisionInfo{
  1036  							"1-1": {
  1037  								Created: makeStamp(now.Add(-time.Minute)),
  1038  							},
  1039  							"1-2": {
  1040  								Created: makeStamp(now),
  1041  							},
  1042  						},
  1043  						Status: "NEW",
  1044  					},
  1045  				},
  1046  			},
  1047  			revisions: map[string][]string{
  1048  				"foo": {"2-1", "1-2"},
  1049  			},
  1050  		},
  1051  	}
  1052  
  1053  	for _, tc := range testcases {
  1054  		client := &Client{
  1055  			handlers: map[string]*gerritInstanceHandler{
  1056  				"foo": {
  1057  					instance: "foo",
  1058  					projects: map[string]*config.GerritQueryFilter{"bar": nil},
  1059  					changeService: &fgc{
  1060  						changes:  tc.changes,
  1061  						instance: "foo",
  1062  						comments: tc.comments,
  1063  					},
  1064  					log: logrus.WithField("host", "foo"),
  1065  				},
  1066  				"baz": {
  1067  					instance: "baz",
  1068  					projects: map[string]*config.GerritQueryFilter{"boo": nil},
  1069  					changeService: &fgc{
  1070  						changes:  tc.changes,
  1071  						instance: "baz",
  1072  					},
  1073  					log: logrus.WithField("host", "baz"),
  1074  				},
  1075  			},
  1076  		}
  1077  
  1078  		testLastSync := LastSyncState{"foo": tc.lastUpdate, "baz": tc.lastUpdate}
  1079  		changes := client.QueryChanges(testLastSync, 2)
  1080  
  1081  		revisions := map[string][]string{}
  1082  		messages := map[string][]gerrit.ChangeMessageInfo{}
  1083  		seen := sets.NewInt()
  1084  		for instance, changes := range changes {
  1085  			revisions[instance] = []string{}
  1086  			for _, change := range changes {
  1087  				if seen.Has(change.Number) {
  1088  					t.Errorf("Change number %d appears multiple times in the query results.", change.Number)
  1089  				}
  1090  				seen.Insert(change.Number)
  1091  				revisions[instance] = append(revisions[instance], change.CurrentRevision)
  1092  				messages[change.ChangeID] = append(messages[change.ChangeID], change.Messages...)
  1093  			}
  1094  		}
  1095  
  1096  		if !reflect.DeepEqual(revisions, tc.revisions) {
  1097  			t.Errorf("tc %s - wrong revisions: got %#v, expect %#v", tc.name, revisions, tc.revisions)
  1098  		}
  1099  
  1100  		if tc.messages != nil && !reflect.DeepEqual(messages, tc.messages) {
  1101  			t.Errorf("tc %s - wrong messages:\nhave %#v,\nwant %#v", tc.name, messages, tc.messages)
  1102  		}
  1103  	}
  1104  }