// Package integration provides integration tests for memory leak detection. // //go:build integration // +build integration package integration import ( "runtime" "testing" "time" "github.com/awnumar/memguard" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/security/securebytes" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/security/securestring" ) // TestSecureStringMemoryLeak verifies SecureString doesn't leak memory. func TestSecureStringMemoryLeak(t *testing.T) { // Force GC before measurement runtime.GC() time.Sleep(100 * time.Millisecond) var memBefore runtime.MemStats runtime.ReadMemStats(&memBefore) // Perform 10000 SecureString operations for i := 0; i < 10000; i++ { s, err := securestring.NewSecureString("test data that should be cleaned up properly") if err != nil { t.Fatalf("Failed to create SecureString: %v", err) } _ = s.String() // Access the string s.Wipe() } // Force GC after operations runtime.GC() time.Sleep(100 * time.Millisecond) var memAfter runtime.MemStats runtime.ReadMemStats(&memAfter) // Check for memory leak - allow up to 5MB growth for test overhead heapGrowth := int64(memAfter.HeapAlloc) - int64(memBefore.HeapAlloc) if heapGrowth > 5*1024*1024 { t.Errorf("Possible memory leak in SecureString: heap grew by %d bytes", heapGrowth) } } // TestSecureBytesMemoryLeak verifies SecureBytes doesn't leak memory. func TestSecureBytesMemoryLeak(t *testing.T) { // Force GC before measurement runtime.GC() time.Sleep(100 * time.Millisecond) var memBefore runtime.MemStats runtime.ReadMemStats(&memBefore) // Perform 10000 SecureBytes operations for i := 0; i < 10000; i++ { data := make([]byte, 64) for j := range data { data[j] = byte(i % 256) } sb, err := securebytes.NewSecureBytes(data) if err != nil { t.Fatalf("Failed to create SecureBytes: %v", err) } _ = sb.Bytes() // Access the bytes sb.Wipe() } // Force GC after operations runtime.GC() time.Sleep(100 * time.Millisecond) var memAfter runtime.MemStats runtime.ReadMemStats(&memAfter) // Check for memory leak heapGrowth := int64(memAfter.HeapAlloc) - int64(memBefore.HeapAlloc) if heapGrowth > 5*1024*1024 { t.Errorf("Possible memory leak in SecureBytes: heap grew by %d bytes", heapGrowth) } } // TestMemguardWipeBytesEffectiveness verifies memguard.WipeBytes actually zeros memory. func TestMemguardWipeBytesEffectiveness(t *testing.T) { // Create sensitive data sensitiveData := []byte("super-secret-password-12345") originalLen := len(sensitiveData) // Wipe the data memguard.WipeBytes(sensitiveData) // Verify all bytes are zero for i, b := range sensitiveData { if b != 0 { t.Errorf("Byte at position %d not wiped: got %d, expected 0", i, b) } } // Verify length unchanged if len(sensitiveData) != originalLen { t.Errorf("Length changed after wipe: got %d, expected %d", len(sensitiveData), originalLen) } } // TestSecureStringWipeEffectiveness verifies SecureString.Wipe() actually cleans up. func TestSecureStringWipeEffectiveness(t *testing.T) { secret := "my-super-secret-token" ss, err := securestring.NewSecureString(secret) if err != nil { t.Fatalf("Failed to create SecureString: %v", err) } // Verify we can access the string before wiping if ss.String() != secret { t.Error("SecureString didn't store the original value correctly") } // Wipe the secure string ss.Wipe() // After wiping, String() should return empty if ss.String() != "" { t.Error("SecureString.String() should return empty after Wipe()") } } // TestLockedBufferMemoryLeak verifies LockedBuffer doesn't leak memory. func TestLockedBufferMemoryLeak(t *testing.T) { // Force GC before measurement runtime.GC() time.Sleep(100 * time.Millisecond) var memBefore runtime.MemStats runtime.ReadMemStats(&memBefore) // Perform 10000 LockedBuffer operations for i := 0; i < 10000; i++ { buf := memguard.NewBuffer(64) copy(buf.Bytes(), []byte("test-data-for-locked-buffer")) buf.Destroy() } // Force GC after operations runtime.GC() time.Sleep(100 * time.Millisecond) var memAfter runtime.MemStats runtime.ReadMemStats(&memAfter) // Check for memory leak heapGrowth := int64(memAfter.HeapAlloc) - int64(memBefore.HeapAlloc) if heapGrowth > 5*1024*1024 { t.Errorf("Possible memory leak in LockedBuffer: heap grew by %d bytes", heapGrowth) } } // TestConcurrentSecureStringOperations tests thread safety. func TestConcurrentSecureStringOperations(t *testing.T) { done := make(chan bool, 10) for i := 0; i < 10; i++ { go func(id int) { defer func() { done <- true }() for j := 0; j < 1000; j++ { ss, err := securestring.NewSecureString("concurrent-test-data") if err != nil { t.Errorf("Goroutine %d: Failed to create SecureString: %v", id, err) return } _ = ss.String() ss.Wipe() } }(i) } // Wait for all goroutines for i := 0; i < 10; i++ { <-done } } // TestRepeatedWipeCalls verifies multiple Wipe() calls don't panic. func TestRepeatedWipeCalls(t *testing.T) { ss, err := securestring.NewSecureString("test-data") if err != nil { t.Fatalf("Failed to create SecureString: %v", err) } // Multiple wipe calls should be safe for i := 0; i < 10; i++ { err := ss.Wipe() if err != nil { t.Errorf("Wipe() call %d returned error: %v", i, err) } } } // Run integration tests with: // go test -tags=integration -v ./test/integration/