mirror of https://github.com/jetkvm/kvm.git
367 lines
11 KiB
Go
367 lines
11 KiB
Go
package audio
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/jetkvm/kvm/internal/usbgadget"
|
|
)
|
|
|
|
// Unit tests for the audio package
|
|
|
|
func TestAudioQuality(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
quality AudioQuality
|
|
expected string
|
|
}{
|
|
{"Low Quality", AudioQualityLow, "low"},
|
|
{"Medium Quality", AudioQualityMedium, "medium"},
|
|
{"High Quality", AudioQualityHigh, "high"},
|
|
{"Ultra Quality", AudioQualityUltra, "ultra"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Test quality setting
|
|
SetAudioQuality(tt.quality)
|
|
config := GetAudioConfig()
|
|
assert.Equal(t, tt.quality, config.Quality)
|
|
assert.Greater(t, config.Bitrate, 0)
|
|
assert.Greater(t, config.SampleRate, 0)
|
|
assert.Greater(t, config.Channels, 0)
|
|
assert.Greater(t, config.FrameSize, time.Duration(0))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMicrophoneQuality(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
quality AudioQuality
|
|
}{
|
|
{"Low Quality", AudioQualityLow},
|
|
{"Medium Quality", AudioQualityMedium},
|
|
{"High Quality", AudioQualityHigh},
|
|
{"Ultra Quality", AudioQualityUltra},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Test microphone quality setting
|
|
SetMicrophoneQuality(tt.quality)
|
|
config := GetMicrophoneConfig()
|
|
assert.Equal(t, tt.quality, config.Quality)
|
|
assert.Equal(t, 1, config.Channels) // Microphone is always mono
|
|
assert.Greater(t, config.Bitrate, 0)
|
|
assert.Greater(t, config.SampleRate, 0)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAudioQualityPresets(t *testing.T) {
|
|
presets := GetAudioQualityPresets()
|
|
require.NotEmpty(t, presets)
|
|
|
|
// Test that all quality levels have presets
|
|
for quality := AudioQualityLow; quality <= AudioQualityUltra; quality++ {
|
|
config, exists := presets[quality]
|
|
require.True(t, exists, "Preset should exist for quality %d", quality)
|
|
assert.Equal(t, quality, config.Quality)
|
|
assert.Greater(t, config.Bitrate, 0)
|
|
assert.Greater(t, config.SampleRate, 0)
|
|
assert.Greater(t, config.Channels, 0)
|
|
assert.Greater(t, config.FrameSize, time.Duration(0))
|
|
}
|
|
|
|
// Test that higher quality has higher bitrate
|
|
lowConfig := presets[AudioQualityLow]
|
|
mediumConfig := presets[AudioQualityMedium]
|
|
highConfig := presets[AudioQualityHigh]
|
|
ultraConfig := presets[AudioQualityUltra]
|
|
|
|
assert.Less(t, lowConfig.Bitrate, mediumConfig.Bitrate)
|
|
assert.Less(t, mediumConfig.Bitrate, highConfig.Bitrate)
|
|
assert.Less(t, highConfig.Bitrate, ultraConfig.Bitrate)
|
|
}
|
|
|
|
func TestMicrophoneQualityPresets(t *testing.T) {
|
|
presets := GetMicrophoneQualityPresets()
|
|
require.NotEmpty(t, presets)
|
|
|
|
// Test that all quality levels have presets
|
|
for quality := AudioQualityLow; quality <= AudioQualityUltra; quality++ {
|
|
config, exists := presets[quality]
|
|
require.True(t, exists, "Microphone preset should exist for quality %d", quality)
|
|
assert.Equal(t, quality, config.Quality)
|
|
assert.Equal(t, 1, config.Channels) // Always mono
|
|
assert.Greater(t, config.Bitrate, 0)
|
|
assert.Greater(t, config.SampleRate, 0)
|
|
}
|
|
}
|
|
|
|
func TestAudioMetrics(t *testing.T) {
|
|
// Test initial metrics
|
|
metrics := GetAudioMetrics()
|
|
assert.GreaterOrEqual(t, metrics.FramesReceived, int64(0))
|
|
assert.GreaterOrEqual(t, metrics.FramesDropped, int64(0))
|
|
assert.GreaterOrEqual(t, metrics.BytesProcessed, int64(0))
|
|
assert.GreaterOrEqual(t, metrics.ConnectionDrops, int64(0))
|
|
|
|
// Test recording metrics
|
|
RecordFrameReceived(1024)
|
|
metrics = GetAudioMetrics()
|
|
assert.Greater(t, metrics.BytesProcessed, int64(0))
|
|
assert.Greater(t, metrics.FramesReceived, int64(0))
|
|
|
|
RecordFrameDropped()
|
|
metrics = GetAudioMetrics()
|
|
assert.Greater(t, metrics.FramesDropped, int64(0))
|
|
|
|
RecordConnectionDrop()
|
|
metrics = GetAudioMetrics()
|
|
assert.Greater(t, metrics.ConnectionDrops, int64(0))
|
|
}
|
|
|
|
func TestMaxAudioFrameSize(t *testing.T) {
|
|
frameSize := GetMaxAudioFrameSize()
|
|
assert.Greater(t, frameSize, 0)
|
|
assert.Equal(t, GetConfig().MaxAudioFrameSize, frameSize)
|
|
}
|
|
|
|
func TestMetricsUpdateInterval(t *testing.T) {
|
|
// Test getting current interval
|
|
interval := GetMetricsUpdateInterval()
|
|
assert.Greater(t, interval, time.Duration(0))
|
|
|
|
// Test setting new interval
|
|
newInterval := 2 * time.Second
|
|
SetMetricsUpdateInterval(newInterval)
|
|
updatedInterval := GetMetricsUpdateInterval()
|
|
assert.Equal(t, newInterval, updatedInterval)
|
|
}
|
|
|
|
func TestAudioConfigConsistency(t *testing.T) {
|
|
// Test that setting audio quality updates the config consistently
|
|
for quality := AudioQualityLow; quality <= AudioQualityUltra; quality++ {
|
|
SetAudioQuality(quality)
|
|
config := GetAudioConfig()
|
|
presets := GetAudioQualityPresets()
|
|
expectedConfig := presets[quality]
|
|
|
|
assert.Equal(t, expectedConfig.Quality, config.Quality)
|
|
assert.Equal(t, expectedConfig.Bitrate, config.Bitrate)
|
|
assert.Equal(t, expectedConfig.SampleRate, config.SampleRate)
|
|
assert.Equal(t, expectedConfig.Channels, config.Channels)
|
|
assert.Equal(t, expectedConfig.FrameSize, config.FrameSize)
|
|
}
|
|
}
|
|
|
|
func TestMicrophoneConfigConsistency(t *testing.T) {
|
|
// Test that setting microphone quality updates the config consistently
|
|
for quality := AudioQualityLow; quality <= AudioQualityUltra; quality++ {
|
|
SetMicrophoneQuality(quality)
|
|
config := GetMicrophoneConfig()
|
|
presets := GetMicrophoneQualityPresets()
|
|
expectedConfig := presets[quality]
|
|
|
|
assert.Equal(t, expectedConfig.Quality, config.Quality)
|
|
assert.Equal(t, expectedConfig.Bitrate, config.Bitrate)
|
|
assert.Equal(t, expectedConfig.SampleRate, config.SampleRate)
|
|
assert.Equal(t, expectedConfig.Channels, config.Channels)
|
|
assert.Equal(t, expectedConfig.FrameSize, config.FrameSize)
|
|
}
|
|
}
|
|
|
|
// Benchmark tests
|
|
func BenchmarkGetAudioConfig(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_ = GetAudioConfig()
|
|
}
|
|
}
|
|
|
|
func BenchmarkGetAudioMetrics(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_ = GetAudioMetrics()
|
|
}
|
|
}
|
|
|
|
func BenchmarkRecordFrameReceived(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
RecordFrameReceived(1024)
|
|
}
|
|
}
|
|
|
|
func BenchmarkSetAudioQuality(b *testing.B) {
|
|
qualities := []AudioQuality{AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra}
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
SetAudioQuality(qualities[i%len(qualities)])
|
|
}
|
|
}
|
|
|
|
// TestAudioUsbGadgetIntegration tests audio functionality with USB gadget reconfiguration
|
|
// This test simulates the production scenario where audio devices are enabled/disabled
|
|
// through USB gadget configuration changes
|
|
func TestAudioUsbGadgetIntegration(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping integration test in short mode")
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
initialAudioEnabled bool
|
|
newAudioEnabled bool
|
|
expectedTransition string
|
|
}{
|
|
{
|
|
name: "EnableAudio",
|
|
initialAudioEnabled: false,
|
|
newAudioEnabled: true,
|
|
expectedTransition: "disabled_to_enabled",
|
|
},
|
|
{
|
|
name: "DisableAudio",
|
|
initialAudioEnabled: true,
|
|
newAudioEnabled: false,
|
|
expectedTransition: "enabled_to_disabled",
|
|
},
|
|
{
|
|
name: "NoChange",
|
|
initialAudioEnabled: true,
|
|
newAudioEnabled: true,
|
|
expectedTransition: "no_change",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Simulate initial USB device configuration
|
|
initialDevices := &usbgadget.Devices{
|
|
Keyboard: true,
|
|
AbsoluteMouse: true,
|
|
RelativeMouse: true,
|
|
MassStorage: true,
|
|
Audio: tt.initialAudioEnabled,
|
|
}
|
|
|
|
// Simulate new USB device configuration
|
|
newDevices := &usbgadget.Devices{
|
|
Keyboard: true,
|
|
AbsoluteMouse: true,
|
|
RelativeMouse: true,
|
|
MassStorage: true,
|
|
Audio: tt.newAudioEnabled,
|
|
}
|
|
|
|
// Test audio configuration validation
|
|
err := validateAudioDeviceConfiguration(tt.newAudioEnabled)
|
|
assert.NoError(t, err, "Audio configuration should be valid")
|
|
|
|
// Test audio state transition simulation
|
|
transition := simulateAudioStateTransition(ctx, initialDevices, newDevices)
|
|
assert.Equal(t, tt.expectedTransition, transition, "Audio state transition should match expected")
|
|
|
|
// Test that audio configuration is consistent after transition
|
|
if tt.newAudioEnabled {
|
|
config := GetAudioConfig()
|
|
assert.Greater(t, config.Bitrate, 0, "Audio bitrate should be positive when enabled")
|
|
assert.Greater(t, config.SampleRate, 0, "Audio sample rate should be positive when enabled")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// validateAudioDeviceConfiguration simulates the audio validation that happens in production
|
|
func validateAudioDeviceConfiguration(enabled bool) error {
|
|
if !enabled {
|
|
return nil // No validation needed when disabled
|
|
}
|
|
|
|
// Simulate audio device availability checks
|
|
// In production, this would check for ALSA devices, audio hardware, etc.
|
|
config := GetAudioConfig()
|
|
if config.Bitrate <= 0 {
|
|
return assert.AnError
|
|
}
|
|
if config.SampleRate <= 0 {
|
|
return assert.AnError
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// simulateAudioStateTransition simulates the audio process management during USB reconfiguration
|
|
func simulateAudioStateTransition(ctx context.Context, initial, new *usbgadget.Devices) string {
|
|
previousAudioEnabled := initial.Audio
|
|
newAudioEnabled := new.Audio
|
|
|
|
if previousAudioEnabled == newAudioEnabled {
|
|
return "no_change"
|
|
}
|
|
|
|
if !newAudioEnabled {
|
|
// Simulate stopping audio processes
|
|
// In production, this would stop AudioInputManager and audioSupervisor
|
|
time.Sleep(10 * time.Millisecond) // Simulate process stop time
|
|
return "enabled_to_disabled"
|
|
}
|
|
|
|
if newAudioEnabled {
|
|
// Simulate starting audio processes after USB reconfiguration
|
|
// In production, this would start audioSupervisor and broadcast events
|
|
time.Sleep(10 * time.Millisecond) // Simulate process start time
|
|
return "disabled_to_enabled"
|
|
}
|
|
|
|
return "unknown"
|
|
}
|
|
|
|
// TestAudioUsbGadgetTimeout tests that audio operations don't timeout during USB reconfiguration
|
|
func TestAudioUsbGadgetTimeout(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping timeout test in short mode")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
// Test that audio configuration changes complete within reasonable time
|
|
start := time.Now()
|
|
|
|
// Simulate multiple rapid USB device configuration changes
|
|
for i := 0; i < 10; i++ {
|
|
audioEnabled := i%2 == 0
|
|
devices := &usbgadget.Devices{
|
|
Keyboard: true,
|
|
AbsoluteMouse: true,
|
|
RelativeMouse: true,
|
|
MassStorage: true,
|
|
Audio: audioEnabled,
|
|
}
|
|
|
|
err := validateAudioDeviceConfiguration(devices.Audio)
|
|
assert.NoError(t, err, "Audio validation should not fail")
|
|
|
|
// Ensure we don't timeout
|
|
select {
|
|
case <-ctx.Done():
|
|
t.Fatal("Audio configuration test timed out")
|
|
default:
|
|
// Continue
|
|
}
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
t.Logf("Audio USB gadget configuration test completed in %v", elapsed)
|
|
assert.Less(t, elapsed, 3*time.Second, "Audio configuration should complete quickly")
|
|
}
|