Skip to content

tests: virtual HID device test harness for all four backends (hidraw, libusb, winapi, darwin)#815

Merged
Youw merged 9 commits into
masterfrom
virtual-device-tests
Jul 13, 2026
Merged

tests: virtual HID device test harness for all four backends (hidraw, libusb, winapi, darwin)#815
Youw merged 9 commits into
masterfrom
virtual-device-tests

Conversation

@Youw

@Youw Youw commented Jun 7, 2026

Copy link
Copy Markdown
Member

Summary

A reusable virtual HID device test harness: HIDAPI tests run against a virtual device, so they need no physical hardware. Tests are written purely against the public HIDAPI API plus a small backend-agnostic test_virtual_device interface, so the same test runs on every backend that ships a provider.

Each virtual device carries a few pre-recorded scenarios: the test sends a Feature report whose first payload byte is a TEST_VDEV_CMD_* command, and the device replays the matching canned input report. All device behaviour lives in the per-backend provider; the test code is 100% platform-neutral. Further tests can build on this harness (e.g. the hid_read_interrupt test in #799).

What's here

  • src/tests/test_device_io.c — a device-I/O smoke test: open → write an output report → trigger + read back two canned input reports → close. Exits with CTest's skip code (77) when no virtual device can be created, so ordinary builds on any host stay green.
  • src/tests/test_virtual_device.h — the shared contract (report size, commands, canned payloads); test_platform.h — portable thread/timing helpers for tests.
  • Four virtual-device providers:
Platform / backend Provider Mechanism CI
Linux / hidraw test_virtual_device_uhid.c kernel /dev/uhid runs + passes every push (ubuntu-cmake)
Linux / libusb test_virtual_device_rawgadget.c /dev/raw-gadget + dummy_hcd, inside a VM self-skips per push; runs + passes in libusb-vhid-test
Windows / winapi test_virtual_device_win.c + src/tests/windows/driver/ modified vhidmini2 UMDF2 driver self-skips per push; runs + passes in win-vhid-test
macOS / darwin test_virtual_device_mac.c IOHIDUserDevice (IOKit) self-skips per push (macos-cmake); runs on a real Mac
  • -DHIDAPI_WITH_TESTS=ON (offered on Linux/Windows/macOS, default OFF) builds src/tests for whichever backends are present. Requires CMake ≥ 3.16 for correct skip reporting (older CMake gets a warning and no tests).

CI

  • Per-push (builds.yml): the test is built on all platforms and run where possible. ubuntu-cmake loads uhid and runs DeviceIO_hidraw for real; DeviceIO_libusb, Windows and macOS build the test and self-skip — the matrix stays green everywhere.
  • win-vhid-test.yml: builds and self-signs the vhidmini2 driver on windows-2022 (whose MSBuild matches the cached WDK's driver build tasks), then installs it via devcon on windows-latest and runs DeviceIO_winapi against the real device. ✅ passing.
  • libusb-vhid-test.yml: the hosted kernel has no USB gadget subsystem, so DeviceIO_libusb runs inside a lightweight virtme-ng + QEMU VM booting a generic Ubuntu kernel (with raw_gadget loaded and dummy_hcd built out-of-tree), against a real virtual USB device. ✅ passing. The same approach works locally and on WSL2.

The two privileged jobs run via workflow_dispatch or automatically on a pull request carrying the ci-virtual-device label, keeping them out of the per-push matrix.

Licensing

src/tests/windows/driver/ vendors a modified Microsoft vhidmini2 UMDF2 sample under its original MS-PL license — a complete LICENSE.txt plus a README.md documenting provenance and modifications live in that directory. It is a standalone test fixture (built, installed and removed out-of-band by CI; never linked into or shipped with the library), separate from HIDAPI's own licensing. Downstream projects consuming HIDAPI as a subproject can simply not enable the tests.

Platforms without a provider

FreeBSD/NetBSD/OpenBSD have no userspace facility to create a virtual HID/USB device, so there is deliberately no provider for them — src/tests/README.md documents the details. Running the macOS provider on a real Mac (Apple Developer account, com.apple.developer.hid.virtual.device entitlement, Accessibility consent) is documented there as well.

Assisted-by: claude-code:claude-opus-4-8
Assisted-by: claude-code:claude-fable-5

@Youw Youw added the ci-virtual-device Run the virtual HID device CI jobs (Windows driver + libusb raw-gadget) label Jun 7, 2026
@mcuee

mcuee commented Jun 7, 2026

Copy link
Copy Markdown
Member

Great. We need this kind of virtual test device for hidapi (and also libusb).

@Youw Youw force-pushed the virtual-device-tests branch from 1cdc8d5 to b330600 Compare June 7, 2026 11:54
@mcuee

mcuee commented Jun 7, 2026

Copy link
Copy Markdown
Member

@Youw

FYI on WDK, the Windows runners have WDK installed already.

Nuget WDK -- smaller but lacking some tools, which may be okay for this purpose
Built-in WDK -- good enough
EWDK -- great for local build of libusb-win32/libusbK but not good for CI.

Reference:

@Youw Youw force-pushed the virtual-device-tests branch 2 times, most recently from 5a24890 to 6089973 Compare June 7, 2026 13:51
@Youw

Youw commented Jun 7, 2026

Copy link
Copy Markdown
Member Author

Thanks @mcuee, that's useful. The win-vhid-test job currently downloads WDK 26100.6584 explicitly because the first preinstalled option I tried (the Chocolatey windowsdriverkit11 VSIX) was VS2019-only, so msbuild under the VS2022 toolset couldn't find wdf.h. Good to know the built-in (or NuGet) WDK should be enough — I'll try pointing the job at the preinstalled/NuGet WDK to drop that ~2–3 min download and the external fetch, and confirm the WDF headers resolve against the VS2022 toolset. Will report back.

This reply was generated by Claude Code at my request.

@Youw

Youw commented Jun 7, 2026

Copy link
Copy Markdown
Member Author

This reply was generated by Claude Code at my request.

https://www.instagram.com/p/DXcOy5riYPS/

(I home community don't mind a little humor here)

@Youw Youw force-pushed the virtual-device-tests branch 2 times, most recently from d3877f1 to 35b5186 Compare June 7, 2026 19:41
@Youw

Youw commented Jun 7, 2026

Copy link
Copy Markdown
Member Author

Followed up on the WDK tip — thanks again. On the current windows-latest image the runner ships the Windows SDK (10.0.26100) but not the WDK: there are no UMDF (wdf) headers under Windows Kits\10\Include, so the vhidmini2 UMDF2 build still needs the WDK installed.

So rather than dropping the download, I cached it. The win-vhid-test job now caches a self-contained offline WDK installer layout (actions/cache@v4, keyed by WDK version) and installs from it — the WDK is downloaded only once and restored from cache on later runs. Verified: on a cache hit the download step is skipped, the driver builds, and DeviceIO_winapi passes. I also kept a preinstalled-WDK fast-path, so if a future image ships the WDK with UMDF headers it's used automatically with no download.

(The NuGet WDK would be a cleaner long-term route, but it needs reworking the legacy .vcxproj, so I left that for later.)

Posted by Claude Code at my request.

@Youw Youw force-pushed the virtual-device-tests branch 2 times, most recently from a0792ee to 3ae4b91 Compare June 7, 2026 21:26
@Youw Youw requested a review from mcuee June 8, 2026 11:38
@Youw

Youw commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

I see two general issues with this PR:

  • we're including MS-licensed code, even though it is only used for testing, even though we're not linking against it and not shipping it nor artifacts; not sure if this could be an issue (probably not for HIDAPI itself, but might be relevant to some other projects that using HIDAPI as a subproject)
  • the amount of code generated by Claude is significant and I'm not in capacity to actually review and understand all of it; but taking this is a test code and development driven by defining the expected outcome/result first - I'm still leaning towards accepting it, of course with remark that if anytihng breaks, me (Claude of course) will take care/fix it. It not really compfortable with this aproach, and not planning on doing such thing (accepting w/o fully reviewing/understanding) for actuall library/production code, but this still worth mentioning.

CC: @JoergAtGithub - not sure if your'e watching this issue, you might be interested in this in general.

@mcuee

mcuee commented Jun 8, 2026

Copy link
Copy Markdown
Member
  1. we're including MS-licensed code, even though it is only used for testing, even though we're not linking against it and not shipping it nor artifacts; not sure if this could be an issue (probably not for HIDAPI itself, but might be relevant to some other projects that using HIDAPI as a subproject)

I do not see this as an issue. Just need to mention it clearly. They can just skip the test codes.

the amount of code generated by Claude is significant and I'm not in capacity to actually review and understand all of it; but taking this is a test code and development driven by defining the expected outcome/result first - I'm still leaning towards accepting it, of course with remark that if anytihng breaks, me (Claude of course) will take care/fix it. It not really compfortable with this aproach, and not planning on doing such thing (accepting w/o fully reviewing/understanding) for actuall library/production code, but this still worth mentioning.

Again I think this is okay for this testing codes. I agree this should not be the case for the core library codes.

@mcuee

mcuee commented Jun 8, 2026

Copy link
Copy Markdown
Member

First issue -- macOS compiler error -- maybe my version is newer than the runner (just upgraded to macOS 26.5.1)

mcuee in ~/build/libusb/hidapi_pr815 on virtual-device-tests % sw_vers 
ProductName:		macOS
ProductVersion:		26.5.1
BuildVersion:		25F80
mcuee in ~/build/libusb/hidapi_pr815 on virtual-device-tests % cc -v
Apple clang version 21.0.0 (clang-2100.1.1.101)
Target: arm64-apple-darwin25.5.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

[ 14%] Building C object src/mac/CMakeFiles/hidapi_darwin.dir/hid.c.o
/Users/mcuee/build/libusb/hidapi_pr815/mac/hid.c:548:14: error: variable length array folded to constant array as an extension
      [-Werror,-Wgnu-folding-constant]
  548 |         wchar_t buf[BUF_LEN];
      |                     ^~~~~~~
1 error generated.
make[2]: *** [src/mac/CMakeFiles/hidapi_darwin.dir/hid.c.o] Error 1
make[1]: *** [src/mac/CMakeFiles/hidapi_darwin.dir/all] Error 2
make: *** [all] Error 2
[ 25%] Building C object src/mac/CMakeFiles/hidapi_darwin.dir/hid.c.o
/Users/mcuee/build/libusb/hidapi_pr815/mac/hid.c:548:14: error: variable length array folded to constant array as an extension
      [-Werror,-Wgnu-folding-constant]
  548 |         wchar_t buf[BUF_LEN];
      |                     ^~~~~~~
1 error generated.
make[2]: *** [src/mac/CMakeFiles/hidapi_darwin.dir/hid.c.o] Error 1
make[1]: *** [src/mac/CMakeFiles/hidapi_darwin.dir/all] Error 2
make: *** [all] Error 2
./test_pr815.sh: line 17: cd: ../framewort: No such file or directory
[ 25%] Building C object src/mac/CMakeFiles/hidapi_darwin.dir/hid.c.o
/Users/mcuee/build/libusb/hidapi_pr815/mac/hid.c:548:14: error: variable length array folded to constant array as an extension
      [-Werror,-Wgnu-folding-constant]
  548 |         wchar_t buf[BUF_LEN];
      |                     ^~~~~~~
1 error generated.

@mcuee

mcuee commented Jun 8, 2026

Copy link
Copy Markdown
Member

@Youw

Just wondering if you can make the documentation to carry out the tests using a physical Mac. Never did such stuff on a Mac.

BTW, is it possible to use sudo to replace the entitlement and consent?

 * Important: creating an IOHIDUserDevice is gated on macOS by the
 * com.apple.developer.hid.virtual.device entitlement and an interactive
 * Accessibility (TCC) consent prompt, neither of which can be satisfied
 * head-less. So on hosted CI this provider returns TEST_VDEV_UNAVAILABLE and
 * the test is skipped; it is meant to actually run on a developer machine or a
 * self-hosted runner where the consent has been granted.

@Youw

Youw commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

BTW, is it possible to use sudo to replace the entitlement and consent?

Unfortunately now. CI runner already has sude - we would have done so already.

Just wondering if you can make the documentation to carry out the tests using a physical Mac.

You actually need an Apple Developer account (cost $100 a year) with your own certificate/provision generated. I'll update the docs to have a clear mention of this. I actuall haven't tested this myself either. Don't really have time to do so.

Youw added 2 commits June 8, 2026 16:18
Add a reusable harness that runs HIDAPI tests against a *virtual* HID device,
needing no physical hardware. Tests use only the public HIDAPI API plus a
backend-agnostic test_virtual_device interface, so the same test runs against
every backend that ships a virtual-device provider. Devices expose pre-recorded
"scenarios": the test sends a Feature report carrying a command byte and the
device replays the matching canned input report.

- src/tests/test_device_io.c: a simple open -> write -> exchange reports ->
  close smoke test (skips via CTest code 77 when no virtual device is present).
- Providers:
    test_virtual_device_uhid.c       Linux hidraw  (kernel /dev/uhid)
    test_virtual_device_rawgadget.c  Linux libusb  (/dev/raw-gadget + dummy_hcd)
    test_virtual_device_win.c (+windows/driver/)   Windows winapi (vhidmini2 UMDF2)
    test_virtual_device_mac.c        macOS darwin  (IOHIDUserDevice)
  Each self-skips when its device can't be created.
- HIDAPI_WITH_TESTS is offered on Linux/macOS too; src/tests builds the test for
  whichever backend(s) are present.
- src/tests/README.md documents the providers, the scenario protocol, and why
  FreeBSD/NetBSD/OpenBSD have no provider (no userspace HID/USB-create facility).
- src/tests/windows/driver/ vendors a modified Microsoft vhidmini2 UMDF2 sample
  under its original Microsoft Public License (MS-PL); LICENSE.txt and README.md
  there record the provenance and modifications and keep it separate from
  HIDAPI's own license (the driver is a standalone test fixture, not linked into
  the library).

Assisted-by: Claude:claude-opus-4.8
- builds.yml: build the test on Linux/Windows/macOS and run it where possible
  (ubuntu-cmake loads uhid and runs DeviceIO_hidraw; Windows/macOS/libusb build
  and self-skip), keeping the per-push matrix green.
- win-vhid-test.yml: build, self-sign and install the vhidmini2 driver on a
  hosted windows-latest runner and run DeviceIO_winapi against it.
- libusb-vhid-test.yml (+ .github/vmrun-libusb.sh): the hosted kernel has no USB
  gadget subsystem, so run DeviceIO_libusb inside a virtme-ng VM that boots a
  generic kernel (building dummy_hcd out-of-tree and loading raw_gadget) against
  a real virtual USB device.

Both privileged jobs run only via workflow_dispatch or a 'ci-virtual-device'
pull-request label, so they stay out of the per-push matrix.

Assisted-by: Claude:claude-opus-4.8
@Youw Youw force-pushed the virtual-device-tests branch from 3ae4b91 to 1f62270 Compare June 8, 2026 13:18
@Youw

Youw commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

Hi @mcuee — this is Claude Code, posting on @Youw's behalf. A few updates on the points you raised, plus one request for your help at the end.

macOS build error (mac/hid.c:548, -Wgnu-folding-constant). This is a pre-existing issue in the core library, not in this PR's test code: create_device_info_with_usage() sizes a local buffer with a const int, which in C isn't a constant expression, so newer Apple clang (21) folds it from a VLA — and the project's -pedantic -Werror makes that fatal. The CI macOS runner's older clang doesn't flag it, which is why this branch built green. A small fix (use an enum constant) is in its own PR: #816. Per the plan, once #816 is reviewed/merged this branch will be rebased on top so the virtual-device tests build on up-to-date Macs.

Running on a real Mac. Added a "Running the macOS (darwin) provider on a real Mac" section to src/tests/README.md with the known requirements — a paid Apple Developer account, a signing cert/provisioning profile carrying the com.apple.developer.hid.virtual.device entitlement, and granting Accessibility (TCC) consent — and a note that sudo can't substitute for the entitlement or the prompt. It's flagged as the known requirements rather than a verified recipe, since this path hasn't been exercised by the maintainers.

MS-licensed driver. The vendored vhidmini2 driver now ships its upstream MS-PL LICENSE.txt and a README.md in src/tests/windows/driver/ stating it's MS-PL (separate from HIDAPI's own license) and a standalone test fixture downstream projects can simply skip — matching your "just need to mention it clearly."

My request — this is Claude asking for your help, on @Youw's behalf: could you confirm #816 builds cleanly for you on macOS 26.5.1 / Apple clang 21? I can't reproduce the warning on the CI runner's older clang and don't have a current Mac to test on myself, so a confirmation from your setup would be a big help in verifying the fix actually resolves the error you hit.

Posted by Claude Code at @Youw's request.

@mcuee

mcuee commented Jun 8, 2026

Copy link
Copy Markdown
Member

Great. #816 fixed the macOS build issue.

Youw added 3 commits July 13, 2026 18:31
Brings in the windows-cmake vcvars fix (setup-msvc-dev + windows-mingw job),
the mac -Wgnu-folding-constant fix, the static-analysis cleanups and the
AGENTS.md contribution rules, so this branch's CI runs against current master.

Assisted-by: claude-code:claude-fable-5
- test_platform.h: pthread_timedjoin_np() is a glibc/BSD extension that does
  not exist on macOS; implement the timed join portably by polling a
  completion flag (set by the thread wrapper, read with acquire semantics)
  and joining once it is set. Drops the _GNU_SOURCE requirement.

- test_virtual_device_mac.c: only treat *Feature* SET_REPORTs as scenario
  triggers; hid_write() Output reports arrive through the same callback on
  macOS and must not replay input (matches the shared contract and the other
  providers).

- windows/driver: the test device is unnumbered, but WriteReport/GetFeature/
  GetInputReport/SetOutputReport still gated on the sample's report id 1 and
  therefore rejected every request (hidclass passes id 0 for an unnumbered
  device); accept report id 0 and return 0 as the input-report id, so the
  descriptor-advertised Output/Input/Feature paths actually work.

- src/tests/CMakeLists.txt: SKIP_RETURN_CODE needs CMake 3.16; with an older
  CMake a legitimate provider self-skip would be reported as a FAILure, so
  warn and skip adding the tests instead.

Findings surfaced by a multi-model adversarial code review.

Assisted-by: claude-code:claude-fable-5
The job still set up MSVC by calling the hard-coded "Enterprise" vcvars64.bat
path, which no longer exists on windows-latest (the same breakage builds.yml
already fixed in #817). Use the setup-msvc-dev action and invoke msbuild from
that environment instead.

Assisted-by: claude-code:claude-fable-5
@Youw Youw changed the title tests: virtual HID device test harness (Linux/Windows/macOS/libusb) tests: virtual HID device test harness for all four backends (hidraw, libusb, winapi, darwin) Jul 13, 2026
The windows-latest and windows-2025 labels now both resolve to the VS2026
image, whose only MSBuild is 18.x; the cached WDK 10.0.26100.6584 layout
ships DriverKit build tasks only up to 17.0, so the driver build fails with
MSB4062 ("The ValidateNTTargetVersion task could not be loaded from ...
Microsoft.DriverKit.Build.Tasks.18.0.dll"). Pin the job to windows-2022,
which still carries VS2022 / MSBuild 17. Can move to a newer image once a
WDK that ships the 18.0 build tasks is cached here.

Assisted-by: claude-code:claude-fable-5
@Youw Youw force-pushed the virtual-device-tests branch from 0300db0 to d2bc584 Compare July 13, 2026 17:08
Youw added 2 commits July 13, 2026 20:18
No single hosted image can run the whole flow anymore: the cached WDK
(10.0.26100.6584) ships DriverKit build tasks only for MSBuild 17 (VS2022),
which the windows-2025* images no longer carry (VS2026/MSBuild 18 fails with
MSB4062), while the driver package's INF depends on MsHidUmdf.inf semantics
from build 22000, so it does not install on Server 2022.

Build the driver on windows-2022 (VS2022 + cached WDK) and pass the signed
package - plus devcon.exe from the WDK - as an artifact to a windows-latest
(Server 2025) job that installs it and runs DeviceIO_winapi against it.
Both halves run in environments where they are known to work; merge them
back into one job once a WDK with the 18.0 build tasks is cached here.

Assisted-by: claude-code:claude-fable-5
@Youw Youw marked this pull request as ready for review July 13, 2026 18:33
@Youw Youw merged commit debef6a into master Jul 13, 2026
28 checks passed
@Youw Youw deleted the virtual-device-tests branch July 13, 2026 21:46
Youw added a commit that referenced this pull request Jul 13, 2026
Brings in #813 (IWYU + leak guards), #815 (virtual-device test harness),
#817 (CI vcvars fix, already cherry-picked) and #708 (libusb hid_error).
Conflicts in libusb/hid.c (hotplug context vs last_global_error, refactored
hid_enumerate vs its error reporting) and linux/hid.c (includes) resolved
by keeping both sides' functionality.

Assisted-by: claude-code:claude-fable-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-virtual-device Run the virtual HID device CI jobs (Windows driver + libusb raw-gadget)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants