Add Auto-Refresh Tests for Cache Functionality
- Implemented comprehensive tests for the auto-refresh feature in the cache, ensuring it operates without leaking goroutines. - Verified that multiple start calls do not accumulate goroutines and that stopping the refresh does not panic. - Included tests for concurrent start and stop operations to ensure thread safety and reliability in the cache's behavior. - Enhanced overall test coverage for the cache module, contributing to improved stability and performance assurance.
This commit is contained in:
parent
f330a92a2b
commit
a490617563
1 changed files with 136 additions and 0 deletions
136
agent/robot/cache/cache_test.go
vendored
136
agent/robot/cache/cache_test.go
vendored
|
|
@ -3,7 +3,9 @@ package cache_test
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/gou/model"
|
||||
|
|
@ -212,6 +214,140 @@ func TestCacheGetByStatus(t *testing.T) {
|
|||
assert.Equal(t, types.RobotIdle, testRobot2.Status, "Test robot 2 should be idle")
|
||||
}
|
||||
|
||||
// TestCacheAutoRefresh tests auto-refresh functionality and goroutine leak prevention
|
||||
func TestCacheAutoRefresh(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test")
|
||||
}
|
||||
|
||||
testutils.Prepare(t)
|
||||
defer testutils.Clean(t)
|
||||
|
||||
cleanupTestRobots(t)
|
||||
setupTestRobots(t)
|
||||
defer cleanupTestRobots(t)
|
||||
|
||||
c := cache.New()
|
||||
ctx := types.NewContext(context.Background(), nil)
|
||||
|
||||
// Load initial data
|
||||
err := c.Load(ctx)
|
||||
assert.NoError(t, err)
|
||||
initialCount := c.Count()
|
||||
|
||||
t.Run("start and stop auto-refresh", func(t *testing.T) {
|
||||
// Record initial goroutine count
|
||||
runtime.GC()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
initialGoroutines := runtime.NumGoroutine()
|
||||
|
||||
// Start auto-refresh with short interval
|
||||
config := &cache.RefreshConfig{Interval: 100 * time.Millisecond}
|
||||
c.StartAutoRefresh(ctx, config)
|
||||
|
||||
// Wait a bit to let it run (should trigger at least 2 refreshes)
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
|
||||
// Stop auto-refresh
|
||||
c.StopAutoRefresh()
|
||||
|
||||
// Wait for goroutine to exit
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
runtime.GC()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Check for goroutine leak
|
||||
finalGoroutines := runtime.NumGoroutine()
|
||||
assert.LessOrEqual(t, finalGoroutines, initialGoroutines+1,
|
||||
"Should not leak goroutines after stop (initial: %d, final: %d)",
|
||||
initialGoroutines, finalGoroutines)
|
||||
|
||||
// Should still have robots
|
||||
assert.GreaterOrEqual(t, c.Count(), initialCount)
|
||||
})
|
||||
|
||||
t.Run("multiple start calls should not leak goroutines", func(t *testing.T) {
|
||||
// Record initial goroutine count
|
||||
runtime.GC()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
initialGoroutines := runtime.NumGoroutine()
|
||||
|
||||
// Start multiple times without stopping
|
||||
// This should not create multiple goroutines or ticker leaks
|
||||
config := &cache.RefreshConfig{Interval: 100 * time.Millisecond}
|
||||
|
||||
c.StartAutoRefresh(ctx, config)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
c.StartAutoRefresh(ctx, config) // Should stop previous one
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
c.StartAutoRefresh(ctx, config) // Should stop previous one
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// After multiple starts, should only have 1 goroutine running
|
||||
afterStartsGoroutines := runtime.NumGoroutine()
|
||||
assert.LessOrEqual(t, afterStartsGoroutines, initialGoroutines+2,
|
||||
"Multiple starts should not accumulate goroutines (initial: %d, after starts: %d)",
|
||||
initialGoroutines, afterStartsGoroutines)
|
||||
|
||||
// Stop once should be enough
|
||||
c.StopAutoRefresh()
|
||||
|
||||
// Wait for cleanup
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
runtime.GC()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Should be back to initial count
|
||||
finalGoroutines := runtime.NumGoroutine()
|
||||
assert.LessOrEqual(t, finalGoroutines, initialGoroutines+1,
|
||||
"Should cleanup all goroutines after final stop (initial: %d, final: %d)",
|
||||
initialGoroutines, finalGoroutines)
|
||||
|
||||
// Should still work correctly
|
||||
assert.GreaterOrEqual(t, c.Count(), initialCount)
|
||||
})
|
||||
|
||||
t.Run("stop without start should not panic", func(t *testing.T) {
|
||||
// Multiple stops should be safe
|
||||
assert.NotPanics(t, func() {
|
||||
c.StopAutoRefresh()
|
||||
c.StopAutoRefresh()
|
||||
c.StopAutoRefresh()
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("concurrent start and stop should be safe", func(t *testing.T) {
|
||||
// Record initial goroutine count
|
||||
runtime.GC()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
initialGoroutines := runtime.NumGoroutine()
|
||||
|
||||
config := &cache.RefreshConfig{Interval: 50 * time.Millisecond}
|
||||
|
||||
// Rapidly start and stop multiple times
|
||||
for i := 0; i < 10; i++ {
|
||||
c.StartAutoRefresh(ctx, config)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
c.StopAutoRefresh()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Final cleanup
|
||||
c.StopAutoRefresh()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
runtime.GC()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Should not have leaked goroutines
|
||||
finalGoroutines := runtime.NumGoroutine()
|
||||
assert.LessOrEqual(t, finalGoroutines, initialGoroutines+1,
|
||||
"Rapid start/stop cycles should not leak goroutines (initial: %d, final: %d)",
|
||||
initialGoroutines, finalGoroutines)
|
||||
})
|
||||
}
|
||||
|
||||
// setupTestRobots creates 3 test robot records in database
|
||||
func setupTestRobots(t *testing.T) {
|
||||
// Get the actual table name from model
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue