mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
c3f23cb99b
* Resolve issue with opaque tokens not being parsed correctly * Increase test coverage * Further improvements to test coverage and code quality * Add new providers. * fixup! Add new providers. * Cleanup. * fixup! Cleanup. * fixup! fixup! Cleanup. * fixup! fixup! fixup! Cleanup. * fixup! fixup! fixup! fixup! Cleanup. * Memory management optimisation 24 bytes per Put < 256-4096 bytes per buffer allocation avoided (10-170x difference) * Pooling cleanup.
617 lines
19 KiB
Go
617 lines
19 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// isRaceDetectorEnabled returns true if the Go race detector is enabled.
|
|
// This is determined by checking the build info for the race build tag.
|
|
func isRaceDetectorEnabled() bool {
|
|
info, ok := debug.ReadBuildInfo()
|
|
if !ok {
|
|
return false
|
|
}
|
|
for _, setting := range info.Settings {
|
|
if setting.Key == "-race" && setting.Value == "true" {
|
|
return true
|
|
}
|
|
}
|
|
// Alternative method: check if GORACE environment variable is set
|
|
return os.Getenv("GORACE") != ""
|
|
}
|
|
|
|
func TestProfilingManager(t *testing.T) {
|
|
logger := NewLogger("debug")
|
|
pm := NewProfilingManager(logger)
|
|
|
|
// Test taking a snapshot
|
|
snapshot, err := pm.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to take snapshot: %v", err)
|
|
}
|
|
|
|
if snapshot == nil {
|
|
t.Fatal("Snapshot is nil")
|
|
}
|
|
|
|
if snapshot.RuntimeStats.Alloc == 0 {
|
|
t.Error("Runtime stats Alloc should not be zero")
|
|
}
|
|
|
|
if snapshot.Timestamp.IsZero() {
|
|
t.Error("Snapshot timestamp should not be zero")
|
|
}
|
|
}
|
|
|
|
func TestMemoryTestOrchestrator(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping test in short mode")
|
|
}
|
|
|
|
logger := NewLogger("debug")
|
|
config := LeakDetectionConfig{
|
|
EnableLeakDetection: true,
|
|
LeakThresholdMB: 10,
|
|
}
|
|
|
|
mto := NewMemoryTestOrchestrator(config, logger)
|
|
|
|
// Test registering a component
|
|
sessionManager, err := NewSessionManager("test-key-32-chars-long-for-testing", false, "", logger)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create session manager: %v", err)
|
|
}
|
|
|
|
profiler := NewSessionPoolProfiler(sessionManager, logger)
|
|
mto.RegisterComponent("session_pool", profiler)
|
|
|
|
// Test getting leak analysis (should return false initially since no checks have been performed)
|
|
_, exists := mto.GetLeakAnalysis("session_pool")
|
|
if exists {
|
|
t.Error("Should not have leak analysis before any checks are performed")
|
|
}
|
|
|
|
// Perform a manual leak check
|
|
baseline, err := profiler.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to take baseline snapshot: %v", err)
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond) // Small delay
|
|
|
|
// Manually trigger leak check with baseline
|
|
baselineSnapshots := make(map[string]*MemorySnapshot)
|
|
baselineSnapshots["session_pool"] = baseline
|
|
mto.performLeakCheck(baselineSnapshots)
|
|
|
|
// Now test getting leak analysis
|
|
analysis, exists := mto.GetLeakAnalysis("session_pool")
|
|
if !exists {
|
|
t.Error("Should have leak analysis after performing checks")
|
|
}
|
|
|
|
if analysis == nil {
|
|
t.Error("Leak analysis should not be nil after checks")
|
|
}
|
|
}
|
|
|
|
func TestComponentProfilers(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping test in short mode")
|
|
}
|
|
|
|
logger := NewLogger("debug")
|
|
|
|
// Test Session Pool Profiler
|
|
sessionManager, err := NewSessionManager("test-key-32-chars-long-for-testing", false, "", logger)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create session manager: %v", err)
|
|
}
|
|
|
|
spp := NewSessionPoolProfiler(sessionManager, logger)
|
|
snapshot, err := spp.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to take session pool snapshot: %v", err)
|
|
}
|
|
|
|
if snapshot == nil {
|
|
t.Fatal("Session pool snapshot is nil")
|
|
}
|
|
|
|
// Test Cache Memory Profiler
|
|
cache := NewCache()
|
|
cmp := NewCacheMemoryProfiler(cache, logger)
|
|
snapshot, err = cmp.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to take cache snapshot: %v", err)
|
|
}
|
|
|
|
if snapshot == nil {
|
|
t.Fatal("Cache snapshot is nil")
|
|
}
|
|
|
|
// Test HTTP Client Profiler
|
|
httpClient := CreateDefaultHTTPClient()
|
|
hcp := NewHTTPClientProfiler(httpClient, logger)
|
|
snapshot, err = hcp.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to take HTTP client snapshot: %v", err)
|
|
}
|
|
|
|
if snapshot == nil {
|
|
t.Fatal("HTTP client snapshot is nil")
|
|
}
|
|
|
|
// Token Compression Profiler removed - use internal/pool statistics instead
|
|
t.Log("Token compression profiler deprecated - use internal/pool stats")
|
|
}
|
|
|
|
func TestLeakAnalysis(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping test in short mode")
|
|
}
|
|
|
|
logger := NewLogger("debug")
|
|
pm := NewProfilingManager(logger)
|
|
|
|
// Create baseline snapshot
|
|
baseline, err := pm.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create baseline: %v", err)
|
|
}
|
|
|
|
// Wait a bit and create current snapshot
|
|
time.Sleep(10 * time.Millisecond)
|
|
current, err := pm.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create current snapshot: %v", err)
|
|
}
|
|
|
|
// Test leak analysis
|
|
analysis := pm.AnalyzeLeaks(baseline, current)
|
|
if analysis == nil {
|
|
t.Fatal("Leak analysis is nil")
|
|
}
|
|
|
|
// Analysis should not have leaks for normal operation
|
|
if analysis.HasLeak {
|
|
t.Logf("Leak detected: %s", analysis.LeakDescription)
|
|
// This is acceptable as the test environment may have varying memory usage
|
|
}
|
|
}
|
|
|
|
func TestGlobalInstances(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping test in short mode")
|
|
}
|
|
|
|
// Test global profiling manager
|
|
gpm := GetGlobalProfilingManager()
|
|
if gpm == nil {
|
|
t.Fatal("Global profiling manager is nil")
|
|
}
|
|
|
|
// Test global test orchestrator
|
|
gto := GetGlobalTestOrchestrator()
|
|
if gto == nil {
|
|
t.Fatal("Global test orchestrator is nil")
|
|
}
|
|
|
|
// Test that they're singletons
|
|
gpm2 := GetGlobalProfilingManager()
|
|
if gpm != gpm2 {
|
|
t.Error("Global profiling manager should be singleton")
|
|
}
|
|
|
|
gto2 := GetGlobalTestOrchestrator()
|
|
if gto != gto2 {
|
|
t.Error("Global test orchestrator should be singleton")
|
|
}
|
|
}
|
|
|
|
func TestProfilingConfig(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping test in short mode")
|
|
}
|
|
|
|
config := ProfilingConfig{
|
|
EnableHeapProfiling: true,
|
|
EnableGoroutineProfiling: true,
|
|
SnapshotInterval: 30 * time.Second,
|
|
LeakThresholdMB: 50,
|
|
MaxSnapshots: 100,
|
|
EnableContinuousMonitoring: true,
|
|
MonitoringInterval: 60 * time.Second,
|
|
}
|
|
|
|
if !config.EnableHeapProfiling {
|
|
t.Error("Heap profiling should be enabled")
|
|
}
|
|
|
|
if !config.EnableGoroutineProfiling {
|
|
t.Error("Goroutine profiling should be enabled")
|
|
}
|
|
|
|
if config.LeakThresholdMB != 50 {
|
|
t.Errorf("Expected leak threshold 50, got %d", config.LeakThresholdMB)
|
|
}
|
|
}
|
|
|
|
func TestLeakDetectionConfig(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping test in short mode")
|
|
}
|
|
|
|
config := LeakDetectionConfig{
|
|
EnableLeakDetection: true,
|
|
LeakThresholdMB: 50,
|
|
GoroutineLeakThreshold: 10,
|
|
SessionPoolThreshold: 100,
|
|
CacheMemoryThreshold: 20 * 1024 * 1024,
|
|
HTTPClientThreshold: 50,
|
|
TokenCompressionThreshold: 2 * 1024 * 1024,
|
|
}
|
|
|
|
if !config.EnableLeakDetection {
|
|
t.Error("Leak detection should be enabled")
|
|
}
|
|
|
|
if config.LeakThresholdMB != 50 {
|
|
t.Errorf("Expected leak threshold 50, got %d", config.LeakThresholdMB)
|
|
}
|
|
|
|
if config.CacheMemoryThreshold != 20*1024*1024 {
|
|
t.Errorf("Expected cache threshold 20MB, got %d", config.CacheMemoryThreshold)
|
|
}
|
|
}
|
|
|
|
// ProviderMetadataProfiler monitors provider metadata fetching and caching operations
|
|
type ProviderMetadataProfiler struct {
|
|
metadataCache *MetadataCache
|
|
httpClient *http.Client
|
|
logger *Logger
|
|
providerURL string
|
|
}
|
|
|
|
// NewProviderMetadataProfiler creates a new provider metadata profiler
|
|
func NewProviderMetadataProfiler(metadataCache *MetadataCache, httpClient *http.Client, providerURL string, logger *Logger) *ProviderMetadataProfiler {
|
|
if logger == nil {
|
|
logger = newNoOpLogger()
|
|
}
|
|
return &ProviderMetadataProfiler{
|
|
metadataCache: metadataCache,
|
|
httpClient: httpClient,
|
|
providerURL: providerURL,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// TakeSnapshot captures current memory statistics for metadata operations
|
|
func (pmp *ProviderMetadataProfiler) TakeSnapshot() (*MemorySnapshot, error) {
|
|
snapshot := &MemorySnapshot{
|
|
Timestamp: time.Now(),
|
|
CustomMetrics: make(map[string]interface{}),
|
|
}
|
|
|
|
// Capture runtime memory statistics
|
|
runtime.ReadMemStats(&snapshot.RuntimeStats)
|
|
|
|
// Add metadata-specific metrics
|
|
snapshot.CustomMetrics["metadata_cache_size"] = 1 // Placeholder for cache size
|
|
snapshot.CustomMetrics["metadata_fetch_count"] = 0 // Placeholder for fetch count
|
|
snapshot.CustomMetrics["background_goroutines"] = runtime.NumGoroutine()
|
|
|
|
return snapshot, nil
|
|
}
|
|
|
|
// StartProfiling begins profiling (no-op for metadata profiler)
|
|
func (pmp *ProviderMetadataProfiler) StartProfiling(config ProfilingConfig) error {
|
|
return nil
|
|
}
|
|
|
|
// StopProfiling ends profiling
|
|
func (pmp *ProviderMetadataProfiler) StopProfiling() (*MemorySnapshot, error) {
|
|
return pmp.TakeSnapshot()
|
|
}
|
|
|
|
// GetCurrentStats returns current memory statistics
|
|
func (pmp *ProviderMetadataProfiler) GetCurrentStats() *runtime.MemStats {
|
|
stats := &runtime.MemStats{}
|
|
runtime.ReadMemStats(stats)
|
|
return stats
|
|
}
|
|
|
|
// AnalyzeLeaks analyzes metadata operations for memory leaks
|
|
func (pmp *ProviderMetadataProfiler) AnalyzeLeaks(baseline, current *MemorySnapshot) *LeakAnalysis {
|
|
analysis := &LeakAnalysis{
|
|
SuspectedLeaks: make([]string, 0),
|
|
Recommendations: make([]string, 0),
|
|
}
|
|
|
|
if baseline == nil || current == nil {
|
|
analysis.LeakDescription = "Insufficient metadata data"
|
|
return analysis
|
|
}
|
|
|
|
// Check for memory leaks
|
|
memoryIncrease := current.RuntimeStats.Alloc - baseline.RuntimeStats.Alloc
|
|
if memoryIncrease > 5*1024*1024 { // 5MB threshold for metadata operations
|
|
analysis.HasLeak = true
|
|
analysis.SuspectedLeaks = append(analysis.SuspectedLeaks,
|
|
"Metadata operations memory usage increased significantly")
|
|
analysis.Recommendations = append(analysis.Recommendations,
|
|
"Check for metadata cache not being cleaned up properly")
|
|
}
|
|
|
|
// Check for goroutine leaks
|
|
goroutineIncrease := current.CustomMetrics["background_goroutines"].(int) - baseline.CustomMetrics["background_goroutines"].(int)
|
|
if goroutineIncrease > 2 { // Allow some variance
|
|
analysis.HasLeak = true
|
|
analysis.SuspectedLeaks = append(analysis.SuspectedLeaks,
|
|
fmt.Sprintf("Goroutine count increased by %d during metadata operations", goroutineIncrease))
|
|
analysis.Recommendations = append(analysis.Recommendations,
|
|
"Check for background goroutines not being cleaned up")
|
|
}
|
|
|
|
return analysis
|
|
}
|
|
|
|
// TestProviderMetadataMemoryLeakDetection tests for memory leaks in provider metadata operations
|
|
func TestProviderMetadataMemoryLeakDetection(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping provider metadata memory leak detection test in short mode")
|
|
}
|
|
|
|
// Reset singleton cache manager to ensure clean state
|
|
ResetUniversalCacheManagerForTesting()
|
|
defer ResetUniversalCacheManagerForTesting() // Clean up after test
|
|
|
|
logger := NewLogger("debug")
|
|
|
|
strictMode := os.Getenv("STRICT_MEMORY_TEST") == "true"
|
|
if strictMode {
|
|
t.Log("Running in strict memory test mode - will fail on detected leaks")
|
|
} else {
|
|
t.Log("Running in lenient memory test mode - will log warnings instead of failing")
|
|
}
|
|
|
|
config := LeakDetectionConfig{
|
|
EnableLeakDetection: true,
|
|
LeakThresholdMB: 10,
|
|
}
|
|
|
|
mto := NewMemoryTestOrchestrator(config, logger)
|
|
|
|
// Create mock HTTP server for metadata endpoint with failure simulation
|
|
requestCount := 0
|
|
serverFailures := 0
|
|
mockServer := &http.Server{
|
|
Addr: "localhost:0", // Let system assign port
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
requestCount++
|
|
if r.URL.Path == "/.well-known/openid-configuration" {
|
|
// Simulate occasional failures to test cache extension
|
|
if requestCount%4 == 0 { // Fail every 4th request
|
|
serverFailures++
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
metadata := ProviderMetadata{
|
|
Issuer: "https://mock-provider.com",
|
|
AuthURL: "https://mock-provider.com/auth",
|
|
TokenURL: "https://mock-provider.com/token",
|
|
JWKSURL: "https://mock-provider.com/jwks",
|
|
RevokeURL: "https://mock-provider.com/revoke",
|
|
EndSessionURL: "https://mock-provider.com/logout",
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Cache-Control", "max-age=3600") // 1 hour cache hint
|
|
json.NewEncoder(w).Encode(metadata)
|
|
} else {
|
|
http.NotFound(w, r)
|
|
}
|
|
}),
|
|
}
|
|
|
|
// Start mock server
|
|
listener, err := net.Listen("tcp", "localhost:0")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create listener: %v", err)
|
|
}
|
|
go mockServer.Serve(listener)
|
|
defer mockServer.Close()
|
|
|
|
providerURL := fmt.Sprintf("http://%s", listener.Addr().String())
|
|
httpClient := CreateDefaultHTTPClient()
|
|
|
|
// Create metadata cache
|
|
metadataCache := NewMetadataCacheWithLogger(nil, logger)
|
|
|
|
// Create profiler
|
|
profiler := NewProviderMetadataProfiler(metadataCache, httpClient, providerURL, logger)
|
|
mto.RegisterComponent("provider_metadata", profiler)
|
|
|
|
// Take initial baseline
|
|
baseline, err := profiler.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to take baseline snapshot: %v", err)
|
|
}
|
|
|
|
initialGoroutines := runtime.NumGoroutine()
|
|
|
|
// Phase 1: Simulate periodic metadata fetching with some failures
|
|
t.Log("Phase 1: Testing periodic fetching with occasional failures...")
|
|
for i := 0; i < 20; i++ {
|
|
_, err := metadataCache.GetMetadata(providerURL, httpClient, logger)
|
|
if err != nil {
|
|
t.Logf("Metadata fetch %d failed (expected for cache extension testing): %v", i+1, err)
|
|
} else {
|
|
t.Logf("Metadata fetch %d succeeded", i+1)
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
// Wait for background cleanup (normally every 5 minutes)
|
|
time.Sleep(300 * time.Millisecond)
|
|
|
|
// Take intermediate snapshot
|
|
intermediate, err := profiler.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to take intermediate snapshot: %v", err)
|
|
}
|
|
|
|
// Phase 2: Continue with more fetches to test sustained operation
|
|
// Adjust iterations based on race detector presence to avoid timeouts
|
|
var phase2Iterations int
|
|
var sleepDuration time.Duration
|
|
if isRaceDetectorEnabled() {
|
|
// With race detector: reduce iterations significantly to stay well within timeout
|
|
phase2Iterations = 100
|
|
sleepDuration = 100 * time.Millisecond // Slightly longer sleep to reduce CPU contention
|
|
t.Log("Phase 2: Testing sustained operation with 100 iterations (race detector enabled)...")
|
|
} else {
|
|
// Without race detector: use original values for thorough testing
|
|
phase2Iterations = 1000
|
|
sleepDuration = 50 * time.Millisecond
|
|
t.Log("Phase 2: Testing sustained operation with 1000 iterations...")
|
|
}
|
|
|
|
for i := 20; i < 20+phase2Iterations; i++ {
|
|
_, err := metadataCache.GetMetadata(providerURL, httpClient, logger)
|
|
if err != nil {
|
|
t.Logf("Metadata fetch %d failed: %v", i+1, err)
|
|
}
|
|
time.Sleep(sleepDuration)
|
|
}
|
|
|
|
// Take final snapshot
|
|
current, err := profiler.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to take current snapshot: %v", err)
|
|
}
|
|
|
|
finalGoroutines := runtime.NumGoroutine()
|
|
|
|
// Analyze for leaks
|
|
analysis := profiler.AnalyzeLeaks(baseline, current)
|
|
|
|
// Assertions for memory leaks
|
|
if analysis.HasLeak {
|
|
if strictMode {
|
|
t.Errorf("Memory leak detected in provider metadata operations: %s", analysis.LeakDescription)
|
|
for _, leak := range analysis.SuspectedLeaks {
|
|
t.Errorf("Suspected leak: %s", leak)
|
|
}
|
|
} else {
|
|
t.Logf("Memory leak warning in provider metadata operations: %s", analysis.LeakDescription)
|
|
for _, leak := range analysis.SuspectedLeaks {
|
|
t.Logf("Suspected leak: %s", leak)
|
|
}
|
|
}
|
|
for _, rec := range analysis.Recommendations {
|
|
t.Logf("Recommendation: %s", rec)
|
|
}
|
|
}
|
|
|
|
// Check total memory growth
|
|
totalMemoryIncrease := current.RuntimeStats.Alloc - baseline.RuntimeStats.Alloc
|
|
if totalMemoryIncrease > 20*1024*1024 { // 20MB threshold for entire test
|
|
if strictMode {
|
|
t.Errorf("Total memory usage increased by %.2f MB during metadata operations", float64(totalMemoryIncrease)/(1024*1024))
|
|
} else {
|
|
t.Logf("Total memory usage increased by %.2f MB during metadata operations", float64(totalMemoryIncrease)/(1024*1024))
|
|
}
|
|
}
|
|
|
|
// Check for gradual memory growth patterns
|
|
intermediateMemoryIncrease := intermediate.RuntimeStats.Alloc - baseline.RuntimeStats.Alloc
|
|
if intermediateMemoryIncrease > 10*1024*1024 { // 10MB threshold for first phase
|
|
if strictMode {
|
|
t.Errorf("Memory usage increased by %.2f MB during first phase of metadata operations", float64(intermediateMemoryIncrease)/(1024*1024))
|
|
} else {
|
|
t.Logf("Memory usage increased by %.2f MB during first phase of metadata operations", float64(intermediateMemoryIncrease)/(1024*1024))
|
|
}
|
|
}
|
|
|
|
// Check goroutine count stability
|
|
goroutineIncrease := finalGoroutines - initialGoroutines
|
|
if goroutineIncrease > 5 { // Allow some variance for test environment
|
|
if strictMode {
|
|
t.Errorf("Goroutine count increased by %d during metadata operations (initial: %d, final: %d)",
|
|
goroutineIncrease, initialGoroutines, finalGoroutines)
|
|
} else {
|
|
t.Logf("Goroutine count increased by %d during metadata operations (initial: %d, final: %d)",
|
|
goroutineIncrease, initialGoroutines, finalGoroutines)
|
|
}
|
|
}
|
|
|
|
// Phase 3: Test cache extension behavior on persistent failures
|
|
t.Log("Phase 3: Testing cache extension on persistent failures...")
|
|
|
|
// Stop mock server to simulate provider unavailability
|
|
mockServer.Close()
|
|
|
|
// Try multiple fetches after server shutdown
|
|
postShutdownFailures := 0
|
|
for i := 0; i < 5; i++ {
|
|
_, err = metadataCache.GetMetadata(providerURL, httpClient, logger)
|
|
if err != nil {
|
|
postShutdownFailures++
|
|
t.Logf("Expected failure %d after server shutdown: %v", i+1, err)
|
|
} else {
|
|
t.Logf("Unexpected success %d after server shutdown - cache extension working", i+1)
|
|
}
|
|
time.Sleep(200 * time.Millisecond)
|
|
}
|
|
|
|
if postShutdownFailures == 0 {
|
|
if strictMode {
|
|
t.Error("Expected some metadata fetches to fail after server shutdown")
|
|
} else {
|
|
t.Log("Warning: No metadata fetches failed after server shutdown - cache extension may not be working as expected")
|
|
}
|
|
}
|
|
|
|
// Phase 4: Test background goroutine lifecycle and cleanup
|
|
t.Log("Phase 4: Testing background goroutine lifecycle...")
|
|
|
|
// Wait longer to allow background cleanup to run
|
|
time.Sleep(GetTestDuration(1 * time.Second))
|
|
|
|
// Take final snapshot after cleanup
|
|
finalAfterCleanup, err := profiler.TakeSnapshot()
|
|
if err != nil {
|
|
t.Fatalf("Failed to take final snapshot after cleanup: %v", err)
|
|
}
|
|
|
|
// Check if memory decreased after cleanup
|
|
if finalAfterCleanup.RuntimeStats.Alloc < current.RuntimeStats.Alloc {
|
|
memoryDecrease := current.RuntimeStats.Alloc - finalAfterCleanup.RuntimeStats.Alloc
|
|
t.Logf("Memory decreased by %.2f MB after cleanup phase", float64(memoryDecrease)/(1024*1024))
|
|
}
|
|
|
|
// Clean up resources
|
|
// The cache manager cleanup is handled by the defer at the beginning of the test
|
|
|
|
t.Logf("Test completed: %d total requests, %d server failures, %d post-shutdown failures",
|
|
requestCount, serverFailures, postShutdownFailures)
|
|
t.Logf("Memory usage: baseline=%.2f MB, intermediate=%.2f MB, final=%.2f MB",
|
|
float64(baseline.RuntimeStats.Alloc)/(1024*1024),
|
|
float64(intermediate.RuntimeStats.Alloc)/(1024*1024),
|
|
float64(current.RuntimeStats.Alloc)/(1024*1024))
|
|
}
|
|
|
|
// TestMemoryPoolLeakDetection tests for memory leaks in memory pool operations
|
|
func TestMemoryPoolLeakDetection(t *testing.T) {
|
|
t.Skip("Deprecated - memory pool profilers removed. Use internal/pool statistics instead")
|
|
}
|