mirror of https://github.com/jetkvm/kvm.git
feat(audio): enhance validation and add logging standards
refactor validation functions to be more comprehensive and add detailed error messages add new logging standards implementation for audio components remove deprecated validation_enhanced.go file add performance-critical tests for validation functions
This commit is contained in:
parent
6355dd87be
commit
9dda569523
|
@ -0,0 +1,323 @@
|
|||
package audio
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// AudioLoggerStandards provides standardized logging patterns for audio components
|
||||
type AudioLoggerStandards struct {
|
||||
logger zerolog.Logger
|
||||
component string
|
||||
}
|
||||
|
||||
// NewAudioLogger creates a new standardized logger for an audio component
|
||||
func NewAudioLogger(logger zerolog.Logger, component string) *AudioLoggerStandards {
|
||||
return &AudioLoggerStandards{
|
||||
logger: logger.With().Str("component", component).Logger(),
|
||||
component: component,
|
||||
}
|
||||
}
|
||||
|
||||
// Component Lifecycle Logging
|
||||
|
||||
// LogComponentStarting logs component initialization start
|
||||
func (als *AudioLoggerStandards) LogComponentStarting() {
|
||||
als.logger.Debug().Msg("starting component")
|
||||
}
|
||||
|
||||
// LogComponentStarted logs successful component start
|
||||
func (als *AudioLoggerStandards) LogComponentStarted() {
|
||||
als.logger.Debug().Msg("component started successfully")
|
||||
}
|
||||
|
||||
// LogComponentStopping logs component shutdown start
|
||||
func (als *AudioLoggerStandards) LogComponentStopping() {
|
||||
als.logger.Debug().Msg("stopping component")
|
||||
}
|
||||
|
||||
// LogComponentStopped logs successful component stop
|
||||
func (als *AudioLoggerStandards) LogComponentStopped() {
|
||||
als.logger.Debug().Msg("component stopped")
|
||||
}
|
||||
|
||||
// LogComponentReady logs component ready state
|
||||
func (als *AudioLoggerStandards) LogComponentReady() {
|
||||
als.logger.Info().Msg("component ready")
|
||||
}
|
||||
|
||||
// Error Logging with Context
|
||||
|
||||
// LogError logs a general error with context
|
||||
func (als *AudioLoggerStandards) LogError(err error, msg string) {
|
||||
als.logger.Error().Err(err).Msg(msg)
|
||||
}
|
||||
|
||||
// LogErrorWithContext logs an error with additional context fields
|
||||
func (als *AudioLoggerStandards) LogErrorWithContext(err error, msg string, fields map[string]interface{}) {
|
||||
event := als.logger.Error().Err(err)
|
||||
for key, value := range fields {
|
||||
event = event.Interface(key, value)
|
||||
}
|
||||
event.Msg(msg)
|
||||
}
|
||||
|
||||
// LogValidationError logs validation failures with specific context
|
||||
func (als *AudioLoggerStandards) LogValidationError(err error, validationType string, value interface{}) {
|
||||
als.logger.Error().Err(err).
|
||||
Str("validation_type", validationType).
|
||||
Interface("invalid_value", value).
|
||||
Msg("validation failed")
|
||||
}
|
||||
|
||||
// LogConnectionError logs connection-related errors
|
||||
func (als *AudioLoggerStandards) LogConnectionError(err error, endpoint string, retryCount int) {
|
||||
als.logger.Error().Err(err).
|
||||
Str("endpoint", endpoint).
|
||||
Int("retry_count", retryCount).
|
||||
Msg("connection failed")
|
||||
}
|
||||
|
||||
// LogProcessError logs process-related errors with PID context
|
||||
func (als *AudioLoggerStandards) LogProcessError(err error, pid int, msg string) {
|
||||
als.logger.Error().Err(err).
|
||||
Int("pid", pid).
|
||||
Msg(msg)
|
||||
}
|
||||
|
||||
// Performance and Metrics Logging
|
||||
|
||||
// LogPerformanceMetrics logs standardized performance metrics
|
||||
func (als *AudioLoggerStandards) LogPerformanceMetrics(metrics map[string]interface{}) {
|
||||
event := als.logger.Info()
|
||||
for key, value := range metrics {
|
||||
event = event.Interface(key, value)
|
||||
}
|
||||
event.Msg("performance metrics")
|
||||
}
|
||||
|
||||
// LogLatencyMetrics logs latency-specific metrics
|
||||
func (als *AudioLoggerStandards) LogLatencyMetrics(current, average, max time.Duration, jitter time.Duration) {
|
||||
als.logger.Info().
|
||||
Dur("current_latency", current).
|
||||
Dur("average_latency", average).
|
||||
Dur("max_latency", max).
|
||||
Dur("jitter", jitter).
|
||||
Msg("latency metrics")
|
||||
}
|
||||
|
||||
// LogFrameMetrics logs frame processing metrics
|
||||
func (als *AudioLoggerStandards) LogFrameMetrics(processed, dropped int64, rate float64) {
|
||||
als.logger.Info().
|
||||
Int64("frames_processed", processed).
|
||||
Int64("frames_dropped", dropped).
|
||||
Float64("processing_rate", rate).
|
||||
Msg("frame processing metrics")
|
||||
}
|
||||
|
||||
// LogBufferMetrics logs buffer utilization metrics
|
||||
func (als *AudioLoggerStandards) LogBufferMetrics(size, used, peak int, utilizationPercent float64) {
|
||||
als.logger.Info().
|
||||
Int("buffer_size", size).
|
||||
Int("buffer_used", used).
|
||||
Int("buffer_peak", peak).
|
||||
Float64("utilization_percent", utilizationPercent).
|
||||
Msg("buffer metrics")
|
||||
}
|
||||
|
||||
// Warning Logging
|
||||
|
||||
// LogWarning logs a general warning
|
||||
func (als *AudioLoggerStandards) LogWarning(msg string) {
|
||||
als.logger.Warn().Msg(msg)
|
||||
}
|
||||
|
||||
// LogWarningWithError logs a warning with error context
|
||||
func (als *AudioLoggerStandards) LogWarningWithError(err error, msg string) {
|
||||
als.logger.Warn().Err(err).Msg(msg)
|
||||
}
|
||||
|
||||
// LogThresholdWarning logs warnings when thresholds are exceeded
|
||||
func (als *AudioLoggerStandards) LogThresholdWarning(metric string, current, threshold interface{}, msg string) {
|
||||
als.logger.Warn().
|
||||
Str("metric", metric).
|
||||
Interface("current_value", current).
|
||||
Interface("threshold", threshold).
|
||||
Msg(msg)
|
||||
}
|
||||
|
||||
// LogRetryWarning logs retry attempts with context
|
||||
func (als *AudioLoggerStandards) LogRetryWarning(operation string, attempt, maxAttempts int, delay time.Duration) {
|
||||
als.logger.Warn().
|
||||
Str("operation", operation).
|
||||
Int("attempt", attempt).
|
||||
Int("max_attempts", maxAttempts).
|
||||
Dur("retry_delay", delay).
|
||||
Msg("retrying operation")
|
||||
}
|
||||
|
||||
// LogRecoveryWarning logs recovery from error conditions
|
||||
func (als *AudioLoggerStandards) LogRecoveryWarning(condition string, duration time.Duration) {
|
||||
als.logger.Warn().
|
||||
Str("condition", condition).
|
||||
Dur("recovery_time", duration).
|
||||
Msg("recovered from error condition")
|
||||
}
|
||||
|
||||
// Debug and Trace Logging
|
||||
|
||||
// LogDebug logs debug information
|
||||
func (als *AudioLoggerStandards) LogDebug(msg string) {
|
||||
als.logger.Debug().Msg(msg)
|
||||
}
|
||||
|
||||
// LogDebugWithFields logs debug information with structured fields
|
||||
func (als *AudioLoggerStandards) LogDebugWithFields(msg string, fields map[string]interface{}) {
|
||||
event := als.logger.Debug()
|
||||
for key, value := range fields {
|
||||
event = event.Interface(key, value)
|
||||
}
|
||||
event.Msg(msg)
|
||||
}
|
||||
|
||||
// LogOperationTrace logs operation tracing for debugging
|
||||
func (als *AudioLoggerStandards) LogOperationTrace(operation string, duration time.Duration, success bool) {
|
||||
als.logger.Debug().
|
||||
Str("operation", operation).
|
||||
Dur("duration", duration).
|
||||
Bool("success", success).
|
||||
Msg("operation trace")
|
||||
}
|
||||
|
||||
// LogDataFlow logs data flow for debugging
|
||||
func (als *AudioLoggerStandards) LogDataFlow(source, destination string, bytes int, frameCount int) {
|
||||
als.logger.Debug().
|
||||
Str("source", source).
|
||||
Str("destination", destination).
|
||||
Int("bytes", bytes).
|
||||
Int("frame_count", frameCount).
|
||||
Msg("data flow")
|
||||
}
|
||||
|
||||
// Configuration and State Logging
|
||||
|
||||
// LogConfigurationChange logs configuration updates
|
||||
func (als *AudioLoggerStandards) LogConfigurationChange(configType string, oldValue, newValue interface{}) {
|
||||
als.logger.Info().
|
||||
Str("config_type", configType).
|
||||
Interface("old_value", oldValue).
|
||||
Interface("new_value", newValue).
|
||||
Msg("configuration changed")
|
||||
}
|
||||
|
||||
// LogStateTransition logs component state changes
|
||||
func (als *AudioLoggerStandards) LogStateTransition(fromState, toState string, reason string) {
|
||||
als.logger.Info().
|
||||
Str("from_state", fromState).
|
||||
Str("to_state", toState).
|
||||
Str("reason", reason).
|
||||
Msg("state transition")
|
||||
}
|
||||
|
||||
// LogResourceAllocation logs resource allocation/deallocation
|
||||
func (als *AudioLoggerStandards) LogResourceAllocation(resourceType string, allocated bool, amount interface{}) {
|
||||
level := als.logger.Debug()
|
||||
if allocated {
|
||||
level.Str("action", "allocated")
|
||||
} else {
|
||||
level.Str("action", "deallocated")
|
||||
}
|
||||
level.Str("resource_type", resourceType).
|
||||
Interface("amount", amount).
|
||||
Msg("resource allocation")
|
||||
}
|
||||
|
||||
// Network and IPC Logging
|
||||
|
||||
// LogConnectionEvent logs connection lifecycle events
|
||||
func (als *AudioLoggerStandards) LogConnectionEvent(event, endpoint string, connectionID string) {
|
||||
als.logger.Info().
|
||||
Str("event", event).
|
||||
Str("endpoint", endpoint).
|
||||
Str("connection_id", connectionID).
|
||||
Msg("connection event")
|
||||
}
|
||||
|
||||
// LogIPCEvent logs IPC communication events
|
||||
func (als *AudioLoggerStandards) LogIPCEvent(event, socketPath string, bytes int) {
|
||||
als.logger.Debug().
|
||||
Str("event", event).
|
||||
Str("socket_path", socketPath).
|
||||
Int("bytes", bytes).
|
||||
Msg("IPC event")
|
||||
}
|
||||
|
||||
// LogNetworkStats logs network statistics
|
||||
func (als *AudioLoggerStandards) LogNetworkStats(sent, received int64, latency time.Duration, packetLoss float64) {
|
||||
als.logger.Info().
|
||||
Int64("bytes_sent", sent).
|
||||
Int64("bytes_received", received).
|
||||
Dur("network_latency", latency).
|
||||
Float64("packet_loss_percent", packetLoss).
|
||||
Msg("network statistics")
|
||||
}
|
||||
|
||||
// Process and System Logging
|
||||
|
||||
// LogProcessEvent logs process lifecycle events
|
||||
func (als *AudioLoggerStandards) LogProcessEvent(event string, pid int, exitCode *int) {
|
||||
event_log := als.logger.Info().
|
||||
Str("event", event).
|
||||
Int("pid", pid)
|
||||
if exitCode != nil {
|
||||
event_log = event_log.Int("exit_code", *exitCode)
|
||||
}
|
||||
event_log.Msg("process event")
|
||||
}
|
||||
|
||||
// LogSystemResource logs system resource usage
|
||||
func (als *AudioLoggerStandards) LogSystemResource(cpuPercent, memoryMB float64, goroutines int) {
|
||||
als.logger.Info().
|
||||
Float64("cpu_percent", cpuPercent).
|
||||
Float64("memory_mb", memoryMB).
|
||||
Int("goroutines", goroutines).
|
||||
Msg("system resources")
|
||||
}
|
||||
|
||||
// LogPriorityChange logs thread priority changes
|
||||
func (als *AudioLoggerStandards) LogPriorityChange(tid, oldPriority, newPriority int, policy string) {
|
||||
als.logger.Debug().
|
||||
Int("tid", tid).
|
||||
Int("old_priority", oldPriority).
|
||||
Int("new_priority", newPriority).
|
||||
Str("policy", policy).
|
||||
Msg("thread priority changed")
|
||||
}
|
||||
|
||||
// Utility Functions
|
||||
|
||||
// GetLogger returns the underlying zerolog.Logger for advanced usage
|
||||
func (als *AudioLoggerStandards) GetLogger() zerolog.Logger {
|
||||
return als.logger
|
||||
}
|
||||
|
||||
// WithFields returns a new logger with additional persistent fields
|
||||
func (als *AudioLoggerStandards) WithFields(fields map[string]interface{}) *AudioLoggerStandards {
|
||||
event := als.logger.With()
|
||||
for key, value := range fields {
|
||||
event = event.Interface(key, value)
|
||||
}
|
||||
return &AudioLoggerStandards{
|
||||
logger: event.Logger(),
|
||||
component: als.component,
|
||||
}
|
||||
}
|
||||
|
||||
// WithSubComponent creates a logger for a sub-component
|
||||
func (als *AudioLoggerStandards) WithSubComponent(subComponent string) *AudioLoggerStandards {
|
||||
return &AudioLoggerStandards{
|
||||
logger: als.logger.With().Str("sub_component", subComponent).Logger(),
|
||||
component: als.component + "." + subComponent,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,389 @@
|
|||
//go:build cgo
|
||||
// +build cgo
|
||||
|
||||
package audio
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestPerformanceCriticalPaths tests the most frequently executed code paths
|
||||
// to ensure they remain efficient and don't interfere with KVM functionality
|
||||
func TestPerformanceCriticalPaths(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
testFunc func(t *testing.T)
|
||||
}{
|
||||
{"AudioFrameProcessingLatency", testAudioFrameProcessingLatency},
|
||||
{"MetricsUpdateOverhead", testMetricsUpdateOverhead},
|
||||
{"ConfigurationAccessSpeed", testConfigurationAccessSpeed},
|
||||
{"ValidationFunctionSpeed", testValidationFunctionSpeed},
|
||||
{"MemoryAllocationPatterns", testMemoryAllocationPatterns},
|
||||
{"ConcurrentAccessPerformance", testConcurrentAccessPerformance},
|
||||
{"BufferPoolEfficiency", testBufferPoolEfficiency},
|
||||
{"AtomicOperationOverhead", testAtomicOperationOverhead},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping performance test in short mode")
|
||||
}
|
||||
tt.testFunc(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// testAudioFrameProcessingLatency tests the latency of audio frame processing
|
||||
// This is the most critical path that must not interfere with KVM
|
||||
func testAudioFrameProcessingLatency(t *testing.T) {
|
||||
const (
|
||||
frameCount = 1000
|
||||
maxLatencyPerFrame = 100 * time.Microsecond // Very strict requirement
|
||||
)
|
||||
|
||||
// Create test frame data
|
||||
frameData := make([]byte, 1920) // Typical frame size
|
||||
for i := range frameData {
|
||||
frameData[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
// Measure frame processing latency
|
||||
start := time.Now()
|
||||
for i := 0; i < frameCount; i++ {
|
||||
// Simulate the critical path: validation + metrics update
|
||||
err := ValidateAudioFrameFast(frameData)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Record frame received (atomic operation)
|
||||
RecordFrameReceived(len(frameData))
|
||||
}
|
||||
elapsed := time.Since(start)
|
||||
|
||||
avgLatencyPerFrame := elapsed / frameCount
|
||||
t.Logf("Average frame processing latency: %v", avgLatencyPerFrame)
|
||||
|
||||
// Ensure frame processing is fast enough to not interfere with KVM
|
||||
assert.Less(t, avgLatencyPerFrame, maxLatencyPerFrame,
|
||||
"Frame processing latency %v exceeds maximum %v - may interfere with KVM",
|
||||
avgLatencyPerFrame, maxLatencyPerFrame)
|
||||
|
||||
// Ensure total processing time is reasonable
|
||||
maxTotalTime := 50 * time.Millisecond
|
||||
assert.Less(t, elapsed, maxTotalTime,
|
||||
"Total processing time %v exceeds maximum %v", elapsed, maxTotalTime)
|
||||
}
|
||||
|
||||
// testMetricsUpdateOverhead tests the overhead of metrics updates
|
||||
func testMetricsUpdateOverhead(t *testing.T) {
|
||||
const iterations = 10000
|
||||
|
||||
// Test RecordFrameReceived performance
|
||||
start := time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
RecordFrameReceived(1024)
|
||||
}
|
||||
recordLatency := time.Since(start) / iterations
|
||||
|
||||
// Test GetAudioMetrics performance
|
||||
start = time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
_ = GetAudioMetrics()
|
||||
}
|
||||
getLatency := time.Since(start) / iterations
|
||||
|
||||
t.Logf("RecordFrameReceived latency: %v", recordLatency)
|
||||
t.Logf("GetAudioMetrics latency: %v", getLatency)
|
||||
|
||||
// Metrics operations should be optimized for JetKVM's ARM Cortex-A7 @ 1GHz
|
||||
// With 256MB RAM, we need to be conservative with performance expectations
|
||||
assert.Less(t, recordLatency, 50*time.Microsecond, "RecordFrameReceived too slow")
|
||||
assert.Less(t, getLatency, 20*time.Microsecond, "GetAudioMetrics too slow")
|
||||
}
|
||||
|
||||
// testConfigurationAccessSpeed tests configuration access performance
|
||||
func testConfigurationAccessSpeed(t *testing.T) {
|
||||
const iterations = 10000
|
||||
|
||||
// Test GetAudioConfig performance
|
||||
start := time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
_ = GetAudioConfig()
|
||||
}
|
||||
configLatency := time.Since(start) / iterations
|
||||
|
||||
// Test GetConfig performance
|
||||
start = time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
_ = GetConfig()
|
||||
}
|
||||
constantsLatency := time.Since(start) / iterations
|
||||
|
||||
t.Logf("GetAudioConfig latency: %v", configLatency)
|
||||
t.Logf("GetConfig latency: %v", constantsLatency)
|
||||
|
||||
// Configuration access should be very fast
|
||||
assert.Less(t, configLatency, 100*time.Nanosecond, "GetAudioConfig too slow")
|
||||
assert.Less(t, constantsLatency, 100*time.Nanosecond, "GetConfig too slow")
|
||||
}
|
||||
|
||||
// testValidationFunctionSpeed tests validation function performance
|
||||
func testValidationFunctionSpeed(t *testing.T) {
|
||||
const iterations = 10000
|
||||
frameData := make([]byte, 1920)
|
||||
|
||||
// Test ValidateAudioFrameFast (most critical)
|
||||
start := time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
err := ValidateAudioFrameFast(frameData)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
fastValidationLatency := time.Since(start) / iterations
|
||||
|
||||
// Test ValidateAudioQuality
|
||||
start = time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
err := ValidateAudioQuality(AudioQualityMedium)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
qualityValidationLatency := time.Since(start) / iterations
|
||||
|
||||
// Test ValidateBufferSize
|
||||
start = time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
err := ValidateBufferSize(1024)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
bufferValidationLatency := time.Since(start) / iterations
|
||||
|
||||
t.Logf("ValidateAudioFrameFast latency: %v", fastValidationLatency)
|
||||
t.Logf("ValidateAudioQuality latency: %v", qualityValidationLatency)
|
||||
t.Logf("ValidateBufferSize latency: %v", bufferValidationLatency)
|
||||
|
||||
// Validation functions optimized for ARM Cortex-A7 single core @ 1GHz
|
||||
// Conservative thresholds to ensure KVM functionality isn't impacted
|
||||
assert.Less(t, fastValidationLatency, 100*time.Microsecond, "ValidateAudioFrameFast too slow")
|
||||
assert.Less(t, qualityValidationLatency, 50*time.Microsecond, "ValidateAudioQuality too slow")
|
||||
assert.Less(t, bufferValidationLatency, 50*time.Microsecond, "ValidateBufferSize too slow")
|
||||
}
|
||||
|
||||
// testMemoryAllocationPatterns tests memory allocation efficiency
|
||||
func testMemoryAllocationPatterns(t *testing.T) {
|
||||
// Test that frequent operations don't cause excessive allocations
|
||||
var m1, m2 runtime.MemStats
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&m1)
|
||||
|
||||
// Perform operations that should minimize allocations
|
||||
for i := 0; i < 1000; i++ {
|
||||
_ = GetAudioConfig()
|
||||
_ = GetAudioMetrics()
|
||||
RecordFrameReceived(1024)
|
||||
_ = ValidateAudioQuality(AudioQualityMedium)
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&m2)
|
||||
|
||||
allocations := m2.Mallocs - m1.Mallocs
|
||||
t.Logf("Memory allocations for 1000 operations: %d", allocations)
|
||||
|
||||
// Should have minimal allocations for these hot path operations
|
||||
assert.Less(t, allocations, uint64(100), "Too many memory allocations in hot path")
|
||||
}
|
||||
|
||||
// testConcurrentAccessPerformance tests performance under concurrent access
|
||||
func testConcurrentAccessPerformance(t *testing.T) {
|
||||
const (
|
||||
numGoroutines = 10
|
||||
operationsPerGoroutine = 1000
|
||||
)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
start := time.Now()
|
||||
|
||||
// Launch concurrent goroutines performing audio operations
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
frameData := make([]byte, 1920)
|
||||
|
||||
for j := 0; j < operationsPerGoroutine; j++ {
|
||||
// Simulate concurrent audio processing
|
||||
_ = ValidateAudioFrameFast(frameData)
|
||||
RecordFrameReceived(len(frameData))
|
||||
_ = GetAudioMetrics()
|
||||
_ = GetAudioConfig()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
elapsed := time.Since(start)
|
||||
|
||||
totalOperations := numGoroutines * operationsPerGoroutine * 4 // 4 operations per iteration
|
||||
avgLatency := elapsed / time.Duration(totalOperations)
|
||||
|
||||
t.Logf("Concurrent access: %d operations in %v (avg: %v per operation)",
|
||||
totalOperations, elapsed, avgLatency)
|
||||
|
||||
// Concurrent access should not significantly degrade performance
|
||||
assert.Less(t, avgLatency, 1*time.Microsecond, "Concurrent access too slow")
|
||||
}
|
||||
|
||||
// testBufferPoolEfficiency tests buffer pool performance
|
||||
func testBufferPoolEfficiency(t *testing.T) {
|
||||
// Test buffer acquisition and release performance
|
||||
const iterations = 1000
|
||||
|
||||
start := time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
// Simulate buffer pool usage (if available)
|
||||
buffer := make([]byte, 1920) // Fallback to allocation
|
||||
_ = buffer
|
||||
// In real implementation, this would be pool.Get() and pool.Put()
|
||||
}
|
||||
elapsed := time.Since(start)
|
||||
|
||||
avgLatency := elapsed / iterations
|
||||
t.Logf("Buffer allocation latency: %v per buffer", avgLatency)
|
||||
|
||||
// Buffer operations should be fast
|
||||
assert.Less(t, avgLatency, 1*time.Microsecond, "Buffer allocation too slow")
|
||||
}
|
||||
|
||||
// testAtomicOperationOverhead tests atomic operation performance
|
||||
func testAtomicOperationOverhead(t *testing.T) {
|
||||
const iterations = 10000
|
||||
var counter int64
|
||||
|
||||
// Test atomic increment performance
|
||||
start := time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
atomic.AddInt64(&counter, 1)
|
||||
}
|
||||
atomicLatency := time.Since(start) / iterations
|
||||
|
||||
// Test atomic load performance
|
||||
start = time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
_ = atomic.LoadInt64(&counter)
|
||||
}
|
||||
loadLatency := time.Since(start) / iterations
|
||||
|
||||
t.Logf("Atomic add latency: %v", atomicLatency)
|
||||
t.Logf("Atomic load latency: %v", loadLatency)
|
||||
|
||||
// Atomic operations on ARM Cortex-A7 - realistic expectations
|
||||
assert.Less(t, atomicLatency, 1*time.Microsecond, "Atomic add too slow")
|
||||
assert.Less(t, loadLatency, 500*time.Nanosecond, "Atomic load too slow")
|
||||
}
|
||||
|
||||
// TestRegressionDetection tests for performance regressions
|
||||
func TestRegressionDetection(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping regression test in short mode")
|
||||
}
|
||||
|
||||
// Baseline performance expectations
|
||||
baselines := map[string]time.Duration{
|
||||
"frame_processing": 100 * time.Microsecond,
|
||||
"metrics_update": 500 * time.Nanosecond,
|
||||
"config_access": 100 * time.Nanosecond,
|
||||
"validation": 200 * time.Nanosecond,
|
||||
}
|
||||
|
||||
// Test frame processing
|
||||
frameData := make([]byte, 1920)
|
||||
start := time.Now()
|
||||
for i := 0; i < 100; i++ {
|
||||
_ = ValidateAudioFrameFast(frameData)
|
||||
RecordFrameReceived(len(frameData))
|
||||
}
|
||||
frameProcessingTime := time.Since(start) / 100
|
||||
|
||||
// Test metrics update
|
||||
start = time.Now()
|
||||
for i := 0; i < 1000; i++ {
|
||||
RecordFrameReceived(1024)
|
||||
}
|
||||
metricsUpdateTime := time.Since(start) / 1000
|
||||
|
||||
// Test config access
|
||||
start = time.Now()
|
||||
for i := 0; i < 1000; i++ {
|
||||
_ = GetAudioConfig()
|
||||
}
|
||||
configAccessTime := time.Since(start) / 1000
|
||||
|
||||
// Test validation
|
||||
start = time.Now()
|
||||
for i := 0; i < 1000; i++ {
|
||||
_ = ValidateAudioQuality(AudioQualityMedium)
|
||||
}
|
||||
validationTime := time.Since(start) / 1000
|
||||
|
||||
// Performance regression thresholds for JetKVM hardware:
|
||||
// - ARM Cortex-A7 @ 1GHz single core
|
||||
// - 256MB DDR3L RAM
|
||||
// - Must not interfere with primary KVM functionality
|
||||
assert.Less(t, frameProcessingTime, baselines["frame_processing"],
|
||||
"Frame processing regression: %v > %v", frameProcessingTime, baselines["frame_processing"])
|
||||
assert.Less(t, metricsUpdateTime, 100*time.Microsecond,
|
||||
"Metrics update regression: %v > 100μs", metricsUpdateTime)
|
||||
assert.Less(t, configAccessTime, 10*time.Microsecond,
|
||||
"Config access regression: %v > 10μs", configAccessTime)
|
||||
assert.Less(t, validationTime, 10*time.Microsecond,
|
||||
"Validation regression: %v > 10μs", validationTime)
|
||||
|
||||
t.Logf("Performance results:")
|
||||
t.Logf(" Frame processing: %v (baseline: %v)", frameProcessingTime, baselines["frame_processing"])
|
||||
t.Logf(" Metrics update: %v (baseline: %v)", metricsUpdateTime, baselines["metrics_update"])
|
||||
t.Logf(" Config access: %v (baseline: %v)", configAccessTime, baselines["config_access"])
|
||||
t.Logf(" Validation: %v (baseline: %v)", validationTime, baselines["validation"])
|
||||
}
|
||||
|
||||
// TestMemoryLeakDetection tests for memory leaks in critical paths
|
||||
func TestMemoryLeakDetection(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping memory leak test in short mode")
|
||||
}
|
||||
|
||||
var m1, m2 runtime.MemStats
|
||||
|
||||
// Baseline measurement
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&m1)
|
||||
|
||||
// Perform many operations that should not leak memory
|
||||
for cycle := 0; cycle < 10; cycle++ {
|
||||
for i := 0; i < 1000; i++ {
|
||||
frameData := make([]byte, 1920)
|
||||
_ = ValidateAudioFrameFast(frameData)
|
||||
RecordFrameReceived(len(frameData))
|
||||
_ = GetAudioMetrics()
|
||||
_ = GetAudioConfig()
|
||||
}
|
||||
// Force garbage collection between cycles
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
// Final measurement
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&m2)
|
||||
|
||||
memoryGrowth := int64(m2.Alloc) - int64(m1.Alloc)
|
||||
t.Logf("Memory growth after 10,000 operations: %d bytes", memoryGrowth)
|
||||
|
||||
// Memory growth should be minimal (less than 1MB)
|
||||
assert.Less(t, memoryGrowth, int64(1024*1024),
|
||||
"Excessive memory growth detected: %d bytes", memoryGrowth)
|
||||
}
|
|
@ -2,6 +2,7 @@ package audio
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -18,28 +19,46 @@ var (
|
|||
ErrInvalidMetricsInterval = errors.New("invalid metrics interval")
|
||||
ErrInvalidSampleRate = errors.New("invalid sample rate")
|
||||
ErrInvalidChannels = errors.New("invalid channels")
|
||||
ErrInvalidBitrate = errors.New("invalid bitrate")
|
||||
ErrInvalidFrameDuration = errors.New("invalid frame duration")
|
||||
ErrInvalidOffset = errors.New("invalid offset")
|
||||
ErrInvalidLength = errors.New("invalid length")
|
||||
)
|
||||
|
||||
// ValidateAudioQuality validates audio quality enum values
|
||||
// ValidateAudioQuality validates audio quality enum values with enhanced checks
|
||||
func ValidateAudioQuality(quality AudioQuality) error {
|
||||
// Perform validation
|
||||
switch quality {
|
||||
case AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra:
|
||||
return nil
|
||||
default:
|
||||
return ErrInvalidAudioQuality
|
||||
// Validate enum range
|
||||
if quality < AudioQualityLow || quality > AudioQualityUltra {
|
||||
return fmt.Errorf("%w: quality value %d outside valid range [%d, %d]",
|
||||
ErrInvalidAudioQuality, int(quality), int(AudioQualityLow), int(AudioQualityUltra))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateFrameData validates audio frame data
|
||||
// ValidateFrameData validates audio frame data with comprehensive boundary checks
|
||||
func ValidateFrameData(data []byte) error {
|
||||
if len(data) == 0 {
|
||||
return ErrInvalidFrameData
|
||||
if data == nil {
|
||||
return fmt.Errorf("%w: frame data is nil", ErrInvalidFrameData)
|
||||
}
|
||||
// Use config value
|
||||
maxFrameSize := GetConfig().MaxAudioFrameSize
|
||||
if len(data) > maxFrameSize {
|
||||
return ErrInvalidFrameSize
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("%w: frame data is empty", ErrInvalidFrameData)
|
||||
}
|
||||
|
||||
config := GetConfig()
|
||||
// Check minimum frame size
|
||||
if len(data) < config.MinFrameSize {
|
||||
return fmt.Errorf("%w: frame size %d below minimum %d",
|
||||
ErrInvalidFrameSize, len(data), config.MinFrameSize)
|
||||
}
|
||||
// Check maximum frame size
|
||||
if len(data) > config.MaxAudioFrameSize {
|
||||
return fmt.Errorf("%w: frame size %d exceeds maximum %d",
|
||||
ErrInvalidFrameSize, len(data), config.MaxAudioFrameSize)
|
||||
}
|
||||
// Validate frame alignment for audio samples (must be even for 16-bit samples)
|
||||
if len(data)%2 != 0 {
|
||||
return fmt.Errorf("%w: frame size %d not aligned for 16-bit samples",
|
||||
ErrInvalidFrameSize, len(data))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -61,38 +80,45 @@ func ValidateZeroCopyFrame(frame *ZeroCopyAudioFrame) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ValidateBufferSize validates buffer size parameters
|
||||
// ValidateBufferSize validates buffer size parameters with enhanced boundary checks
|
||||
func ValidateBufferSize(size int) error {
|
||||
if size <= 0 {
|
||||
return ErrInvalidBufferSize
|
||||
return fmt.Errorf("%w: buffer size %d must be positive", ErrInvalidBufferSize, size)
|
||||
}
|
||||
// Use config value
|
||||
maxBuffer := GetConfig().SocketMaxBuffer
|
||||
if size > maxBuffer {
|
||||
return ErrInvalidBufferSize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateThreadPriority validates thread priority values
|
||||
func ValidateThreadPriority(priority int) error {
|
||||
// Use config values
|
||||
config := GetConfig()
|
||||
if priority < config.MinNiceValue || priority > config.RTAudioHighPriority {
|
||||
return ErrInvalidPriority
|
||||
// Use SocketMaxBuffer as the upper limit for general buffer validation
|
||||
// This allows for socket buffers while still preventing extremely large allocations
|
||||
if size > config.SocketMaxBuffer {
|
||||
return fmt.Errorf("%w: buffer size %d exceeds maximum %d",
|
||||
ErrInvalidBufferSize, size, config.SocketMaxBuffer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateLatency validates latency values
|
||||
// ValidateThreadPriority validates thread priority values with system limits
|
||||
func ValidateThreadPriority(priority int) error {
|
||||
const minPriority, maxPriority = -20, 19
|
||||
if priority < minPriority || priority > maxPriority {
|
||||
return fmt.Errorf("%w: priority %d outside valid range [%d, %d]",
|
||||
ErrInvalidPriority, priority, minPriority, maxPriority)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateLatency validates latency duration values with reasonable bounds
|
||||
func ValidateLatency(latency time.Duration) error {
|
||||
if latency < 0 {
|
||||
return ErrInvalidLatency
|
||||
return fmt.Errorf("%w: latency %v cannot be negative", ErrInvalidLatency, latency)
|
||||
}
|
||||
// Use config value
|
||||
maxLatency := GetConfig().MaxLatency
|
||||
if latency > maxLatency {
|
||||
return ErrInvalidLatency
|
||||
config := GetConfig()
|
||||
minLatency := time.Millisecond // Minimum reasonable latency
|
||||
if latency > 0 && latency < minLatency {
|
||||
return fmt.Errorf("%w: latency %v below minimum %v",
|
||||
ErrInvalidLatency, latency, minLatency)
|
||||
}
|
||||
if latency > config.MaxLatency {
|
||||
return fmt.Errorf("%w: latency %v exceeds maximum %v",
|
||||
ErrInvalidLatency, latency, config.MaxLatency)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -194,3 +220,128 @@ func ValidateLatencyConfig(config LatencyConfig) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSampleRate validates audio sample rate values
|
||||
func ValidateSampleRate(sampleRate int) error {
|
||||
if sampleRate <= 0 {
|
||||
return fmt.Errorf("%w: sample rate %d must be positive", ErrInvalidSampleRate, sampleRate)
|
||||
}
|
||||
config := GetConfig()
|
||||
validRates := config.ValidSampleRates
|
||||
for _, rate := range validRates {
|
||||
if sampleRate == rate {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%w: sample rate %d not in supported rates %v",
|
||||
ErrInvalidSampleRate, sampleRate, validRates)
|
||||
}
|
||||
|
||||
// ValidateChannelCount validates audio channel count
|
||||
func ValidateChannelCount(channels int) error {
|
||||
if channels <= 0 {
|
||||
return fmt.Errorf("%w: channel count %d must be positive", ErrInvalidChannels, channels)
|
||||
}
|
||||
config := GetConfig()
|
||||
if channels > config.MaxChannels {
|
||||
return fmt.Errorf("%w: channel count %d exceeds maximum %d",
|
||||
ErrInvalidChannels, channels, config.MaxChannels)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBitrate validates audio bitrate values (expects kbps)
|
||||
func ValidateBitrate(bitrate int) error {
|
||||
if bitrate <= 0 {
|
||||
return fmt.Errorf("%w: bitrate %d must be positive", ErrInvalidBitrate, bitrate)
|
||||
}
|
||||
config := GetConfig()
|
||||
// Convert kbps to bps for comparison with config limits
|
||||
bitrateInBps := bitrate * 1000
|
||||
if bitrateInBps < config.MinOpusBitrate {
|
||||
return fmt.Errorf("%w: bitrate %d kbps (%d bps) below minimum %d bps",
|
||||
ErrInvalidBitrate, bitrate, bitrateInBps, config.MinOpusBitrate)
|
||||
}
|
||||
if bitrateInBps > config.MaxOpusBitrate {
|
||||
return fmt.Errorf("%w: bitrate %d kbps (%d bps) exceeds maximum %d bps",
|
||||
ErrInvalidBitrate, bitrate, bitrateInBps, config.MaxOpusBitrate)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateFrameDuration validates frame duration values
|
||||
func ValidateFrameDuration(duration time.Duration) error {
|
||||
if duration <= 0 {
|
||||
return fmt.Errorf("%w: frame duration %v must be positive", ErrInvalidFrameDuration, duration)
|
||||
}
|
||||
config := GetConfig()
|
||||
if duration < config.MinFrameDuration {
|
||||
return fmt.Errorf("%w: frame duration %v below minimum %v",
|
||||
ErrInvalidFrameDuration, duration, config.MinFrameDuration)
|
||||
}
|
||||
if duration > config.MaxFrameDuration {
|
||||
return fmt.Errorf("%w: frame duration %v exceeds maximum %v",
|
||||
ErrInvalidFrameDuration, duration, config.MaxFrameDuration)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAudioConfigComplete performs comprehensive audio configuration validation
|
||||
func ValidateAudioConfigComplete(config AudioConfig) error {
|
||||
if err := ValidateAudioQuality(config.Quality); err != nil {
|
||||
return fmt.Errorf("quality validation failed: %w", err)
|
||||
}
|
||||
if err := ValidateBitrate(config.Bitrate); err != nil {
|
||||
return fmt.Errorf("bitrate validation failed: %w", err)
|
||||
}
|
||||
if err := ValidateSampleRate(config.SampleRate); err != nil {
|
||||
return fmt.Errorf("sample rate validation failed: %w", err)
|
||||
}
|
||||
if err := ValidateChannelCount(config.Channels); err != nil {
|
||||
return fmt.Errorf("channel count validation failed: %w", err)
|
||||
}
|
||||
if err := ValidateFrameDuration(config.FrameSize); err != nil {
|
||||
return fmt.Errorf("frame duration validation failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAudioConfigConstants validates audio configuration constants
|
||||
func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
|
||||
// Validate that audio quality constants are within valid ranges
|
||||
for _, quality := range []AudioQuality{AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra} {
|
||||
if err := ValidateAudioQuality(quality); err != nil {
|
||||
return fmt.Errorf("invalid audio quality constant %v: %w", quality, err)
|
||||
}
|
||||
}
|
||||
// Validate configuration values if config is provided
|
||||
if config != nil {
|
||||
if config.MaxFrameSize <= 0 {
|
||||
return fmt.Errorf("invalid MaxFrameSize: %d", config.MaxFrameSize)
|
||||
}
|
||||
if config.SampleRate <= 0 {
|
||||
return fmt.Errorf("invalid SampleRate: %d", config.SampleRate)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAudioFrameFast performs fast validation of audio frame data
|
||||
func ValidateAudioFrameFast(data []byte) error {
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("%w: frame data is empty", ErrInvalidFrameData)
|
||||
}
|
||||
maxFrameSize := GetConfig().MaxAudioFrameSize
|
||||
if len(data) > maxFrameSize {
|
||||
return fmt.Errorf("%w: frame size %d exceeds maximum %d", ErrInvalidFrameSize, len(data), maxFrameSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WrapWithMetadata wraps error with metadata for enhanced validation context
|
||||
func WrapWithMetadata(err error, component, operation string, metadata map[string]interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s.%s: %w (metadata: %+v)", component, operation, err, metadata)
|
||||
}
|
||||
|
|
|
@ -1,479 +0,0 @@
|
|||
package audio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// Validation errors
|
||||
var (
|
||||
ErrInvalidFrameLength = errors.New("invalid frame length")
|
||||
ErrFrameDataCorrupted = errors.New("frame data appears corrupted")
|
||||
ErrBufferAlignment = errors.New("buffer alignment invalid")
|
||||
ErrInvalidSampleFormat = errors.New("invalid sample format")
|
||||
ErrInvalidTimestamp = errors.New("invalid timestamp")
|
||||
ErrConfigurationMismatch = errors.New("configuration mismatch")
|
||||
ErrResourceExhaustion = errors.New("resource exhaustion detected")
|
||||
ErrInvalidPointer = errors.New("invalid pointer")
|
||||
ErrBufferOverflow = errors.New("buffer overflow detected")
|
||||
ErrInvalidState = errors.New("invalid state")
|
||||
ErrOperationTimeout = errors.New("operation timeout")
|
||||
ErrSystemOverload = errors.New("system overload detected")
|
||||
ErrHardwareFailure = errors.New("hardware failure")
|
||||
ErrNetworkError = errors.New("network error")
|
||||
ErrMemoryExhaustion = errors.New("memory exhaustion")
|
||||
)
|
||||
|
||||
// ValidationLevel defines the level of validation to perform
|
||||
type ValidationLevel int
|
||||
|
||||
const (
|
||||
ValidationMinimal ValidationLevel = iota // Only critical safety checks
|
||||
ValidationStandard // Standard validation for production
|
||||
ValidationStrict // Comprehensive validation for debugging
|
||||
)
|
||||
|
||||
// ValidationConfig controls validation behavior
|
||||
type ValidationConfig struct {
|
||||
Level ValidationLevel
|
||||
EnableRangeChecks bool
|
||||
EnableAlignmentCheck bool
|
||||
EnableDataIntegrity bool
|
||||
MaxValidationTime time.Duration
|
||||
}
|
||||
|
||||
// GetValidationConfig returns the current validation configuration
|
||||
func GetValidationConfig() ValidationConfig {
|
||||
// Use direct config access
|
||||
config := GetConfig()
|
||||
return ValidationConfig{
|
||||
Level: ValidationStandard,
|
||||
EnableRangeChecks: true,
|
||||
EnableAlignmentCheck: true,
|
||||
EnableDataIntegrity: false, // Disabled by default for performance
|
||||
MaxValidationTime: config.MaxValidationTime, // Configurable validation timeout
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateAudioFrameFast performs minimal validation for performance-critical paths
|
||||
func ValidateAudioFrameFast(data []byte) error {
|
||||
if len(data) == 0 {
|
||||
return ErrInvalidFrameData
|
||||
}
|
||||
|
||||
// Quick bounds check using direct config access
|
||||
config := GetConfig()
|
||||
if len(data) > config.MaxAudioFrameSize {
|
||||
return fmt.Errorf("%w: frame size %d exceeds maximum %d", ErrInvalidFrameSize, len(data), config.MaxAudioFrameSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAudioFrameComprehensive performs thorough validation
|
||||
func ValidateAudioFrameComprehensive(data []byte, expectedSampleRate int, expectedChannels int) error {
|
||||
validationConfig := GetValidationConfig()
|
||||
start := time.Now()
|
||||
|
||||
// Timeout protection for validation
|
||||
defer func() {
|
||||
if time.Since(start) > validationConfig.MaxValidationTime {
|
||||
// Log validation timeout but don't fail
|
||||
getValidationLogger().Warn().Dur("duration", time.Since(start)).Msg("validation timeout exceeded")
|
||||
}
|
||||
}()
|
||||
|
||||
// Basic validation first
|
||||
if err := ValidateAudioFrameFast(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Range validation
|
||||
if validationConfig.EnableRangeChecks {
|
||||
// Use direct config access
|
||||
config := GetConfig()
|
||||
if len(data) < config.MinFrameSize {
|
||||
return fmt.Errorf("%w: frame size %d below minimum %d", ErrInvalidFrameSize, len(data), config.MinFrameSize)
|
||||
}
|
||||
|
||||
// Validate frame length matches expected sample format
|
||||
expectedFrameSize := (expectedSampleRate * expectedChannels * 2) / 1000 * int(config.AudioQualityMediumFrameSize/time.Millisecond)
|
||||
if abs(len(data)-expectedFrameSize) > config.FrameSizeTolerance {
|
||||
return fmt.Errorf("%w: frame size %d doesn't match expected %d (±%d)", ErrInvalidFrameLength, len(data), expectedFrameSize, config.FrameSizeTolerance)
|
||||
}
|
||||
}
|
||||
|
||||
// Alignment validation for ARM32 compatibility
|
||||
if validationConfig.EnableAlignmentCheck {
|
||||
if uintptr(unsafe.Pointer(&data[0]))%4 != 0 {
|
||||
return fmt.Errorf("%w: buffer not 4-byte aligned for ARM32", ErrBufferAlignment)
|
||||
}
|
||||
}
|
||||
|
||||
// Data integrity checks (expensive, only for debugging)
|
||||
if validationConfig.EnableDataIntegrity && validationConfig.Level == ValidationStrict {
|
||||
if err := validateAudioDataIntegrity(data, expectedChannels); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateZeroCopyFrameEnhanced performs enhanced zero-copy frame validation
|
||||
func ValidateZeroCopyFrameEnhanced(frame *ZeroCopyAudioFrame) error {
|
||||
if frame == nil {
|
||||
return fmt.Errorf("%w: frame is nil", ErrInvalidPointer)
|
||||
}
|
||||
|
||||
// Check reference count validity
|
||||
frame.mutex.RLock()
|
||||
refCount := frame.refCount
|
||||
length := frame.length
|
||||
capacity := frame.capacity
|
||||
frame.mutex.RUnlock()
|
||||
|
||||
if refCount <= 0 {
|
||||
return fmt.Errorf("%w: invalid reference count %d", ErrInvalidState, refCount)
|
||||
}
|
||||
|
||||
if length < 0 || capacity < 0 {
|
||||
return fmt.Errorf("%w: negative length (%d) or capacity (%d)", ErrInvalidState, length, capacity)
|
||||
}
|
||||
|
||||
if length > capacity {
|
||||
return fmt.Errorf("%w: length %d exceeds capacity %d", ErrBufferOverflow, length, capacity)
|
||||
}
|
||||
|
||||
// Validate the underlying data
|
||||
data := frame.Data()
|
||||
return ValidateAudioFrameFast(data)
|
||||
}
|
||||
|
||||
// ValidateBufferBounds performs bounds checking with overflow protection
|
||||
func ValidateBufferBounds(buffer []byte, offset, length int) error {
|
||||
if buffer == nil {
|
||||
return fmt.Errorf("%w: buffer is nil", ErrInvalidPointer)
|
||||
}
|
||||
|
||||
if offset < 0 {
|
||||
return fmt.Errorf("%w: negative offset %d", ErrInvalidState, offset)
|
||||
}
|
||||
|
||||
if length < 0 {
|
||||
return fmt.Errorf("%w: negative length %d", ErrInvalidState, length)
|
||||
}
|
||||
|
||||
// Check for integer overflow
|
||||
if offset > len(buffer) {
|
||||
return fmt.Errorf("%w: offset %d exceeds buffer length %d", ErrBufferOverflow, offset, len(buffer))
|
||||
}
|
||||
|
||||
// Safe addition check for overflow
|
||||
if offset+length < offset || offset+length > len(buffer) {
|
||||
return fmt.Errorf("%w: range [%d:%d] exceeds buffer length %d", ErrBufferOverflow, offset, offset+length, len(buffer))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAudioConfiguration performs comprehensive configuration validation
|
||||
func ValidateAudioConfiguration(config AudioConfig) error {
|
||||
if err := ValidateAudioQuality(config.Quality); err != nil {
|
||||
return fmt.Errorf("quality validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Use direct config access
|
||||
configConstants := GetConfig()
|
||||
minOpusBitrate := configConstants.MinOpusBitrate
|
||||
maxOpusBitrate := configConstants.MaxOpusBitrate
|
||||
maxChannels := configConstants.MaxChannels
|
||||
validSampleRates := configConstants.ValidSampleRates
|
||||
minFrameDuration := configConstants.MinFrameDuration
|
||||
maxFrameDuration := configConstants.MaxFrameDuration
|
||||
|
||||
// Validate bitrate ranges
|
||||
if config.Bitrate < minOpusBitrate || config.Bitrate > maxOpusBitrate {
|
||||
return fmt.Errorf("%w: bitrate %d outside valid range [%d, %d]", ErrInvalidConfiguration, config.Bitrate, minOpusBitrate, maxOpusBitrate)
|
||||
}
|
||||
|
||||
// Validate sample rate
|
||||
validSampleRate := false
|
||||
for _, rate := range validSampleRates {
|
||||
if config.SampleRate == rate {
|
||||
validSampleRate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validSampleRate {
|
||||
return fmt.Errorf("%w: sample rate %d not in supported rates %v", ErrInvalidSampleRate, config.SampleRate, validSampleRates)
|
||||
}
|
||||
|
||||
// Validate channels
|
||||
if config.Channels < 1 || config.Channels > maxChannels {
|
||||
return fmt.Errorf("%w: channels %d outside valid range [1, %d]", ErrInvalidChannels, config.Channels, maxChannels)
|
||||
}
|
||||
|
||||
// Validate frame size
|
||||
if config.FrameSize < minFrameDuration || config.FrameSize > maxFrameDuration {
|
||||
return fmt.Errorf("%w: frame size %v outside valid range [%v, %v]", ErrInvalidConfiguration, config.FrameSize, minFrameDuration, maxFrameDuration)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAudioConfigConstants performs comprehensive validation of AudioConfigConstants
|
||||
func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
|
||||
if config == nil {
|
||||
return fmt.Errorf("%w: configuration is nil", ErrInvalidConfiguration)
|
||||
}
|
||||
|
||||
// Validate basic audio parameters
|
||||
if config.MaxAudioFrameSize <= 0 {
|
||||
return fmt.Errorf("%w: MaxAudioFrameSize must be positive", ErrInvalidConfiguration)
|
||||
}
|
||||
if config.SampleRate <= 0 {
|
||||
return fmt.Errorf("%w: SampleRate must be positive", ErrInvalidSampleRate)
|
||||
}
|
||||
if config.Channels <= 0 || config.Channels > 8 {
|
||||
return fmt.Errorf("%w: Channels must be between 1 and 8", ErrInvalidChannels)
|
||||
}
|
||||
|
||||
// Validate Opus parameters
|
||||
if config.OpusBitrate < 6000 || config.OpusBitrate > 510000 {
|
||||
return fmt.Errorf("%w: OpusBitrate must be between 6000 and 510000", ErrInvalidConfiguration)
|
||||
}
|
||||
if config.OpusComplexity < 0 || config.OpusComplexity > 10 {
|
||||
return fmt.Errorf("%w: OpusComplexity must be between 0 and 10", ErrInvalidConfiguration)
|
||||
}
|
||||
|
||||
// Validate bitrate ranges
|
||||
if config.MinOpusBitrate <= 0 || config.MaxOpusBitrate <= 0 {
|
||||
return fmt.Errorf("%w: MinOpusBitrate and MaxOpusBitrate must be positive", ErrInvalidConfiguration)
|
||||
}
|
||||
if config.MinOpusBitrate >= config.MaxOpusBitrate {
|
||||
return fmt.Errorf("%w: MinOpusBitrate must be less than MaxOpusBitrate", ErrInvalidConfiguration)
|
||||
}
|
||||
|
||||
// Validate sample rate ranges
|
||||
if config.MinSampleRate <= 0 || config.MaxSampleRate <= 0 {
|
||||
return fmt.Errorf("%w: MinSampleRate and MaxSampleRate must be positive", ErrInvalidSampleRate)
|
||||
}
|
||||
if config.MinSampleRate >= config.MaxSampleRate {
|
||||
return fmt.Errorf("%w: MinSampleRate must be less than MaxSampleRate", ErrInvalidSampleRate)
|
||||
}
|
||||
|
||||
// Validate frame duration ranges
|
||||
if config.MinFrameDuration <= 0 || config.MaxFrameDuration <= 0 {
|
||||
return fmt.Errorf("%w: MinFrameDuration and MaxFrameDuration must be positive", ErrInvalidConfiguration)
|
||||
}
|
||||
if config.MinFrameDuration >= config.MaxFrameDuration {
|
||||
return fmt.Errorf("%w: MinFrameDuration must be less than MaxFrameDuration", ErrInvalidConfiguration)
|
||||
}
|
||||
|
||||
// Validate buffer sizes
|
||||
if config.SocketMinBuffer <= 0 || config.SocketMaxBuffer <= 0 {
|
||||
return fmt.Errorf("%w: SocketMinBuffer and SocketMaxBuffer must be positive", ErrInvalidBufferSize)
|
||||
}
|
||||
if config.SocketMinBuffer >= config.SocketMaxBuffer {
|
||||
return fmt.Errorf("%w: SocketMinBuffer must be less than SocketMaxBuffer", ErrInvalidBufferSize)
|
||||
}
|
||||
|
||||
// Validate priority ranges
|
||||
if config.MinNiceValue < -20 || config.MinNiceValue > 19 {
|
||||
return fmt.Errorf("%w: MinNiceValue must be between -20 and 19", ErrInvalidPriority)
|
||||
}
|
||||
if config.MaxNiceValue < -20 || config.MaxNiceValue > 19 {
|
||||
return fmt.Errorf("%w: MaxNiceValue must be between -20 and 19", ErrInvalidPriority)
|
||||
}
|
||||
if config.MinNiceValue >= config.MaxNiceValue {
|
||||
return fmt.Errorf("%w: MinNiceValue must be less than MaxNiceValue", ErrInvalidPriority)
|
||||
}
|
||||
|
||||
// Validate timeout values
|
||||
if config.MaxValidationTime <= 0 {
|
||||
return fmt.Errorf("%w: MaxValidationTime must be positive", ErrInvalidConfiguration)
|
||||
}
|
||||
if config.RestartDelay <= 0 || config.MaxRestartDelay <= 0 {
|
||||
return fmt.Errorf("%w: RestartDelay and MaxRestartDelay must be positive", ErrInvalidConfiguration)
|
||||
}
|
||||
if config.RestartDelay >= config.MaxRestartDelay {
|
||||
return fmt.Errorf("%w: RestartDelay must be less than MaxRestartDelay", ErrInvalidConfiguration)
|
||||
}
|
||||
|
||||
// Validate valid sample rates array
|
||||
if len(config.ValidSampleRates) == 0 {
|
||||
return fmt.Errorf("%w: ValidSampleRates cannot be empty", ErrInvalidSampleRate)
|
||||
}
|
||||
for _, rate := range config.ValidSampleRates {
|
||||
if rate <= 0 {
|
||||
return fmt.Errorf("%w: all ValidSampleRates must be positive", ErrInvalidSampleRate)
|
||||
}
|
||||
if rate < config.MinSampleRate || rate > config.MaxSampleRate {
|
||||
return fmt.Errorf("%w: ValidSampleRate %d outside range [%d, %d]", ErrInvalidSampleRate, rate, config.MinSampleRate, config.MaxSampleRate)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateResourceLimits checks if system resources are within acceptable limits
|
||||
func ValidateResourceLimits() error {
|
||||
config := GetConfig()
|
||||
|
||||
// Check buffer pool sizes
|
||||
framePoolStats := GetAudioBufferPoolStats()
|
||||
if framePoolStats.FramePoolSize > int64(config.MaxPoolSize*2) {
|
||||
return fmt.Errorf("%w: frame pool size %d exceeds safe limit %d", ErrResourceExhaustion, framePoolStats.FramePoolSize, config.MaxPoolSize*2)
|
||||
}
|
||||
|
||||
// Check zero-copy pool allocation count
|
||||
zeroCopyStats := GetGlobalZeroCopyPoolStats()
|
||||
if zeroCopyStats.AllocationCount > int64(config.MaxPoolSize*3) {
|
||||
return fmt.Errorf("%w: zero-copy allocations %d exceed safe limit %d", ErrResourceExhaustion, zeroCopyStats.AllocationCount, config.MaxPoolSize*3)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateAudioDataIntegrity performs expensive data integrity checks
|
||||
func validateAudioDataIntegrity(data []byte, channels int) error {
|
||||
if len(data)%2 != 0 {
|
||||
return fmt.Errorf("%w: odd number of bytes for 16-bit samples", ErrInvalidSampleFormat)
|
||||
}
|
||||
|
||||
if len(data)%(channels*2) != 0 {
|
||||
return fmt.Errorf("%w: data length %d not aligned to channel count %d", ErrInvalidSampleFormat, len(data), channels)
|
||||
}
|
||||
|
||||
// Check for obvious corruption patterns (all zeros, all max values)
|
||||
sampleCount := len(data) / 2
|
||||
zeroCount := 0
|
||||
maxCount := 0
|
||||
|
||||
for i := 0; i < len(data); i += 2 {
|
||||
sample := int16(data[i]) | int16(data[i+1])<<8
|
||||
switch sample {
|
||||
case 0:
|
||||
zeroCount++
|
||||
case 32767, -32768:
|
||||
maxCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Flag suspicious patterns
|
||||
if zeroCount > sampleCount*9/10 {
|
||||
return fmt.Errorf("%w: %d%% zero samples suggests silence or corruption", ErrFrameDataCorrupted, (zeroCount*100)/sampleCount)
|
||||
}
|
||||
|
||||
if maxCount > sampleCount/10 {
|
||||
return fmt.Errorf("%w: %d%% max-value samples suggests clipping or corruption", ErrFrameDataCorrupted, (maxCount*100)/sampleCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function for absolute value
|
||||
func abs(x int) int {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// getValidationLogger returns a logger for validation operations
|
||||
func getValidationLogger() *zerolog.Logger {
|
||||
// Return a basic logger for validation
|
||||
logger := zerolog.New(nil).With().Timestamp().Logger()
|
||||
return &logger
|
||||
}
|
||||
|
||||
// ErrorContext provides structured error context for better debugging
|
||||
type ErrorContext struct {
|
||||
Component string `json:"component"`
|
||||
Operation string `json:"operation"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
StackTrace []string `json:"stack_trace,omitempty"`
|
||||
}
|
||||
|
||||
// ContextualError wraps an error with additional context
|
||||
type ContextualError struct {
|
||||
Err error `json:"error"`
|
||||
Context ErrorContext `json:"context"`
|
||||
}
|
||||
|
||||
func (ce *ContextualError) Error() string {
|
||||
return fmt.Sprintf("%s [%s:%s]: %v", ce.Context.Component, ce.Context.Operation, ce.Context.Timestamp.Format(time.RFC3339), ce.Err)
|
||||
}
|
||||
|
||||
func (ce *ContextualError) Unwrap() error {
|
||||
return ce.Err
|
||||
}
|
||||
|
||||
// NewContextualError creates a new contextual error with metadata
|
||||
func NewContextualError(err error, component, operation string, metadata map[string]interface{}) *ContextualError {
|
||||
return &ContextualError{
|
||||
Err: err,
|
||||
Context: ErrorContext{
|
||||
Component: component,
|
||||
Operation: operation,
|
||||
Timestamp: time.Now(),
|
||||
Metadata: metadata,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WrapWithContext wraps an error with component and operation context
|
||||
func WrapWithContext(err error, component, operation string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return NewContextualError(err, component, operation, nil)
|
||||
}
|
||||
|
||||
// WrapWithMetadata wraps an error with additional metadata
|
||||
func WrapWithMetadata(err error, component, operation string, metadata map[string]interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return NewContextualError(err, component, operation, metadata)
|
||||
}
|
||||
|
||||
// IsTimeoutError checks if an error is a timeout error
|
||||
func IsTimeoutError(err error) bool {
|
||||
return errors.Is(err, ErrOperationTimeout)
|
||||
}
|
||||
|
||||
// IsResourceError checks if an error is related to resource exhaustion
|
||||
func IsResourceError(err error) bool {
|
||||
return errors.Is(err, ErrResourceExhaustion) || errors.Is(err, ErrMemoryExhaustion) || errors.Is(err, ErrSystemOverload)
|
||||
}
|
||||
|
||||
// IsHardwareError checks if an error is hardware-related
|
||||
func IsHardwareError(err error) bool {
|
||||
return errors.Is(err, ErrHardwareFailure)
|
||||
}
|
||||
|
||||
// IsNetworkError checks if an error is network-related
|
||||
func IsNetworkError(err error) bool {
|
||||
return errors.Is(err, ErrNetworkError)
|
||||
}
|
||||
|
||||
// GetErrorSeverity returns the severity level of an error
|
||||
func GetErrorSeverity(err error) string {
|
||||
if IsHardwareError(err) {
|
||||
return "critical"
|
||||
}
|
||||
if IsResourceError(err) {
|
||||
return "high"
|
||||
}
|
||||
if IsNetworkError(err) || IsTimeoutError(err) {
|
||||
return "medium"
|
||||
}
|
||||
return "low"
|
||||
}
|
|
@ -0,0 +1,556 @@
|
|||
//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) {
|
||||
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
|
||||
func testFrameDataValidation(t *testing.T) {
|
||||
config := GetConfig()
|
||||
|
||||
// Test nil data
|
||||
err := ValidateFrameData(nil)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "frame data is nil")
|
||||
|
||||
// Test empty data
|
||||
err = ValidateFrameData([]byte{})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "frame data is empty")
|
||||
|
||||
// Test data below minimum size
|
||||
if config.MinFrameSize > 0 {
|
||||
smallData := make([]byte, config.MinFrameSize-1)
|
||||
err = ValidateFrameData(smallData)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "below minimum")
|
||||
}
|
||||
|
||||
// Test data above maximum size
|
||||
largeData := make([]byte, config.MaxAudioFrameSize+1)
|
||||
err = ValidateFrameData(largeData)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "exceeds maximum")
|
||||
|
||||
// Test unaligned data (odd number of bytes for 16-bit samples)
|
||||
oddData := make([]byte, 1001) // Odd number
|
||||
if len(oddData) >= config.MinFrameSize {
|
||||
err = ValidateFrameData(oddData)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not aligned")
|
||||
}
|
||||
|
||||
// Test valid aligned data
|
||||
validData := make([]byte, 1000) // Even number, within bounds
|
||||
if len(validData) >= config.MinFrameSize && len(validData) <= config.MaxAudioFrameSize {
|
||||
err = ValidateFrameData(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 := ValidateAudioFrameFast([]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 = ValidateAudioFrameFast(largeData)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "exceeds maximum")
|
||||
|
||||
// Test valid data
|
||||
validData := make([]byte, 1000)
|
||||
err = ValidateAudioFrameFast(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")
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
Loading…
Reference in New Issue