Pulse/internal/ai/approval/approval_memory_regression_test.go

108 lines
2.6 KiB
Go

package approval
import (
"fmt"
"runtime"
"runtime/debug"
"testing"
"time"
)
func TestApprovalStoreMemoryStability(t *testing.T) {
if testing.Short() {
t.Skip("skipping memory regression in short mode")
}
store, err := NewStore(StoreConfig{
DataDir: t.TempDir(),
DefaultTimeout: 2 * time.Minute,
MaxApprovals: 50,
DisablePersistence: true,
})
if err != nil {
t.Fatalf("NewStore: %v", err)
}
approvalsPerCycle := 120
executionsPerCycle := 60
warmupCycles := 5
measureCycles := 12
runCycle := func(offset int) {
for i := 0; i < approvalsPerCycle; i++ {
idx := offset + i
req := &ApprovalRequest{
ExecutionID: fmt.Sprintf("exec-%d", idx),
ToolID: "pulse_exec",
Command: "systemctl status nginx",
TargetType: "vm",
TargetID: fmt.Sprintf("vm-%02d", idx%20),
Context: "status check",
}
if err := store.CreateApproval(req); err != nil {
t.Fatalf("CreateApproval: %v", err)
}
var updated *ApprovalRequest
if idx%2 == 0 {
approved, err := store.Approve(req.ID, "admin")
if err != nil {
t.Fatalf("Approve: %v", err)
}
updated = approved
} else {
denied, err := store.Deny(req.ID, "admin", "not needed")
if err != nil {
t.Fatalf("Deny: %v", err)
}
updated = denied
}
if updated != nil {
decidedAt := time.Now().Add(-48 * time.Hour)
updated.DecidedAt = &decidedAt
}
}
for i := 0; i < executionsPerCycle; i++ {
idx := offset + i
state := &ExecutionState{
ID: fmt.Sprintf("exec-state-%d", idx),
CreatedAt: time.Now().Add(-2 * time.Hour),
ExpiresAt: time.Now().Add(-1 * time.Hour),
Messages: []map[string]interface{}{{"role": "user", "content": "status"}},
OriginalRequest: map[string]interface{}{"prompt": "status"},
}
if err := store.StoreExecution(state); err != nil {
t.Fatalf("StoreExecution: %v", err)
}
}
store.CleanupExpired()
}
for i := 0; i < warmupCycles; i++ {
runCycle(i * approvalsPerCycle)
}
runtime.GC()
debug.FreeOSMemory()
var baseline runtime.MemStats
runtime.ReadMemStats(&baseline)
for i := 0; i < measureCycles; i++ {
runCycle(100000 + i*approvalsPerCycle)
}
runtime.GC()
debug.FreeOSMemory()
var after runtime.MemStats
runtime.ReadMemStats(&after)
if baseline.HeapAlloc > 0 {
allowed := baseline.HeapAlloc + 5*1024*1024
growthRatio := float64(after.HeapAlloc) / float64(baseline.HeapAlloc)
if after.HeapAlloc > allowed && growthRatio > 1.25 {
t.Fatalf("heap allocation grew too much: baseline=%d final=%d ratio=%.2f", baseline.HeapAlloc, after.HeapAlloc, growthRatio)
}
}
}