nuclei/pkg/protocols/common/protocolstate/memguardian_test.go

124 lines
3.7 KiB
Go

package protocolstate
import (
"context"
"testing"
"time"
"github.com/projectdiscovery/utils/memguardian"
"github.com/stretchr/testify/require"
"github.com/tarunKoyalwar/goleak"
)
// TestMemGuardianGoroutineLeak tests that MemGuardian properly cleans up goroutines
func TestMemGuardianGoroutineLeak(t *testing.T) {
defer goleak.VerifyNone(t,
goleak.IgnoreAnyContainingPkg("go.opencensus.io/stats/view"),
goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"),
goleak.IgnoreAnyContainingPkg("github.com/go-rod/rod"),
goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/interactsh/pkg/server"),
goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"),
)
// Initialize memguardian if not already initialized
if memguardian.DefaultMemGuardian == nil {
var err error
memguardian.DefaultMemGuardian, err = memguardian.New()
require.NoError(t, err, "Failed to initialize memguardian")
}
t.Run("StartAndStopMemGuardian", func(t *testing.T) {
// Test that starting and stopping memguardian doesn't leak goroutines
ctx := context.Background()
// Start MemGuardian
StartActiveMemGuardian(ctx)
require.NotNil(t, memTimer, "memTimer should be initialized")
require.NotNil(t, cancelFunc, "cancelFunc should be initialized")
// Give it a moment to start
time.Sleep(10 * time.Millisecond)
// Stop MemGuardian
StopActiveMemGuardian()
// Give goroutine time to exit
time.Sleep(20 * time.Millisecond)
// Verify cleanup
require.Nil(t, memTimer, "memTimer should be nil after stop")
require.Nil(t, cancelFunc, "cancelFunc should be nil after stop")
})
t.Run("MultipleStartStop", func(t *testing.T) {
// Test multiple start/stop cycles
for i := 0; i < 3; i++ {
ctx := context.Background()
StartActiveMemGuardian(ctx)
time.Sleep(5 * time.Millisecond)
StopActiveMemGuardian()
time.Sleep(10 * time.Millisecond)
}
})
t.Run("ContextCancellation", func(t *testing.T) {
// Test that context cancellation properly stops the goroutine
ctx, cancel := context.WithCancel(context.Background())
StartActiveMemGuardian(ctx)
require.NotNil(t, memTimer, "memTimer should be initialized")
// Cancel context to trigger goroutine exit
cancel()
// Give it time to process cancellation
time.Sleep(20 * time.Millisecond)
// Clean up
StopActiveMemGuardian()
time.Sleep(10 * time.Millisecond)
})
t.Run("IdempotentStart", func(t *testing.T) {
// Test that multiple starts don't create multiple goroutines
ctx := context.Background()
StartActiveMemGuardian(ctx)
firstTimer := memTimer
// Start again - should be idempotent
StartActiveMemGuardian(ctx)
require.Equal(t, firstTimer, memTimer, "memTimer should be the same")
require.NotNil(t, cancelFunc, "cancelFunc should still be set")
StopActiveMemGuardian()
time.Sleep(10 * time.Millisecond)
})
}
// TestMemGuardianReset tests resetting global state
func TestMemGuardianReset(t *testing.T) {
defer goleak.VerifyNone(t,
goleak.IgnoreAnyContainingPkg("go.opencensus.io/stats/view"),
goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"),
goleak.IgnoreAnyContainingPkg("github.com/go-rod/rod"),
goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/interactsh/pkg/server"),
goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"),
)
// Ensure clean state
StopActiveMemGuardian()
time.Sleep(20 * time.Millisecond) // Allow any existing goroutines to exit
// Test that we can start after stop
ctx := context.Background()
StartActiveMemGuardian(ctx)
// Verify it started
require.NotNil(t, memTimer, "memTimer should be initialized after restart")
// Clean up
StopActiveMemGuardian()
time.Sleep(10 * time.Millisecond) // Allow cleanup
}