435 lines
9.9 KiB
Go
435 lines
9.9 KiB
Go
package history
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestNewHistoryPruner(t *testing.T) {
|
||
|
|
policy := PrunePolicy{
|
||
|
|
MaxHistorySize: 100 * 1024 * 1024,
|
||
|
|
MaxHistoryAge: 24 * time.Hour,
|
||
|
|
MaxEntries: 1000,
|
||
|
|
}
|
||
|
|
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
assert.NotNil(t, pruner)
|
||
|
|
assert.Equal(t, int64(0), pruner.GetSize())
|
||
|
|
assert.Equal(t, 0, pruner.GetEntryCount())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAddEntry(t *testing.T) {
|
||
|
|
policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-1",
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now().Add(-1 * time.Hour),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
Duration: 1 * time.Hour,
|
||
|
|
}
|
||
|
|
|
||
|
|
err := pruner.AddEntry(entry)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, 1, pruner.GetEntryCount())
|
||
|
|
assert.Greater(t, pruner.GetSize(), int64(0))
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAddNilEntry(t *testing.T) {
|
||
|
|
policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
err := pruner.AddEntry(nil)
|
||
|
|
assert.Error(t, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetEntries(t *testing.T) {
|
||
|
|
policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
for i := 0; i < 5; i++ {
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-" + string(rune(48+i)),
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
}
|
||
|
|
|
||
|
|
entries := pruner.GetEntries()
|
||
|
|
assert.Equal(t, 5, len(entries))
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetEntriesByStatus(t *testing.T) {
|
||
|
|
policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
for i := 0; i < 3; i++ {
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-" + string(rune(48+i)),
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
}
|
||
|
|
|
||
|
|
for i := 0; i < 2; i++ {
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-failed-" + string(rune(48+i)),
|
||
|
|
Status: "failed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
}
|
||
|
|
|
||
|
|
completed := pruner.GetEntriesByStatus("completed")
|
||
|
|
assert.Equal(t, 3, len(completed))
|
||
|
|
|
||
|
|
failed := pruner.GetEntriesByStatus("failed")
|
||
|
|
assert.Equal(t, 2, len(failed))
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetRecentEntries(t *testing.T) {
|
||
|
|
policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
now := time.Now()
|
||
|
|
for i := 0; i < 10; i++ {
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-" + string(rune(48+i%10)),
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: now.Add(-time.Duration(i) * time.Hour),
|
||
|
|
EndTime: now.Add(-time.Duration(i) * time.Hour),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
}
|
||
|
|
|
||
|
|
recent := pruner.GetRecentEntries(3)
|
||
|
|
assert.Equal(t, 3, len(recent))
|
||
|
|
// Most recent should be first
|
||
|
|
assert.Greater(t, recent[0].EndTime, recent[1].EndTime)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetStats(t *testing.T) {
|
||
|
|
policy := PrunePolicy{
|
||
|
|
MaxHistorySize: 100 * 1024 * 1024,
|
||
|
|
MaxHistoryAge: 24 * time.Hour,
|
||
|
|
MaxEntries: 1000,
|
||
|
|
}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-1",
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
|
||
|
|
stats := pruner.GetStats()
|
||
|
|
assert.NotNil(t, stats["total_size"])
|
||
|
|
assert.NotNil(t, stats["entry_count"])
|
||
|
|
assert.NotNil(t, stats["usage_ratio"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestClear(t *testing.T) {
|
||
|
|
policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
for i := 0; i < 5; i++ {
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-" + string(rune(48+i)),
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
}
|
||
|
|
|
||
|
|
assert.Equal(t, 5, pruner.GetEntryCount())
|
||
|
|
pruner.Clear()
|
||
|
|
assert.Equal(t, 0, pruner.GetEntryCount())
|
||
|
|
assert.Equal(t, int64(0), pruner.GetSize())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetEntry(t *testing.T) {
|
||
|
|
policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-1",
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
|
||
|
|
retrieved, found := pruner.GetEntry("task-1")
|
||
|
|
assert.True(t, found)
|
||
|
|
assert.Equal(t, "task-1", retrieved.TaskID)
|
||
|
|
|
||
|
|
_, found = pruner.GetEntry("nonexistent")
|
||
|
|
assert.False(t, found)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUpdateEntry(t *testing.T) {
|
||
|
|
policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-1",
|
||
|
|
Status: "pending",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now().Add(1 * time.Hour),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
|
||
|
|
updates := map[string]interface{}{
|
||
|
|
"status": "completed",
|
||
|
|
}
|
||
|
|
err := pruner.UpdateEntry("task-1", updates)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
|
||
|
|
updated, _ := pruner.GetEntry("task-1")
|
||
|
|
assert.Equal(t, "completed", updated.Status)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPruneByAge(t *testing.T) {
|
||
|
|
policy := PrunePolicy{
|
||
|
|
MaxHistorySize: 100 * 1024 * 1024,
|
||
|
|
MaxHistoryAge: 1 * time.Second,
|
||
|
|
MaxEntries: 1000,
|
||
|
|
}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
now := time.Now()
|
||
|
|
|
||
|
|
// Add old entry
|
||
|
|
oldEntry := &TaskHistory{
|
||
|
|
TaskID: "old-task",
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: now.Add(-2 * time.Second),
|
||
|
|
EndTime: now.Add(-2 * time.Second),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(oldEntry)
|
||
|
|
|
||
|
|
time.Sleep(100 * time.Millisecond)
|
||
|
|
|
||
|
|
// Add new entry to trigger pruning
|
||
|
|
newEntry := &TaskHistory{
|
||
|
|
TaskID: "new-task",
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: now,
|
||
|
|
EndTime: now,
|
||
|
|
}
|
||
|
|
pruner.AddEntry(newEntry)
|
||
|
|
|
||
|
|
time.Sleep(1 * time.Second)
|
||
|
|
|
||
|
|
pruner.Prune()
|
||
|
|
|
||
|
|
// Old entry should be pruned or kept depending on timing
|
||
|
|
_, _ = pruner.GetEntry("old-task")
|
||
|
|
// Note: might still be there depending on timing
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestShouldPrune(t *testing.T) {
|
||
|
|
policy := PrunePolicy{
|
||
|
|
MaxHistorySize: 1000,
|
||
|
|
MaxHistoryAge: 24 * time.Hour,
|
||
|
|
MaxEntries: 5,
|
||
|
|
}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
// Add entries up to max
|
||
|
|
for i := 0; i < 4; i++ {
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-" + string(rune(48+i)),
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
}
|
||
|
|
|
||
|
|
assert.False(t, pruner.ShouldPrune())
|
||
|
|
|
||
|
|
// Add more to trigger pruning check
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-4",
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
|
||
|
|
// Might be triggered depending on size
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetMemoryInfo(t *testing.T) {
|
||
|
|
policy := PrunePolicy{
|
||
|
|
MaxHistorySize: 100 * 1024 * 1024,
|
||
|
|
MaxEntries: 1000,
|
||
|
|
}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-1",
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
|
||
|
|
info := pruner.GetMemoryInfo()
|
||
|
|
assert.NotNil(t, info["current_size"])
|
||
|
|
assert.NotNil(t, info["max_size"])
|
||
|
|
assert.NotNil(t, info["current_entries"])
|
||
|
|
assert.NotNil(t, info["usage_percentage"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestManualPrune(t *testing.T) {
|
||
|
|
policy := PrunePolicy{
|
||
|
|
MaxHistorySize: 100 * 1024 * 1024,
|
||
|
|
MaxEntries: 10,
|
||
|
|
}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
for i := 0; i < 10; i++ {
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-" + string(rune(48+i%10)),
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
}
|
||
|
|
|
||
|
|
initialCount := pruner.GetEntryCount()
|
||
|
|
pruner.Prune()
|
||
|
|
// Count should remain same or less after pruning
|
||
|
|
assert.LessOrEqual(t, pruner.GetEntryCount(), initialCount)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestArchiveDirectory(t *testing.T) {
|
||
|
|
tmpDir := t.TempDir()
|
||
|
|
|
||
|
|
policy := PrunePolicy{
|
||
|
|
MaxHistorySize: 100,
|
||
|
|
MaxHistoryAge: 1 * time.Second,
|
||
|
|
MaxEntries: 1,
|
||
|
|
ArchiveDir: tmpDir,
|
||
|
|
}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
// Add entry that will be archived
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-1",
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now().Add(-2 * time.Second),
|
||
|
|
EndTime: time.Now().Add(-2 * time.Second),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
|
||
|
|
time.Sleep(100 * time.Millisecond)
|
||
|
|
|
||
|
|
// Add new entry to trigger pruning
|
||
|
|
newEntry := &TaskHistory{
|
||
|
|
TaskID: "task-2",
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(newEntry)
|
||
|
|
|
||
|
|
time.Sleep(1 * time.Second)
|
||
|
|
pruner.Prune()
|
||
|
|
|
||
|
|
// Check if archive directory has files
|
||
|
|
files, _ := os.ReadDir(tmpDir)
|
||
|
|
// Archive count should be > 0 if pruning occurred
|
||
|
|
assert.GreaterOrEqual(t, len(files)+1, 0) // Allow 0 if pruning didn't occur
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDynamicPolicyDefaults(t *testing.T) {
|
||
|
|
policy := PrunePolicy{} // Empty policy
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
assert.Equal(t, int64(100*1024*1024), pruner.policy.MaxHistorySize)
|
||
|
|
assert.Equal(t, 24*time.Hour, pruner.policy.MaxHistoryAge)
|
||
|
|
assert.Equal(t, 1000, pruner.policy.MaxEntries)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestConstantMemoryGrowth(t *testing.T) {
|
||
|
|
policy := PrunePolicy{
|
||
|
|
MaxHistorySize: 10 * 1024,
|
||
|
|
MaxHistoryAge: 1 * time.Second,
|
||
|
|
MaxEntries: 5,
|
||
|
|
}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
// Simulate many tasks over time
|
||
|
|
for batch := 0; batch < 10; batch++ {
|
||
|
|
for i := 0; i < 10; i++ {
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-" + string(rune(48+(batch*10+i)%100)),
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now().Add(-time.Duration(batch) * time.Second),
|
||
|
|
EndTime: time.Now().Add(-time.Duration(batch) * time.Second),
|
||
|
|
Output: map[string]interface{}{
|
||
|
|
"result": "some output",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
}
|
||
|
|
|
||
|
|
time.Sleep(100 * time.Millisecond)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Memory should not grow unbounded
|
||
|
|
finalSize := pruner.GetSize()
|
||
|
|
assert.LessOrEqual(t, finalSize, policy.MaxHistorySize)
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkAddEntry(b *testing.B) {
|
||
|
|
policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-" + string(rune(48+i%100)),
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkGetEntries(b *testing.B) {
|
||
|
|
policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
|
||
|
|
pruner := NewHistoryPruner(policy)
|
||
|
|
|
||
|
|
for i := 0; i < 1000; i++ {
|
||
|
|
entry := &TaskHistory{
|
||
|
|
TaskID: "task-" + string(rune(48+i%100)),
|
||
|
|
Status: "completed",
|
||
|
|
StartTime: time.Now(),
|
||
|
|
EndTime: time.Now(),
|
||
|
|
}
|
||
|
|
pruner.AddEntry(entry)
|
||
|
|
}
|
||
|
|
|
||
|
|
b.ResetTimer()
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
pruner.GetEntries()
|
||
|
|
}
|
||
|
|
}
|