Files
sing-box/service/usbip/client_assignment_test.go
T
世界 cec39eb00c usbip: unify platform services behind host interfaces
Collapse ServerService and ClientService to one definition each by
hiding Linux vs Darwin behind ExportHost, ImportHost, and Export
seams. Along the way, extract three state machines that previously
lived as scattered fields on the service structs:

- LeaseManager owns its own mutex and closes the
  availability-vs-insert TOCTOU by checking export busy inside Issue
  under the same lock as the insert.
- DataSession gives the three per-import data-plane implementations
  (Linux kernel handoff, Darwin server data session, Darwin virtual
  controller) a uniform Done/Err/Close interface.
- clientAssignment encapsulates the matched/import-all target state
  and exposes ApplyMatched/ApplyAll diffs to ClientService, which
  keeps worker goroutine lifecycle.

Service busy tracking moves off the per-platform serverExport struct
onto ServerService.busy, since it follows the lease/import lifecycle
rather than physical claim/release. linux_test.go is migrated to
construct ServerService and ClientService through the new host
interfaces.
2026-06-09 10:42:26 +08:00

130 lines
3.8 KiB
Go

//go:build linux || (darwin && cgo)
package usbip
import (
"sort"
"testing"
"github.com/sagernet/sing-box/option"
"github.com/stretchr/testify/require"
)
func TestClientAssignmentApplyAllEmitsStartForNewDesired(t *testing.T) {
t.Parallel()
assignment := newClientAssignment(nil)
entries := []DeviceEntry{standardTestDeviceEntry("1-1"), standardTestDeviceEntry("2-1")}
start, stop := assignment.ApplyAll(entries)
sort.Strings(start)
require.Equal(t, []string{"1-1", "2-1"}, start)
require.Empty(t, stop)
require.True(t, assignment.IsRetryDesired("1-1"))
require.True(t, assignment.IsRetryDesired("2-1"))
}
func TestClientAssignmentApplyAllEmitsStopForUndesiredInactive(t *testing.T) {
t.Parallel()
assignment := newClientAssignment(nil)
entries := []DeviceEntry{standardTestDeviceEntry("1-1"), standardTestDeviceEntry("2-1")}
_, _ = assignment.ApplyAll(entries)
start, stop := assignment.ApplyAll(nil)
require.Empty(t, start)
sort.Strings(stop)
require.Equal(t, []string{"1-1", "2-1"}, stop)
require.False(t, assignment.IsRetryDesired("1-1"))
}
func TestClientAssignmentApplyAllKeepsActiveButUndesiredRegistered(t *testing.T) {
t.Parallel()
assignment := newClientAssignment(nil)
entries := []DeviceEntry{standardTestDeviceEntry("1-1")}
_, _ = assignment.ApplyAll(entries)
assignment.SetActive("1-1", true)
start, stop := assignment.ApplyAll(nil)
require.Empty(t, start)
require.Empty(t, stop)
// Worker still registered; once it becomes inactive, the next Apply
// will emit it as stop.
require.False(t, assignment.IsRetryDesired("1-1"))
assignment.SetActive("1-1", false)
_, stop = assignment.ApplyAll(nil)
require.Equal(t, []string{"1-1"}, stop)
}
func TestClientAssignmentApplyMatchedAssignsFixedBusID(t *testing.T) {
t.Parallel()
assignment := newClientAssignment([]option.USBIPDeviceMatch{{BusID: "1-1"}})
entries := []DeviceEntry{standardTestDeviceEntry("1-1")}
next, prev := assignment.ApplyMatched(entries, nil)
require.Equal(t, []string{"1-1"}, next)
require.Equal(t, []string{""}, prev)
}
func TestClientAssignmentApplyMatchedReassignsHigherPriorityWhenDeviceReturns(t *testing.T) {
t.Parallel()
matches := []option.USBIPDeviceMatch{
{VendorID: 0x1d6b, ProductID: 0x0002},
{VendorID: 0x1d6b, ProductID: 0x0002},
}
assignment := newClientAssignment(matches)
entries := []DeviceEntry{
standardTestDeviceEntry("1-1"),
standardTestDeviceEntry("2-1"),
}
next, _ := assignment.ApplyMatched(entries, nil)
sort.Strings(next)
require.Equal(t, []string{"1-1", "2-1"}, next)
// Drop the first device. Its slot is vacated; the second worker
// keeps its busid since target 1 still matches 2-1.
missing := []DeviceEntry{standardTestDeviceEntry("2-1")}
next, prev := assignment.ApplyMatched(missing, nil)
require.Equal(t, []string{"", "2-1"}, next)
sort.Strings(prev)
require.Equal(t, []string{"1-1", "2-1"}, prev)
// Device returns and fills the empty slot.
next, prev = assignment.ApplyMatched(entries, nil)
require.Equal(t, []string{"1-1", "2-1"}, next)
require.Equal(t, []string{"", "2-1"}, prev)
}
func TestClientAssignmentDropRegisteredRemovesBusID(t *testing.T) {
t.Parallel()
assignment := newClientAssignment(nil)
_, _ = assignment.ApplyAll([]DeviceEntry{standardTestDeviceEntry("1-1")})
require.True(t, assignment.IsRetryDesired("1-1"))
assignment.DropRegistered("1-1")
require.False(t, assignment.IsRetryDesired("1-1"))
}
func TestClientAssignmentClearRegisteredClearsRegistry(t *testing.T) {
t.Parallel()
assignment := newClientAssignment(nil)
_, _ = assignment.ApplyAll([]DeviceEntry{
standardTestDeviceEntry("1-1"),
standardTestDeviceEntry("2-1"),
})
require.True(t, assignment.IsRetryDesired("1-1"))
require.True(t, assignment.IsRetryDesired("2-1"))
assignment.ClearRegistered()
require.False(t, assignment.IsRetryDesired("1-1"))
require.False(t, assignment.IsRetryDesired("2-1"))
}