-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: improve healtcheck implementation
- Loading branch information
1 parent
7770345
commit a00637b
Showing
2 changed files
with
338 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
package couchbase | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"github.com/Trendyol/go-dcp/config" | ||
"github.com/Trendyol/go-dcp/logger" | ||
_ "github.com/Trendyol/go-dcp/logger" | ||
"github.com/Trendyol/go-dcp/models" | ||
"github.com/Trendyol/go-dcp/wrapper" | ||
"github.com/couchbase/gocbcore/v10" | ||
"sync" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func init() { | ||
logger.InitDefaultLogger("info") | ||
} | ||
|
||
func TestHealthCheck_Start_Stop(t *testing.T) { | ||
// Arrange | ||
cfg := &config.HealthCheck{ | ||
Interval: 100 * time.Millisecond, | ||
} | ||
|
||
pingCh := make(chan struct{}, 3) | ||
|
||
mc := &mockClient{} | ||
mc.PingFunc = func() (*models.PingResult, error) { | ||
mc.PingCallCount++ | ||
|
||
// Non-blocking send to avoid deadlocks if channel is full | ||
select { | ||
case pingCh <- struct{}{}: | ||
default: | ||
} | ||
|
||
return &models.PingResult{}, nil | ||
} | ||
|
||
sut := NewHealthCheck(cfg, mc) | ||
|
||
// Act | ||
sut.Start() | ||
|
||
expectedPings := 3 | ||
|
||
// Assert | ||
|
||
// Use a timeout to prevent the test from hanging indefinitely | ||
timeout := time.After(2 * time.Second) | ||
|
||
for i := 0; i < expectedPings; i++ { | ||
select { | ||
case <-pingCh: | ||
// Ping was called, continue to wait for the next one | ||
case <-timeout: | ||
// Timeout occurred before receiving all expected Ping calls | ||
sut.Stop() | ||
t.Fatalf("Timed out waiting for %d Ping calls, only received %d", expectedPings, i) | ||
} | ||
} | ||
|
||
sut.Stop() | ||
|
||
// Assert | ||
if mc.PingCallCount < expectedPings { | ||
t.Fatalf("Ping should have been called at least %d times, but it was called %d times", expectedPings, mc.PingCallCount) | ||
} | ||
} | ||
|
||
func TestHealthCheck_PingFailure(t *testing.T) { | ||
// Arrange | ||
cfg := &config.HealthCheck{ | ||
Interval: 100 * time.Millisecond, | ||
} | ||
|
||
mc := &mockClient{} | ||
|
||
pingCh := make(chan struct{}, 3) | ||
|
||
// Define the behavior for Ping: fail the first two times, then succeed. | ||
mc.PingFunc = func() (*models.PingResult, error) { | ||
mc.PingCallCount++ | ||
|
||
select { | ||
case pingCh <- struct{}{}: | ||
default: | ||
} | ||
|
||
if mc.PingCallCount <= 2 { | ||
return nil, errors.New("ping failed") | ||
} | ||
return &models.PingResult{}, nil | ||
} | ||
|
||
sut := NewHealthCheck(cfg, mc) | ||
|
||
// Act | ||
sut.Start() | ||
|
||
expectedPings := 3 | ||
|
||
// Use a timeout to prevent the test from hanging indefinitely | ||
timeout := time.After(10 * time.Second) | ||
|
||
// Wait for the expected number of Ping calls | ||
for i := 0; i < expectedPings; i++ { | ||
select { | ||
case <-pingCh: | ||
// Ping was called, continue to wait for the next one | ||
case <-timeout: | ||
// Timeout occurred before receiving all expected Ping calls | ||
sut.Stop() | ||
t.Fatalf("Timed out waiting for %d Ping calls, only received %d", expectedPings, i) | ||
} | ||
} | ||
|
||
// Stop the health check after receiving the expected number of Ping calls | ||
sut.Stop() | ||
|
||
// Assert | ||
if mc.PingCallCount < expectedPings { | ||
t.Fatalf("Ping should have been called at least %d times, but it was called %d times", expectedPings, mc.PingCallCount) | ||
} | ||
} | ||
|
||
func TestHealthCheck_PanicFailure(t *testing.T) { | ||
// Arrange | ||
cfg := &config.HealthCheck{ | ||
Interval: 100 * time.Millisecond, | ||
} | ||
|
||
mc := &mockClient{} | ||
|
||
pingCh := make(chan struct{}, 5) | ||
|
||
mc.PingFunc = func() (*models.PingResult, error) { | ||
mc.PingCallCount++ | ||
pingCh <- struct{}{} | ||
return nil, errors.New("ping failed") | ||
} | ||
|
||
var wg sync.WaitGroup | ||
sut := healthCheck{ | ||
config: cfg, | ||
client: mc, | ||
wg: wg, | ||
} | ||
|
||
// Act | ||
wg.Add(1) | ||
go func() { | ||
defer func() { | ||
if r := recover(); r == nil { | ||
t.Fatal("test should be panic!") | ||
} | ||
}() | ||
|
||
sut.run(context.Background()) | ||
}() | ||
|
||
// Assert | ||
expectedPings := 5 | ||
timeout := time.After(10 * time.Second) | ||
|
||
for i := 0; i < expectedPings; i++ { | ||
select { | ||
case <-pingCh: | ||
case <-timeout: | ||
t.Fatalf("Timed out waiting for Ping call %d", i+1) | ||
} | ||
} | ||
|
||
if mc.PingCallCount < expectedPings { | ||
t.Fatalf("Ping should have been called at least %d times, but it was called %d times", expectedPings, mc.PingCallCount) | ||
} | ||
} | ||
|
||
type mockClient struct { | ||
PingFunc func() (*models.PingResult, error) | ||
PingCallCount int | ||
} | ||
|
||
var _ Client = (*mockClient)(nil) | ||
|
||
func (m *mockClient) Ping() (*models.PingResult, error) { | ||
if m.PingFunc != nil { | ||
return m.PingFunc() | ||
} | ||
|
||
m.PingCallCount++ | ||
return &models.PingResult{}, nil | ||
} | ||
|
||
func (m *mockClient) GetAgent() *gocbcore.Agent { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) GetMetaAgent() *gocbcore.Agent { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) Connect() error { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) Close() { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) DcpConnect(useExpiryOpcode bool, useChangeStreams bool) error { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) DcpClose() { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) GetVBucketSeqNos(awareCollection bool) (*wrapper.ConcurrentSwissMap[uint16, uint64], error) { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) GetNumVBuckets() int { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) GetFailOverLogs(vbID uint16) ([]gocbcore.FailoverEntry, error) { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) OpenStream(vbID uint16, collectionIDs map[uint32]string, offset *models.Offset, observer Observer) error { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) CloseStream(vbID uint16) error { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) GetCollectionIDs(scopeName string, collectionNames []string) map[uint32]string { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) GetAgentConfigSnapshot() (*gocbcore.ConfigSnapshot, error) { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) GetDcpAgentConfigSnapshot() (*gocbcore.ConfigSnapshot, error) { | ||
//TODO implement me | ||
panic("implement me") | ||
} | ||
|
||
func (m *mockClient) GetAgentQueues() []*models.AgentQueue { | ||
//TODO implement me | ||
panic("implement me") | ||
} |