tests: hotplug API and virtual-device hotplug scenarios - #826
Conversation
Two new test tiers for the hotplug API: - test_hotplug_api.c (HotplugAPI_<backend>): argument validation, callback-handle properties, implicit init, hid_exit() teardown and register/deregister thread churn. Needs no device or privileges, so it runs against every backend in the ordinary CI matrix. - test_hotplug.c (Hotplug_<backend>): device-backed hotplug scenarios (async delivery, exactly-once ENUMERATE pass, callback-return deregistration, pass-before-live ordering, ARRIVED/LEFT payloads, filtering, dispatch order, deregistration post-condition and re-entrant registration) against a virtual device whose presence is toggled with the new test_virtual_device_unplug()/_replug() calls. Implemented for the uhid provider (UHID_DESTROY/UHID_CREATE2 on the same fd); the other providers return TEST_VDEV_UNAVAILABLE and their hotplug tests self-skip until presence toggling is implemented. Assisted-by: claude-code:claude-fable-5
Make the tier-2 Hotplug_<backend> test drive real plug/unplug on two more providers so it exercises the async backends instead of self-skipping. libusb (raw_gadget VM): the provider now unbinds/rebinds the gadget from the dummy UDC (close the raw-gadget fd to disconnect; reopen + INIT + RUN to reconnect), producing real libusb LEFT/ARRIVED events. Windows (vhidmini UMDF driver): the provider locates the root devnode by its INF hardware id (the instance path is PnP-derived from the setup class, not knowable a priori) and toggles presence by disabling/enabling the child HID PDO, so the UMDF host stays up and the HID interface cleanly drops and returns. The device identity is aligned with the static driver (primary PID 0x9001 and serial "HIDAPI-HOTPLUG-TEST" on Windows; other platforms keep 0x9002), and create() reports UNAVAILABLE for a device that is not present so the second-device sub-test skips. cfgmgr32 is linked for the winapi provider targets. Both run only under the ci-virtual-device label (raw_gadget VM / installed driver) and skip cleanly elsewhere. Assisted-by: claude-code:claude-opus-4-8
Full implementation review (fresh pass, head
|
Publish T15's results before the flag the main thread waits on, relax the handle-uniqueness assertion to match the documented contract, make the rawgadget teardown signal both workers until both exit and guard rg_plug against a redundant plug, re-enable a left-disabled HID child before the Windows provider's presence probe, and refresh the docs that still said only uhid can toggle presence. Assisted-by: claude-code:claude-opus-5
The already-plugged guard added in the previous commit mistook the calloc'd fd 0 for an open gadget fd, so the first plug closed the process's stdin; initialise fd to -1 before rg_plug() sees it. Also set ep0_exited on ep0_thread_fn's allocation-failure return, so teardown does not spend its whole signalling budget on a thread that already left. Assisted-by: claude-code:claude-opus-5
Re-review of fix commits
|
Adds the test suite for the hotplug API (#764 / connection-callback), in two tiers, built on the virtual-device test harness from #815.
What is in here
src/tests/test_hotplug_api.c— tier 1,HotplugAPI_<backend>: everything in the hotplug contract that is observable without a device event (argument validation, callback-handle properties, implicithid_init(),hid_exit()teardown incl. a register→immediate-hid_exitstress loop, and a 2-thread register/deregister churn). Needs no device, no privileges: registered for all four backends and runs in the ordinary per-push CI matrix. Self-skips (77) when a backend reports hotplug as unsupported at runtime (e.g. libusb withoutLIBUSB_CAP_HAS_HOTPLUG).src/tests/test_hotplug.c— tier 2,Hotplug_<backend>: device-backed scenarios: async delivery, exactly-once ENUMERATE pass, callback-return deregistration (live + mid-pass), pass-before-live ordering, ARRIVED/LEFT payloads and path correlation, LEFT-without-ENUMERATE, VID/PID filtering, registration-order dispatch, the deregistration post-condition, and register/deregister from within a callback.test_virtual_device_unplug()/test_virtual_device_replug()— new provider calls that toggle device presence without destroying the provider context. Implemented for the uhid provider (UHID_DESTROY/UHID_CREATE2on the same fd); the rawgadget/vhidmini2/IOHIDUserDevice providers returnTEST_VDEV_UNAVAILABLEfor now, so theirHotplug_*tests self-skip everywhere (they compile and are registered, ready for when toggling is implemented).The tier-2 test follows a strict synchronization discipline (see
src/tests/README.md): callbacks only deep-copy events into a lock-protected log; every expectation is a deadline-based predicate poll; absence assertions sit behind event barriers, not time windows; no retries.Where each test runs
HotplugAPI_hidraw,HotplugAPI_libusbbuilds.ymlubuntu-cmakeHotplugAPI_winapibuilds.ymlwindows-cmake (MSVC, NMake, ClangCL, MinGW)HotplugAPI_darwinbuilds.ymlmacos-cmakeHotplug_hidrawbuilds.ymlubuntu-cmake (viamodprobe uhid+sudo ctest)Hotplug_libusblibusb-vhid-testjob) until rawgadget unplug/replug existsHotplug_winapiwin-vhid-testjob) until driver-side presence toggling existsHotplug_darwinDeviceIO_darwinThe behavioral expectations encode the async-ENUMERATE contract, i.e. they require #822, #823, #824 and #825. On
connection-callbackas-is, tier 1 fails (e.g.*callback_handleis not zeroed on a failed registration yet), so this PR's CI will be red until those four PRs land — that is expected and is the reason this PR is a draft.Validation ahead of that: an integration branch (
hotplug-integration=connection-callback+ all four backend PRs + these tests) runs the same CI; there,HotplugAPI_hidraw/HotplugAPI_libusbpass in a local WSL run of both backends andHotplugAPI_winapipasses locally on Windows/MSVC.Relates to: #764
Assisted-by: claude-code:claude-fable-5