mirror of https://github.com/jetkvm/kvm.git
319 lines
7.0 KiB
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
|
|
} |