monorepo/cloud/maplefile-backend/test/integration/memory_leak_test.go

207 lines
5.4 KiB
Go

// 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/