mirror of https://github.com/jetkvm/kvm.git
330 lines
8.6 KiB
Go
330 lines
8.6 KiB
Go
//go:build arm && linux
|
|
|
|
package usbgadget
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// Hardware integration tests for USB gadget operations
|
|
// These tests perform real hardware operations with proper cleanup and timeout handling
|
|
|
|
var (
|
|
testConfig = &Config{
|
|
VendorId: "0x1d6b", // The Linux Foundation
|
|
ProductId: "0x0104", // Multifunction Composite Gadget
|
|
SerialNumber: "",
|
|
Manufacturer: "JetKVM",
|
|
Product: "USB Emulation Device",
|
|
strictMode: false, // Disable strict mode for hardware tests
|
|
}
|
|
testDevices = &Devices{
|
|
AbsoluteMouse: true,
|
|
RelativeMouse: true,
|
|
Keyboard: true,
|
|
MassStorage: true,
|
|
}
|
|
testGadgetName = "jetkvm-test"
|
|
)
|
|
|
|
func TestUsbGadgetHardwareInit(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping hardware test in short mode")
|
|
}
|
|
|
|
// Create context with timeout to prevent hanging
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Ensure clean state before test
|
|
cleanupUsbGadget(t, testGadgetName)
|
|
|
|
// Test USB gadget initialization with timeout
|
|
var gadget *UsbGadget
|
|
done := make(chan bool, 1)
|
|
var initErr error
|
|
|
|
go func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Logf("USB gadget initialization panicked: %v", r)
|
|
initErr = assert.AnError
|
|
}
|
|
done <- true
|
|
}()
|
|
|
|
gadget = NewUsbGadget(testGadgetName, testDevices, testConfig, nil)
|
|
if gadget == nil {
|
|
initErr = assert.AnError
|
|
}
|
|
}()
|
|
|
|
// Wait for initialization or timeout
|
|
select {
|
|
case <-done:
|
|
if initErr != nil {
|
|
t.Fatalf("USB gadget initialization failed: %v", initErr)
|
|
}
|
|
assert.NotNil(t, gadget, "USB gadget should be initialized")
|
|
case <-ctx.Done():
|
|
t.Fatal("USB gadget initialization timed out")
|
|
}
|
|
|
|
// Cleanup after test
|
|
defer func() {
|
|
if gadget != nil {
|
|
gadget.CloseHidFiles()
|
|
}
|
|
cleanupUsbGadget(t, testGadgetName)
|
|
}()
|
|
|
|
// Validate gadget state
|
|
assert.NotNil(t, gadget, "USB gadget should not be nil")
|
|
|
|
// Test UDC binding state
|
|
bound, err := gadget.IsUDCBound()
|
|
assert.NoError(t, err, "Should be able to check UDC binding state")
|
|
t.Logf("UDC bound state: %v", bound)
|
|
}
|
|
|
|
func TestUsbGadgetHardwareReconfiguration(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping hardware test in short mode")
|
|
}
|
|
|
|
// Create context with timeout
|
|
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
|
defer cancel()
|
|
|
|
// Ensure clean state
|
|
cleanupUsbGadget(t, testGadgetName)
|
|
|
|
// Initialize first gadget
|
|
gadget1 := createUsbGadgetWithTimeout(t, ctx, testGadgetName, testDevices, testConfig)
|
|
defer func() {
|
|
if gadget1 != nil {
|
|
gadget1.CloseHidFiles()
|
|
}
|
|
}()
|
|
|
|
// Validate initial state
|
|
assert.NotNil(t, gadget1, "First USB gadget should be initialized")
|
|
|
|
// Close first gadget properly
|
|
gadget1.CloseHidFiles()
|
|
gadget1 = nil
|
|
|
|
// Wait for cleanup to complete
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// Test reconfiguration with different report descriptor
|
|
altGadgetConfig := make(map[string]gadgetConfigItem)
|
|
for k, v := range defaultGadgetConfig {
|
|
altGadgetConfig[k] = v
|
|
}
|
|
|
|
// Modify absolute mouse configuration
|
|
oldAbsoluteMouseConfig := altGadgetConfig["absolute_mouse"]
|
|
oldAbsoluteMouseConfig.reportDesc = absoluteMouseCombinedReportDesc
|
|
altGadgetConfig["absolute_mouse"] = oldAbsoluteMouseConfig
|
|
|
|
// Create second gadget with modified configuration
|
|
gadget2 := createUsbGadgetWithTimeoutAndConfig(t, ctx, testGadgetName, altGadgetConfig, testDevices, testConfig)
|
|
defer func() {
|
|
if gadget2 != nil {
|
|
gadget2.CloseHidFiles()
|
|
}
|
|
cleanupUsbGadget(t, testGadgetName)
|
|
}()
|
|
|
|
assert.NotNil(t, gadget2, "Second USB gadget should be initialized")
|
|
|
|
// Validate UDC binding after reconfiguration
|
|
udcs := getUdcs()
|
|
assert.NotEmpty(t, udcs, "Should have at least one UDC")
|
|
|
|
if len(udcs) > 0 {
|
|
udc := udcs[0]
|
|
t.Logf("Available UDC: %s", udc)
|
|
|
|
// Check UDC binding state
|
|
udcStr, err := os.ReadFile("/sys/kernel/config/usb_gadget/" + testGadgetName + "/UDC")
|
|
if err == nil {
|
|
t.Logf("UDC binding: %s", strings.TrimSpace(string(udcStr)))
|
|
} else {
|
|
t.Logf("Could not read UDC binding: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUsbGadgetHardwareStressTest(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping stress test in short mode")
|
|
}
|
|
|
|
// Create context with longer timeout for stress test
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
defer cancel()
|
|
|
|
// Ensure clean state
|
|
cleanupUsbGadget(t, testGadgetName)
|
|
|
|
// Perform multiple rapid reconfigurations
|
|
for i := 0; i < 3; i++ {
|
|
t.Logf("Stress test iteration %d", i+1)
|
|
|
|
// Create gadget
|
|
gadget := createUsbGadgetWithTimeout(t, ctx, testGadgetName, testDevices, testConfig)
|
|
if gadget == nil {
|
|
t.Fatalf("Failed to create USB gadget in iteration %d", i+1)
|
|
}
|
|
|
|
// Validate gadget
|
|
assert.NotNil(t, gadget, "USB gadget should be created in iteration %d", i+1)
|
|
|
|
// Test basic operations
|
|
bound, err := gadget.IsUDCBound()
|
|
assert.NoError(t, err, "Should be able to check UDC state in iteration %d", i+1)
|
|
t.Logf("Iteration %d: UDC bound = %v", i+1, bound)
|
|
|
|
// Cleanup
|
|
gadget.CloseHidFiles()
|
|
gadget = nil
|
|
|
|
// Wait between iterations
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// Check for timeout
|
|
select {
|
|
case <-ctx.Done():
|
|
t.Fatal("Stress test timed out")
|
|
default:
|
|
// Continue
|
|
}
|
|
}
|
|
|
|
// Final cleanup
|
|
cleanupUsbGadget(t, testGadgetName)
|
|
}
|
|
|
|
// Helper functions for hardware tests
|
|
|
|
// createUsbGadgetWithTimeout creates a USB gadget with timeout protection
|
|
func createUsbGadgetWithTimeout(t *testing.T, ctx context.Context, name string, devices *Devices, config *Config) *UsbGadget {
|
|
return createUsbGadgetWithTimeoutAndConfig(t, ctx, name, defaultGadgetConfig, devices, config)
|
|
}
|
|
|
|
// createUsbGadgetWithTimeoutAndConfig creates a USB gadget with custom config and timeout protection
|
|
func createUsbGadgetWithTimeoutAndConfig(t *testing.T, ctx context.Context, name string, gadgetConfig map[string]gadgetConfigItem, devices *Devices, config *Config) *UsbGadget {
|
|
var gadget *UsbGadget
|
|
done := make(chan bool, 1)
|
|
var createErr error
|
|
|
|
go func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Logf("USB gadget creation panicked: %v", r)
|
|
createErr = assert.AnError
|
|
}
|
|
done <- true
|
|
}()
|
|
|
|
gadget = newUsbGadget(name, gadgetConfig, devices, config, nil)
|
|
if gadget == nil {
|
|
createErr = assert.AnError
|
|
}
|
|
}()
|
|
|
|
// Wait for creation or timeout
|
|
select {
|
|
case <-done:
|
|
if createErr != nil {
|
|
t.Logf("USB gadget creation failed: %v", createErr)
|
|
return nil
|
|
}
|
|
return gadget
|
|
case <-ctx.Done():
|
|
t.Logf("USB gadget creation timed out")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// cleanupUsbGadget ensures clean state by removing any existing USB gadget configuration
|
|
func cleanupUsbGadget(t *testing.T, name string) {
|
|
t.Logf("Cleaning up USB gadget: %s", name)
|
|
|
|
// Try to unbind UDC first
|
|
udcPath := "/sys/kernel/config/usb_gadget/" + name + "/UDC"
|
|
if _, err := os.Stat(udcPath); err == nil {
|
|
// Read current UDC binding
|
|
if udcData, err := os.ReadFile(udcPath); err == nil && len(strings.TrimSpace(string(udcData))) > 0 {
|
|
// Unbind UDC
|
|
if err := os.WriteFile(udcPath, []byte(""), 0644); err != nil {
|
|
t.Logf("Failed to unbind UDC: %v", err)
|
|
} else {
|
|
t.Logf("Successfully unbound UDC")
|
|
// Wait for unbinding to complete
|
|
time.Sleep(200 * time.Millisecond)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove gadget directory if it exists
|
|
gadgetPath := "/sys/kernel/config/usb_gadget/" + name
|
|
if _, err := os.Stat(gadgetPath); err == nil {
|
|
// Try to remove configuration links first
|
|
configPath := gadgetPath + "/configs/c.1"
|
|
if entries, err := os.ReadDir(configPath); err == nil {
|
|
for _, entry := range entries {
|
|
if entry.Type()&os.ModeSymlink != 0 {
|
|
linkPath := configPath + "/" + entry.Name()
|
|
if err := os.Remove(linkPath); err != nil {
|
|
t.Logf("Failed to remove config link %s: %v", linkPath, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the gadget directory (this should cascade remove everything)
|
|
if err := os.RemoveAll(gadgetPath); err != nil {
|
|
t.Logf("Failed to remove gadget directory: %v", err)
|
|
} else {
|
|
t.Logf("Successfully removed gadget directory")
|
|
}
|
|
}
|
|
|
|
// Wait for cleanup to complete
|
|
time.Sleep(300 * time.Millisecond)
|
|
}
|
|
|
|
// validateHardwareState checks the current hardware state
|
|
func validateHardwareState(t *testing.T, gadget *UsbGadget) {
|
|
if gadget == nil {
|
|
return
|
|
}
|
|
|
|
// Check UDC binding state
|
|
bound, err := gadget.IsUDCBound()
|
|
if err != nil {
|
|
t.Logf("Warning: Could not check UDC binding state: %v", err)
|
|
} else {
|
|
t.Logf("UDC bound: %v", bound)
|
|
}
|
|
|
|
// Check available UDCs
|
|
udcs := getUdcs()
|
|
t.Logf("Available UDCs: %v", udcs)
|
|
|
|
// Check configfs mount
|
|
if _, err := os.Stat("/sys/kernel/config"); err != nil {
|
|
t.Logf("Warning: configfs not available: %v", err)
|
|
} else {
|
|
t.Logf("configfs is available")
|
|
}
|
|
} |