Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
tracker_test.go14 kB
package temporal import ( "math" "testing" "time" ) func TestConfig_Default(t *testing.T) { cfg := DefaultConfig() if cfg.MaxTrackedNodes <= 0 { t.Error("MaxTrackedNodes should be positive") } if cfg.SessionTimeoutSeconds <= 0 { t.Error("SessionTimeoutSeconds should be positive") } } func TestConfig_HighPrecision(t *testing.T) { cfg := HighPrecisionConfig() def := DefaultConfig() if cfg.MinAccessesForPrediction <= def.MinAccessesForPrediction { t.Error("HighPrecision should require more accesses") } } func TestConfig_LowMemory(t *testing.T) { cfg := LowMemoryConfig() def := DefaultConfig() if cfg.MaxTrackedNodes >= def.MaxTrackedNodes { t.Error("LowMemory should track fewer nodes") } } func TestTracker_New(t *testing.T) { tracker := NewTracker(DefaultConfig()) if tracker == nil { t.Fatal("NewTracker returned nil") } stats := tracker.GetGlobalStats() if stats.TotalAccesses != 0 { t.Errorf("TotalAccesses = %v, want 0", stats.TotalAccesses) } if stats.TrackedNodes != 0 { t.Errorf("TrackedNodes = %v, want 0", stats.TrackedNodes) } } func TestTracker_RecordAccess(t *testing.T) { tracker := NewTracker(DefaultConfig()) tracker.RecordAccess("node-1") tracker.RecordAccess("node-2") tracker.RecordAccess("node-1") stats := tracker.GetGlobalStats() if stats.TotalAccesses != 3 { t.Errorf("TotalAccesses = %v, want 3", stats.TotalAccesses) } if stats.TrackedNodes != 2 { t.Errorf("TrackedNodes = %v, want 2", stats.TrackedNodes) } } func TestTracker_GetStats(t *testing.T) { tracker := NewTracker(DefaultConfig()) // Unknown node stats := tracker.GetStats("unknown") if stats != nil { t.Error("Should return nil for unknown node") } // Record some accesses baseTime := time.Now() tracker.RecordAccessAt("node-1", baseTime) tracker.RecordAccessAt("node-1", baseTime.Add(10*time.Second)) tracker.RecordAccessAt("node-1", baseTime.Add(20*time.Second)) stats = tracker.GetStats("node-1") if stats == nil { t.Fatal("Should return stats for known node") } if stats.TotalAccesses != 3 { t.Errorf("TotalAccesses = %v, want 3", stats.TotalAccesses) } if stats.NodeID != "node-1" { t.Errorf("NodeID = %v, want node-1", stats.NodeID) } } func TestTracker_PredictNextAccess_UnknownNode(t *testing.T) { tracker := NewTracker(DefaultConfig()) pred := tracker.PredictNextAccess("unknown") if pred != nil { t.Error("Should return nil for unknown node") } } func TestTracker_PredictNextAccess_InsufficientData(t *testing.T) { tracker := NewTracker(DefaultConfig()) tracker.RecordAccess("node-1") pred := tracker.PredictNextAccess("node-1") if pred == nil { t.Fatal("Should return prediction even with insufficient data") } if pred.Confidence != 0 { t.Errorf("Confidence = %v, want 0 (insufficient data)", pred.Confidence) } if pred.AccessRateTrend != "unknown" { t.Errorf("Trend = %v, want unknown", pred.AccessRateTrend) } } func TestTracker_PredictNextAccess_RegularPattern(t *testing.T) { tracker := NewTracker(DefaultConfig()) // Simulate regular access pattern (every 10 seconds) baseTime := time.Now() for i := 0; i < 10; i++ { tracker.RecordAccessAt("node-1", baseTime.Add(time.Duration(i*10)*time.Second)) } pred := tracker.PredictNextAccess("node-1") if pred == nil { t.Fatal("Should return prediction") } t.Logf("Regular pattern prediction:") t.Logf(" Predicted time: %v", pred.PredictedTime) t.Logf(" Seconds until: %.1f", pred.SecondsUntil) t.Logf(" Confidence: %.3f", pred.Confidence) t.Logf(" Trend: %s", pred.AccessRateTrend) t.Logf(" Based on: %d accesses", pred.BasedOnAccesses) // Should have SOME confidence after 10 accesses (> 0) // Confidence varies based on filter uncertainty if pred.Confidence <= 0 { t.Errorf("Confidence should be positive: %v", pred.Confidence) } if pred.BasedOnAccesses != 10 { t.Errorf("BasedOnAccesses = %v, want 10", pred.BasedOnAccesses) } } func TestTracker_GetAccessRateTrend(t *testing.T) { tracker := NewTracker(DefaultConfig()) // Unknown node vel, trend := tracker.GetAccessRateTrend("unknown") if trend != "unknown" { t.Errorf("Trend = %v, want unknown", trend) } if vel != 0 { t.Errorf("Velocity = %v, want 0", vel) } // Record accesses with increasing frequency baseTime := time.Now() intervals := []int{100, 80, 60, 40, 20, 10, 5} // Decreasing intervals = increasing rate currentTime := baseTime for _, interval := range intervals { tracker.RecordAccessAt("node-1", currentTime) currentTime = currentTime.Add(time.Duration(interval) * time.Second) } vel, trend = tracker.GetAccessRateTrend("node-1") t.Logf("Increasing access rate: velocity=%.4f, trend=%s", vel, trend) } func TestTracker_SessionDetection(t *testing.T) { cfg := DefaultConfig() cfg.SessionTimeoutSeconds = 60 // 1 minute tracker := NewTracker(cfg) baseTime := time.Now() // Session 1: rapid accesses for i := 0; i < 5; i++ { tracker.RecordAccessAt("node-1", baseTime.Add(time.Duration(i*10)*time.Second)) } // Gap of 2 minutes (new session) tracker.RecordAccessAt("node-1", baseTime.Add(170*time.Second)) stats := tracker.GetStats("node-1") t.Logf("Session detection: SessionCount=%d", stats.SessionCount) if stats.SessionCount < 2 { t.Errorf("SessionCount = %v, want >= 2", stats.SessionCount) } } func TestTracker_HotColdNodes(t *testing.T) { tracker := NewTracker(DefaultConfig()) baseTime := time.Now() // Node 1: increasing access rate (hot) intervals1 := []int{100, 80, 60, 40, 20} currentTime := baseTime for _, interval := range intervals1 { tracker.RecordAccessAt("hot-node", currentTime) currentTime = currentTime.Add(time.Duration(interval) * time.Second) } // Node 2: decreasing access rate (cold) intervals2 := []int{20, 40, 60, 80, 100} currentTime = baseTime for _, interval := range intervals2 { tracker.RecordAccessAt("cold-node", currentTime) currentTime = currentTime.Add(time.Duration(interval) * time.Second) } hot := tracker.GetHotNodes(10) cold := tracker.GetColdNodes(10) t.Logf("Hot nodes: %v", hot) t.Logf("Cold nodes: %v", cold) // Hot node should have positive velocity // Cold node should have negative velocity } func TestTracker_PatternDetection(t *testing.T) { tracker := NewTracker(DefaultConfig()) baseTime := time.Date(2024, 1, 1, 9, 0, 0, 0, time.UTC) // Monday 9am // Simulate daily pattern: access at 9am each day for 2 weeks for day := 0; day < 14; day++ { accessTime := baseTime.Add(time.Duration(day*24) * time.Hour) tracker.RecordAccessAt("daily-node", accessTime) } stats := tracker.GetStats("daily-node") t.Logf("Pattern detection:") t.Logf(" HasDailyPattern: %v", stats.HasDailyPattern) t.Logf(" HasWeeklyPattern: %v", stats.HasWeeklyPattern) t.Logf(" PeakHour: %d", stats.PeakHour) t.Logf(" PeakDay: %d", stats.PeakDay) if stats.PeakHour != 9 { t.Errorf("PeakHour = %v, want 9", stats.PeakHour) } } func TestTracker_LRUEviction(t *testing.T) { cfg := DefaultConfig() cfg.MaxTrackedNodes = 3 tracker := NewTracker(cfg) // Add 4 nodes (should evict oldest) tracker.RecordAccess("node-1") tracker.RecordAccess("node-2") tracker.RecordAccess("node-3") tracker.RecordAccess("node-4") stats := tracker.GetGlobalStats() if stats.TrackedNodes != 3 { t.Errorf("TrackedNodes = %v, want 3 (after eviction)", stats.TrackedNodes) } // node-1 should have been evicted if tracker.GetStats("node-1") != nil { t.Error("node-1 should have been evicted") } } func TestTracker_Reset(t *testing.T) { tracker := NewTracker(DefaultConfig()) tracker.RecordAccess("node-1") tracker.RecordAccess("node-2") stats := tracker.GetGlobalStats() if stats.TotalAccesses != 2 { t.Fatal("Setup failed") } tracker.Reset() stats = tracker.GetGlobalStats() if stats.TotalAccesses != 0 { t.Errorf("TotalAccesses = %v, want 0 after reset", stats.TotalAccesses) } if stats.TrackedNodes != 0 { t.Errorf("TrackedNodes = %v, want 0 after reset", stats.TrackedNodes) } } // ===== Prediction Accuracy Tests ===== func TestPrediction_RegularInterval(t *testing.T) { tracker := NewTracker(DefaultConfig()) // Regular 10-second intervals baseTime := time.Now().Add(-100 * time.Second) // Start in past for i := 0; i < 10; i++ { tracker.RecordAccessAt("node", baseTime.Add(time.Duration(i*10)*time.Second)) } pred := tracker.PredictNextAccess("node") if pred == nil { t.Fatal("Should have prediction") } // Last access was at baseTime + 90s // Next should be around baseTime + 100s expectedNext := baseTime.Add(100 * time.Second) actualDiff := math.Abs(pred.PredictedTime.Sub(expectedNext).Seconds()) t.Logf("Regular interval prediction:") t.Logf(" Expected: %v", expectedNext) t.Logf(" Predicted: %v", pred.PredictedTime) t.Logf(" Difference: %.1f seconds", actualDiff) // Should be within 30 seconds of expected if actualDiff > 30 { t.Errorf("Prediction off by %.1f seconds (expected within 30)", actualDiff) } } func TestPrediction_AcceleratingAccess(t *testing.T) { tracker := NewTracker(DefaultConfig()) // Accelerating access: 20s, 15s, 10s, 5s intervals baseTime := time.Now().Add(-60 * time.Second) intervals := []int{0, 20, 35, 45, 50} // Cumulative seconds for _, offset := range intervals { tracker.RecordAccessAt("node", baseTime.Add(time.Duration(offset)*time.Second)) } pred := tracker.PredictNextAccess("node") if pred == nil { t.Fatal("Should have prediction") } t.Logf("Accelerating access prediction:") t.Logf(" Trend: %s", pred.AccessRateTrend) t.Logf(" Confidence: %.3f", pred.Confidence) t.Logf(" Seconds until: %.1f", pred.SecondsUntil) // With accelerating access, next interval should be short if pred.SecondsUntil > 30 { t.Logf("Note: Predicted interval (%.1f) seems long for accelerating pattern", pred.SecondsUntil) } } func TestPrediction_DeceleratingAccess(t *testing.T) { tracker := NewTracker(DefaultConfig()) // Decelerating access: 5s, 10s, 15s, 20s intervals baseTime := time.Now().Add(-60 * time.Second) intervals := []int{0, 5, 15, 30, 50} // Cumulative seconds for _, offset := range intervals { tracker.RecordAccessAt("node", baseTime.Add(time.Duration(offset)*time.Second)) } pred := tracker.PredictNextAccess("node") if pred == nil { t.Fatal("Should have prediction") } t.Logf("Decelerating access prediction:") t.Logf(" Trend: %s", pred.AccessRateTrend) t.Logf(" Confidence: %.3f", pred.Confidence) t.Logf(" Seconds until: %.1f", pred.SecondsUntil) // With decelerating access, trend should show decreasing // (though our trend is about rate velocity, not interval) } // ===== Benchmarks ===== func BenchmarkTracker_RecordAccess(b *testing.B) { tracker := NewTracker(DefaultConfig()) b.ResetTimer() for i := 0; i < b.N; i++ { tracker.RecordAccess("node-1") } } func BenchmarkTracker_RecordAccess_ManyNodes(b *testing.B) { tracker := NewTracker(DefaultConfig()) b.ResetTimer() for i := 0; i < b.N; i++ { nodeID := "node-" + string(rune('A'+i%26)) tracker.RecordAccess(nodeID) } } func BenchmarkTracker_PredictNextAccess(b *testing.B) { tracker := NewTracker(DefaultConfig()) // Setup: record some accesses for i := 0; i < 100; i++ { tracker.RecordAccess("node-1") } b.ResetTimer() for i := 0; i < b.N; i++ { tracker.PredictNextAccess("node-1") } } func BenchmarkTracker_GetStats(b *testing.B) { tracker := NewTracker(DefaultConfig()) // Setup for i := 0; i < 100; i++ { tracker.RecordAccess("node-1") } b.ResetTimer() for i := 0; i < b.N; i++ { tracker.GetStats("node-1") } } // ===== Missing Coverage Tests ===== func TestTracker_IsSessionBoundary(t *testing.T) { tracker := NewTracker(DefaultConfig()) // Unknown node should return false if tracker.IsSessionBoundary("unknown") { t.Error("Unknown node should not be session boundary") } // Fresh node with no velocity history should return false tracker.RecordAccess("node-1") if tracker.IsSessionBoundary("node-1") { t.Error("New node with single access should not be boundary") } // Record multiple accesses to establish velocity baseTime := time.Now() for i := 0; i < 10; i++ { tracker.RecordAccessAt("node-2", baseTime.Add(time.Duration(i*10)*time.Second)) } // Check - may or may not be boundary depending on pattern _ = tracker.IsSessionBoundary("node-2") // Just ensure it doesn't panic and returns a bool t.Log("IsSessionBoundary executed successfully") } func TestTracker_Cleanup(t *testing.T) { cfg := DefaultConfig() cfg.MaxTrackedNodes = 5 // Small limit to test eviction tracker := NewTracker(cfg) // Add more nodes than max for i := 0; i < 10; i++ { tracker.RecordAccess("node-" + string(rune('A'+i))) } // Should have evicted some nodes stats := tracker.GetGlobalStats() if stats.TrackedNodes > cfg.MaxTrackedNodes { t.Errorf("Should have evicted nodes, have %d, max %d", stats.TrackedNodes, cfg.MaxTrackedNodes) } t.Logf("Tracked %d nodes after adding 10 (max: %d)", stats.TrackedNodes, cfg.MaxTrackedNodes) } func TestTracker_GetHotNodes(t *testing.T) { tracker := NewTracker(DefaultConfig()) baseTime := time.Now() // Create nodes with different access frequencies for i := 0; i < 5; i++ { // Hot nodes - accessed many times recently for j := 0; j < (5 - i); j++ { tracker.RecordAccessAt("hot-"+string(rune('A'+i)), baseTime.Add(time.Duration(j)*time.Second)) } } hotNodes := tracker.GetHotNodes(3) if len(hotNodes) > 3 { t.Errorf("Requested 3 hot nodes, got %d", len(hotNodes)) } t.Logf("Hot nodes: %v", hotNodes) } func TestTracker_GetColdNodes(t *testing.T) { tracker := NewTracker(DefaultConfig()) baseTime := time.Now() // Create old accesses for i := 0; i < 5; i++ { tracker.RecordAccessAt("cold-"+string(rune('A'+i)), baseTime.Add(-time.Duration(i+1)*time.Hour)) } coldNodes := tracker.GetColdNodes(3) if len(coldNodes) > 3 { t.Errorf("Requested 3 cold nodes, got %d", len(coldNodes)) } t.Logf("Cold nodes: %v", coldNodes) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server