mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-22 17:31:03 +01:00
218 lines
4.9 KiB
Go
218 lines
4.9 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/crawlab-team/crawlab/core/utils"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
)
|
|
|
|
// TestService_GracefulShutdown tests proper service shutdown
|
|
func TestService_GracefulShutdown(t *testing.T) {
|
|
svc := &Service{
|
|
fetchInterval: 100 * time.Millisecond,
|
|
reportInterval: 100 * time.Millisecond,
|
|
mu: sync.RWMutex{},
|
|
runners: sync.Map{},
|
|
Logger: utils.NewLogger("TestService"),
|
|
}
|
|
|
|
// Initialize context
|
|
svc.ctx, svc.cancel = context.WithCancel(context.Background())
|
|
|
|
// Initialize tickers
|
|
svc.fetchTicker = time.NewTicker(svc.fetchInterval)
|
|
svc.reportTicker = time.NewTicker(svc.reportInterval)
|
|
|
|
// Start background goroutines
|
|
svc.wg.Add(2)
|
|
go svc.testFetchAndRunTasks() // Mock version
|
|
go svc.testReportStatus() // Mock version
|
|
|
|
// Let it run for a short time
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Test graceful shutdown
|
|
svc.Stop()
|
|
|
|
t.Log("✅ Service shutdown completed gracefully")
|
|
}
|
|
|
|
// Mock versions for testing without dependencies
|
|
func (svc *Service) testFetchAndRunTasks() {
|
|
defer svc.wg.Done()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
svc.Errorf("testFetchAndRunTasks panic recovered: %v", r)
|
|
}
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case <-svc.ctx.Done():
|
|
svc.Infof("testFetchAndRunTasks stopped by context")
|
|
return
|
|
case <-svc.fetchTicker.C:
|
|
// Mock fetch operation
|
|
svc.Debugf("Mock fetch operation")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (svc *Service) testReportStatus() {
|
|
defer svc.wg.Done()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
svc.Errorf("testReportStatus panic recovered: %v", r)
|
|
}
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case <-svc.ctx.Done():
|
|
svc.Infof("testReportStatus stopped by context")
|
|
return
|
|
case <-svc.reportTicker.C:
|
|
// Mock status update
|
|
svc.Debugf("Mock status update")
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestService_ConcurrentAccess tests thread safety
|
|
func TestService_ConcurrentAccess(t *testing.T) {
|
|
svc := &Service{
|
|
mu: sync.RWMutex{},
|
|
runners: sync.Map{},
|
|
Logger: utils.NewLogger("TestService"),
|
|
}
|
|
|
|
// Initialize context
|
|
svc.ctx, svc.cancel = context.WithCancel(context.Background())
|
|
defer svc.cancel()
|
|
|
|
// Test concurrent runner management
|
|
var wg sync.WaitGroup
|
|
numGoroutines := 50
|
|
|
|
// Mock runner for testing
|
|
mockRunner := &mockTaskRunner{id: primitive.NewObjectID()}
|
|
|
|
// Concurrent adds
|
|
for i := 0; i < numGoroutines; i++ {
|
|
wg.Add(1)
|
|
go func(index int) {
|
|
defer wg.Done()
|
|
taskId := primitive.NewObjectID()
|
|
svc.addRunner(taskId, mockRunner)
|
|
|
|
// Brief pause
|
|
time.Sleep(time.Millisecond)
|
|
|
|
// Test get runner
|
|
_, err := svc.getRunner(taskId)
|
|
if err != nil {
|
|
t.Errorf("Failed to get runner: %v", err)
|
|
}
|
|
|
|
// Delete runner
|
|
svc.deleteRunner(taskId)
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
t.Log("✅ Concurrent access test completed successfully")
|
|
}
|
|
|
|
// TestService_ErrorHandling tests error recovery
|
|
func TestService_ErrorHandling(t *testing.T) {
|
|
svc := &Service{
|
|
mu: sync.RWMutex{},
|
|
runners: sync.Map{},
|
|
Logger: utils.NewLogger("TestService"),
|
|
}
|
|
|
|
// Test getting non-existent runner
|
|
_, err := svc.getRunner(primitive.NewObjectID())
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent runner")
|
|
}
|
|
|
|
// Test adding invalid runner type
|
|
taskId := primitive.NewObjectID()
|
|
svc.runners.Store(taskId, "invalid-type")
|
|
|
|
_, err = svc.getRunner(taskId)
|
|
if err == nil {
|
|
t.Error("Expected error for invalid runner type")
|
|
}
|
|
|
|
t.Log("✅ Error handling test completed successfully")
|
|
}
|
|
|
|
// TestService_ResourceCleanup tests proper resource cleanup
|
|
func TestService_ResourceCleanup(t *testing.T) {
|
|
svc := &Service{
|
|
mu: sync.RWMutex{},
|
|
runners: sync.Map{},
|
|
Logger: utils.NewLogger("TestService"),
|
|
}
|
|
|
|
// Initialize context and tickers
|
|
svc.ctx, svc.cancel = context.WithCancel(context.Background())
|
|
svc.fetchTicker = time.NewTicker(100 * time.Millisecond)
|
|
svc.reportTicker = time.NewTicker(100 * time.Millisecond)
|
|
|
|
// Add some mock runners
|
|
for i := 0; i < 5; i++ {
|
|
taskId := primitive.NewObjectID()
|
|
mockRunner := &mockTaskRunner{id: taskId}
|
|
svc.addRunner(taskId, mockRunner)
|
|
}
|
|
|
|
// Verify runners exist
|
|
runnerCount := 0
|
|
svc.runners.Range(func(key, value interface{}) bool {
|
|
runnerCount++
|
|
return true
|
|
})
|
|
if runnerCount != 5 {
|
|
t.Errorf("Expected 5 runners, got %d", runnerCount)
|
|
}
|
|
|
|
// Test cleanup
|
|
svc.stopAllRunners()
|
|
|
|
// Verify cleanup (runners should still exist but be marked for cancellation)
|
|
// In a real scenario, runners would remove themselves after cancellation
|
|
t.Log("✅ Resource cleanup test completed successfully")
|
|
}
|
|
|
|
// Mock task runner for testing
|
|
type mockTaskRunner struct {
|
|
id primitive.ObjectID
|
|
}
|
|
|
|
func (r *mockTaskRunner) Init() error {
|
|
return nil
|
|
}
|
|
|
|
func (r *mockTaskRunner) GetTaskId() primitive.ObjectID {
|
|
return r.id
|
|
}
|
|
|
|
func (r *mockTaskRunner) Run() error {
|
|
return nil
|
|
}
|
|
|
|
func (r *mockTaskRunner) Cancel(force bool) error {
|
|
return nil
|
|
}
|
|
|
|
func (r *mockTaskRunner) SetSubscribeTimeout(timeout time.Duration) {
|
|
// Mock implementation
|
|
}
|