diff --git a/cmd/repo-updater/repoupdater/server.go b/cmd/repo-updater/repoupdater/server.go index 3d15af7f075..611be6f7834 100644 --- a/cmd/repo-updater/repoupdater/server.go +++ b/cmd/repo-updater/repoupdater/server.go @@ -52,9 +52,6 @@ type Server struct { ChangesetSyncRegistry interface { // EnqueueChangesetSyncs will queue the supplied changesets to sync ASAP. EnqueueChangesetSyncs(ctx context.Context, ids []int64) error - // HandleExternalServiceSync should be called when an external service changes so that - // the registry can start or stop the syncer associated with the service - HandleExternalServiceSync(es api.ExternalService) } RateLimitSyncer interface { // SyncRateLimiters should be called when an external service changes so that @@ -245,9 +242,6 @@ func (s *Server) handleExternalServiceSync(w http.ResponseWriter, r *http.Reques log15.Warn("Handling rate limiter sync", "err", err) } } - if s.ChangesetSyncRegistry != nil { - s.ChangesetSyncRegistry.HandleExternalServiceSync(req.ExternalService) - } log15.Info("server.external-service-sync", "synced", req.ExternalService.Kind) respond(w, http.StatusOK, &protocol.ExternalServiceSyncResult{ diff --git a/enterprise/internal/batches/background.go b/enterprise/internal/batches/background.go index 96e10b28f5c..0b2cac5d16c 100644 --- a/enterprise/internal/batches/background.go +++ b/enterprise/internal/batches/background.go @@ -11,7 +11,6 @@ import ( "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/store" "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/syncer" "github.com/sourcegraph/sourcegraph/internal/actor" - "github.com/sourcegraph/sourcegraph/internal/api" "github.com/sourcegraph/sourcegraph/internal/database/dbutil" "github.com/sourcegraph/sourcegraph/internal/encryption" "github.com/sourcegraph/sourcegraph/internal/goroutine" @@ -30,18 +29,7 @@ func InitBackgroundJobs( ) interface { // EnqueueChangesetSyncs will queue the supplied changesets to sync ASAP. EnqueueChangesetSyncs(ctx context.Context, ids []int64) error - // HandleExternalServiceSync should be called when an external service changes so that - // the registry can start or stop the syncer associated with the service - HandleExternalServiceSync(es api.ExternalService) } { - observationContext := &observation.Context{ - Logger: log15.Root(), - Tracer: &trace.Tracer{Tracer: opentracing.GlobalTracer()}, - Registerer: prometheus.DefaultRegisterer, - } - - cstore := store.New(db, observationContext, key) - // We use an internal actor so that we can freely load dependencies from // the database without repository permissions being enforced. // We do check for repository permissions consciously in the Rewirer when @@ -49,9 +37,20 @@ func InitBackgroundJobs( // host, we manually check for BatchChangesCredentials. ctx = actor.WithInternalActor(ctx) - syncRegistry := syncer.NewSyncRegistry(ctx, cstore, cf) + observationContext := &observation.Context{ + Logger: log15.Root(), + Tracer: &trace.Tracer{Tracer: opentracing.GlobalTracer()}, + Registerer: prometheus.DefaultRegisterer, + } + bstore := store.New(db, observationContext, key) - go goroutine.MonitorBackgroundRoutines(ctx, background.Routines(ctx, cstore, cf, observationContext)...) + syncRegistry := syncer.NewSyncRegistry(ctx, bstore, cf, observationContext) + + routines := background.Routines(ctx, bstore, cf, observationContext) + + routines = append(routines, syncRegistry) + + go goroutine.MonitorBackgroundRoutines(ctx, routines...) return syncRegistry } diff --git a/enterprise/internal/batches/syncer/gen.go b/enterprise/internal/batches/syncer/gen.go new file mode 100644 index 00000000000..80bf1631a3a --- /dev/null +++ b/enterprise/internal/batches/syncer/gen.go @@ -0,0 +1,3 @@ +package syncer + +//go:generate ../../../../dev/mockgen.sh github.com/sourcegraph/sourcegraph/enterprise/internal/batches/syncer -i SyncStore -o mock_iface_test.go diff --git a/enterprise/internal/batches/syncer/mock_iface_test.go b/enterprise/internal/batches/syncer/mock_iface_test.go new file mode 100644 index 00000000000..2f9de3d016e --- /dev/null +++ b/enterprise/internal/batches/syncer/mock_iface_test.go @@ -0,0 +1,1555 @@ +// Code generated by go-mockgen 1.1.2; DO NOT EDIT. + +package syncer + +import ( + "context" + "sync" + "time" + + store "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/store" + types "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/types" + database "github.com/sourcegraph/sourcegraph/internal/database" + dbutil "github.com/sourcegraph/sourcegraph/internal/database/dbutil" +) + +// MockSyncStore is a mock implementation of the SyncStore interface (from +// the package +// github.com/sourcegraph/sourcegraph/enterprise/internal/batches/syncer) +// used for unit testing. +type MockSyncStore struct { + // ClockFunc is an instance of a mock function object controlling the + // behavior of the method Clock. + ClockFunc *SyncStoreClockFunc + // DBFunc is an instance of a mock function object controlling the + // behavior of the method DB. + DBFunc *SyncStoreDBFunc + // ExternalServicesFunc is an instance of a mock function object + // controlling the behavior of the method ExternalServices. + ExternalServicesFunc *SyncStoreExternalServicesFunc + // GetChangesetFunc is an instance of a mock function object controlling + // the behavior of the method GetChangeset. + GetChangesetFunc *SyncStoreGetChangesetFunc + // GetExternalServiceIDsFunc is an instance of a mock function object + // controlling the behavior of the method GetExternalServiceIDs. + GetExternalServiceIDsFunc *SyncStoreGetExternalServiceIDsFunc + // GetSiteCredentialFunc is an instance of a mock function object + // controlling the behavior of the method GetSiteCredential. + GetSiteCredentialFunc *SyncStoreGetSiteCredentialFunc + // ListChangesetSyncDataFunc is an instance of a mock function object + // controlling the behavior of the method ListChangesetSyncData. + ListChangesetSyncDataFunc *SyncStoreListChangesetSyncDataFunc + // ListCodeHostsFunc is an instance of a mock function object + // controlling the behavior of the method ListCodeHosts. + ListCodeHostsFunc *SyncStoreListCodeHostsFunc + // ReposFunc is an instance of a mock function object controlling the + // behavior of the method Repos. + ReposFunc *SyncStoreReposFunc + // TransactFunc is an instance of a mock function object controlling the + // behavior of the method Transact. + TransactFunc *SyncStoreTransactFunc + // UpdateChangesetCodeHostStateFunc is an instance of a mock function + // object controlling the behavior of the method + // UpdateChangesetCodeHostState. + UpdateChangesetCodeHostStateFunc *SyncStoreUpdateChangesetCodeHostStateFunc + // UpsertChangesetEventsFunc is an instance of a mock function object + // controlling the behavior of the method UpsertChangesetEvents. + UpsertChangesetEventsFunc *SyncStoreUpsertChangesetEventsFunc + // UserCredentialsFunc is an instance of a mock function object + // controlling the behavior of the method UserCredentials. + UserCredentialsFunc *SyncStoreUserCredentialsFunc +} + +// NewMockSyncStore creates a new mock of the SyncStore interface. All +// methods return zero values for all results, unless overwritten. +func NewMockSyncStore() *MockSyncStore { + return &MockSyncStore{ + ClockFunc: &SyncStoreClockFunc{ + defaultHook: func() func() time.Time { + return nil + }, + }, + DBFunc: &SyncStoreDBFunc{ + defaultHook: func() dbutil.DB { + return nil + }, + }, + ExternalServicesFunc: &SyncStoreExternalServicesFunc{ + defaultHook: func() *database.ExternalServiceStore { + return nil + }, + }, + GetChangesetFunc: &SyncStoreGetChangesetFunc{ + defaultHook: func(context.Context, store.GetChangesetOpts) (*types.Changeset, error) { + return nil, nil + }, + }, + GetExternalServiceIDsFunc: &SyncStoreGetExternalServiceIDsFunc{ + defaultHook: func(context.Context, store.GetExternalServiceIDsOpts) ([]int64, error) { + return nil, nil + }, + }, + GetSiteCredentialFunc: &SyncStoreGetSiteCredentialFunc{ + defaultHook: func(context.Context, store.GetSiteCredentialOpts) (*types.SiteCredential, error) { + return nil, nil + }, + }, + ListChangesetSyncDataFunc: &SyncStoreListChangesetSyncDataFunc{ + defaultHook: func(context.Context, store.ListChangesetSyncDataOpts) ([]*types.ChangesetSyncData, error) { + return nil, nil + }, + }, + ListCodeHostsFunc: &SyncStoreListCodeHostsFunc{ + defaultHook: func(context.Context, store.ListCodeHostsOpts) ([]*types.CodeHost, error) { + return nil, nil + }, + }, + ReposFunc: &SyncStoreReposFunc{ + defaultHook: func() *database.RepoStore { + return nil + }, + }, + TransactFunc: &SyncStoreTransactFunc{ + defaultHook: func(context.Context) (*store.Store, error) { + return nil, nil + }, + }, + UpdateChangesetCodeHostStateFunc: &SyncStoreUpdateChangesetCodeHostStateFunc{ + defaultHook: func(context.Context, *types.Changeset) error { + return nil + }, + }, + UpsertChangesetEventsFunc: &SyncStoreUpsertChangesetEventsFunc{ + defaultHook: func(context.Context, ...*types.ChangesetEvent) error { + return nil + }, + }, + UserCredentialsFunc: &SyncStoreUserCredentialsFunc{ + defaultHook: func() *database.UserCredentialsStore { + return nil + }, + }, + } +} + +// NewMockSyncStoreFrom creates a new mock of the MockSyncStore interface. +// All methods delegate to the given implementation, unless overwritten. +func NewMockSyncStoreFrom(i SyncStore) *MockSyncStore { + return &MockSyncStore{ + ClockFunc: &SyncStoreClockFunc{ + defaultHook: i.Clock, + }, + DBFunc: &SyncStoreDBFunc{ + defaultHook: i.DB, + }, + ExternalServicesFunc: &SyncStoreExternalServicesFunc{ + defaultHook: i.ExternalServices, + }, + GetChangesetFunc: &SyncStoreGetChangesetFunc{ + defaultHook: i.GetChangeset, + }, + GetExternalServiceIDsFunc: &SyncStoreGetExternalServiceIDsFunc{ + defaultHook: i.GetExternalServiceIDs, + }, + GetSiteCredentialFunc: &SyncStoreGetSiteCredentialFunc{ + defaultHook: i.GetSiteCredential, + }, + ListChangesetSyncDataFunc: &SyncStoreListChangesetSyncDataFunc{ + defaultHook: i.ListChangesetSyncData, + }, + ListCodeHostsFunc: &SyncStoreListCodeHostsFunc{ + defaultHook: i.ListCodeHosts, + }, + ReposFunc: &SyncStoreReposFunc{ + defaultHook: i.Repos, + }, + TransactFunc: &SyncStoreTransactFunc{ + defaultHook: i.Transact, + }, + UpdateChangesetCodeHostStateFunc: &SyncStoreUpdateChangesetCodeHostStateFunc{ + defaultHook: i.UpdateChangesetCodeHostState, + }, + UpsertChangesetEventsFunc: &SyncStoreUpsertChangesetEventsFunc{ + defaultHook: i.UpsertChangesetEvents, + }, + UserCredentialsFunc: &SyncStoreUserCredentialsFunc{ + defaultHook: i.UserCredentials, + }, + } +} + +// SyncStoreClockFunc describes the behavior when the Clock method of the +// parent MockSyncStore instance is invoked. +type SyncStoreClockFunc struct { + defaultHook func() func() time.Time + hooks []func() func() time.Time + history []SyncStoreClockFuncCall + mutex sync.Mutex +} + +// Clock delegates to the next hook function in the queue and stores the +// parameter and result values of this invocation. +func (m *MockSyncStore) Clock() func() time.Time { + r0 := m.ClockFunc.nextHook()() + m.ClockFunc.appendCall(SyncStoreClockFuncCall{r0}) + return r0 +} + +// SetDefaultHook sets function that is called when the Clock method of the +// parent MockSyncStore instance is invoked and the hook queue is empty. +func (f *SyncStoreClockFunc) SetDefaultHook(hook func() func() time.Time) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// Clock method of the parent MockSyncStore instance invokes the hook at the +// front of the queue and discards it. After the queue is empty, the default +// hook function is invoked for any future action. +func (f *SyncStoreClockFunc) PushHook(hook func() func() time.Time) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreClockFunc) SetDefaultReturn(r0 func() time.Time) { + f.SetDefaultHook(func() func() time.Time { + return r0 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreClockFunc) PushReturn(r0 func() time.Time) { + f.PushHook(func() func() time.Time { + return r0 + }) +} + +func (f *SyncStoreClockFunc) nextHook() func() func() time.Time { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreClockFunc) appendCall(r0 SyncStoreClockFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreClockFuncCall objects describing +// the invocations of this function. +func (f *SyncStoreClockFunc) History() []SyncStoreClockFuncCall { + f.mutex.Lock() + history := make([]SyncStoreClockFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreClockFuncCall is an object that describes an invocation of +// method Clock on an instance of MockSyncStore. +type SyncStoreClockFuncCall struct { + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 func() time.Time +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreClockFuncCall) Args() []interface{} { + return []interface{}{} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreClockFuncCall) Results() []interface{} { + return []interface{}{c.Result0} +} + +// SyncStoreDBFunc describes the behavior when the DB method of the parent +// MockSyncStore instance is invoked. +type SyncStoreDBFunc struct { + defaultHook func() dbutil.DB + hooks []func() dbutil.DB + history []SyncStoreDBFuncCall + mutex sync.Mutex +} + +// DB delegates to the next hook function in the queue and stores the +// parameter and result values of this invocation. +func (m *MockSyncStore) DB() dbutil.DB { + r0 := m.DBFunc.nextHook()() + m.DBFunc.appendCall(SyncStoreDBFuncCall{r0}) + return r0 +} + +// SetDefaultHook sets function that is called when the DB method of the +// parent MockSyncStore instance is invoked and the hook queue is empty. +func (f *SyncStoreDBFunc) SetDefaultHook(hook func() dbutil.DB) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// DB method of the parent MockSyncStore instance invokes the hook at the +// front of the queue and discards it. After the queue is empty, the default +// hook function is invoked for any future action. +func (f *SyncStoreDBFunc) PushHook(hook func() dbutil.DB) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreDBFunc) SetDefaultReturn(r0 dbutil.DB) { + f.SetDefaultHook(func() dbutil.DB { + return r0 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreDBFunc) PushReturn(r0 dbutil.DB) { + f.PushHook(func() dbutil.DB { + return r0 + }) +} + +func (f *SyncStoreDBFunc) nextHook() func() dbutil.DB { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreDBFunc) appendCall(r0 SyncStoreDBFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreDBFuncCall objects describing the +// invocations of this function. +func (f *SyncStoreDBFunc) History() []SyncStoreDBFuncCall { + f.mutex.Lock() + history := make([]SyncStoreDBFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreDBFuncCall is an object that describes an invocation of method +// DB on an instance of MockSyncStore. +type SyncStoreDBFuncCall struct { + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 dbutil.DB +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreDBFuncCall) Args() []interface{} { + return []interface{}{} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreDBFuncCall) Results() []interface{} { + return []interface{}{c.Result0} +} + +// SyncStoreExternalServicesFunc describes the behavior when the +// ExternalServices method of the parent MockSyncStore instance is invoked. +type SyncStoreExternalServicesFunc struct { + defaultHook func() *database.ExternalServiceStore + hooks []func() *database.ExternalServiceStore + history []SyncStoreExternalServicesFuncCall + mutex sync.Mutex +} + +// ExternalServices delegates to the next hook function in the queue and +// stores the parameter and result values of this invocation. +func (m *MockSyncStore) ExternalServices() *database.ExternalServiceStore { + r0 := m.ExternalServicesFunc.nextHook()() + m.ExternalServicesFunc.appendCall(SyncStoreExternalServicesFuncCall{r0}) + return r0 +} + +// SetDefaultHook sets function that is called when the ExternalServices +// method of the parent MockSyncStore instance is invoked and the hook queue +// is empty. +func (f *SyncStoreExternalServicesFunc) SetDefaultHook(hook func() *database.ExternalServiceStore) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// ExternalServices method of the parent MockSyncStore instance invokes the +// hook at the front of the queue and discards it. After the queue is empty, +// the default hook function is invoked for any future action. +func (f *SyncStoreExternalServicesFunc) PushHook(hook func() *database.ExternalServiceStore) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreExternalServicesFunc) SetDefaultReturn(r0 *database.ExternalServiceStore) { + f.SetDefaultHook(func() *database.ExternalServiceStore { + return r0 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreExternalServicesFunc) PushReturn(r0 *database.ExternalServiceStore) { + f.PushHook(func() *database.ExternalServiceStore { + return r0 + }) +} + +func (f *SyncStoreExternalServicesFunc) nextHook() func() *database.ExternalServiceStore { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreExternalServicesFunc) appendCall(r0 SyncStoreExternalServicesFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreExternalServicesFuncCall objects +// describing the invocations of this function. +func (f *SyncStoreExternalServicesFunc) History() []SyncStoreExternalServicesFuncCall { + f.mutex.Lock() + history := make([]SyncStoreExternalServicesFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreExternalServicesFuncCall is an object that describes an +// invocation of method ExternalServices on an instance of MockSyncStore. +type SyncStoreExternalServicesFuncCall struct { + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 *database.ExternalServiceStore +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreExternalServicesFuncCall) Args() []interface{} { + return []interface{}{} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreExternalServicesFuncCall) Results() []interface{} { + return []interface{}{c.Result0} +} + +// SyncStoreGetChangesetFunc describes the behavior when the GetChangeset +// method of the parent MockSyncStore instance is invoked. +type SyncStoreGetChangesetFunc struct { + defaultHook func(context.Context, store.GetChangesetOpts) (*types.Changeset, error) + hooks []func(context.Context, store.GetChangesetOpts) (*types.Changeset, error) + history []SyncStoreGetChangesetFuncCall + mutex sync.Mutex +} + +// GetChangeset delegates to the next hook function in the queue and stores +// the parameter and result values of this invocation. +func (m *MockSyncStore) GetChangeset(v0 context.Context, v1 store.GetChangesetOpts) (*types.Changeset, error) { + r0, r1 := m.GetChangesetFunc.nextHook()(v0, v1) + m.GetChangesetFunc.appendCall(SyncStoreGetChangesetFuncCall{v0, v1, r0, r1}) + return r0, r1 +} + +// SetDefaultHook sets function that is called when the GetChangeset method +// of the parent MockSyncStore instance is invoked and the hook queue is +// empty. +func (f *SyncStoreGetChangesetFunc) SetDefaultHook(hook func(context.Context, store.GetChangesetOpts) (*types.Changeset, error)) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// GetChangeset method of the parent MockSyncStore instance invokes the hook +// at the front of the queue and discards it. After the queue is empty, the +// default hook function is invoked for any future action. +func (f *SyncStoreGetChangesetFunc) PushHook(hook func(context.Context, store.GetChangesetOpts) (*types.Changeset, error)) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreGetChangesetFunc) SetDefaultReturn(r0 *types.Changeset, r1 error) { + f.SetDefaultHook(func(context.Context, store.GetChangesetOpts) (*types.Changeset, error) { + return r0, r1 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreGetChangesetFunc) PushReturn(r0 *types.Changeset, r1 error) { + f.PushHook(func(context.Context, store.GetChangesetOpts) (*types.Changeset, error) { + return r0, r1 + }) +} + +func (f *SyncStoreGetChangesetFunc) nextHook() func(context.Context, store.GetChangesetOpts) (*types.Changeset, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreGetChangesetFunc) appendCall(r0 SyncStoreGetChangesetFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreGetChangesetFuncCall objects +// describing the invocations of this function. +func (f *SyncStoreGetChangesetFunc) History() []SyncStoreGetChangesetFuncCall { + f.mutex.Lock() + history := make([]SyncStoreGetChangesetFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreGetChangesetFuncCall is an object that describes an invocation +// of method GetChangeset on an instance of MockSyncStore. +type SyncStoreGetChangesetFuncCall struct { + // Arg0 is the value of the 1st argument passed to this method + // invocation. + Arg0 context.Context + // Arg1 is the value of the 2nd argument passed to this method + // invocation. + Arg1 store.GetChangesetOpts + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 *types.Changeset + // Result1 is the value of the 2nd result returned from this method + // invocation. + Result1 error +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreGetChangesetFuncCall) Args() []interface{} { + return []interface{}{c.Arg0, c.Arg1} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreGetChangesetFuncCall) Results() []interface{} { + return []interface{}{c.Result0, c.Result1} +} + +// SyncStoreGetExternalServiceIDsFunc describes the behavior when the +// GetExternalServiceIDs method of the parent MockSyncStore instance is +// invoked. +type SyncStoreGetExternalServiceIDsFunc struct { + defaultHook func(context.Context, store.GetExternalServiceIDsOpts) ([]int64, error) + hooks []func(context.Context, store.GetExternalServiceIDsOpts) ([]int64, error) + history []SyncStoreGetExternalServiceIDsFuncCall + mutex sync.Mutex +} + +// GetExternalServiceIDs delegates to the next hook function in the queue +// and stores the parameter and result values of this invocation. +func (m *MockSyncStore) GetExternalServiceIDs(v0 context.Context, v1 store.GetExternalServiceIDsOpts) ([]int64, error) { + r0, r1 := m.GetExternalServiceIDsFunc.nextHook()(v0, v1) + m.GetExternalServiceIDsFunc.appendCall(SyncStoreGetExternalServiceIDsFuncCall{v0, v1, r0, r1}) + return r0, r1 +} + +// SetDefaultHook sets function that is called when the +// GetExternalServiceIDs method of the parent MockSyncStore instance is +// invoked and the hook queue is empty. +func (f *SyncStoreGetExternalServiceIDsFunc) SetDefaultHook(hook func(context.Context, store.GetExternalServiceIDsOpts) ([]int64, error)) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// GetExternalServiceIDs method of the parent MockSyncStore instance invokes +// the hook at the front of the queue and discards it. After the queue is +// empty, the default hook function is invoked for any future action. +func (f *SyncStoreGetExternalServiceIDsFunc) PushHook(hook func(context.Context, store.GetExternalServiceIDsOpts) ([]int64, error)) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreGetExternalServiceIDsFunc) SetDefaultReturn(r0 []int64, r1 error) { + f.SetDefaultHook(func(context.Context, store.GetExternalServiceIDsOpts) ([]int64, error) { + return r0, r1 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreGetExternalServiceIDsFunc) PushReturn(r0 []int64, r1 error) { + f.PushHook(func(context.Context, store.GetExternalServiceIDsOpts) ([]int64, error) { + return r0, r1 + }) +} + +func (f *SyncStoreGetExternalServiceIDsFunc) nextHook() func(context.Context, store.GetExternalServiceIDsOpts) ([]int64, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreGetExternalServiceIDsFunc) appendCall(r0 SyncStoreGetExternalServiceIDsFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreGetExternalServiceIDsFuncCall +// objects describing the invocations of this function. +func (f *SyncStoreGetExternalServiceIDsFunc) History() []SyncStoreGetExternalServiceIDsFuncCall { + f.mutex.Lock() + history := make([]SyncStoreGetExternalServiceIDsFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreGetExternalServiceIDsFuncCall is an object that describes an +// invocation of method GetExternalServiceIDs on an instance of +// MockSyncStore. +type SyncStoreGetExternalServiceIDsFuncCall struct { + // Arg0 is the value of the 1st argument passed to this method + // invocation. + Arg0 context.Context + // Arg1 is the value of the 2nd argument passed to this method + // invocation. + Arg1 store.GetExternalServiceIDsOpts + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 []int64 + // Result1 is the value of the 2nd result returned from this method + // invocation. + Result1 error +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreGetExternalServiceIDsFuncCall) Args() []interface{} { + return []interface{}{c.Arg0, c.Arg1} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreGetExternalServiceIDsFuncCall) Results() []interface{} { + return []interface{}{c.Result0, c.Result1} +} + +// SyncStoreGetSiteCredentialFunc describes the behavior when the +// GetSiteCredential method of the parent MockSyncStore instance is invoked. +type SyncStoreGetSiteCredentialFunc struct { + defaultHook func(context.Context, store.GetSiteCredentialOpts) (*types.SiteCredential, error) + hooks []func(context.Context, store.GetSiteCredentialOpts) (*types.SiteCredential, error) + history []SyncStoreGetSiteCredentialFuncCall + mutex sync.Mutex +} + +// GetSiteCredential delegates to the next hook function in the queue and +// stores the parameter and result values of this invocation. +func (m *MockSyncStore) GetSiteCredential(v0 context.Context, v1 store.GetSiteCredentialOpts) (*types.SiteCredential, error) { + r0, r1 := m.GetSiteCredentialFunc.nextHook()(v0, v1) + m.GetSiteCredentialFunc.appendCall(SyncStoreGetSiteCredentialFuncCall{v0, v1, r0, r1}) + return r0, r1 +} + +// SetDefaultHook sets function that is called when the GetSiteCredential +// method of the parent MockSyncStore instance is invoked and the hook queue +// is empty. +func (f *SyncStoreGetSiteCredentialFunc) SetDefaultHook(hook func(context.Context, store.GetSiteCredentialOpts) (*types.SiteCredential, error)) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// GetSiteCredential method of the parent MockSyncStore instance invokes the +// hook at the front of the queue and discards it. After the queue is empty, +// the default hook function is invoked for any future action. +func (f *SyncStoreGetSiteCredentialFunc) PushHook(hook func(context.Context, store.GetSiteCredentialOpts) (*types.SiteCredential, error)) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreGetSiteCredentialFunc) SetDefaultReturn(r0 *types.SiteCredential, r1 error) { + f.SetDefaultHook(func(context.Context, store.GetSiteCredentialOpts) (*types.SiteCredential, error) { + return r0, r1 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreGetSiteCredentialFunc) PushReturn(r0 *types.SiteCredential, r1 error) { + f.PushHook(func(context.Context, store.GetSiteCredentialOpts) (*types.SiteCredential, error) { + return r0, r1 + }) +} + +func (f *SyncStoreGetSiteCredentialFunc) nextHook() func(context.Context, store.GetSiteCredentialOpts) (*types.SiteCredential, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreGetSiteCredentialFunc) appendCall(r0 SyncStoreGetSiteCredentialFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreGetSiteCredentialFuncCall objects +// describing the invocations of this function. +func (f *SyncStoreGetSiteCredentialFunc) History() []SyncStoreGetSiteCredentialFuncCall { + f.mutex.Lock() + history := make([]SyncStoreGetSiteCredentialFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreGetSiteCredentialFuncCall is an object that describes an +// invocation of method GetSiteCredential on an instance of MockSyncStore. +type SyncStoreGetSiteCredentialFuncCall struct { + // Arg0 is the value of the 1st argument passed to this method + // invocation. + Arg0 context.Context + // Arg1 is the value of the 2nd argument passed to this method + // invocation. + Arg1 store.GetSiteCredentialOpts + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 *types.SiteCredential + // Result1 is the value of the 2nd result returned from this method + // invocation. + Result1 error +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreGetSiteCredentialFuncCall) Args() []interface{} { + return []interface{}{c.Arg0, c.Arg1} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreGetSiteCredentialFuncCall) Results() []interface{} { + return []interface{}{c.Result0, c.Result1} +} + +// SyncStoreListChangesetSyncDataFunc describes the behavior when the +// ListChangesetSyncData method of the parent MockSyncStore instance is +// invoked. +type SyncStoreListChangesetSyncDataFunc struct { + defaultHook func(context.Context, store.ListChangesetSyncDataOpts) ([]*types.ChangesetSyncData, error) + hooks []func(context.Context, store.ListChangesetSyncDataOpts) ([]*types.ChangesetSyncData, error) + history []SyncStoreListChangesetSyncDataFuncCall + mutex sync.Mutex +} + +// ListChangesetSyncData delegates to the next hook function in the queue +// and stores the parameter and result values of this invocation. +func (m *MockSyncStore) ListChangesetSyncData(v0 context.Context, v1 store.ListChangesetSyncDataOpts) ([]*types.ChangesetSyncData, error) { + r0, r1 := m.ListChangesetSyncDataFunc.nextHook()(v0, v1) + m.ListChangesetSyncDataFunc.appendCall(SyncStoreListChangesetSyncDataFuncCall{v0, v1, r0, r1}) + return r0, r1 +} + +// SetDefaultHook sets function that is called when the +// ListChangesetSyncData method of the parent MockSyncStore instance is +// invoked and the hook queue is empty. +func (f *SyncStoreListChangesetSyncDataFunc) SetDefaultHook(hook func(context.Context, store.ListChangesetSyncDataOpts) ([]*types.ChangesetSyncData, error)) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// ListChangesetSyncData method of the parent MockSyncStore instance invokes +// the hook at the front of the queue and discards it. After the queue is +// empty, the default hook function is invoked for any future action. +func (f *SyncStoreListChangesetSyncDataFunc) PushHook(hook func(context.Context, store.ListChangesetSyncDataOpts) ([]*types.ChangesetSyncData, error)) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreListChangesetSyncDataFunc) SetDefaultReturn(r0 []*types.ChangesetSyncData, r1 error) { + f.SetDefaultHook(func(context.Context, store.ListChangesetSyncDataOpts) ([]*types.ChangesetSyncData, error) { + return r0, r1 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreListChangesetSyncDataFunc) PushReturn(r0 []*types.ChangesetSyncData, r1 error) { + f.PushHook(func(context.Context, store.ListChangesetSyncDataOpts) ([]*types.ChangesetSyncData, error) { + return r0, r1 + }) +} + +func (f *SyncStoreListChangesetSyncDataFunc) nextHook() func(context.Context, store.ListChangesetSyncDataOpts) ([]*types.ChangesetSyncData, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreListChangesetSyncDataFunc) appendCall(r0 SyncStoreListChangesetSyncDataFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreListChangesetSyncDataFuncCall +// objects describing the invocations of this function. +func (f *SyncStoreListChangesetSyncDataFunc) History() []SyncStoreListChangesetSyncDataFuncCall { + f.mutex.Lock() + history := make([]SyncStoreListChangesetSyncDataFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreListChangesetSyncDataFuncCall is an object that describes an +// invocation of method ListChangesetSyncData on an instance of +// MockSyncStore. +type SyncStoreListChangesetSyncDataFuncCall struct { + // Arg0 is the value of the 1st argument passed to this method + // invocation. + Arg0 context.Context + // Arg1 is the value of the 2nd argument passed to this method + // invocation. + Arg1 store.ListChangesetSyncDataOpts + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 []*types.ChangesetSyncData + // Result1 is the value of the 2nd result returned from this method + // invocation. + Result1 error +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreListChangesetSyncDataFuncCall) Args() []interface{} { + return []interface{}{c.Arg0, c.Arg1} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreListChangesetSyncDataFuncCall) Results() []interface{} { + return []interface{}{c.Result0, c.Result1} +} + +// SyncStoreListCodeHostsFunc describes the behavior when the ListCodeHosts +// method of the parent MockSyncStore instance is invoked. +type SyncStoreListCodeHostsFunc struct { + defaultHook func(context.Context, store.ListCodeHostsOpts) ([]*types.CodeHost, error) + hooks []func(context.Context, store.ListCodeHostsOpts) ([]*types.CodeHost, error) + history []SyncStoreListCodeHostsFuncCall + mutex sync.Mutex +} + +// ListCodeHosts delegates to the next hook function in the queue and stores +// the parameter and result values of this invocation. +func (m *MockSyncStore) ListCodeHosts(v0 context.Context, v1 store.ListCodeHostsOpts) ([]*types.CodeHost, error) { + r0, r1 := m.ListCodeHostsFunc.nextHook()(v0, v1) + m.ListCodeHostsFunc.appendCall(SyncStoreListCodeHostsFuncCall{v0, v1, r0, r1}) + return r0, r1 +} + +// SetDefaultHook sets function that is called when the ListCodeHosts method +// of the parent MockSyncStore instance is invoked and the hook queue is +// empty. +func (f *SyncStoreListCodeHostsFunc) SetDefaultHook(hook func(context.Context, store.ListCodeHostsOpts) ([]*types.CodeHost, error)) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// ListCodeHosts method of the parent MockSyncStore instance invokes the +// hook at the front of the queue and discards it. After the queue is empty, +// the default hook function is invoked for any future action. +func (f *SyncStoreListCodeHostsFunc) PushHook(hook func(context.Context, store.ListCodeHostsOpts) ([]*types.CodeHost, error)) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreListCodeHostsFunc) SetDefaultReturn(r0 []*types.CodeHost, r1 error) { + f.SetDefaultHook(func(context.Context, store.ListCodeHostsOpts) ([]*types.CodeHost, error) { + return r0, r1 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreListCodeHostsFunc) PushReturn(r0 []*types.CodeHost, r1 error) { + f.PushHook(func(context.Context, store.ListCodeHostsOpts) ([]*types.CodeHost, error) { + return r0, r1 + }) +} + +func (f *SyncStoreListCodeHostsFunc) nextHook() func(context.Context, store.ListCodeHostsOpts) ([]*types.CodeHost, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreListCodeHostsFunc) appendCall(r0 SyncStoreListCodeHostsFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreListCodeHostsFuncCall objects +// describing the invocations of this function. +func (f *SyncStoreListCodeHostsFunc) History() []SyncStoreListCodeHostsFuncCall { + f.mutex.Lock() + history := make([]SyncStoreListCodeHostsFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreListCodeHostsFuncCall is an object that describes an invocation +// of method ListCodeHosts on an instance of MockSyncStore. +type SyncStoreListCodeHostsFuncCall struct { + // Arg0 is the value of the 1st argument passed to this method + // invocation. + Arg0 context.Context + // Arg1 is the value of the 2nd argument passed to this method + // invocation. + Arg1 store.ListCodeHostsOpts + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 []*types.CodeHost + // Result1 is the value of the 2nd result returned from this method + // invocation. + Result1 error +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreListCodeHostsFuncCall) Args() []interface{} { + return []interface{}{c.Arg0, c.Arg1} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreListCodeHostsFuncCall) Results() []interface{} { + return []interface{}{c.Result0, c.Result1} +} + +// SyncStoreReposFunc describes the behavior when the Repos method of the +// parent MockSyncStore instance is invoked. +type SyncStoreReposFunc struct { + defaultHook func() *database.RepoStore + hooks []func() *database.RepoStore + history []SyncStoreReposFuncCall + mutex sync.Mutex +} + +// Repos delegates to the next hook function in the queue and stores the +// parameter and result values of this invocation. +func (m *MockSyncStore) Repos() *database.RepoStore { + r0 := m.ReposFunc.nextHook()() + m.ReposFunc.appendCall(SyncStoreReposFuncCall{r0}) + return r0 +} + +// SetDefaultHook sets function that is called when the Repos method of the +// parent MockSyncStore instance is invoked and the hook queue is empty. +func (f *SyncStoreReposFunc) SetDefaultHook(hook func() *database.RepoStore) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// Repos method of the parent MockSyncStore instance invokes the hook at the +// front of the queue and discards it. After the queue is empty, the default +// hook function is invoked for any future action. +func (f *SyncStoreReposFunc) PushHook(hook func() *database.RepoStore) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreReposFunc) SetDefaultReturn(r0 *database.RepoStore) { + f.SetDefaultHook(func() *database.RepoStore { + return r0 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreReposFunc) PushReturn(r0 *database.RepoStore) { + f.PushHook(func() *database.RepoStore { + return r0 + }) +} + +func (f *SyncStoreReposFunc) nextHook() func() *database.RepoStore { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreReposFunc) appendCall(r0 SyncStoreReposFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreReposFuncCall objects describing +// the invocations of this function. +func (f *SyncStoreReposFunc) History() []SyncStoreReposFuncCall { + f.mutex.Lock() + history := make([]SyncStoreReposFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreReposFuncCall is an object that describes an invocation of +// method Repos on an instance of MockSyncStore. +type SyncStoreReposFuncCall struct { + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 *database.RepoStore +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreReposFuncCall) Args() []interface{} { + return []interface{}{} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreReposFuncCall) Results() []interface{} { + return []interface{}{c.Result0} +} + +// SyncStoreTransactFunc describes the behavior when the Transact method of +// the parent MockSyncStore instance is invoked. +type SyncStoreTransactFunc struct { + defaultHook func(context.Context) (*store.Store, error) + hooks []func(context.Context) (*store.Store, error) + history []SyncStoreTransactFuncCall + mutex sync.Mutex +} + +// Transact delegates to the next hook function in the queue and stores the +// parameter and result values of this invocation. +func (m *MockSyncStore) Transact(v0 context.Context) (*store.Store, error) { + r0, r1 := m.TransactFunc.nextHook()(v0) + m.TransactFunc.appendCall(SyncStoreTransactFuncCall{v0, r0, r1}) + return r0, r1 +} + +// SetDefaultHook sets function that is called when the Transact method of +// the parent MockSyncStore instance is invoked and the hook queue is empty. +func (f *SyncStoreTransactFunc) SetDefaultHook(hook func(context.Context) (*store.Store, error)) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// Transact method of the parent MockSyncStore instance invokes the hook at +// the front of the queue and discards it. After the queue is empty, the +// default hook function is invoked for any future action. +func (f *SyncStoreTransactFunc) PushHook(hook func(context.Context) (*store.Store, error)) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreTransactFunc) SetDefaultReturn(r0 *store.Store, r1 error) { + f.SetDefaultHook(func(context.Context) (*store.Store, error) { + return r0, r1 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreTransactFunc) PushReturn(r0 *store.Store, r1 error) { + f.PushHook(func(context.Context) (*store.Store, error) { + return r0, r1 + }) +} + +func (f *SyncStoreTransactFunc) nextHook() func(context.Context) (*store.Store, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreTransactFunc) appendCall(r0 SyncStoreTransactFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreTransactFuncCall objects +// describing the invocations of this function. +func (f *SyncStoreTransactFunc) History() []SyncStoreTransactFuncCall { + f.mutex.Lock() + history := make([]SyncStoreTransactFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreTransactFuncCall is an object that describes an invocation of +// method Transact on an instance of MockSyncStore. +type SyncStoreTransactFuncCall struct { + // Arg0 is the value of the 1st argument passed to this method + // invocation. + Arg0 context.Context + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 *store.Store + // Result1 is the value of the 2nd result returned from this method + // invocation. + Result1 error +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreTransactFuncCall) Args() []interface{} { + return []interface{}{c.Arg0} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreTransactFuncCall) Results() []interface{} { + return []interface{}{c.Result0, c.Result1} +} + +// SyncStoreUpdateChangesetCodeHostStateFunc describes the behavior when the +// UpdateChangesetCodeHostState method of the parent MockSyncStore instance +// is invoked. +type SyncStoreUpdateChangesetCodeHostStateFunc struct { + defaultHook func(context.Context, *types.Changeset) error + hooks []func(context.Context, *types.Changeset) error + history []SyncStoreUpdateChangesetCodeHostStateFuncCall + mutex sync.Mutex +} + +// UpdateChangesetCodeHostState delegates to the next hook function in the +// queue and stores the parameter and result values of this invocation. +func (m *MockSyncStore) UpdateChangesetCodeHostState(v0 context.Context, v1 *types.Changeset) error { + r0 := m.UpdateChangesetCodeHostStateFunc.nextHook()(v0, v1) + m.UpdateChangesetCodeHostStateFunc.appendCall(SyncStoreUpdateChangesetCodeHostStateFuncCall{v0, v1, r0}) + return r0 +} + +// SetDefaultHook sets function that is called when the +// UpdateChangesetCodeHostState method of the parent MockSyncStore instance +// is invoked and the hook queue is empty. +func (f *SyncStoreUpdateChangesetCodeHostStateFunc) SetDefaultHook(hook func(context.Context, *types.Changeset) error) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// UpdateChangesetCodeHostState method of the parent MockSyncStore instance +// invokes the hook at the front of the queue and discards it. After the +// queue is empty, the default hook function is invoked for any future +// action. +func (f *SyncStoreUpdateChangesetCodeHostStateFunc) PushHook(hook func(context.Context, *types.Changeset) error) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreUpdateChangesetCodeHostStateFunc) SetDefaultReturn(r0 error) { + f.SetDefaultHook(func(context.Context, *types.Changeset) error { + return r0 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreUpdateChangesetCodeHostStateFunc) PushReturn(r0 error) { + f.PushHook(func(context.Context, *types.Changeset) error { + return r0 + }) +} + +func (f *SyncStoreUpdateChangesetCodeHostStateFunc) nextHook() func(context.Context, *types.Changeset) error { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreUpdateChangesetCodeHostStateFunc) appendCall(r0 SyncStoreUpdateChangesetCodeHostStateFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of +// SyncStoreUpdateChangesetCodeHostStateFuncCall objects describing the +// invocations of this function. +func (f *SyncStoreUpdateChangesetCodeHostStateFunc) History() []SyncStoreUpdateChangesetCodeHostStateFuncCall { + f.mutex.Lock() + history := make([]SyncStoreUpdateChangesetCodeHostStateFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreUpdateChangesetCodeHostStateFuncCall is an object that describes +// an invocation of method UpdateChangesetCodeHostState on an instance of +// MockSyncStore. +type SyncStoreUpdateChangesetCodeHostStateFuncCall struct { + // Arg0 is the value of the 1st argument passed to this method + // invocation. + Arg0 context.Context + // Arg1 is the value of the 2nd argument passed to this method + // invocation. + Arg1 *types.Changeset + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 error +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreUpdateChangesetCodeHostStateFuncCall) Args() []interface{} { + return []interface{}{c.Arg0, c.Arg1} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreUpdateChangesetCodeHostStateFuncCall) Results() []interface{} { + return []interface{}{c.Result0} +} + +// SyncStoreUpsertChangesetEventsFunc describes the behavior when the +// UpsertChangesetEvents method of the parent MockSyncStore instance is +// invoked. +type SyncStoreUpsertChangesetEventsFunc struct { + defaultHook func(context.Context, ...*types.ChangesetEvent) error + hooks []func(context.Context, ...*types.ChangesetEvent) error + history []SyncStoreUpsertChangesetEventsFuncCall + mutex sync.Mutex +} + +// UpsertChangesetEvents delegates to the next hook function in the queue +// and stores the parameter and result values of this invocation. +func (m *MockSyncStore) UpsertChangesetEvents(v0 context.Context, v1 ...*types.ChangesetEvent) error { + r0 := m.UpsertChangesetEventsFunc.nextHook()(v0, v1...) + m.UpsertChangesetEventsFunc.appendCall(SyncStoreUpsertChangesetEventsFuncCall{v0, v1, r0}) + return r0 +} + +// SetDefaultHook sets function that is called when the +// UpsertChangesetEvents method of the parent MockSyncStore instance is +// invoked and the hook queue is empty. +func (f *SyncStoreUpsertChangesetEventsFunc) SetDefaultHook(hook func(context.Context, ...*types.ChangesetEvent) error) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// UpsertChangesetEvents method of the parent MockSyncStore instance invokes +// the hook at the front of the queue and discards it. After the queue is +// empty, the default hook function is invoked for any future action. +func (f *SyncStoreUpsertChangesetEventsFunc) PushHook(hook func(context.Context, ...*types.ChangesetEvent) error) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreUpsertChangesetEventsFunc) SetDefaultReturn(r0 error) { + f.SetDefaultHook(func(context.Context, ...*types.ChangesetEvent) error { + return r0 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreUpsertChangesetEventsFunc) PushReturn(r0 error) { + f.PushHook(func(context.Context, ...*types.ChangesetEvent) error { + return r0 + }) +} + +func (f *SyncStoreUpsertChangesetEventsFunc) nextHook() func(context.Context, ...*types.ChangesetEvent) error { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreUpsertChangesetEventsFunc) appendCall(r0 SyncStoreUpsertChangesetEventsFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreUpsertChangesetEventsFuncCall +// objects describing the invocations of this function. +func (f *SyncStoreUpsertChangesetEventsFunc) History() []SyncStoreUpsertChangesetEventsFuncCall { + f.mutex.Lock() + history := make([]SyncStoreUpsertChangesetEventsFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreUpsertChangesetEventsFuncCall is an object that describes an +// invocation of method UpsertChangesetEvents on an instance of +// MockSyncStore. +type SyncStoreUpsertChangesetEventsFuncCall struct { + // Arg0 is the value of the 1st argument passed to this method + // invocation. + Arg0 context.Context + // Arg1 is a slice containing the values of the variadic arguments + // passed to this method invocation. + Arg1 []*types.ChangesetEvent + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 error +} + +// Args returns an interface slice containing the arguments of this +// invocation. The variadic slice argument is flattened in this array such +// that one positional argument and three variadic arguments would result in +// a slice of four, not two. +func (c SyncStoreUpsertChangesetEventsFuncCall) Args() []interface{} { + trailing := []interface{}{} + for _, val := range c.Arg1 { + trailing = append(trailing, val) + } + + return append([]interface{}{c.Arg0}, trailing...) +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreUpsertChangesetEventsFuncCall) Results() []interface{} { + return []interface{}{c.Result0} +} + +// SyncStoreUserCredentialsFunc describes the behavior when the +// UserCredentials method of the parent MockSyncStore instance is invoked. +type SyncStoreUserCredentialsFunc struct { + defaultHook func() *database.UserCredentialsStore + hooks []func() *database.UserCredentialsStore + history []SyncStoreUserCredentialsFuncCall + mutex sync.Mutex +} + +// UserCredentials delegates to the next hook function in the queue and +// stores the parameter and result values of this invocation. +func (m *MockSyncStore) UserCredentials() *database.UserCredentialsStore { + r0 := m.UserCredentialsFunc.nextHook()() + m.UserCredentialsFunc.appendCall(SyncStoreUserCredentialsFuncCall{r0}) + return r0 +} + +// SetDefaultHook sets function that is called when the UserCredentials +// method of the parent MockSyncStore instance is invoked and the hook queue +// is empty. +func (f *SyncStoreUserCredentialsFunc) SetDefaultHook(hook func() *database.UserCredentialsStore) { + f.defaultHook = hook +} + +// PushHook adds a function to the end of hook queue. Each invocation of the +// UserCredentials method of the parent MockSyncStore instance invokes the +// hook at the front of the queue and discards it. After the queue is empty, +// the default hook function is invoked for any future action. +func (f *SyncStoreUserCredentialsFunc) PushHook(hook func() *database.UserCredentialsStore) { + f.mutex.Lock() + f.hooks = append(f.hooks, hook) + f.mutex.Unlock() +} + +// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns +// the given values. +func (f *SyncStoreUserCredentialsFunc) SetDefaultReturn(r0 *database.UserCredentialsStore) { + f.SetDefaultHook(func() *database.UserCredentialsStore { + return r0 + }) +} + +// PushReturn calls PushDefaultHook with a function that returns the given +// values. +func (f *SyncStoreUserCredentialsFunc) PushReturn(r0 *database.UserCredentialsStore) { + f.PushHook(func() *database.UserCredentialsStore { + return r0 + }) +} + +func (f *SyncStoreUserCredentialsFunc) nextHook() func() *database.UserCredentialsStore { + f.mutex.Lock() + defer f.mutex.Unlock() + + if len(f.hooks) == 0 { + return f.defaultHook + } + + hook := f.hooks[0] + f.hooks = f.hooks[1:] + return hook +} + +func (f *SyncStoreUserCredentialsFunc) appendCall(r0 SyncStoreUserCredentialsFuncCall) { + f.mutex.Lock() + f.history = append(f.history, r0) + f.mutex.Unlock() +} + +// History returns a sequence of SyncStoreUserCredentialsFuncCall objects +// describing the invocations of this function. +func (f *SyncStoreUserCredentialsFunc) History() []SyncStoreUserCredentialsFuncCall { + f.mutex.Lock() + history := make([]SyncStoreUserCredentialsFuncCall, len(f.history)) + copy(history, f.history) + f.mutex.Unlock() + + return history +} + +// SyncStoreUserCredentialsFuncCall is an object that describes an +// invocation of method UserCredentials on an instance of MockSyncStore. +type SyncStoreUserCredentialsFuncCall struct { + // Result0 is the value of the 1st result returned from this method + // invocation. + Result0 *database.UserCredentialsStore +} + +// Args returns an interface slice containing the arguments of this +// invocation. +func (c SyncStoreUserCredentialsFuncCall) Args() []interface{} { + return []interface{}{} +} + +// Results returns an interface slice containing the results of this +// invocation. +func (c SyncStoreUserCredentialsFuncCall) Results() []interface{} { + return []interface{}{c.Result0} +} diff --git a/enterprise/internal/batches/syncer/store.go b/enterprise/internal/batches/syncer/store.go new file mode 100644 index 00000000000..4c66b55e16e --- /dev/null +++ b/enterprise/internal/batches/syncer/store.go @@ -0,0 +1,27 @@ +package syncer + +import ( + "context" + "time" + + "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/store" + btypes "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/types" + "github.com/sourcegraph/sourcegraph/internal/database" + "github.com/sourcegraph/sourcegraph/internal/database/dbutil" +) + +type SyncStore interface { + ListCodeHosts(ctx context.Context, opts store.ListCodeHostsOpts) ([]*btypes.CodeHost, error) + ListChangesetSyncData(context.Context, store.ListChangesetSyncDataOpts) ([]*btypes.ChangesetSyncData, error) + GetChangeset(context.Context, store.GetChangesetOpts) (*btypes.Changeset, error) + UpdateChangesetCodeHostState(ctx context.Context, cs *btypes.Changeset) error + UpsertChangesetEvents(ctx context.Context, cs ...*btypes.ChangesetEvent) error + GetSiteCredential(ctx context.Context, opts store.GetSiteCredentialOpts) (*btypes.SiteCredential, error) + Transact(context.Context) (*store.Store, error) + Repos() *database.RepoStore + ExternalServices() *database.ExternalServiceStore + Clock() func() time.Time + DB() dbutil.DB + GetExternalServiceIDs(ctx context.Context, opts store.GetExternalServiceIDsOpts) ([]int64, error) + UserCredentials() *database.UserCredentialsStore +} diff --git a/enterprise/internal/batches/syncer/syncer.go b/enterprise/internal/batches/syncer/syncer.go index 44b964ce93b..ea02600c3c2 100644 --- a/enterprise/internal/batches/syncer/syncer.go +++ b/enterprise/internal/batches/syncer/syncer.go @@ -9,25 +9,29 @@ import ( "github.com/cockroachdb/errors" "github.com/inconshreveable/log15" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/sources" "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/state" "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/store" btypes "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/types" - "github.com/sourcegraph/sourcegraph/internal/api" "github.com/sourcegraph/sourcegraph/internal/conf" - "github.com/sourcegraph/sourcegraph/internal/database" - "github.com/sourcegraph/sourcegraph/internal/database/dbutil" + "github.com/sourcegraph/sourcegraph/internal/goroutine" "github.com/sourcegraph/sourcegraph/internal/httpcli" + "github.com/sourcegraph/sourcegraph/internal/observation" "github.com/sourcegraph/sourcegraph/internal/types" ) +// externalServiceSyncerInterval is the time in between synchronizations with the +// database to start/stop syncers as needed. +const externalServiceSyncerInterval = 1 * time.Minute + // SyncRegistry manages a changesetSyncer per code host type SyncRegistry struct { ctx context.Context + cancel context.CancelFunc syncStore SyncStore httpFactory *httpcli.Factory + metrics *syncerMetrics // Used to receive high priority sync requests priorityNotify chan []int64 @@ -37,45 +41,64 @@ type SyncRegistry struct { syncers map[string]*changesetSyncer } -type SyncStore interface { - ListCodeHosts(ctx context.Context, opts store.ListCodeHostsOpts) ([]*btypes.CodeHost, error) - ListChangesetSyncData(context.Context, store.ListChangesetSyncDataOpts) ([]*btypes.ChangesetSyncData, error) - GetChangeset(context.Context, store.GetChangesetOpts) (*btypes.Changeset, error) - UpdateChangesetCodeHostState(ctx context.Context, cs *btypes.Changeset) error - UpsertChangesetEvents(ctx context.Context, cs ...*btypes.ChangesetEvent) error - GetSiteCredential(ctx context.Context, opts store.GetSiteCredentialOpts) (*btypes.SiteCredential, error) - Transact(context.Context) (*store.Store, error) - Repos() *database.RepoStore - ExternalServices() *database.ExternalServiceStore - Clock() func() time.Time - DB() dbutil.DB - GetExternalServiceIDs(ctx context.Context, opts store.GetExternalServiceIDsOpts) ([]int64, error) - UserCredentials() *database.UserCredentialsStore -} +var _ goroutine.BackgroundRoutine = &SyncRegistry{} // NewSyncRegistry creates a new sync registry which starts a syncer for each code host and will update them // when external services are changed, added or removed. -func NewSyncRegistry(ctx context.Context, cstore SyncStore, cf *httpcli.Factory) *SyncRegistry { - r := &SyncRegistry{ +func NewSyncRegistry(ctx context.Context, bstore SyncStore, cf *httpcli.Factory, observationContext *observation.Context) *SyncRegistry { + ctx, cancel := context.WithCancel(ctx) + return &SyncRegistry{ ctx: ctx, - syncStore: cstore, + cancel: cancel, + syncStore: bstore, httpFactory: cf, priorityNotify: make(chan []int64, 500), syncers: make(map[string]*changesetSyncer), + metrics: makeMetrics(observationContext), } +} - if err := r.syncCodeHosts(ctx); err != nil { +func (s *SyncRegistry) Start() { + // Fetch initial list of syncers. + if err := s.syncCodeHosts(s.ctx); err != nil { log15.Error("Fetching initial list of code hosts", "err", err) } - go r.handlePriorityItems() + goroutine.Go(func() { + s.handlePriorityItems() + }) - return r + externalServiceSyncer := goroutine.NewPeriodicGoroutine( + s.ctx, + externalServiceSyncerInterval, + goroutine.NewHandlerWithErrorMessage("Batch Changes syncer external service sync", func(ctx context.Context) error { + return s.syncCodeHosts(ctx) + }), + ) + + goroutine.MonitorBackgroundRoutines(s.ctx, externalServiceSyncer) } -// Add adds a syncer for the code host associated with the supplied code host if the syncer hasn't +func (s *SyncRegistry) Stop() { + s.cancel() +} + +// EnqueueChangesetSyncs will enqueue the changesets with the supplied ids for high priority syncing. +// An error indicates that no changesets have been enqueued. +func (s *SyncRegistry) EnqueueChangesetSyncs(ctx context.Context, ids []int64) error { + // The channel below is buffered so we'll usually send without blocking. + // It is important not to block here as this method is called from the UI + select { + case s.priorityNotify <- ids: + default: + return errors.New("high priority sync capacity reached") + } + return nil +} + +// addCodeHostSyncer adds a syncer for the code host associated with the supplied code host if the syncer hasn't // already been added and starts it. -func (s *SyncRegistry) Add(codeHost *btypes.CodeHost) { +func (s *SyncRegistry) addCodeHostSyncer(codeHost *btypes.CodeHost) { // This should never happen since the store does the filtering for us, but let's be super duper extra cautious. if !codeHost.IsSupported() { log15.Info("Code host not support by batch changes", "type", codeHost.ExternalServiceType, "url", codeHost.ExternalServiceID) @@ -101,33 +124,14 @@ func (s *SyncRegistry) Add(codeHost *btypes.CodeHost) { codeHostURL: syncerKey, cancel: cancel, priorityNotify: make(chan []int64, 500), + metrics: s.metrics, } s.syncers[syncerKey] = syncer - go syncer.Run(ctx) -} - -// EnqueueChangesetSyncs will enqueue the changesets with the supplied ids for high priority syncing. -// An error indicates that no changesets have been enqueued. -func (s *SyncRegistry) EnqueueChangesetSyncs(ctx context.Context, ids []int64) error { - // The channel below is buffered so we'll usually send without blocking. - // It is important not to block here as this method is called from the UI - select { - case s.priorityNotify <- ids: - default: - return errors.New("high priority sync capacity reached") - } - return nil -} - -// HandleExternalServiceSync handles changes to external services. -func (s *SyncRegistry) HandleExternalServiceSync(es api.ExternalService) { - if btypes.IsKindSupported(es.Kind) { - if err := s.syncCodeHosts(s.ctx); err != nil { - log15.Error("Syncing on change of code hosts", "err", err) - } - } + goroutine.Go(func() { + syncer.Run(ctx) + }) } // handlePriorityItems fetches changesets in the priority queue from the database and passes them @@ -189,7 +193,7 @@ func (s *SyncRegistry) syncCodeHosts(ctx context.Context) error { // Add and start syncers for _, host := range codeHosts { codeHostsByExternalServiceID[host.ExternalServiceID] = host - s.Add(host) + s.addCodeHostSyncer(host) } s.mu.Lock() @@ -214,6 +218,8 @@ type changesetSyncer struct { syncStore SyncStore httpFactory *httpcli.Factory + metrics *syncerMetrics + codeHostURL string // scheduleInterval determines how often a new schedule will be computed. @@ -230,42 +236,52 @@ type changesetSyncer struct { cancel context.CancelFunc } -var syncerMetrics = struct { +type syncerMetrics struct { syncs *prometheus.CounterVec priorityQueued *prometheus.CounterVec syncDuration *prometheus.HistogramVec computeScheduleDuration *prometheus.HistogramVec scheduleSize *prometheus.GaugeVec behindSchedule *prometheus.GaugeVec -}{} +} -func init() { - syncerMetrics.syncs = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "src_repoupdater_changeset_syncer_syncs", - Help: "Total number of changeset syncs", - }, []string{"codehost", "success"}) - syncerMetrics.priorityQueued = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "src_repoupdater_changeset_syncer_priority_queued", - Help: "Total number of priority items added to queue", - }, []string{"codehost"}) - syncerMetrics.syncDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Name: "src_repoupdater_changeset_syncer_sync_duration_seconds", - Help: "Time spent syncing changesets", - Buckets: []float64{1, 2, 5, 10, 30, 60, 120}, - }, []string{"codehost", "success"}) - syncerMetrics.computeScheduleDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Name: "src_repoupdater_changeset_syncer_compute_schedule_duration_seconds", - Help: "Time spent computing changeset schedule", - Buckets: []float64{1, 2, 5, 10, 30, 60, 120}, - }, []string{"codehost", "success"}) - syncerMetrics.scheduleSize = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "src_repoupdater_changeset_syncer_schedule_size", - Help: "The number of changesets scheduled to sync", - }, []string{"codehost"}) - syncerMetrics.behindSchedule = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "src_repoupdater_changeset_syncer_behind_schedule", - Help: "The number of changesets behind schedule", - }, []string{"codehost"}) +func makeMetrics(observationContext *observation.Context) *syncerMetrics { + metrics := &syncerMetrics{ + syncs: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: "src_repoupdater_changeset_syncer_syncs", + Help: "Total number of changeset syncs", + }, []string{"codehost", "success"}), + priorityQueued: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: "src_repoupdater_changeset_syncer_priority_queued", + Help: "Total number of priority items added to queue", + }, []string{"codehost"}), + syncDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: "src_repoupdater_changeset_syncer_sync_duration_seconds", + Help: "Time spent syncing changesets", + Buckets: []float64{1, 2, 5, 10, 30, 60, 120}, + }, []string{"codehost", "success"}), + computeScheduleDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: "src_repoupdater_changeset_syncer_compute_schedule_duration_seconds", + Help: "Time spent computing changeset schedule", + Buckets: []float64{1, 2, 5, 10, 30, 60, 120}, + }, []string{"codehost", "success"}), + scheduleSize: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "src_repoupdater_changeset_syncer_schedule_size", + Help: "The number of changesets scheduled to sync", + }, []string{"codehost"}), + behindSchedule: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "src_repoupdater_changeset_syncer_behind_schedule", + Help: "The number of changesets behind schedule", + }, []string{"codehost"}), + } + observationContext.Registerer.MustRegister(metrics.syncs) + observationContext.Registerer.MustRegister(metrics.priorityQueued) + observationContext.Registerer.MustRegister(metrics.syncDuration) + observationContext.Registerer.MustRegister(metrics.computeScheduleDuration) + observationContext.Registerer.MustRegister(metrics.scheduleSize) + observationContext.Registerer.MustRegister(metrics.behindSchedule) + + return metrics } // Run will start the process of changeset syncing. It is long running @@ -330,12 +346,12 @@ func (s *changesetSyncer) Run(ctx context.Context) { start := s.syncStore.Clock()() schedule, err := s.computeSchedule(ctx) labelValues := []string{s.codeHostURL, strconv.FormatBool(err == nil)} - syncerMetrics.computeScheduleDuration.WithLabelValues(labelValues...).Observe(s.syncStore.Clock()().Sub(start).Seconds()) + s.metrics.computeScheduleDuration.WithLabelValues(labelValues...).Observe(s.syncStore.Clock()().Sub(start).Seconds()) if err != nil { log15.Error("Computing queue", "err", err) continue } - syncerMetrics.scheduleSize.WithLabelValues(s.codeHostURL).Set(float64(len(schedule))) + s.metrics.scheduleSize.WithLabelValues(s.codeHostURL).Set(float64(len(schedule))) s.queue.Upsert(schedule...) var behindSchedule int now := s.syncStore.Clock()() @@ -344,13 +360,13 @@ func (s *changesetSyncer) Run(ctx context.Context) { behindSchedule++ } } - syncerMetrics.behindSchedule.WithLabelValues(s.codeHostURL).Set(float64(behindSchedule)) + s.metrics.behindSchedule.WithLabelValues(s.codeHostURL).Set(float64(behindSchedule)) case <-timerChan: start := s.syncStore.Clock()() err := s.syncFunc(ctx, next.changesetID) labelValues := []string{s.codeHostURL, strconv.FormatBool(err == nil)} - syncerMetrics.syncDuration.WithLabelValues(labelValues...).Observe(s.syncStore.Clock()().Sub(start).Seconds()) - syncerMetrics.syncs.WithLabelValues(labelValues...).Add(1) + s.metrics.syncDuration.WithLabelValues(labelValues...).Observe(s.syncStore.Clock()().Sub(start).Seconds()) + s.metrics.syncs.WithLabelValues(labelValues...).Add(1) if err != nil { log15.Error("Syncing changeset", "err", err) @@ -359,7 +375,7 @@ func (s *changesetSyncer) Run(ctx context.Context) { // Remove item now that it has been processed s.queue.Remove(next.changesetID) - syncerMetrics.scheduleSize.WithLabelValues(s.codeHostURL).Dec() + s.metrics.scheduleSize.WithLabelValues(s.codeHostURL).Dec() case ids := <-s.priorityNotify: if timer != nil { timer.Stop() @@ -377,9 +393,9 @@ func (s *changesetSyncer) Run(ctx context.Context) { } item.priority = priorityHigh s.queue.Upsert(item) - syncerMetrics.scheduleSize.WithLabelValues(s.codeHostURL).Inc() + s.metrics.scheduleSize.WithLabelValues(s.codeHostURL).Inc() } - syncerMetrics.priorityQueued.WithLabelValues(s.codeHostURL).Add(float64(len(ids))) + s.metrics.priorityQueued.WithLabelValues(s.codeHostURL).Add(float64(len(ids))) } } } diff --git a/enterprise/internal/batches/syncer/syncer_test.go b/enterprise/internal/batches/syncer/syncer_test.go index 446af765209..df94f1fd1bc 100644 --- a/enterprise/internal/batches/syncer/syncer_test.go +++ b/enterprise/internal/batches/syncer/syncer_test.go @@ -12,33 +12,37 @@ import ( btypes "github.com/sourcegraph/sourcegraph/enterprise/internal/batches/types" "github.com/sourcegraph/sourcegraph/internal/api" "github.com/sourcegraph/sourcegraph/internal/database" - "github.com/sourcegraph/sourcegraph/internal/database/dbtesting" - "github.com/sourcegraph/sourcegraph/internal/database/dbutil" "github.com/sourcegraph/sourcegraph/internal/extsvc" "github.com/sourcegraph/sourcegraph/internal/extsvc/auth" "github.com/sourcegraph/sourcegraph/internal/httpcli" + "github.com/sourcegraph/sourcegraph/internal/observation" "github.com/sourcegraph/sourcegraph/internal/timeutil" "github.com/sourcegraph/sourcegraph/internal/types" ) +func newTestStore() *MockSyncStore { + s := NewMockSyncStore() + s.ClockFunc.SetDefaultReturn(timeutil.Now) + return s +} + func TestSyncerRun(t *testing.T) { t.Parallel() t.Run("Sync due", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) now := time.Now() - syncStore := MockSyncStore{ - listChangesetSyncData: func(ctx context.Context, opts store.ListChangesetSyncDataOpts) ([]*btypes.ChangesetSyncData, error) { - return []*btypes.ChangesetSyncData{ - { - ChangesetID: 1, - UpdatedAt: now.Add(-2 * maxSyncDelay), - LatestEvent: now.Add(-2 * maxSyncDelay), - ExternalUpdatedAt: now.Add(-2 * maxSyncDelay), - }, - }, nil + + syncStore := newTestStore() + syncStore.ListChangesetSyncDataFunc.SetDefaultReturn([]*btypes.ChangesetSyncData{ + { + ChangesetID: 1, + UpdatedAt: now.Add(-2 * maxSyncDelay), + LatestEvent: now.Add(-2 * maxSyncDelay), + ExternalUpdatedAt: now.Add(-2 * maxSyncDelay), }, - } + }, nil) + syncFunc := func(ctx context.Context, ids int64) error { cancel() return nil @@ -47,6 +51,7 @@ func TestSyncerRun(t *testing.T) { syncStore: syncStore, scheduleInterval: 10 * time.Minute, syncFunc: syncFunc, + metrics: makeMetrics(&observation.TestContext), } go syncer.Run(ctx) select { @@ -61,34 +66,31 @@ func TestSyncerRun(t *testing.T) { defer cancel() now := time.Now() updateCalled := false - syncStore := MockSyncStore{ - getChangeset: func(context.Context, store.GetChangesetOpts) (*btypes.Changeset, error) { - // Return ErrNoResults, which is the result you get when the changeset preconditions aren't met anymore. - // The sync data checks for the reconciler state and if it changed since the sync data was loaded, - // we don't get back the changeset here and skip it. - // - // If we don't return ErrNoResults, the rest of the test will fail, because not all - // methods of sync store are mocked. - return nil, store.ErrNoResults + syncStore := newTestStore() + // Return ErrNoResults, which is the result you get when the changeset preconditions aren't met anymore. + // The sync data checks for the reconciler state and if it changed since the sync data was loaded, + // we don't get back the changeset here and skip it. + // + // If we don't return ErrNoResults, the rest of the test will fail, because not all + // methods of sync store are mocked. + syncStore.GetChangesetFunc.SetDefaultReturn(nil, store.ErrNoResults) + syncStore.UpdateChangesetCodeHostStateFunc.SetDefaultHook(func(context.Context, *btypes.Changeset) error { + updateCalled = true + return nil + }) + syncStore.ListChangesetSyncDataFunc.SetDefaultReturn([]*btypes.ChangesetSyncData{ + { + ChangesetID: 1, + UpdatedAt: now.Add(-2 * maxSyncDelay), + LatestEvent: now.Add(-2 * maxSyncDelay), + ExternalUpdatedAt: now.Add(-2 * maxSyncDelay), }, - updateChangesetCodeHostState: func(context.Context, *btypes.Changeset) error { - updateCalled = true - return nil - }, - listChangesetSyncData: func(ctx context.Context, opts store.ListChangesetSyncDataOpts) ([]*btypes.ChangesetSyncData, error) { - return []*btypes.ChangesetSyncData{ - { - ChangesetID: 1, - UpdatedAt: now.Add(-2 * maxSyncDelay), - LatestEvent: now.Add(-2 * maxSyncDelay), - ExternalUpdatedAt: now.Add(-2 * maxSyncDelay), - }, - }, nil - }, - } + }, nil) + syncer := &changesetSyncer{ syncStore: syncStore, scheduleInterval: 10 * time.Minute, + metrics: makeMetrics(&observation.TestContext), } syncer.Run(ctx) if updateCalled { @@ -100,18 +102,16 @@ func TestSyncerRun(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() now := time.Now() - syncStore := MockSyncStore{ - listChangesetSyncData: func(ctx context.Context, opts store.ListChangesetSyncDataOpts) ([]*btypes.ChangesetSyncData, error) { - return []*btypes.ChangesetSyncData{ - { - ChangesetID: 1, - UpdatedAt: now, - LatestEvent: now, - ExternalUpdatedAt: now, - }, - }, nil + syncStore := newTestStore() + syncStore.ListChangesetSyncDataFunc.SetDefaultReturn([]*btypes.ChangesetSyncData{ + { + ChangesetID: 1, + UpdatedAt: now, + LatestEvent: now, + ExternalUpdatedAt: now, }, - } + }, nil) + var syncCalled bool syncFunc := func(ctx context.Context, ids int64) error { syncCalled = true @@ -121,6 +121,7 @@ func TestSyncerRun(t *testing.T) { syncStore: syncStore, scheduleInterval: 10 * time.Minute, syncFunc: syncFunc, + metrics: makeMetrics(&observation.TestContext), } syncer.Run(ctx) if syncCalled { @@ -131,20 +132,17 @@ func TestSyncerRun(t *testing.T) { t.Run("Priority added", func(t *testing.T) { // Empty schedule but then we add an item ctx, cancel := context.WithCancel(context.Background()) - syncStore := MockSyncStore{ - listChangesetSyncData: func(ctx context.Context, opts store.ListChangesetSyncDataOpts) ([]*btypes.ChangesetSyncData, error) { - return []*btypes.ChangesetSyncData{}, nil - }, - } + syncFunc := func(ctx context.Context, ids int64) error { cancel() return nil } syncer := &changesetSyncer{ - syncStore: syncStore, + syncStore: newTestStore(), scheduleInterval: 10 * time.Minute, syncFunc: syncFunc, priorityNotify: make(chan []int64, 1), + metrics: makeMetrics(&observation.TestContext), } syncer.priorityNotify <- []int64{1} go syncer.Run(ctx) @@ -168,24 +166,27 @@ func TestSyncRegistry(t *testing.T) { codeHosts := []*btypes.CodeHost{{ExternalServiceID: externalServiceID, ExternalServiceType: extsvc.TypeGitHub}} - syncStore := MockSyncStore{ - listChangesetSyncData: func(ctx context.Context, opts store.ListChangesetSyncDataOpts) (data []*btypes.ChangesetSyncData, err error) { - return []*btypes.ChangesetSyncData{ - { - ChangesetID: 1, - UpdatedAt: now, - RepoExternalServiceID: externalServiceID, - }, - }, nil + syncStore := newTestStore() + syncStore.ListChangesetSyncDataFunc.SetDefaultReturn([]*btypes.ChangesetSyncData{ + { + ChangesetID: 1, + UpdatedAt: now, + RepoExternalServiceID: externalServiceID, }, - listCodeHosts: func(c context.Context, lcho store.ListCodeHostsOpts) ([]*btypes.CodeHost, error) { - return codeHosts, nil - }, - } + }, nil) + syncStore.ListCodeHostsFunc.SetDefaultHook(func(c context.Context, lcho store.ListCodeHostsOpts) ([]*btypes.CodeHost, error) { + return codeHosts, nil + }) - r := NewSyncRegistry(ctx, syncStore, nil) + r := NewSyncRegistry(ctx, syncStore, nil, &observation.TestContext) + + go r.Start() + t.Cleanup(r.Stop) + r.syncCodeHosts(ctx) + + assertSyncerCount := func(t *testing.T, want int) { + t.Helper() - assertSyncerCount := func(want int) { r.mu.Lock() if len(r.syncers) != want { t.Fatalf("Expected %d syncer, got %d", want, len(r.syncers)) @@ -193,30 +194,22 @@ func TestSyncRegistry(t *testing.T) { r.mu.Unlock() } - assertSyncerCount(1) + assertSyncerCount(t, 1) // Adding it again should have no effect - r.Add(&btypes.CodeHost{ExternalServiceID: "https://example.com/", ExternalServiceType: extsvc.TypeGitHub}) - assertSyncerCount(1) + r.addCodeHostSyncer(&btypes.CodeHost{ExternalServiceID: externalServiceID, ExternalServiceType: extsvc.TypeGitHub}) + assertSyncerCount(t, 1) // Simulate a service being removed oldCodeHosts := codeHosts codeHosts = []*btypes.CodeHost{} - r.HandleExternalServiceSync(api.ExternalService{ - ID: 1, - Kind: extsvc.KindGitHub, - Config: `{"url": "https://example.com/"}`, - DeletedAt: now, - }) - assertSyncerCount(0) + r.syncCodeHosts(ctx) + assertSyncerCount(t, 0) codeHosts = oldCodeHosts // And added again - r.HandleExternalServiceSync(api.ExternalService{ - ID: 1, - Kind: extsvc.KindGitHub, - }) - assertSyncerCount(1) + r.syncCodeHosts(ctx) + assertSyncerCount(t, 1) syncChan := make(chan int64, 1) @@ -230,6 +223,7 @@ func TestSyncRegistry(t *testing.T) { return nil }, priorityNotify: make(chan []int64, 1), + metrics: makeMetrics(&observation.TestContext), } go syncer.Run(ctx) @@ -297,17 +291,17 @@ func TestLoadChangesetSource(t *testing.T) { t.Cleanup(func() { database.Mocks.ExternalServices.List = nil }) + hasCredential := false - syncStore := &MockSyncStore{ - getSiteCredential: func(ctx context.Context, opts store.GetSiteCredentialOpts) (*btypes.SiteCredential, error) { - if hasCredential { - cred := &btypes.SiteCredential{} - cred.SetAuthenticator(ctx, &auth.OAuthBearerToken{Token: "456"}) - return cred, nil - } - return nil, store.ErrNoResults - }, - } + syncStore := newTestStore() + syncStore.GetSiteCredentialFunc.SetDefaultHook(func(ctx context.Context, opts store.GetSiteCredentialOpts) (*btypes.SiteCredential, error) { + if hasCredential { + cred := &btypes.SiteCredential{} + cred.SetAuthenticator(ctx, &auth.OAuthBearerToken{Token: "456"}) + return cred, nil + } + return nil, store.ErrNoResults + }) // If no site-credential exists, the token from the external service should be used. src, err := loadChangesetSource(ctx, cf, syncStore, repo) @@ -332,70 +326,3 @@ func TestLoadChangesetSource(t *testing.T) { t.Fatalf("invalid token used, want=%q have=%q", want, have) } } - -type MockSyncStore struct { - listCodeHosts func(context.Context, store.ListCodeHostsOpts) ([]*btypes.CodeHost, error) - listChangesetSyncData func(context.Context, store.ListChangesetSyncDataOpts) ([]*btypes.ChangesetSyncData, error) - getChangeset func(context.Context, store.GetChangesetOpts) (*btypes.Changeset, error) - updateChangesetCodeHostState func(context.Context, *btypes.Changeset) error - upsertChangesetEvents func(context.Context, ...*btypes.ChangesetEvent) error - getSiteCredential func(ctx context.Context, opts store.GetSiteCredentialOpts) (*btypes.SiteCredential, error) - getExternalServiceIDs func(ctx context.Context, opts store.GetExternalServiceIDsOpts) ([]int64, error) - transact func(context.Context) (*store.Store, error) -} - -func (m MockSyncStore) ListChangesetSyncData(ctx context.Context, opts store.ListChangesetSyncDataOpts) ([]*btypes.ChangesetSyncData, error) { - return m.listChangesetSyncData(ctx, opts) -} - -func (m MockSyncStore) GetChangeset(ctx context.Context, opts store.GetChangesetOpts) (*btypes.Changeset, error) { - return m.getChangeset(ctx, opts) -} - -func (m MockSyncStore) UpdateChangesetCodeHostState(ctx context.Context, c *btypes.Changeset) error { - return m.updateChangesetCodeHostState(ctx, c) -} - -func (m MockSyncStore) UpsertChangesetEvents(ctx context.Context, cs ...*btypes.ChangesetEvent) error { - return m.upsertChangesetEvents(ctx, cs...) -} - -func (m MockSyncStore) GetSiteCredential(ctx context.Context, opts store.GetSiteCredentialOpts) (*btypes.SiteCredential, error) { - return m.getSiteCredential(ctx, opts) -} - -func (m MockSyncStore) GetExternalServiceIDs(ctx context.Context, opts store.GetExternalServiceIDsOpts) ([]int64, error) { - return m.getExternalServiceIDs(ctx, opts) -} - -func (m MockSyncStore) Transact(ctx context.Context) (*store.Store, error) { - return m.transact(ctx) -} - -func (m MockSyncStore) Repos() *database.RepoStore { - // Return a RepoStore with a nil DB, so tests will fail when a mock is missing. - return database.Repos(&dbtesting.MockDB{}) -} - -func (m MockSyncStore) ExternalServices() *database.ExternalServiceStore { - // Return a ExternalServiceStore with a nil DB, so tests will fail when a mock is missing. - return database.ExternalServices(&dbtesting.MockDB{}) -} - -func (m MockSyncStore) UserCredentials() *database.UserCredentialsStore { - // Return a UserCredentialsStore with a nil DB, so tests will fail when a mock is missing. - return database.UserCredentials(&dbtesting.MockDB{}, nil) -} - -func (m MockSyncStore) DB() dbutil.DB { - // Return a nil DB, so tests will fail when a mock is missing. - return nil -} - -func (m MockSyncStore) Clock() func() time.Time { - return timeutil.Now -} - -func (m MockSyncStore) ListCodeHosts(ctx context.Context, opts store.ListCodeHostsOpts) ([]*btypes.CodeHost, error) { - return m.listCodeHosts(ctx, opts) -}