kvm/internal/audio/test_utils.go

319 lines
7.0 KiB
Go

//go:build integration
// +build integration
package audio
import (
"context"
"net"
"os"
"sync"
"time"
"github.com/jetkvm/kvm/internal/logging"
"github.com/rs/zerolog"
)
// Test utilities and mock implementations for integration tests
// MockAudioIPCServer provides a mock IPC server for testing
type AudioIPCServer struct {
socketPath string
logger zerolog.Logger
listener net.Listener
connections map[net.Conn]bool
mu sync.RWMutex
running bool
}
// Start starts the mock IPC server
func (s *AudioIPCServer) Start(ctx context.Context) error {
// Remove existing socket file
os.Remove(s.socketPath)
listener, err := net.Listen("unix", s.socketPath)
if err != nil {
return err
}
s.listener = listener
s.connections = make(map[net.Conn]bool)
s.mu.Lock()
s.running = true
s.mu.Unlock()
go s.acceptConnections(ctx)
<-ctx.Done()
s.Stop()
return ctx.Err()
}
// Stop stops the mock IPC server
func (s *AudioIPCServer) Stop() {
s.mu.Lock()
defer s.mu.Unlock()
if !s.running {
return
}
s.running = false
if s.listener != nil {
s.listener.Close()
}
// Close all connections
for conn := range s.connections {
conn.Close()
}
// Clean up socket file
os.Remove(s.socketPath)
}
// acceptConnections handles incoming connections
func (s *AudioIPCServer) acceptConnections(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
}
conn, err := s.listener.Accept()
if err != nil {
select {
case <-ctx.Done():
return
default:
s.logger.Error().Err(err).Msg("Failed to accept connection")
continue
}
}
s.mu.Lock()
s.connections[conn] = true
s.mu.Unlock()
go s.handleConnection(ctx, conn)
}
}
// handleConnection handles a single connection
func (s *AudioIPCServer) handleConnection(ctx context.Context, conn net.Conn) {
defer func() {
s.mu.Lock()
delete(s.connections, conn)
s.mu.Unlock()
conn.Close()
}()
buffer := make([]byte, 4096)
for {
select {
case <-ctx.Done():
return
default:
}
// Set read timeout
conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
n, err := conn.Read(buffer)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
return
}
// Process received data (for testing, we just log it)
s.logger.Debug().Int("bytes", n).Msg("Received data from client")
}
}
// AudioInputIPCServer provides a mock input IPC server
type AudioInputIPCServer struct {
*AudioIPCServer
}
// Test message structures
type OutputMessage struct {
Type OutputMessageType
Timestamp int64
Data []byte
}
type InputMessage struct {
Type InputMessageType
Timestamp int64
Data []byte
}
// Test configuration helpers
func getTestConfig() *AudioConfigConstants {
return &AudioConfigConstants{
// Basic audio settings
SampleRate: 48000,
Channels: 2,
MaxAudioFrameSize: 4096,
// IPC settings
OutputMagicNumber: 0x4A4B4F55, // "JKOU"
InputMagicNumber: 0x4A4B4D49, // "JKMI"
WriteTimeout: 5 * time.Second,
HeaderSize: 17,
MaxFrameSize: 4096,
MessagePoolSize: 100,
// Supervisor settings
MaxRestartAttempts: 3,
InitialRestartDelay: 1 * time.Second,
MaxRestartDelay: 30 * time.Second,
HealthCheckInterval: 5 * time.Second,
// Quality presets
AudioQualityLowOutputBitrate: 32000,
AudioQualityMediumOutputBitrate: 96000,
AudioQualityHighOutputBitrate: 192000,
AudioQualityUltraOutputBitrate: 320000,
AudioQualityLowInputBitrate: 16000,
AudioQualityMediumInputBitrate: 64000,
AudioQualityHighInputBitrate: 128000,
AudioQualityUltraInputBitrate: 256000,
AudioQualityLowSampleRate: 24000,
AudioQualityMediumSampleRate: 48000,
AudioQualityHighSampleRate: 48000,
AudioQualityUltraSampleRate: 48000,
AudioQualityLowChannels: 1,
AudioQualityMediumChannels: 2,
AudioQualityHighChannels: 2,
AudioQualityUltraChannels: 2,
AudioQualityLowFrameSize: 20 * time.Millisecond,
AudioQualityMediumFrameSize: 20 * time.Millisecond,
AudioQualityHighFrameSize: 20 * time.Millisecond,
AudioQualityUltraFrameSize: 20 * time.Millisecond,
AudioQualityMicLowSampleRate: 16000,
// Metrics settings
MetricsUpdateInterval: 1 * time.Second,
// Latency settings
DefaultTargetLatencyMS: 50,
DefaultOptimizationIntervalSeconds: 5,
DefaultAdaptiveThreshold: 0.8,
DefaultStatsIntervalSeconds: 5,
// Buffer settings
DefaultBufferPoolSize: 100,
DefaultControlPoolSize: 50,
DefaultFramePoolSize: 200,
DefaultMaxPooledFrames: 500,
DefaultPoolCleanupInterval: 30 * time.Second,
// Process monitoring
MaxCPUPercent: 100.0,
MinCPUPercent: 0.0,
DefaultClockTicks: 100,
DefaultMemoryGB: 4.0,
MaxWarmupSamples: 10,
WarmupCPUSamples: 5,
MetricsChannelBuffer: 100,
MinValidClockTicks: 50,
MaxValidClockTicks: 1000,
PageSize: 4096,
// CGO settings (for cgo builds)
CGOOpusBitrate: 96000,
CGOOpusComplexity: 3,
CGOOpusVBR: 1,
CGOOpusVBRConstraint: 1,
CGOOpusSignalType: 3,
CGOOpusBandwidth: 1105,
CGOOpusDTX: 0,
CGOSampleRate: 48000,
// Batch processing
BatchProcessorFramesPerBatch: 10,
BatchProcessorTimeout: 100 * time.Millisecond,
// Granular metrics
GranularMetricsMaxSamples: 1000,
GranularMetricsLogInterval: 30 * time.Second,
GranularMetricsCleanupInterval: 5 * time.Minute,
}
}
// setupTestEnvironment sets up the test environment
func setupTestEnvironment() {
// Use test configuration
UpdateConfig(getTestConfig())
// Initialize logging for tests
logging.SetLevel("debug")
}
// cleanupTestEnvironment cleans up after tests
func cleanupTestEnvironment() {
// Reset to default configuration
UpdateConfig(DefaultAudioConfig())
}
// createTestLogger creates a logger for testing
func createTestLogger(name string) zerolog.Logger {
return zerolog.New(os.Stdout).With().
Timestamp().
Str("component", name).
Str("test", "true").
Logger()
}
// waitForCondition waits for a condition to be true with timeout
func waitForCondition(condition func() bool, timeout time.Duration, checkInterval time.Duration) bool {
timeout_timer := time.NewTimer(timeout)
defer timeout_timer.Stop()
ticker := time.NewTicker(checkInterval)
defer ticker.Stop()
for {
select {
case <-timeout_timer.C:
return false
case <-ticker.C:
if condition() {
return true
}
}
}
}
// TestHelper provides common test functionality
type TestHelper struct {
tempDir string
logger zerolog.Logger
}
// NewTestHelper creates a new test helper
func NewTestHelper(tempDir string) *TestHelper {
return &TestHelper{
tempDir: tempDir,
logger: createTestLogger("test-helper"),
}
}
// CreateTempSocket creates a temporary socket path
func (h *TestHelper) CreateTempSocket(name string) string {
return filepath.Join(h.tempDir, name)
}
// GetLogger returns the test logger
func (h *TestHelper) GetLogger() zerolog.Logger {
return h.logger
}