//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()) }