kvm/internal/audio/validation_test.go

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