mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-15 04:18:03 +00:00
Weekend fun with further optimisations.
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
package traefikoidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGoroutineManager(t *testing.T) {
|
||||
logger := NewLogger("debug")
|
||||
|
||||
t.Run("start and stop goroutine", func(t *testing.T) {
|
||||
manager := NewGoroutineManager(logger)
|
||||
|
||||
started := make(chan struct{})
|
||||
stopped := make(chan struct{})
|
||||
|
||||
manager.StartGoroutine("test", func(ctx context.Context) {
|
||||
close(started)
|
||||
<-ctx.Done()
|
||||
close(stopped)
|
||||
})
|
||||
|
||||
// Wait for goroutine to start
|
||||
select {
|
||||
case <-started:
|
||||
// Good
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Goroutine did not start")
|
||||
}
|
||||
|
||||
// Stop the goroutine
|
||||
manager.StopGoroutine("test")
|
||||
|
||||
// Wait for goroutine to stop
|
||||
select {
|
||||
case <-stopped:
|
||||
// Good
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Goroutine did not stop")
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
manager.Shutdown(time.Second)
|
||||
})
|
||||
|
||||
t.Run("periodic task", func(t *testing.T) {
|
||||
manager := NewGoroutineManager(logger)
|
||||
|
||||
var counter int32
|
||||
manager.StartPeriodicTask("counter", 50*time.Millisecond, func() {
|
||||
atomic.AddInt32(&counter, 1)
|
||||
})
|
||||
|
||||
// Let it run for a bit
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// Should have executed at least 3 times
|
||||
count := atomic.LoadInt32(&counter)
|
||||
if count < 3 {
|
||||
t.Errorf("Expected at least 3 executions, got %d", count)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
manager.Shutdown(time.Second)
|
||||
|
||||
// Counter should stop increasing
|
||||
finalCount := atomic.LoadInt32(&counter)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if atomic.LoadInt32(&counter) != finalCount {
|
||||
t.Error("Task continued after shutdown")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple goroutines", func(t *testing.T) {
|
||||
manager := NewGoroutineManager(logger)
|
||||
|
||||
var running int32
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
name := string(rune('a' + i))
|
||||
manager.StartGoroutine(name, func(ctx context.Context) {
|
||||
atomic.AddInt32(&running, 1)
|
||||
<-ctx.Done()
|
||||
atomic.AddInt32(&running, -1)
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for all to start
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
if atomic.LoadInt32(&running) != 5 {
|
||||
t.Errorf("Expected 5 running goroutines, got %d", running)
|
||||
}
|
||||
|
||||
// Shutdown all
|
||||
err := manager.Shutdown(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("Shutdown failed: %v", err)
|
||||
}
|
||||
|
||||
if atomic.LoadInt32(&running) != 0 {
|
||||
t.Errorf("Expected 0 running goroutines after shutdown, got %d", running)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("duplicate goroutine name", func(t *testing.T) {
|
||||
manager := NewGoroutineManager(logger)
|
||||
|
||||
var starts int32
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
manager.StartGoroutine("same-name", func(ctx context.Context) {
|
||||
atomic.AddInt32(&starts, 1)
|
||||
<-ctx.Done()
|
||||
})
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Should only start once
|
||||
if atomic.LoadInt32(&starts) != 1 {
|
||||
t.Errorf("Expected 1 start for duplicate name, got %d", starts)
|
||||
}
|
||||
|
||||
manager.Shutdown(time.Second)
|
||||
})
|
||||
|
||||
t.Run("panic recovery", func(t *testing.T) {
|
||||
manager := NewGoroutineManager(logger)
|
||||
defer manager.Shutdown(time.Second)
|
||||
|
||||
// Start a goroutine that will panic
|
||||
manager.StartGoroutine("panicker", func(ctx context.Context) {
|
||||
panic("test panic")
|
||||
})
|
||||
|
||||
// Give it time to panic and recover
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Manager should still be functional - test with a simple task
|
||||
completed := make(chan struct{})
|
||||
manager.StartGoroutine("after-panic", func(ctx context.Context) {
|
||||
close(completed)
|
||||
})
|
||||
|
||||
select {
|
||||
case <-completed:
|
||||
// Good - manager is still functional
|
||||
case <-time.After(time.Second):
|
||||
t.Error("Manager not functional after panic")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("status tracking", func(t *testing.T) {
|
||||
manager := NewGoroutineManager(logger)
|
||||
|
||||
manager.StartGoroutine("task1", func(ctx context.Context) {
|
||||
<-ctx.Done()
|
||||
})
|
||||
|
||||
manager.StartGoroutine("task2", func(ctx context.Context) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
})
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
status := manager.GetStatus()
|
||||
|
||||
if len(status) != 2 {
|
||||
t.Errorf("Expected 2 goroutines in status, got %d", len(status))
|
||||
}
|
||||
|
||||
if status["task1"].Running != true {
|
||||
t.Error("task1 should be running")
|
||||
}
|
||||
|
||||
if status["task2"].Running != false {
|
||||
t.Error("task2 should have finished")
|
||||
}
|
||||
|
||||
manager.Shutdown(time.Second)
|
||||
})
|
||||
|
||||
t.Run("shutdown timeout", func(t *testing.T) {
|
||||
manager := NewGoroutineManager(logger)
|
||||
|
||||
// Start a goroutine that won't stop
|
||||
manager.StartGoroutine("stubborn", func(ctx context.Context) {
|
||||
// Ignore context cancellation
|
||||
select {}
|
||||
})
|
||||
|
||||
// Try to shutdown with short timeout
|
||||
err := manager.Shutdown(100 * time.Millisecond)
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected shutdown timeout error")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user