kvm/internal/audio/validation_test.go
Alex P ece36ce5fd feat(audio): optimize validation and add dynamic opus encoder configuration
Consolidate audio frame validation functions into a single optimized implementation and add dynamic OPUS encoder parameter updates based on quality settings. Initialize validation cache at startup for consistent performance.

Add latency profiler for end-to-end audio pipeline monitoring. Update test cases to use unified validation function and initialize cache.

The changes improve performance by reducing function call overhead and enabling runtime optimization of audio encoding parameters based on quality presets.
2025-08-27 23:44:16 +00:00

542 lines
18 KiB
Go

//go:build cgo
// +build cgo
package audio
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestValidationFunctions provides comprehensive testing of all validation functions
// to ensure they catch breaking changes and regressions effectively
func TestValidationFunctions(t *testing.T) {
// Initialize validation cache for testing
InitValidationCache()
tests := []struct {
name string
testFunc func(t *testing.T)
}{
{"AudioQualityValidation", testAudioQualityValidation},
{"FrameDataValidation", testFrameDataValidation},
{"BufferSizeValidation", testBufferSizeValidation},
{"ThreadPriorityValidation", testThreadPriorityValidation},
{"LatencyValidation", testLatencyValidation},
{"MetricsIntervalValidation", testMetricsIntervalValidation},
{"SampleRateValidation", testSampleRateValidation},
{"ChannelCountValidation", testChannelCountValidation},
{"BitrateValidation", testBitrateValidation},
{"FrameDurationValidation", testFrameDurationValidation},
{"IPCConfigValidation", testIPCConfigValidation},
{"AdaptiveBufferConfigValidation", testAdaptiveBufferConfigValidation},
{"AudioConfigCompleteValidation", testAudioConfigCompleteValidation},
{"ZeroCopyFrameValidation", testZeroCopyFrameValidation},
{"AudioFrameFastValidation", testAudioFrameFastValidation},
{"ErrorWrappingValidation", testErrorWrappingValidation},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.testFunc(t)
})
}
}
// testAudioQualityValidation tests audio quality validation with boundary conditions
func testAudioQualityValidation(t *testing.T) {
// Test valid quality levels
validQualities := []AudioQuality{AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra}
for _, quality := range validQualities {
err := ValidateAudioQuality(quality)
assert.NoError(t, err, "Valid quality %d should pass validation", quality)
}
// Test invalid quality levels
invalidQualities := []AudioQuality{-1, 4, 100, -100}
for _, quality := range invalidQualities {
err := ValidateAudioQuality(quality)
assert.Error(t, err, "Invalid quality %d should fail validation", quality)
assert.Contains(t, err.Error(), "invalid audio quality level", "Error should mention audio quality")
}
}
// testFrameDataValidation tests frame data validation with various edge cases using modern validation
func testFrameDataValidation(t *testing.T) {
config := GetConfig()
// Test empty data
err := ValidateAudioFrame([]byte{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "frame data is empty")
// Test data above maximum size
largeData := make([]byte, config.MaxAudioFrameSize+1)
err = ValidateAudioFrame(largeData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid data
validData := make([]byte, 1000) // Within bounds
if len(validData) <= config.MaxAudioFrameSize {
err = ValidateAudioFrame(validData)
assert.NoError(t, err)
}
}
// testBufferSizeValidation tests buffer size validation
func testBufferSizeValidation(t *testing.T) {
config := GetConfig()
// Test negative and zero sizes
invalidSizes := []int{-1, -100, 0}
for _, size := range invalidSizes {
err := ValidateBufferSize(size)
assert.Error(t, err, "Buffer size %d should be invalid", size)
assert.Contains(t, err.Error(), "must be positive")
}
// Test size exceeding maximum
err := ValidateBufferSize(config.SocketMaxBuffer + 1)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid sizes
validSizes := []int{1, 1024, 4096, config.SocketMaxBuffer}
for _, size := range validSizes {
err := ValidateBufferSize(size)
assert.NoError(t, err, "Buffer size %d should be valid", size)
}
}
// testThreadPriorityValidation tests thread priority validation
func testThreadPriorityValidation(t *testing.T) {
// Test valid priorities
validPriorities := []int{-20, -10, 0, 10, 19}
for _, priority := range validPriorities {
err := ValidateThreadPriority(priority)
assert.NoError(t, err, "Priority %d should be valid", priority)
}
// Test invalid priorities
invalidPriorities := []int{-21, -100, 20, 100}
for _, priority := range invalidPriorities {
err := ValidateThreadPriority(priority)
assert.Error(t, err, "Priority %d should be invalid", priority)
assert.Contains(t, err.Error(), "outside valid range")
}
}
// testLatencyValidation tests latency validation
func testLatencyValidation(t *testing.T) {
config := GetConfig()
// Test negative latency
err := ValidateLatency(-1 * time.Millisecond)
assert.Error(t, err)
assert.Contains(t, err.Error(), "cannot be negative")
// Test zero latency (should be valid)
err = ValidateLatency(0)
assert.NoError(t, err)
// Test very small positive latency
err = ValidateLatency(500 * time.Microsecond)
assert.Error(t, err)
assert.Contains(t, err.Error(), "below minimum")
// Test latency exceeding maximum
err = ValidateLatency(config.MaxLatency + time.Second)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid latencies
validLatencies := []time.Duration{
1 * time.Millisecond,
10 * time.Millisecond,
100 * time.Millisecond,
config.MaxLatency,
}
for _, latency := range validLatencies {
err := ValidateLatency(latency)
assert.NoError(t, err, "Latency %v should be valid", latency)
}
}
// testMetricsIntervalValidation tests metrics interval validation
func testMetricsIntervalValidation(t *testing.T) {
config := GetConfig()
// Test interval below minimum
err := ValidateMetricsInterval(config.MinMetricsUpdateInterval - time.Millisecond)
assert.Error(t, err)
// Test interval above maximum
err = ValidateMetricsInterval(config.MaxMetricsUpdateInterval + time.Second)
assert.Error(t, err)
// Test valid intervals
validIntervals := []time.Duration{
config.MinMetricsUpdateInterval,
config.MaxMetricsUpdateInterval,
(config.MinMetricsUpdateInterval + config.MaxMetricsUpdateInterval) / 2,
}
for _, interval := range validIntervals {
err := ValidateMetricsInterval(interval)
assert.NoError(t, err, "Interval %v should be valid", interval)
}
}
// testSampleRateValidation tests sample rate validation
func testSampleRateValidation(t *testing.T) {
config := GetConfig()
// Test negative and zero sample rates
invalidRates := []int{-1, -48000, 0}
for _, rate := range invalidRates {
err := ValidateSampleRate(rate)
assert.Error(t, err, "Sample rate %d should be invalid", rate)
assert.Contains(t, err.Error(), "must be positive")
}
// Test unsupported sample rates
unsupportedRates := []int{1000, 12345, 96001}
for _, rate := range unsupportedRates {
err := ValidateSampleRate(rate)
assert.Error(t, err, "Sample rate %d should be unsupported", rate)
assert.Contains(t, err.Error(), "not in supported rates")
}
// Test valid sample rates
for _, rate := range config.ValidSampleRates {
err := ValidateSampleRate(rate)
assert.NoError(t, err, "Sample rate %d should be valid", rate)
}
}
// testChannelCountValidation tests channel count validation
func testChannelCountValidation(t *testing.T) {
config := GetConfig()
// Test invalid channel counts
invalidCounts := []int{-1, -10, 0}
for _, count := range invalidCounts {
err := ValidateChannelCount(count)
assert.Error(t, err, "Channel count %d should be invalid", count)
assert.Contains(t, err.Error(), "must be positive")
}
// Test channel count exceeding maximum
err := ValidateChannelCount(config.MaxChannels + 1)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid channel counts
validCounts := []int{1, 2, config.MaxChannels}
for _, count := range validCounts {
err := ValidateChannelCount(count)
assert.NoError(t, err, "Channel count %d should be valid", count)
}
}
// testBitrateValidation tests bitrate validation
func testBitrateValidation(t *testing.T) {
// Test invalid bitrates
invalidBitrates := []int{-1, -1000, 0}
for _, bitrate := range invalidBitrates {
err := ValidateBitrate(bitrate)
assert.Error(t, err, "Bitrate %d should be invalid", bitrate)
assert.Contains(t, err.Error(), "must be positive")
}
// Test bitrate below minimum (in kbps)
err := ValidateBitrate(5) // 5 kbps = 5000 bps < 6000 bps minimum
assert.Error(t, err)
assert.Contains(t, err.Error(), "below minimum")
// Test bitrate above maximum (in kbps)
err = ValidateBitrate(511) // 511 kbps = 511000 bps > 510000 bps maximum
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid bitrates (in kbps)
validBitrates := []int{
6, // 6 kbps = 6000 bps (minimum)
64, // Medium quality preset
128, // High quality preset
192, // Ultra quality preset
510, // 510 kbps = 510000 bps (maximum)
}
for _, bitrate := range validBitrates {
err := ValidateBitrate(bitrate)
assert.NoError(t, err, "Bitrate %d kbps should be valid", bitrate)
}
}
// testFrameDurationValidation tests frame duration validation
func testFrameDurationValidation(t *testing.T) {
config := GetConfig()
// Test invalid durations
invalidDurations := []time.Duration{-1 * time.Millisecond, -1 * time.Second, 0}
for _, duration := range invalidDurations {
err := ValidateFrameDuration(duration)
assert.Error(t, err, "Duration %v should be invalid", duration)
assert.Contains(t, err.Error(), "must be positive")
}
// Test duration below minimum
err := ValidateFrameDuration(config.MinFrameDuration - time.Microsecond)
assert.Error(t, err)
assert.Contains(t, err.Error(), "below minimum")
// Test duration above maximum
err = ValidateFrameDuration(config.MaxFrameDuration + time.Second)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid durations
validDurations := []time.Duration{
config.MinFrameDuration,
config.MaxFrameDuration,
20 * time.Millisecond, // Common frame duration
}
for _, duration := range validDurations {
err := ValidateFrameDuration(duration)
assert.NoError(t, err, "Duration %v should be valid", duration)
}
}
// testIPCConfigValidation tests IPC configuration validation
func testIPCConfigValidation(t *testing.T) {
config := GetConfig()
// Test invalid configurations for input IPC
invalidConfigs := []struct {
sampleRate, channels, frameSize int
description string
}{
{0, 2, 960, "zero sample rate"},
{48000, 0, 960, "zero channels"},
{48000, 2, 0, "zero frame size"},
{config.MinSampleRate - 1, 2, 960, "sample rate below minimum"},
{config.MaxSampleRate + 1, 2, 960, "sample rate above maximum"},
{48000, config.MaxChannels + 1, 960, "too many channels"},
{48000, -1, 960, "negative channels"},
{48000, 2, -1, "negative frame size"},
}
for _, tc := range invalidConfigs {
// Test input IPC validation
err := ValidateInputIPCConfig(tc.sampleRate, tc.channels, tc.frameSize)
assert.Error(t, err, "Input IPC config should be invalid: %s", tc.description)
// Test output IPC validation
err = ValidateOutputIPCConfig(tc.sampleRate, tc.channels, tc.frameSize)
assert.Error(t, err, "Output IPC config should be invalid: %s", tc.description)
}
// Test valid configuration
err := ValidateInputIPCConfig(48000, 2, 960)
assert.NoError(t, err)
err = ValidateOutputIPCConfig(48000, 2, 960)
assert.NoError(t, err)
}
// testAdaptiveBufferConfigValidation tests adaptive buffer configuration validation
func testAdaptiveBufferConfigValidation(t *testing.T) {
config := GetConfig()
// Test invalid configurations
invalidConfigs := []struct {
minSize, maxSize, defaultSize int
description string
}{
{0, 1024, 512, "zero min size"},
{-1, 1024, 512, "negative min size"},
{512, 0, 256, "zero max size"},
{512, -1, 256, "negative max size"},
{512, 1024, 0, "zero default size"},
{512, 1024, -1, "negative default size"},
{1024, 512, 768, "min >= max"},
{512, 1024, 256, "default < min"},
{512, 1024, 2048, "default > max"},
{512, config.SocketMaxBuffer + 1, 1024, "max exceeds global limit"},
}
for _, tc := range invalidConfigs {
err := ValidateAdaptiveBufferConfig(tc.minSize, tc.maxSize, tc.defaultSize)
assert.Error(t, err, "Config should be invalid: %s", tc.description)
}
// Test valid configuration
err := ValidateAdaptiveBufferConfig(512, 4096, 1024)
assert.NoError(t, err)
}
// testAudioConfigCompleteValidation tests complete audio configuration validation
func testAudioConfigCompleteValidation(t *testing.T) {
// Test valid configuration using actual preset values
validConfig := AudioConfig{
Quality: AudioQualityMedium,
Bitrate: 64, // kbps - matches medium quality preset
SampleRate: 48000,
Channels: 2,
FrameSize: 20 * time.Millisecond,
}
err := ValidateAudioConfigComplete(validConfig)
assert.NoError(t, err)
// Test invalid quality
invalidQualityConfig := validConfig
invalidQualityConfig.Quality = AudioQuality(99)
err = ValidateAudioConfigComplete(invalidQualityConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "quality validation failed")
// Test invalid bitrate
invalidBitrateConfig := validConfig
invalidBitrateConfig.Bitrate = -1
err = ValidateAudioConfigComplete(invalidBitrateConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "bitrate validation failed")
// Test invalid sample rate
invalidSampleRateConfig := validConfig
invalidSampleRateConfig.SampleRate = 12345
err = ValidateAudioConfigComplete(invalidSampleRateConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "sample rate validation failed")
// Test invalid channels
invalidChannelsConfig := validConfig
invalidChannelsConfig.Channels = 0
err = ValidateAudioConfigComplete(invalidChannelsConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "channel count validation failed")
// Test invalid frame duration
invalidFrameDurationConfig := validConfig
invalidFrameDurationConfig.FrameSize = -1 * time.Millisecond
err = ValidateAudioConfigComplete(invalidFrameDurationConfig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "frame duration validation failed")
}
// testZeroCopyFrameValidation tests zero-copy frame validation
func testZeroCopyFrameValidation(t *testing.T) {
// Test nil frame
err := ValidateZeroCopyFrame(nil)
assert.Error(t, err)
// Note: We can't easily test ZeroCopyAudioFrame without creating actual instances
// This would require more complex setup, but the validation logic is tested
}
// testAudioFrameFastValidation tests fast audio frame validation
func testAudioFrameFastValidation(t *testing.T) {
config := GetConfig()
// Test empty data
err := ValidateAudioFrame([]byte{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "frame data is empty")
// Test data exceeding maximum size
largeData := make([]byte, config.MaxAudioFrameSize+1)
err = ValidateAudioFrame(largeData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test valid data
validData := make([]byte, 1000)
err = ValidateAudioFrame(validData)
assert.NoError(t, err)
}
// testErrorWrappingValidation tests error wrapping functionality
func testErrorWrappingValidation(t *testing.T) {
// Test wrapping nil error
wrapped := WrapWithMetadata(nil, "component", "operation", map[string]interface{}{"key": "value"})
assert.Nil(t, wrapped)
// Test wrapping actual error
originalErr := assert.AnError
metadata := map[string]interface{}{
"frame_size": 1024,
"quality": "high",
}
wrapped = WrapWithMetadata(originalErr, "audio", "decode", metadata)
require.NotNil(t, wrapped)
assert.Contains(t, wrapped.Error(), "audio.decode")
assert.Contains(t, wrapped.Error(), "assert.AnError")
assert.Contains(t, wrapped.Error(), "metadata")
assert.Contains(t, wrapped.Error(), "frame_size")
assert.Contains(t, wrapped.Error(), "quality")
}
// TestValidationIntegration tests validation functions working together
func TestValidationIntegration(t *testing.T) {
// Test that validation functions work correctly with actual audio configurations
presets := GetAudioQualityPresets()
require.NotEmpty(t, presets)
for quality, config := range presets {
t.Run(fmt.Sprintf("Quality_%d", quality), func(t *testing.T) {
// Validate the preset configuration
err := ValidateAudioConfigComplete(config)
assert.NoError(t, err, "Preset configuration for quality %d should be valid", quality)
// Validate individual components
err = ValidateAudioQuality(config.Quality)
assert.NoError(t, err, "Quality should be valid")
err = ValidateBitrate(config.Bitrate)
assert.NoError(t, err, "Bitrate should be valid")
err = ValidateSampleRate(config.SampleRate)
assert.NoError(t, err, "Sample rate should be valid")
err = ValidateChannelCount(config.Channels)
assert.NoError(t, err, "Channel count should be valid")
err = ValidateFrameDuration(config.FrameSize)
assert.NoError(t, err, "Frame duration should be valid")
})
}
}
// TestValidationPerformance ensures validation functions are efficient
func TestValidationPerformance(t *testing.T) {
if testing.Short() {
t.Skip("Skipping performance test in short mode")
}
// Initialize validation cache for performance testing
InitValidationCache()
// Test that validation functions complete quickly
start := time.Now()
iterations := 10000
for i := 0; i < iterations; i++ {
_ = ValidateAudioQuality(AudioQualityMedium)
_ = ValidateBufferSize(1024)
_ = ValidateChannelCount(2)
_ = ValidateSampleRate(48000)
_ = ValidateBitrate(96) // 96 kbps
}
elapsed := time.Since(start)
perIteration := elapsed / time.Duration(iterations)
// Performance expectations for JetKVM (ARM Cortex-A7 @ 1GHz, 256MB RAM)
// Audio processing must not interfere with primary KVM functionality
assert.Less(t, perIteration, 200*time.Microsecond, "Validation should not impact KVM performance")
t.Logf("Validation performance: %v per iteration", perIteration)
}