438 lines
13 KiB
Go
438 lines
13 KiB
Go
package adapter
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/grafana/dskit/services"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/registry"
|
|
)
|
|
|
|
const testTimeout = 200 * time.Millisecond
|
|
|
|
func TestNewManagerAdapter(t *testing.T) {
|
|
reg := &mockBackgroundServiceRegistry{}
|
|
adapter := NewManagerAdapter(reg)
|
|
|
|
require.NotNil(t, adapter)
|
|
require.Equal(t, reg, adapter.reg)
|
|
require.Nil(t, adapter.manager)
|
|
require.NotNil(t, adapter.dependencyMap)
|
|
}
|
|
|
|
func TestManagerAdapter_Starting(t *testing.T) {
|
|
t.Run("empty registry initializes manager", func(t *testing.T) {
|
|
reg := &mockBackgroundServiceRegistry{services: []registry.BackgroundService{}}
|
|
adapter := NewManagerAdapter(reg).WithDependencies(map[string][]string{
|
|
BackgroundServices: {Core},
|
|
Core: {},
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.StartAsync(ctx)
|
|
require.NoError(t, err)
|
|
|
|
err = adapter.AwaitRunning(ctx)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("registers enabled services and skips disabled", func(t *testing.T) {
|
|
enabledSvc := &mockService{}
|
|
|
|
// Create a different type for the disabled service to distinguish them
|
|
type disabledMockService struct{ mockService }
|
|
disabledSvc := &disabledMockService{mockService{disabled: true}}
|
|
|
|
namedSvc := &mockNamedService{name: "test-service"}
|
|
|
|
reg := &mockBackgroundServiceRegistry{
|
|
services: []registry.BackgroundService{enabledSvc, disabledSvc, namedSvc},
|
|
}
|
|
adapter := NewManagerAdapter(reg).WithDependencies(map[string][]string{
|
|
BackgroundServices: {Core},
|
|
Core: {},
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.StartAsync(ctx)
|
|
require.NoError(t, err)
|
|
|
|
err = adapter.AwaitRunning(ctx)
|
|
require.NoError(t, err)
|
|
|
|
// Verify dependency map was updated correctly
|
|
// Should have entries for enabled services but not disabled ones
|
|
require.Contains(t, adapter.dependencyMap, "*adapter.mockNamedService") // Named service
|
|
require.Contains(t, adapter.dependencyMap, reflect.TypeOf(enabledSvc).String()) // Wrapped service
|
|
require.NotContains(t, adapter.dependencyMap, reflect.TypeOf(disabledSvc).String()) // Disabled service should not be in map
|
|
|
|
// Check that BackgroundServices depends on the enabled services
|
|
bgDeps := adapter.dependencyMap[BackgroundServices]
|
|
require.Contains(t, bgDeps, "*adapter.mockNamedService")
|
|
require.Contains(t, bgDeps, reflect.TypeOf(enabledSvc).String())
|
|
require.NotContains(t, bgDeps, reflect.TypeOf(disabledSvc).String())
|
|
})
|
|
|
|
t.Run("handles services that are already NamedService", func(t *testing.T) {
|
|
namedSvc := &mockNamedService{name: "already-named"}
|
|
reg := &mockBackgroundServiceRegistry{services: []registry.BackgroundService{namedSvc}}
|
|
adapter := NewManagerAdapter(reg)
|
|
adapter.dependencyMap = map[string][]string{
|
|
BackgroundServices: {},
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.StartAsync(ctx)
|
|
require.NoError(t, err)
|
|
|
|
err = adapter.AwaitRunning(ctx)
|
|
require.NoError(t, err)
|
|
|
|
// Verify named service was added to dependency map
|
|
require.Contains(t, adapter.dependencyMap, "*adapter.mockNamedService")
|
|
})
|
|
|
|
t.Run("service already in dependency map is not added again", func(t *testing.T) {
|
|
namedSvc := &mockNamedService{name: "existing-service"}
|
|
reg := &mockBackgroundServiceRegistry{services: []registry.BackgroundService{namedSvc}}
|
|
adapter := NewManagerAdapter(reg)
|
|
|
|
// Pre-populate the dependency map with the service using the actual service name that will be used
|
|
serviceName := "*adapter.mockNamedService"
|
|
adapter.WithDependencies(map[string][]string{
|
|
serviceName: {BackgroundServices},
|
|
Core: {},
|
|
BackgroundServices: {Core},
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.starting(ctx)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, adapter.manager)
|
|
|
|
// Verify the existing dependency was not overwritten
|
|
require.Equal(t, []string{BackgroundServices}, adapter.dependencyMap[serviceName])
|
|
|
|
// Verify BackgroundServices dependencies were not modified (should not contain the service twice)
|
|
finalBgDeps := adapter.dependencyMap[BackgroundServices]
|
|
require.Equal(t, []string{Core}, finalBgDeps)
|
|
})
|
|
|
|
t.Run("service without NamedService interface gets wrapped", func(t *testing.T) {
|
|
plainSvc := &mockService{}
|
|
reg := &mockBackgroundServiceRegistry{services: []registry.BackgroundService{plainSvc}}
|
|
adapter := NewManagerAdapter(reg).WithDependencies(map[string][]string{
|
|
BackgroundServices: {Core},
|
|
Core: {},
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.starting(ctx)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, adapter.manager)
|
|
|
|
// Verify the service was wrapped and added to dependency map using its type name
|
|
expectedServiceName := reflect.TypeOf(plainSvc).String()
|
|
require.Contains(t, adapter.dependencyMap, expectedServiceName)
|
|
|
|
// Verify it was added to BackgroundServices dependencies
|
|
bgDeps := adapter.dependencyMap[BackgroundServices]
|
|
require.Contains(t, bgDeps, expectedServiceName)
|
|
})
|
|
|
|
t.Run("service without CanBeDisabled interface is always enabled", func(t *testing.T) {
|
|
simpleSvc := &simpleBackgroundService{}
|
|
|
|
reg := &mockBackgroundServiceRegistry{services: []registry.BackgroundService{simpleSvc}}
|
|
adapter := NewManagerAdapter(reg).WithDependencies(map[string][]string{
|
|
BackgroundServices: {Core},
|
|
Core: {},
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.starting(ctx)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, adapter.manager)
|
|
|
|
// Verify the service was added (since it doesn't implement CanBeDisabled, it's always enabled)
|
|
expectedServiceName := reflect.TypeOf(simpleSvc).String()
|
|
require.Contains(t, adapter.dependencyMap, expectedServiceName)
|
|
})
|
|
}
|
|
|
|
func TestManagerAdapter_Running(t *testing.T) {
|
|
t.Run("runs with manager", func(t *testing.T) {
|
|
mock := &mockNamedService{name: "mock"}
|
|
reg := &mockBackgroundServiceRegistry{services: []registry.BackgroundService{
|
|
mock,
|
|
}}
|
|
adapter := NewManagerAdapter(reg).WithDependencies(map[string][]string{
|
|
BackgroundServices: {Core},
|
|
Core: {},
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.StartAsync(ctx)
|
|
require.NoError(t, err)
|
|
|
|
err = adapter.AwaitRunning(ctx)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestManagerAdapter_Stopping(t *testing.T) {
|
|
t.Run("stopping method delegates to manager", func(t *testing.T) {
|
|
mock := &mockNamedService{name: "test-service"}
|
|
reg := &mockBackgroundServiceRegistry{services: []registry.BackgroundService{mock}}
|
|
adapter := NewManagerAdapter(reg)
|
|
adapter.dependencyMap = map[string][]string{
|
|
BackgroundServices: {},
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
// Initialize the manager first - need to go through starting to initialize manager
|
|
err := adapter.starting(ctx)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, adapter.manager)
|
|
|
|
// Test the stopping method directly - this covers the stopping function
|
|
err = adapter.stopping(nil)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("stopping with failure reason", func(t *testing.T) {
|
|
mock := &mockNamedService{name: "test-service"}
|
|
reg := &mockBackgroundServiceRegistry{services: []registry.BackgroundService{mock}}
|
|
adapter := NewManagerAdapter(reg)
|
|
adapter.dependencyMap = map[string][]string{
|
|
BackgroundServices: {},
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.starting(ctx)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, adapter.manager)
|
|
|
|
// Test stopping with failure reason
|
|
failure := errors.New("test failure")
|
|
err = adapter.stopping(failure)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestManagerAdapter_Run(t *testing.T) {
|
|
t.Run("successful run lifecycle", func(t *testing.T) {
|
|
reg := &mockBackgroundServiceRegistry{}
|
|
adapter := NewManagerAdapter(reg)
|
|
|
|
// Create a mock basic service that we can control
|
|
mockBasicService := &mockBasicService{}
|
|
adapter.NamedService = mockBasicService
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.Run(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, mockBasicService.startAsyncCalled)
|
|
require.True(t, mockBasicService.awaitTerminatedCalled)
|
|
})
|
|
|
|
t.Run("returns StartAsync error", func(t *testing.T) {
|
|
reg := &mockBackgroundServiceRegistry{}
|
|
adapter := NewManagerAdapter(reg)
|
|
|
|
expectedErr := errors.New("start error")
|
|
mockBasicService := &mockBasicService{startAsyncError: expectedErr}
|
|
adapter.NamedService = mockBasicService
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.Run(ctx)
|
|
require.Error(t, err)
|
|
require.Equal(t, expectedErr, err)
|
|
require.True(t, mockBasicService.startAsyncCalled)
|
|
require.False(t, mockBasicService.awaitTerminatedCalled)
|
|
})
|
|
|
|
t.Run("returns AwaitTerminated error", func(t *testing.T) {
|
|
reg := &mockBackgroundServiceRegistry{}
|
|
adapter := NewManagerAdapter(reg)
|
|
|
|
expectedErr := errors.New("await error")
|
|
mockBasicService := &mockBasicService{awaitTerminatedError: expectedErr}
|
|
adapter.NamedService = mockBasicService
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.Run(ctx)
|
|
require.Error(t, err)
|
|
require.Equal(t, expectedErr, err)
|
|
require.True(t, mockBasicService.startAsyncCalled)
|
|
require.True(t, mockBasicService.awaitTerminatedCalled)
|
|
})
|
|
}
|
|
|
|
func TestManagerAdapter_Shutdown(t *testing.T) {
|
|
t.Run("calls StopAsync and AwaitTerminated", func(t *testing.T) {
|
|
reg := &mockBackgroundServiceRegistry{}
|
|
adapter := NewManagerAdapter(reg)
|
|
|
|
mockBasicService := &mockBasicService{}
|
|
adapter.NamedService = mockBasicService
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.Shutdown(ctx, "test shutdown")
|
|
require.NoError(t, err)
|
|
require.True(t, mockBasicService.stopAsyncCalled)
|
|
require.True(t, mockBasicService.awaitTerminatedCalled)
|
|
})
|
|
|
|
t.Run("returns AwaitTerminated error", func(t *testing.T) {
|
|
reg := &mockBackgroundServiceRegistry{}
|
|
adapter := NewManagerAdapter(reg)
|
|
|
|
expectedErr := errors.New("await error")
|
|
mockBasicService := &mockBasicService{awaitTerminatedError: expectedErr}
|
|
adapter.NamedService = mockBasicService
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
err := adapter.Shutdown(ctx, "test shutdown")
|
|
require.Error(t, err)
|
|
require.Equal(t, expectedErr, err)
|
|
})
|
|
}
|
|
|
|
type mockBackgroundServiceRegistry struct {
|
|
services []registry.BackgroundService
|
|
}
|
|
|
|
func (m *mockBackgroundServiceRegistry) GetServices() []registry.BackgroundService {
|
|
return m.services
|
|
}
|
|
|
|
type mockService struct {
|
|
runFunc func(ctx context.Context) error
|
|
runCalled bool
|
|
runContext context.Context
|
|
runError error
|
|
disabled bool
|
|
}
|
|
|
|
func (m *mockService) Run(ctx context.Context) error {
|
|
m.runCalled = true
|
|
m.runContext = ctx
|
|
|
|
if m.runFunc != nil {
|
|
return m.runFunc(ctx)
|
|
}
|
|
|
|
return m.runError
|
|
}
|
|
|
|
func (m *mockService) IsDisabled() bool {
|
|
return m.disabled
|
|
}
|
|
|
|
type mockNamedService struct {
|
|
mockService
|
|
name string
|
|
}
|
|
|
|
func (m *mockNamedService) ServiceName() string {
|
|
return m.name
|
|
}
|
|
|
|
func (m *mockNamedService) State() services.State {
|
|
return services.New
|
|
}
|
|
|
|
func (m *mockNamedService) AddListener(listener services.Listener) func() {
|
|
return func() {}
|
|
}
|
|
|
|
func (m *mockNamedService) FailureCase() error {
|
|
return nil
|
|
}
|
|
|
|
type mockBasicService struct {
|
|
startAsyncCalled bool
|
|
startAsyncError error
|
|
awaitTerminatedCalled bool
|
|
awaitTerminatedError error
|
|
stopAsyncCalled bool
|
|
}
|
|
|
|
func (m *mockBasicService) StartAsync(ctx context.Context) error {
|
|
m.startAsyncCalled = true
|
|
return m.startAsyncError
|
|
}
|
|
|
|
func (m *mockBasicService) AwaitRunning(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockBasicService) StopAsync() {
|
|
m.stopAsyncCalled = true
|
|
}
|
|
|
|
func (m *mockBasicService) AwaitTerminated(ctx context.Context) error {
|
|
m.awaitTerminatedCalled = true
|
|
return m.awaitTerminatedError
|
|
}
|
|
|
|
func (m *mockBasicService) State() services.State {
|
|
return services.New
|
|
}
|
|
|
|
func (m *mockBasicService) ServiceName() string {
|
|
return "mockBasicService"
|
|
}
|
|
|
|
func (m *mockBasicService) AddListener(listener services.Listener) func() {
|
|
return func() {}
|
|
}
|
|
|
|
func (m *mockBasicService) FailureCase() error {
|
|
return nil
|
|
}
|
|
|
|
// simpleBackgroundService only implements BackgroundService, not CanBeDisabled
|
|
type simpleBackgroundService struct {
|
|
runCalled bool
|
|
}
|
|
|
|
func (s *simpleBackgroundService) Run(ctx context.Context) error {
|
|
s.runCalled = true
|
|
return nil
|
|
}
|