mirror of https://github.com/jetkvm/kvm.git
363 lines
10 KiB
Go
363 lines
10 KiB
Go
//go:build cgo
|
|
// +build cgo
|
|
|
|
package audio
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestRegressionScenarios tests critical edge cases and error conditions
|
|
// that could cause system instability in production
|
|
func TestRegressionScenarios(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
testFunc func(t *testing.T)
|
|
description string
|
|
}{
|
|
{
|
|
name: "IPCConnectionFailure",
|
|
testFunc: testIPCConnectionFailureRecovery,
|
|
description: "Test IPC connection failure and recovery scenarios",
|
|
},
|
|
{
|
|
name: "BufferOverflow",
|
|
testFunc: testBufferOverflowHandling,
|
|
description: "Test buffer overflow protection and recovery",
|
|
},
|
|
{
|
|
name: "SupervisorRapidRestart",
|
|
testFunc: testSupervisorRapidRestartScenario,
|
|
description: "Test supervisor behavior under rapid restart conditions",
|
|
},
|
|
{
|
|
name: "ConcurrentStartStop",
|
|
testFunc: testConcurrentStartStopOperations,
|
|
description: "Test concurrent start/stop operations for race conditions",
|
|
},
|
|
{
|
|
name: "MemoryLeakPrevention",
|
|
testFunc: testMemoryLeakPrevention,
|
|
description: "Test memory leak prevention in long-running scenarios",
|
|
},
|
|
{
|
|
name: "ConfigValidationEdgeCases",
|
|
testFunc: testConfigValidationEdgeCases,
|
|
description: "Test configuration validation with edge case values",
|
|
},
|
|
{
|
|
name: "AtomicOperationConsistency",
|
|
testFunc: testAtomicOperationConsistency,
|
|
description: "Test atomic operations consistency under high concurrency",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Logf("Running regression test: %s - %s", tt.name, tt.description)
|
|
tt.testFunc(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
// testIPCConnectionFailureRecovery tests IPC connection failure scenarios
|
|
func testIPCConnectionFailureRecovery(t *testing.T) {
|
|
manager := NewAudioInputIPCManager()
|
|
require.NotNil(t, manager)
|
|
|
|
// Test start with no IPC server available (should handle gracefully)
|
|
err := manager.Start()
|
|
// Should not panic or crash, may return error depending on implementation
|
|
if err != nil {
|
|
t.Logf("Expected error when no IPC server available: %v", err)
|
|
}
|
|
|
|
// Test that manager can recover after IPC becomes available
|
|
if manager.IsRunning() {
|
|
manager.Stop()
|
|
}
|
|
|
|
// Verify clean state after failure
|
|
assert.False(t, manager.IsRunning())
|
|
assert.False(t, manager.IsReady())
|
|
}
|
|
|
|
// testBufferOverflowHandling tests buffer overflow protection
|
|
func testBufferOverflowHandling(t *testing.T) {
|
|
// Test with extremely large buffer sizes
|
|
extremelyLargeSize := 1024 * 1024 * 100 // 100MB
|
|
err := ValidateBufferSize(extremelyLargeSize)
|
|
assert.Error(t, err, "Should reject extremely large buffer sizes")
|
|
|
|
// Test with negative buffer sizes
|
|
err = ValidateBufferSize(-1)
|
|
assert.Error(t, err, "Should reject negative buffer sizes")
|
|
|
|
// Test with zero buffer size
|
|
err = ValidateBufferSize(0)
|
|
assert.Error(t, err, "Should reject zero buffer size")
|
|
|
|
// Test with maximum valid buffer size
|
|
maxValidSize := GetConfig().SocketMaxBuffer
|
|
err = ValidateBufferSize(int(maxValidSize))
|
|
assert.NoError(t, err, "Should accept maximum valid buffer size")
|
|
}
|
|
|
|
// testSupervisorRapidRestartScenario tests supervisor under rapid restart conditions
|
|
func testSupervisorRapidRestartScenario(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping rapid restart test in short mode")
|
|
}
|
|
|
|
supervisor := NewAudioOutputSupervisor()
|
|
require.NotNil(t, supervisor)
|
|
|
|
// Perform rapid start/stop cycles to test for race conditions
|
|
for i := 0; i < 10; i++ {
|
|
err := supervisor.Start()
|
|
if err != nil {
|
|
t.Logf("Start attempt %d failed (expected in test environment): %v", i, err)
|
|
}
|
|
|
|
// Very short delay to stress test
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
supervisor.Stop()
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
// Verify supervisor is in clean state after rapid cycling
|
|
assert.False(t, supervisor.IsRunning())
|
|
}
|
|
|
|
// testConcurrentStartStopOperations tests concurrent operations for race conditions
|
|
func testConcurrentStartStopOperations(t *testing.T) {
|
|
manager := NewAudioInputIPCManager()
|
|
require.NotNil(t, manager)
|
|
|
|
var wg sync.WaitGroup
|
|
const numGoroutines = 10
|
|
|
|
// Launch multiple goroutines trying to start/stop concurrently
|
|
for i := 0; i < numGoroutines; i++ {
|
|
wg.Add(2)
|
|
|
|
// Start goroutine
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
err := manager.Start()
|
|
if err != nil {
|
|
t.Logf("Concurrent start %d: %v", id, err)
|
|
}
|
|
}(i)
|
|
|
|
// Stop goroutine
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
time.Sleep(5 * time.Millisecond) // Small delay
|
|
manager.Stop()
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Ensure final state is consistent
|
|
manager.Stop() // Final cleanup
|
|
assert.False(t, manager.IsRunning())
|
|
}
|
|
|
|
// testMemoryLeakPrevention tests for memory leaks in long-running scenarios
|
|
func testMemoryLeakPrevention(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping memory leak test in short mode")
|
|
}
|
|
|
|
manager := NewAudioInputIPCManager()
|
|
require.NotNil(t, manager)
|
|
|
|
// Simulate long-running operation with periodic restarts
|
|
for cycle := 0; cycle < 5; cycle++ {
|
|
err := manager.Start()
|
|
if err != nil {
|
|
t.Logf("Start cycle %d failed (expected): %v", cycle, err)
|
|
}
|
|
|
|
// Simulate some activity
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Get metrics to ensure they're not accumulating indefinitely
|
|
metrics := manager.GetMetrics()
|
|
assert.NotNil(t, metrics, "Metrics should be available")
|
|
|
|
manager.Stop()
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
|
|
// Final verification
|
|
assert.False(t, manager.IsRunning())
|
|
}
|
|
|
|
// testConfigValidationEdgeCases tests configuration validation with edge cases
|
|
func testConfigValidationEdgeCases(t *testing.T) {
|
|
// Test sample rate edge cases
|
|
testCases := []struct {
|
|
sampleRate int
|
|
channels int
|
|
frameSize int
|
|
shouldPass bool
|
|
description string
|
|
}{
|
|
{0, 2, 960, false, "zero sample rate"},
|
|
{-1, 2, 960, false, "negative sample rate"},
|
|
{1, 2, 960, false, "extremely low sample rate"},
|
|
{999999, 2, 960, false, "extremely high sample rate"},
|
|
{48000, 0, 960, false, "zero channels"},
|
|
{48000, -1, 960, false, "negative channels"},
|
|
{48000, 100, 960, false, "too many channels"},
|
|
{48000, 2, 0, false, "zero frame size"},
|
|
{48000, 2, -1, false, "negative frame size"},
|
|
{48000, 2, 999999, true, "extremely large frame size"},
|
|
{48000, 2, 960, true, "valid configuration"},
|
|
{44100, 1, 441, true, "valid mono configuration"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
err := ValidateInputIPCConfig(tc.sampleRate, tc.channels, tc.frameSize)
|
|
if tc.shouldPass {
|
|
assert.NoError(t, err, "Should accept valid config: %s", tc.description)
|
|
} else {
|
|
assert.Error(t, err, "Should reject invalid config: %s", tc.description)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// testAtomicOperationConsistency tests atomic operations under high concurrency
|
|
func testAtomicOperationConsistency(t *testing.T) {
|
|
var counter int64
|
|
var wg sync.WaitGroup
|
|
const numGoroutines = 100
|
|
const incrementsPerGoroutine = 1000
|
|
|
|
// Launch multiple goroutines performing atomic operations
|
|
for i := 0; i < numGoroutines; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < incrementsPerGoroutine; j++ {
|
|
atomic.AddInt64(&counter, 1)
|
|
}
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Verify final count is correct
|
|
expected := int64(numGoroutines * incrementsPerGoroutine)
|
|
actual := atomic.LoadInt64(&counter)
|
|
assert.Equal(t, expected, actual, "Atomic operations should be consistent")
|
|
}
|
|
|
|
// TestErrorRecoveryScenarios tests various error recovery scenarios
|
|
func TestErrorRecoveryScenarios(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
testFunc func(t *testing.T)
|
|
}{
|
|
{"NetworkConnectionLoss", testNetworkConnectionLossRecovery},
|
|
{"ProcessCrashRecovery", testProcessCrashRecovery},
|
|
{"ResourceExhaustionRecovery", testResourceExhaustionRecovery},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.testFunc(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
// testNetworkConnectionLossRecovery tests recovery from network connection loss
|
|
func testNetworkConnectionLossRecovery(t *testing.T) {
|
|
// Create a temporary socket that we can close to simulate connection loss
|
|
tempDir := t.TempDir()
|
|
socketPath := fmt.Sprintf("%s/test_recovery.sock", tempDir)
|
|
|
|
// Create and immediately close a socket to test connection failure
|
|
listener, err := net.Listen("unix", socketPath)
|
|
if err != nil {
|
|
t.Skipf("Cannot create test socket: %v", err)
|
|
}
|
|
listener.Close() // Close immediately to simulate connection loss
|
|
|
|
// Remove socket file to ensure connection will fail
|
|
os.Remove(socketPath)
|
|
|
|
// Test that components handle connection loss gracefully
|
|
manager := NewAudioInputIPCManager()
|
|
require.NotNil(t, manager)
|
|
|
|
// This should handle the connection failure gracefully
|
|
err = manager.Start()
|
|
if err != nil {
|
|
t.Logf("Expected connection failure handled: %v", err)
|
|
}
|
|
|
|
// Cleanup
|
|
manager.Stop()
|
|
}
|
|
|
|
// testProcessCrashRecovery tests recovery from process crashes
|
|
func testProcessCrashRecovery(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping process crash test in short mode")
|
|
}
|
|
|
|
supervisor := NewAudioOutputSupervisor()
|
|
require.NotNil(t, supervisor)
|
|
|
|
// Start supervisor (will likely fail in test environment, but should handle gracefully)
|
|
err := supervisor.Start()
|
|
if err != nil {
|
|
t.Logf("Supervisor start failed as expected in test environment: %v", err)
|
|
}
|
|
|
|
// Verify supervisor can be stopped cleanly even after start failure
|
|
supervisor.Stop()
|
|
assert.False(t, supervisor.IsRunning())
|
|
}
|
|
|
|
// testResourceExhaustionRecovery tests recovery from resource exhaustion
|
|
func testResourceExhaustionRecovery(t *testing.T) {
|
|
// Test with resource constraints
|
|
manager := NewAudioInputIPCManager()
|
|
require.NotNil(t, manager)
|
|
|
|
// Simulate resource exhaustion by rapid start/stop cycles
|
|
for i := 0; i < 20; i++ {
|
|
err := manager.Start()
|
|
if err != nil {
|
|
t.Logf("Resource exhaustion cycle %d: %v", i, err)
|
|
}
|
|
manager.Stop()
|
|
// No delay to stress test resource management
|
|
}
|
|
|
|
// Verify system can still function after resource stress
|
|
err := manager.Start()
|
|
if err != nil {
|
|
t.Logf("Final start after resource stress: %v", err)
|
|
}
|
|
manager.Stop()
|
|
assert.False(t, manager.IsRunning())
|
|
}
|