mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
e64fc7f730
* Add redis support for distributed caching * Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * ... and another all nighter. * fixup! ... and another all nighter. * fixup! fixup! ... and another all nighter. * fixup! fixup! fixup! ... and another all nighter. * Resolve issue #85 by adding ability to set custom claims in JWT tokens * Remove redundant validation in auth middleware ( issue #89 ) * Add ability to set cookie prefix for session cookies ( #87 ) * fixup! Add ability to set cookie prefix for session cookies ( #87 ) * Add ability to set cookie max age - issue #91 * Potential fix for code scanning alert no. 10: Size computation for allocation may overflow Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fixup! Merge main into 0.8.0-redis: resolve conflicts --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
932 lines
21 KiB
Go
932 lines
21 KiB
Go
//go:build !yaegi
|
|
|
|
package cleanup
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Mock logger for testing
|
|
type mockLogger struct {
|
|
mu sync.Mutex
|
|
logs []string
|
|
errLogs []string
|
|
debugLog []string
|
|
}
|
|
|
|
func (m *mockLogger) Logf(format string, args ...interface{}) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.logs = append(m.logs, format)
|
|
}
|
|
|
|
func (m *mockLogger) ErrorLogf(format string, args ...interface{}) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.errLogs = append(m.errLogs, format)
|
|
}
|
|
|
|
func (m *mockLogger) DebugLogf(format string, args ...interface{}) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.debugLog = append(m.debugLog, format)
|
|
}
|
|
|
|
func (m *mockLogger) getLogCount() int {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return len(m.logs)
|
|
}
|
|
|
|
// BackgroundTask tests
|
|
func TestNewBackgroundTask(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
var wg sync.WaitGroup
|
|
runCount := 0
|
|
|
|
task := NewBackgroundTask("test-task", 100*time.Millisecond, func() {
|
|
runCount++
|
|
}, logger, &wg)
|
|
|
|
if task == nil {
|
|
t.Fatal("Expected NewBackgroundTask to return non-nil")
|
|
}
|
|
|
|
if task.name != "test-task" {
|
|
t.Errorf("Expected name 'test-task', got '%s'", task.name)
|
|
}
|
|
|
|
if task.interval != 100*time.Millisecond {
|
|
t.Errorf("Expected interval 100ms, got %v", task.interval)
|
|
}
|
|
|
|
if task.IsRunning() {
|
|
t.Error("Expected task to not be running initially")
|
|
}
|
|
}
|
|
|
|
func TestBackgroundTask_Start(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
runCount := int32(0)
|
|
|
|
task := NewBackgroundTask("test-task", 50*time.Millisecond, func() {
|
|
atomic.AddInt32(&runCount, 1)
|
|
}, logger)
|
|
|
|
task.Start()
|
|
|
|
if !task.IsRunning() {
|
|
t.Error("Expected task to be running after Start()")
|
|
}
|
|
|
|
// Wait for at least 2 executions
|
|
time.Sleep(120 * time.Millisecond)
|
|
|
|
task.Stop()
|
|
|
|
count := atomic.LoadInt32(&runCount)
|
|
if count < 2 {
|
|
t.Errorf("Expected at least 2 executions, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestBackgroundTask_Stop(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
task := NewBackgroundTask("test-task", 100*time.Millisecond, func() {}, logger)
|
|
|
|
task.Start()
|
|
time.Sleep(50 * time.Millisecond)
|
|
task.Stop()
|
|
|
|
if task.IsRunning() {
|
|
t.Error("Expected task to not be running after Stop()")
|
|
}
|
|
|
|
// Calling Stop again should not panic
|
|
task.Stop()
|
|
}
|
|
|
|
func TestBackgroundTask_DoubleStart(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
task := NewBackgroundTask("test-task", 100*time.Millisecond, func() {}, logger)
|
|
|
|
task.Start()
|
|
logCountBefore := logger.getLogCount()
|
|
|
|
// Second start should be ignored
|
|
task.Start()
|
|
|
|
logCountAfter := logger.getLogCount()
|
|
if logCountAfter <= logCountBefore {
|
|
t.Error("Expected log message about task already running")
|
|
}
|
|
|
|
task.Stop()
|
|
}
|
|
|
|
func TestBackgroundTask_ExecuteWithPanic(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
panicCount := int32(0)
|
|
|
|
task := NewBackgroundTask("panic-task", 50*time.Millisecond, func() {
|
|
count := atomic.AddInt32(&panicCount, 1)
|
|
if count == 1 {
|
|
panic("test panic")
|
|
}
|
|
}, logger)
|
|
|
|
task.Start()
|
|
time.Sleep(120 * time.Millisecond)
|
|
task.Stop()
|
|
|
|
// Task should recover from panic and continue
|
|
finalCount := atomic.LoadInt32(&panicCount)
|
|
if finalCount < 2 {
|
|
t.Errorf("Expected task to continue after panic, got %d executions", finalCount)
|
|
}
|
|
|
|
stats := task.GetStats()
|
|
if stats["errorCount"].(int64) < 1 {
|
|
t.Error("Expected error count to be at least 1")
|
|
}
|
|
}
|
|
|
|
func TestBackgroundTask_GetStats(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
runCount := int32(0)
|
|
|
|
task := NewBackgroundTask("test-task", 50*time.Millisecond, func() {
|
|
atomic.AddInt32(&runCount, 1)
|
|
}, logger)
|
|
|
|
task.Start()
|
|
time.Sleep(120 * time.Millisecond)
|
|
task.Stop()
|
|
|
|
stats := task.GetStats()
|
|
|
|
if stats["name"] != "test-task" {
|
|
t.Errorf("Expected name 'test-task', got %v", stats["name"])
|
|
}
|
|
|
|
if !stats["isRunning"].(bool) == true {
|
|
// Task should be stopped
|
|
}
|
|
|
|
if stats["runCount"].(int64) < 2 {
|
|
t.Errorf("Expected runCount >= 2, got %v", stats["runCount"])
|
|
}
|
|
}
|
|
|
|
func TestBackgroundTask_WithWaitGroup(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
var wg sync.WaitGroup
|
|
runCount := int32(0)
|
|
|
|
task := NewBackgroundTask("test-task", 50*time.Millisecond, func() {
|
|
atomic.AddInt32(&runCount, 1)
|
|
}, logger, &wg)
|
|
|
|
task.Start()
|
|
|
|
// Wait for task to start
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Stop and wait
|
|
done := make(chan bool)
|
|
go func() {
|
|
task.Stop()
|
|
wg.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
// Success
|
|
case <-time.After(2 * time.Second):
|
|
t.Error("Timeout waiting for task to stop")
|
|
}
|
|
}
|
|
|
|
// TaskRegistry tests
|
|
func TestNewTaskRegistry(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
if registry == nil {
|
|
t.Fatal("Expected NewTaskRegistry to return non-nil")
|
|
}
|
|
|
|
if registry.maxTasks != 10 {
|
|
t.Errorf("Expected maxTasks 10, got %d", registry.maxTasks)
|
|
}
|
|
|
|
if registry.GetTaskCount() != 0 {
|
|
t.Error("Expected initial task count to be 0")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_RegisterTask(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
task := NewBackgroundTask("test-task", 100*time.Millisecond, func() {}, logger)
|
|
err := registry.RegisterTask("test-task", task)
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error, got %v", err)
|
|
}
|
|
|
|
if registry.GetTaskCount() != 1 {
|
|
t.Error("Expected task count to be 1")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_RegisterTask_Duplicate(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
task1 := NewBackgroundTask("test-task", 100*time.Millisecond, func() {}, logger)
|
|
task2 := NewBackgroundTask("test-task", 100*time.Millisecond, func() {}, logger)
|
|
|
|
err1 := registry.RegisterTask("test-task", task1)
|
|
if err1 != nil {
|
|
t.Errorf("Expected no error on first registration, got %v", err1)
|
|
}
|
|
|
|
err2 := registry.RegisterTask("test-task", task2)
|
|
if err2 == nil {
|
|
t.Error("Expected error when registering duplicate task")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_RegisterTask_Nil(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
err := registry.RegisterTask("test-task", nil)
|
|
if err == nil {
|
|
t.Error("Expected error when registering nil task")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_RegisterTask_MaxLimit(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 2)
|
|
|
|
task1 := NewBackgroundTask("task1", 100*time.Millisecond, func() {}, logger)
|
|
task2 := NewBackgroundTask("task2", 100*time.Millisecond, func() {}, logger)
|
|
task3 := NewBackgroundTask("task3", 100*time.Millisecond, func() {}, logger)
|
|
|
|
registry.RegisterTask("task1", task1)
|
|
registry.RegisterTask("task2", task2)
|
|
err := registry.RegisterTask("task3", task3)
|
|
|
|
if err == nil {
|
|
t.Error("Expected error when exceeding max tasks")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_UnregisterTask(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
task := NewBackgroundTask("test-task", 100*time.Millisecond, func() {}, logger)
|
|
registry.RegisterTask("test-task", task)
|
|
|
|
if registry.GetTaskCount() != 1 {
|
|
t.Error("Expected task count to be 1")
|
|
}
|
|
|
|
registry.UnregisterTask("test-task")
|
|
|
|
if registry.GetTaskCount() != 0 {
|
|
t.Error("Expected task count to be 0 after unregister")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_UnregisterTask_Running(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
task := NewBackgroundTask("test-task", 100*time.Millisecond, func() {}, logger)
|
|
registry.RegisterTask("test-task", task)
|
|
task.Start()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
registry.UnregisterTask("test-task")
|
|
|
|
if task.IsRunning() {
|
|
t.Error("Expected task to be stopped after unregister")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_GetTask(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
task := NewBackgroundTask("test-task", 100*time.Millisecond, func() {}, logger)
|
|
registry.RegisterTask("test-task", task)
|
|
|
|
retrieved, exists := registry.GetTask("test-task")
|
|
if !exists {
|
|
t.Error("Expected task to exist")
|
|
}
|
|
|
|
if retrieved != task {
|
|
t.Error("Expected to retrieve the same task")
|
|
}
|
|
|
|
_, exists = registry.GetTask("non-existent")
|
|
if exists {
|
|
t.Error("Expected non-existent task to not exist")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_StopAllTasks(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
task1 := NewBackgroundTask("task1", 100*time.Millisecond, func() {}, logger)
|
|
task2 := NewBackgroundTask("task2", 100*time.Millisecond, func() {}, logger)
|
|
|
|
registry.RegisterTask("task1", task1)
|
|
registry.RegisterTask("task2", task2)
|
|
|
|
task1.Start()
|
|
task2.Start()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
registry.StopAllTasks()
|
|
|
|
if task1.IsRunning() || task2.IsRunning() {
|
|
t.Error("Expected all tasks to be stopped")
|
|
}
|
|
|
|
if registry.GetTaskCount() != 0 {
|
|
t.Error("Expected task count to be 0 after StopAllTasks")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_CreateSingletonTask(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
runCount := int32(0)
|
|
task1, err1 := registry.CreateSingletonTask("singleton", 50*time.Millisecond, func() {
|
|
atomic.AddInt32(&runCount, 1)
|
|
}, logger)
|
|
|
|
if err1 != nil {
|
|
t.Errorf("Expected no error, got %v", err1)
|
|
}
|
|
|
|
if task1 == nil {
|
|
t.Fatal("Expected task to be created")
|
|
}
|
|
|
|
if !task1.IsRunning() {
|
|
t.Error("Expected task to be running")
|
|
}
|
|
|
|
// Try to create same task again
|
|
task2, err2 := registry.CreateSingletonTask("singleton", 50*time.Millisecond, func() {
|
|
atomic.AddInt32(&runCount, 1)
|
|
}, logger)
|
|
|
|
if err2 != nil {
|
|
t.Errorf("Expected no error on second call, got %v", err2)
|
|
}
|
|
|
|
if task2 != task1 {
|
|
t.Error("Expected to get the same task instance")
|
|
}
|
|
|
|
time.Sleep(120 * time.Millisecond)
|
|
task1.Stop()
|
|
|
|
if atomic.LoadInt32(&runCount) < 2 {
|
|
t.Error("Expected task to have run multiple times")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_GetAllTasks(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
task1 := NewBackgroundTask("task1", 100*time.Millisecond, func() {}, logger)
|
|
task2 := NewBackgroundTask("task2", 100*time.Millisecond, func() {}, logger)
|
|
|
|
registry.RegisterTask("task1", task1)
|
|
registry.RegisterTask("task2", task2)
|
|
|
|
allTasks := registry.GetAllTasks()
|
|
|
|
if len(allTasks) != 2 {
|
|
t.Errorf("Expected 2 tasks, got %d", len(allTasks))
|
|
}
|
|
|
|
if _, ok := allTasks["task1"]; !ok {
|
|
t.Error("Expected task1 in results")
|
|
}
|
|
|
|
if _, ok := allTasks["task2"]; !ok {
|
|
t.Error("Expected task2 in results")
|
|
}
|
|
}
|
|
|
|
func TestTaskRegistry_GetStats(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
|
|
task := NewBackgroundTask("test-task", 100*time.Millisecond, func() {}, logger)
|
|
registry.RegisterTask("test-task", task)
|
|
task.Start()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
stats := registry.GetStats()
|
|
|
|
if stats["totalTasks"].(int) != 1 {
|
|
t.Errorf("Expected totalTasks 1, got %v", stats["totalTasks"])
|
|
}
|
|
|
|
if stats["runningTasks"].(int) != 1 {
|
|
t.Errorf("Expected runningTasks 1, got %v", stats["runningTasks"])
|
|
}
|
|
|
|
if _, ok := stats["memory"]; !ok {
|
|
t.Error("Expected memory stats")
|
|
}
|
|
|
|
task.Stop()
|
|
}
|
|
|
|
func TestGlobalTaskRegistry(t *testing.T) {
|
|
// Reset before test
|
|
ResetGlobalTaskRegistry()
|
|
|
|
registry1 := GetGlobalTaskRegistry()
|
|
registry2 := GetGlobalTaskRegistry()
|
|
|
|
if registry1 != registry2 {
|
|
t.Error("Expected singleton to return same instance")
|
|
}
|
|
|
|
// Cleanup
|
|
ResetGlobalTaskRegistry()
|
|
}
|
|
|
|
func TestResetGlobalTaskRegistry(t *testing.T) {
|
|
ResetGlobalTaskRegistry()
|
|
|
|
registry := GetGlobalTaskRegistry()
|
|
logger := &mockLogger{}
|
|
task := NewBackgroundTask("test-task", 100*time.Millisecond, func() {}, logger)
|
|
registry.RegisterTask("test-task", task)
|
|
task.Start()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
ResetGlobalTaskRegistry()
|
|
|
|
// Should get a new instance
|
|
newRegistry := GetGlobalTaskRegistry()
|
|
if newRegistry.GetTaskCount() != 0 {
|
|
t.Error("Expected new registry to be empty")
|
|
}
|
|
}
|
|
|
|
// TaskCircuitBreaker tests
|
|
func TestNewTaskCircuitBreaker(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
cb := NewTaskCircuitBreaker(5, 30*time.Second, logger)
|
|
|
|
if cb == nil {
|
|
t.Fatal("Expected NewTaskCircuitBreaker to return non-nil")
|
|
}
|
|
|
|
if cb.failureThreshold != 5 {
|
|
t.Errorf("Expected failureThreshold 5, got %d", cb.failureThreshold)
|
|
}
|
|
|
|
if cb.timeout != 30*time.Second {
|
|
t.Errorf("Expected timeout 30s, got %v", cb.timeout)
|
|
}
|
|
|
|
if cb.GetState() != CircuitBreakerClosed {
|
|
t.Error("Expected initial state to be closed")
|
|
}
|
|
}
|
|
|
|
func TestTaskCircuitBreaker_CanCreateTask(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
cb := NewTaskCircuitBreaker(3, 100*time.Millisecond, logger)
|
|
|
|
err := cb.CanCreateTask("test-task")
|
|
if err != nil {
|
|
t.Errorf("Expected no error initially, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTaskCircuitBreaker_OnTaskFailure(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
cb := NewTaskCircuitBreaker(3, 100*time.Millisecond, logger)
|
|
|
|
// Record failures
|
|
for i := 0; i < 3; i++ {
|
|
cb.OnTaskFailure("test-task", nil)
|
|
}
|
|
|
|
// Circuit should be open
|
|
if cb.GetState() != CircuitBreakerOpen {
|
|
t.Error("Expected circuit breaker to be open after threshold failures")
|
|
}
|
|
|
|
// Should not be able to create task
|
|
err := cb.CanCreateTask("test-task")
|
|
if err == nil {
|
|
t.Error("Expected error when circuit breaker is open")
|
|
}
|
|
}
|
|
|
|
func TestTaskCircuitBreaker_OnTaskSuccess(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
cb := NewTaskCircuitBreaker(5, 100*time.Millisecond, logger)
|
|
|
|
cb.OnTaskFailure("test-task", nil)
|
|
cb.OnTaskFailure("test-task", nil)
|
|
|
|
cb.OnTaskSuccess("test-task")
|
|
|
|
// Task-specific failures should be reset
|
|
err := cb.CanCreateTask("test-task")
|
|
if err != nil {
|
|
t.Errorf("Expected no error after success, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTaskCircuitBreaker_Reset(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
cb := NewTaskCircuitBreaker(2, 100*time.Millisecond, logger)
|
|
|
|
cb.OnTaskFailure("test-task", nil)
|
|
cb.OnTaskFailure("test-task", nil)
|
|
|
|
if cb.GetState() != CircuitBreakerOpen {
|
|
t.Error("Expected circuit breaker to be open")
|
|
}
|
|
|
|
cb.Reset()
|
|
|
|
if cb.GetState() != CircuitBreakerClosed {
|
|
t.Error("Expected circuit breaker to be closed after reset")
|
|
}
|
|
|
|
err := cb.CanCreateTask("test-task")
|
|
if err != nil {
|
|
t.Errorf("Expected no error after reset, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTaskCircuitBreaker_TimeoutRecovery(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
cb := NewTaskCircuitBreaker(2, 100*time.Millisecond, logger)
|
|
|
|
// Open circuit breaker
|
|
cb.OnTaskFailure("test-task", nil)
|
|
cb.OnTaskFailure("test-task", nil)
|
|
|
|
if cb.GetState() != CircuitBreakerOpen {
|
|
t.Error("Expected circuit breaker to be open")
|
|
}
|
|
|
|
// Wait for timeout
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Circuit breaker should reset, but task-specific failures remain
|
|
// Need to check with a different task name
|
|
err := cb.CanCreateTask("different-task")
|
|
if err != nil {
|
|
t.Errorf("Expected no error for different task after timeout, got %v", err)
|
|
}
|
|
|
|
if cb.GetState() != CircuitBreakerClosed {
|
|
t.Error("Expected circuit breaker to be closed after timeout")
|
|
}
|
|
|
|
// Original task still has too many failures
|
|
err = cb.CanCreateTask("test-task")
|
|
if err == nil {
|
|
t.Error("Expected error for original task with too many failures")
|
|
}
|
|
}
|
|
|
|
// TaskMemoryMonitor tests
|
|
func TestNewTaskMemoryMonitor(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
monitor := NewTaskMemoryMonitor(logger, registry)
|
|
|
|
if monitor == nil {
|
|
t.Fatal("Expected NewTaskMemoryMonitor to return non-nil")
|
|
}
|
|
|
|
if monitor.registry != registry {
|
|
t.Error("Expected registry to be set")
|
|
}
|
|
|
|
if monitor.memoryThreshold != 1024*1024*1024 {
|
|
t.Errorf("Expected default threshold 1GB, got %d", monitor.memoryThreshold)
|
|
}
|
|
}
|
|
|
|
func TestTaskMemoryMonitor_SetMemoryThreshold(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
monitor := NewTaskMemoryMonitor(logger, registry)
|
|
|
|
monitor.SetMemoryThreshold(512 * 1024 * 1024)
|
|
|
|
stats := monitor.GetStats()
|
|
if stats["memoryThreshold"].(uint64) != 512*1024*1024 {
|
|
t.Error("Expected threshold to be updated")
|
|
}
|
|
}
|
|
|
|
func TestTaskMemoryMonitor_StartStop(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
monitor := NewTaskMemoryMonitor(logger, registry)
|
|
|
|
monitor.StartMonitoring()
|
|
|
|
stats := monitor.GetStats()
|
|
if !stats["isMonitoring"].(bool) {
|
|
t.Error("Expected monitor to be running")
|
|
}
|
|
|
|
// Double start should be ignored
|
|
monitor.StartMonitoring()
|
|
|
|
monitor.StopMonitoring()
|
|
|
|
stats = monitor.GetStats()
|
|
if stats["isMonitoring"].(bool) {
|
|
t.Error("Expected monitor to be stopped")
|
|
}
|
|
|
|
// Double stop should be safe
|
|
monitor.StopMonitoring()
|
|
}
|
|
|
|
func TestTaskMemoryMonitor_GetStats(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
registry := NewTaskRegistry(logger, 10)
|
|
monitor := NewTaskMemoryMonitor(logger, registry)
|
|
|
|
stats := monitor.GetStats()
|
|
|
|
if _, ok := stats["isMonitoring"]; !ok {
|
|
t.Error("Expected isMonitoring in stats")
|
|
}
|
|
|
|
if _, ok := stats["currentMemory"]; !ok {
|
|
t.Error("Expected currentMemory in stats")
|
|
}
|
|
|
|
if _, ok := stats["memoryThreshold"]; !ok {
|
|
t.Error("Expected memoryThreshold in stats")
|
|
}
|
|
}
|
|
|
|
// WorkerPool tests
|
|
func TestNewWorkerPool(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
pool := NewWorkerPool(4, 10, logger)
|
|
|
|
if pool == nil {
|
|
t.Fatal("Expected NewWorkerPool to return non-nil")
|
|
}
|
|
|
|
if pool.workers != 4 {
|
|
t.Errorf("Expected 4 workers, got %d", pool.workers)
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_DefaultWorkers(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
pool := NewWorkerPool(0, 0, logger)
|
|
|
|
// Should default to NumCPU
|
|
if pool.workers <= 0 {
|
|
t.Error("Expected positive number of workers")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_StartStop(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
pool := NewWorkerPool(2, 5, logger)
|
|
|
|
pool.Start()
|
|
|
|
metrics := pool.GetMetrics()
|
|
if !metrics["isRunning"].(bool) {
|
|
t.Error("Expected worker pool to be running")
|
|
}
|
|
|
|
// Double start should be ignored
|
|
pool.Start()
|
|
|
|
pool.Stop()
|
|
|
|
metrics = pool.GetMetrics()
|
|
if metrics["isRunning"].(bool) {
|
|
t.Error("Expected worker pool to be stopped")
|
|
}
|
|
|
|
// Double stop should be safe
|
|
pool.Stop()
|
|
}
|
|
|
|
func TestWorkerPool_Submit(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
pool := NewWorkerPool(2, 5, logger)
|
|
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
executed := int32(0)
|
|
var wg sync.WaitGroup
|
|
|
|
for i := 0; i < 3; i++ {
|
|
wg.Add(1)
|
|
err := pool.Submit(func() {
|
|
defer wg.Done()
|
|
atomic.AddInt32(&executed, 1)
|
|
})
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error submitting task, got %v", err)
|
|
}
|
|
}
|
|
|
|
// Wait for tasks to complete
|
|
done := make(chan bool)
|
|
go func() {
|
|
wg.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
// Success
|
|
case <-time.After(2 * time.Second):
|
|
t.Error("Timeout waiting for tasks to complete")
|
|
}
|
|
|
|
if atomic.LoadInt32(&executed) != 3 {
|
|
t.Errorf("Expected 3 tasks executed, got %d", atomic.LoadInt32(&executed))
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_SubmitWhenStopped(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
pool := NewWorkerPool(2, 5, logger)
|
|
|
|
err := pool.Submit(func() {})
|
|
if err == nil {
|
|
t.Error("Expected error when submitting to stopped pool")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_TaskPanic(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
pool := NewWorkerPool(2, 5, logger)
|
|
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
executed := int32(0)
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(2)
|
|
// Submit task that panics
|
|
pool.Submit(func() {
|
|
defer wg.Done()
|
|
panic("test panic")
|
|
})
|
|
|
|
// Submit normal task
|
|
pool.Submit(func() {
|
|
defer wg.Done()
|
|
atomic.AddInt32(&executed, 1)
|
|
})
|
|
|
|
// Wait for tasks
|
|
done := make(chan bool)
|
|
go func() {
|
|
wg.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
// Success
|
|
case <-time.After(2 * time.Second):
|
|
t.Error("Timeout waiting for tasks")
|
|
}
|
|
|
|
// Pool should still be functional
|
|
metrics := pool.GetMetrics()
|
|
if metrics["tasksFailed"].(int64) < 1 {
|
|
t.Error("Expected at least one failed task")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_GetMetrics(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
pool := NewWorkerPool(2, 5, logger)
|
|
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
|
|
pool.Submit(func() {
|
|
defer wg.Done()
|
|
time.Sleep(10 * time.Millisecond)
|
|
})
|
|
|
|
pool.Submit(func() {
|
|
defer wg.Done()
|
|
time.Sleep(10 * time.Millisecond)
|
|
})
|
|
|
|
wg.Wait()
|
|
|
|
metrics := pool.GetMetrics()
|
|
|
|
if metrics["workers"].(int) != 2 {
|
|
t.Errorf("Expected 2 workers, got %v", metrics["workers"])
|
|
}
|
|
|
|
if metrics["tasksProcessed"].(int64) != 2 {
|
|
t.Errorf("Expected 2 processed tasks, got %v", metrics["tasksProcessed"])
|
|
}
|
|
|
|
if metrics["tasksQueued"].(int64) != 2 {
|
|
t.Errorf("Expected 2 queued tasks, got %v", metrics["tasksQueued"])
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_Concurrent(t *testing.T) {
|
|
logger := &mockLogger{}
|
|
pool := NewWorkerPool(4, 20, logger)
|
|
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
executed := int32(0)
|
|
var wg sync.WaitGroup
|
|
|
|
taskCount := 10
|
|
for i := 0; i < taskCount; i++ {
|
|
wg.Add(1)
|
|
err := pool.Submit(func() {
|
|
defer wg.Done()
|
|
atomic.AddInt32(&executed, 1)
|
|
time.Sleep(10 * time.Millisecond)
|
|
})
|
|
|
|
if err != nil {
|
|
wg.Done()
|
|
t.Errorf("Failed to submit task: %v", err)
|
|
}
|
|
}
|
|
|
|
// Wait for all tasks
|
|
done := make(chan bool)
|
|
go func() {
|
|
wg.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
// Success
|
|
case <-time.After(5 * time.Second):
|
|
t.Error("Timeout waiting for concurrent tasks")
|
|
}
|
|
|
|
if atomic.LoadInt32(&executed) != int32(taskCount) {
|
|
t.Errorf("Expected %d tasks executed, got %d", taskCount, atomic.LoadInt32(&executed))
|
|
}
|
|
}
|