kvm/internal/usbgadget/usbgadget_logic_test.go

438 lines
9.8 KiB
Go

package usbgadget
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// Unit tests for USB gadget configuration logic without hardware dependencies
// These tests follow the pattern of audio tests - testing business logic and validation
func TestUsbGadgetConfigValidation(t *testing.T) {
tests := []struct {
name string
config *Config
devices *Devices
expected bool
}{
{
name: "ValidConfig",
config: &Config{
VendorId: "0x1d6b",
ProductId: "0x0104",
Manufacturer: "JetKVM",
Product: "USB Emulation Device",
},
devices: &Devices{
Keyboard: true,
AbsoluteMouse: true,
RelativeMouse: true,
MassStorage: true,
},
expected: true,
},
{
name: "InvalidVendorId",
config: &Config{
VendorId: "invalid",
ProductId: "0x0104",
Manufacturer: "JetKVM",
Product: "USB Emulation Device",
},
devices: &Devices{
Keyboard: true,
},
expected: false,
},
{
name: "EmptyManufacturer",
config: &Config{
VendorId: "0x1d6b",
ProductId: "0x0104",
Manufacturer: "",
Product: "USB Emulation Device",
},
devices: &Devices{
Keyboard: true,
},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateUsbGadgetConfiguration(tt.config, tt.devices)
if tt.expected {
assert.NoError(t, err, "Configuration should be valid")
} else {
assert.Error(t, err, "Configuration should be invalid")
}
})
}
}
func TestUsbGadgetDeviceConfiguration(t *testing.T) {
tests := []struct {
name string
devices *Devices
expectedConfigs []string
}{
{
name: "AllDevicesEnabled",
devices: &Devices{
Keyboard: true,
AbsoluteMouse: true,
RelativeMouse: true,
MassStorage: true,
Audio: true,
},
expectedConfigs: []string{"keyboard", "absolute_mouse", "relative_mouse", "mass_storage_base", "audio"},
},
{
name: "OnlyKeyboard",
devices: &Devices{
Keyboard: true,
},
expectedConfigs: []string{"keyboard"},
},
{
name: "MouseOnly",
devices: &Devices{
AbsoluteMouse: true,
RelativeMouse: true,
},
expectedConfigs: []string{"absolute_mouse", "relative_mouse"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
configs := getEnabledGadgetConfigs(tt.devices)
assert.ElementsMatch(t, tt.expectedConfigs, configs, "Enabled configs should match expected")
})
}
}
func TestUsbGadgetStateTransition(t *testing.T) {
if testing.Short() {
t.Skip("Skipping state transition test in short mode")
}
tests := []struct {
name string
initialDevices *Devices
newDevices *Devices
expectedTransition string
}{
{
name: "EnableAudio",
initialDevices: &Devices{
Keyboard: true,
AbsoluteMouse: true,
Audio: false,
},
newDevices: &Devices{
Keyboard: true,
AbsoluteMouse: true,
Audio: true,
},
expectedTransition: "audio_enabled",
},
{
name: "DisableKeyboard",
initialDevices: &Devices{
Keyboard: true,
AbsoluteMouse: true,
},
newDevices: &Devices{
Keyboard: false,
AbsoluteMouse: true,
},
expectedTransition: "keyboard_disabled",
},
{
name: "NoChange",
initialDevices: &Devices{
Keyboard: true,
AbsoluteMouse: true,
},
newDevices: &Devices{
Keyboard: true,
AbsoluteMouse: true,
},
expectedTransition: "no_change",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
transition := simulateUsbGadgetStateTransition(ctx, tt.initialDevices, tt.newDevices)
assert.Equal(t, tt.expectedTransition, transition, "State transition should match expected")
})
}
}
func TestUsbGadgetConfigurationTimeout(t *testing.T) {
if testing.Short() {
t.Skip("Skipping timeout test in short mode")
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
// Test that configuration validation completes within reasonable time
start := time.Now()
// Simulate multiple rapid configuration changes
for i := 0; i < 20; i++ {
devices := &Devices{
Keyboard: i%2 == 0,
AbsoluteMouse: i%3 == 0,
RelativeMouse: i%4 == 0,
MassStorage: i%5 == 0,
Audio: i%6 == 0,
}
config := &Config{
VendorId: "0x1d6b",
ProductId: "0x0104",
Manufacturer: "JetKVM",
Product: "USB Emulation Device",
}
err := validateUsbGadgetConfiguration(config, devices)
assert.NoError(t, err, "Configuration validation should not fail")
// Ensure we don't timeout
select {
case <-ctx.Done():
t.Fatal("USB gadget configuration test timed out")
default:
// Continue
}
}
elapsed := time.Since(start)
t.Logf("USB gadget configuration test completed in %v", elapsed)
assert.Less(t, elapsed, 2*time.Second, "Configuration validation should complete quickly")
}
func TestReportDescriptorValidation(t *testing.T) {
tests := []struct {
name string
reportDesc []byte
expected bool
}{
{
name: "ValidKeyboardReportDesc",
reportDesc: keyboardReportDesc,
expected: true,
},
{
name: "ValidAbsoluteMouseReportDesc",
reportDesc: absoluteMouseCombinedReportDesc,
expected: true,
},
{
name: "ValidRelativeMouseReportDesc",
reportDesc: relativeMouseCombinedReportDesc,
expected: true,
},
{
name: "EmptyReportDesc",
reportDesc: []byte{},
expected: false,
},
{
name: "InvalidReportDesc",
reportDesc: []byte{0xFF, 0xFF, 0xFF},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateReportDescriptor(tt.reportDesc)
if tt.expected {
assert.NoError(t, err, "Report descriptor should be valid")
} else {
assert.Error(t, err, "Report descriptor should be invalid")
}
})
}
}
// Helper functions for simulation (similar to audio tests)
// validateUsbGadgetConfiguration simulates the validation that happens in production
func validateUsbGadgetConfiguration(config *Config, devices *Devices) error {
if config == nil {
return assert.AnError
}
// Validate vendor ID format
if config.VendorId == "" || len(config.VendorId) < 4 {
return assert.AnError
}
if config.VendorId != "" && config.VendorId[:2] != "0x" {
return assert.AnError
}
// Validate product ID format
if config.ProductId == "" || len(config.ProductId) < 4 {
return assert.AnError
}
if config.ProductId != "" && config.ProductId[:2] != "0x" {
return assert.AnError
}
// Validate required fields
if config.Manufacturer == "" {
return assert.AnError
}
if config.Product == "" {
return assert.AnError
}
// Note: Allow configurations with no devices enabled for testing purposes
// In production, this would typically be validated at a higher level
return nil
}
// getEnabledGadgetConfigs returns the list of enabled gadget configurations
func getEnabledGadgetConfigs(devices *Devices) []string {
var configs []string
if devices.Keyboard {
configs = append(configs, "keyboard")
}
if devices.AbsoluteMouse {
configs = append(configs, "absolute_mouse")
}
if devices.RelativeMouse {
configs = append(configs, "relative_mouse")
}
if devices.MassStorage {
configs = append(configs, "mass_storage_base")
}
if devices.Audio {
configs = append(configs, "audio")
}
return configs
}
// simulateUsbGadgetStateTransition simulates the state management during USB reconfiguration
func simulateUsbGadgetStateTransition(ctx context.Context, initial, new *Devices) string {
// Check for audio changes
if initial.Audio != new.Audio {
if new.Audio {
// Simulate enabling audio device
time.Sleep(5 * time.Millisecond)
return "audio_enabled"
} else {
// Simulate disabling audio device
time.Sleep(5 * time.Millisecond)
return "audio_disabled"
}
}
// Check for keyboard changes
if initial.Keyboard != new.Keyboard {
if new.Keyboard {
time.Sleep(5 * time.Millisecond)
return "keyboard_enabled"
} else {
time.Sleep(5 * time.Millisecond)
return "keyboard_disabled"
}
}
// Check for mouse changes
if initial.AbsoluteMouse != new.AbsoluteMouse || initial.RelativeMouse != new.RelativeMouse {
time.Sleep(5 * time.Millisecond)
return "mouse_changed"
}
// Check for mass storage changes
if initial.MassStorage != new.MassStorage {
time.Sleep(5 * time.Millisecond)
return "mass_storage_changed"
}
return "no_change"
}
// validateReportDescriptor simulates HID report descriptor validation
func validateReportDescriptor(reportDesc []byte) error {
if len(reportDesc) == 0 {
return assert.AnError
}
// Basic HID report descriptor validation
// Check for valid usage page (0x05)
found := false
for i := 0; i < len(reportDesc)-1; i++ {
if reportDesc[i] == 0x05 {
found = true
break
}
}
if !found {
return assert.AnError
}
return nil
}
// Benchmark tests
func BenchmarkValidateUsbGadgetConfiguration(b *testing.B) {
config := &Config{
VendorId: "0x1d6b",
ProductId: "0x0104",
Manufacturer: "JetKVM",
Product: "USB Emulation Device",
}
devices := &Devices{
Keyboard: true,
AbsoluteMouse: true,
RelativeMouse: true,
MassStorage: true,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = validateUsbGadgetConfiguration(config, devices)
}
}
func BenchmarkGetEnabledGadgetConfigs(b *testing.B) {
devices := &Devices{
Keyboard: true,
AbsoluteMouse: true,
RelativeMouse: true,
MassStorage: true,
Audio: true,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = getEnabledGadgetConfigs(devices)
}
}
func BenchmarkValidateReportDescriptor(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = validateReportDescriptor(keyboardReportDesc)
}
}