|
@@ -11,15 +11,21 @@ import (
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
type deviceInfoOutput struct {
|
|
type deviceInfoOutput struct {
|
|
|
- Devices []keyboardLine `json:"devices"`
|
|
|
|
|
- Total int `json:"total"`
|
|
|
|
|
|
|
+ Devices []keyboardLine `json:"devices"`
|
|
|
|
|
+ Total int `json:"total"`
|
|
|
|
|
+ Others []intdevice.HIDDevice `json:"otherHidDevices"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// stubDiscovery replaces both enumerations the command makes. Only the
|
|
|
|
|
+// keyboards are of interest to most tests, so the devices that were passed over
|
|
|
|
|
+// are stubbed empty as well: leaving that one real would make the output depend
|
|
|
|
|
+// on what is plugged into the machine running the tests.
|
|
|
func stubDiscovery(t *testing.T, devices []intdevice.Device) {
|
|
func stubDiscovery(t *testing.T, devices []intdevice.Device) {
|
|
|
t.Helper()
|
|
t.Helper()
|
|
|
- orig := discoverAll
|
|
|
|
|
|
|
+ orig, origOther := discoverAll, discoverOther
|
|
|
discoverAll = func() ([]intdevice.Device, error) { return devices, nil }
|
|
discoverAll = func() ([]intdevice.Device, error) { return devices, nil }
|
|
|
- t.Cleanup(func() { discoverAll = orig })
|
|
|
|
|
|
|
+ discoverOther = func() ([]intdevice.HIDDevice, error) { return nil, nil }
|
|
|
|
|
+ t.Cleanup(func() { discoverAll, discoverOther = orig, origOther })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// runRealKeyboardInfo executes the shipped command so a re-added
|
|
// runRealKeyboardInfo executes the shipped command so a re-added
|
|
@@ -107,6 +113,176 @@ func TestKeyboardInfoEmitsEmptyArrayNotNull(t *testing.T) {
|
|
|
if !strings.Contains(stdout, `"devices": []`) {
|
|
if !strings.Contains(stdout, `"devices": []`) {
|
|
|
t.Errorf("stdout = %q, want an empty array so consumers can iterate unconditionally", stdout)
|
|
t.Errorf("stdout = %q, want an empty array so consumers can iterate unconditionally", stdout)
|
|
|
}
|
|
}
|
|
|
|
|
+ if !strings.Contains(stdout, `"otherHidDevices": []`) {
|
|
|
|
|
+ t.Errorf("stdout = %q, want the passed-over devices as an empty array, not null", stdout)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// An empty result is the one case a user cannot read on their own: it looks the
|
|
|
|
|
+// same whether the keyboard is unplugged or its lighting is on a HID collection
|
|
|
|
|
+// this tool does not address. So the listing names the devices that are there,
|
|
|
|
|
+// with the usage pages they do expose — the missing 0xFF60/0x61 is the finding.
|
|
|
|
|
+func TestKeyboardInfoEmptyResultNamesTheDevicesPassedOver(t *testing.T) {
|
|
|
|
|
+ stubDiscovery(t, nil)
|
|
|
|
|
+ stubOtherDiscovery(t, []intdevice.HIDDevice{
|
|
|
|
|
+ {
|
|
|
|
|
+ VendorID: 0x320f,
|
|
|
|
|
+ ProductID: 0x5044,
|
|
|
|
|
+ Name: "GMMK Pro",
|
|
|
|
|
+ UsagePairs: []intdevice.UsagePair{
|
|
|
|
|
+ {UsagePage: 0x0001, Usage: 0x06},
|
|
|
|
|
+ {UsagePage: 0xff80, Usage: 0x61},
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ stdout, _, err := runRealKeyboardInfo(t)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("keyboard info returned error: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for _, want := range []string{
|
|
|
|
|
+ "No keyboard with the QMK Raw HID interface found",
|
|
|
|
|
+ "usage page 0xFF60, usage 0x61",
|
|
|
|
|
+ "0x320F/0x5044",
|
|
|
|
|
+ "GMMK Pro",
|
|
|
|
|
+ "0xFF80/0x61",
|
|
|
|
|
+ } {
|
|
|
|
|
+ if !strings.Contains(stdout, want) {
|
|
|
|
|
+ t.Errorf("stdout = %q, want it to contain %q", stdout, want)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// The preamble says what was looked for and why a board has it, and it must not
|
|
|
|
|
+// claim VIA was checked: nothing is opened here, and VIA is never what the
|
|
|
|
|
+// collection filter asks about. A sentence that says it did would send a user
|
|
|
|
|
+// looking for a probe that does not exist.
|
|
|
|
|
+func TestKeyboardInfoEmptyResultDoesNotClaimToHaveCheckedVIA(t *testing.T) {
|
|
|
|
|
+ stubDiscovery(t, nil)
|
|
|
|
|
+ stubOtherDiscovery(t, nil)
|
|
|
|
|
+
|
|
|
|
|
+ stdout, _, err := runRealKeyboardInfo(t)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("keyboard info returned error: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if !strings.Contains(stdout, "VIA's build cannot be built without it") {
|
|
|
|
|
+ t.Errorf("stdout = %q, want it to say why a QMK firmware has the collection", stdout)
|
|
|
|
|
+ }
|
|
|
|
|
+ if strings.Contains(stdout, "no VIA") || strings.Contains(stdout, "does not support VIA") {
|
|
|
|
|
+ t.Errorf("stdout = %q, want no claim about VIA: this command never opened the board", stdout)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// And the opposite: a keyboard was found, so the devices that were passed over
|
|
|
|
|
+// are only ever a dock or a mouse, and a listing beside the line the user came
|
|
|
|
|
+// for buries it.
|
|
|
|
|
+func TestKeyboardInfoListsOtherDevicesOnlyWhenNoneWasFound(t *testing.T) {
|
|
|
|
|
+ stubDiscovery(t, []intdevice.Device{
|
|
|
|
|
+ {Index: 1, Path: "/dev/hidraw7", VendorID: 0x36b0, ProductID: 0x309f, Name: "Impact 80"},
|
|
|
|
|
+ })
|
|
|
|
|
+ stubOtherDiscovery(t, []intdevice.HIDDevice{
|
|
|
|
|
+ {VendorID: 0x046d, ProductID: 0xc041, Name: "USB Gaming Mouse",
|
|
|
|
|
+ UsagePairs: []intdevice.UsagePair{{UsagePage: 0xff00, Usage: 0x01}}},
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ stdout, _, err := runRealKeyboardInfo(t)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("keyboard info returned error: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if !strings.Contains(stdout, "Impact 80") {
|
|
|
|
|
+ t.Errorf("stdout = %q, want the keyboard that was found", stdout)
|
|
|
|
|
+ }
|
|
|
|
|
+ if strings.Contains(stdout, "USB Gaming Mouse") {
|
|
|
|
|
+ t.Errorf("stdout = %q, want no listing of passed-over devices when a keyboard was found", stdout)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// With nothing found and nothing else connected, the sentence has to say that
|
|
|
|
|
+// rather than print a heading with nothing under it.
|
|
|
|
|
+func TestKeyboardInfoEmptyResultWithNoHIDDevicesAtAll(t *testing.T) {
|
|
|
|
|
+ stubDiscovery(t, nil)
|
|
|
|
|
+ stubOtherDiscovery(t, nil)
|
|
|
|
|
+
|
|
|
|
|
+ stdout, _, err := runRealKeyboardInfo(t)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("keyboard info returned error: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if !strings.Contains(stdout, "No other HID device is connected") {
|
|
|
|
|
+ t.Errorf("stdout = %q, want it to say that no HID device is connected", stdout)
|
|
|
|
|
+ }
|
|
|
|
|
+ if strings.Contains(stdout, "These HID devices are connected") {
|
|
|
|
|
+ t.Errorf("stdout = %q, want no heading for a list that is not there", stdout)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// A consumer asking "why not this one" reads the JSON, so the devices that were
|
|
|
|
|
+// passed over are in it whether or not a keyboard was found.
|
|
|
|
|
+func TestKeyboardInfoJSONCarriesTheOtherHIDDevicesWhateverItFinds(t *testing.T) {
|
|
|
|
|
+ withJSON(t)
|
|
|
|
|
+ stubDiscovery(t, []intdevice.Device{
|
|
|
|
|
+ {Index: 1, Path: "/dev/hidraw7", VendorID: 0x36b0, ProductID: 0x309f, Name: "Impact 80"},
|
|
|
|
|
+ })
|
|
|
|
|
+ stubOtherDiscovery(t, []intdevice.HIDDevice{
|
|
|
|
|
+ {VendorID: 0x320f, ProductID: 0x5044, Name: "GMMK Pro",
|
|
|
|
|
+ UsagePairs: []intdevice.UsagePair{{UsagePage: 0xff80, Usage: 0x61}}},
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ stdout, _, err := runRealKeyboardInfo(t)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("keyboard info returned error: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var got deviceInfoOutput
|
|
|
|
|
+ if err := json.Unmarshal([]byte(stdout), &got); err != nil {
|
|
|
|
|
+ t.Fatalf("stdout is not valid JSON: %v (output %q)", err, stdout)
|
|
|
|
|
+ }
|
|
|
|
|
+ if len(got.Devices) != 1 {
|
|
|
|
|
+ t.Fatalf("devices = %d entries, want 1", len(got.Devices))
|
|
|
|
|
+ }
|
|
|
|
|
+ if len(got.Others) != 1 {
|
|
|
|
|
+ t.Fatalf("otherHidDevices = %d entries, want 1", len(got.Others))
|
|
|
|
|
+ }
|
|
|
|
|
+ if got.Others[0].VendorID != 0x320f {
|
|
|
|
|
+ t.Errorf("otherHidDevices[0].vendorId = %04X, want 320F", got.Others[0].VendorID)
|
|
|
|
|
+ }
|
|
|
|
|
+ if len(got.Others[0].UsagePairs) != 1 || got.Others[0].UsagePairs[0].UsagePage != 0xff80 {
|
|
|
|
|
+ t.Errorf("otherHidDevices[0].usagePages = %+v, want the page it was passed over for", got.Others[0].UsagePairs)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// The passed-over devices are never selectable, so they carry no index and none
|
|
|
|
|
+// is printed: a number there would be one --device does not take.
|
|
|
|
|
+func TestKeyboardInfoGivesNoNumberToADeviceThatCannotBeSelected(t *testing.T) {
|
|
|
|
|
+ stubDiscovery(t, nil)
|
|
|
|
|
+ stubOtherDiscovery(t, []intdevice.HIDDevice{
|
|
|
|
|
+ {VendorID: 0x320f, ProductID: 0x5044, Name: "GMMK Pro",
|
|
|
|
|
+ UsagePairs: []intdevice.UsagePair{{UsagePage: 0xff80, Usage: 0x61}}},
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ stdout, _, err := runRealKeyboardInfo(t)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("keyboard info returned error: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for _, line := range strings.Split(stdout, "\n") {
|
|
|
|
|
+ if !strings.Contains(line, "0x320F/0x5044") {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ if strings.HasPrefix(strings.TrimSpace(line), "1 ") {
|
|
|
|
|
+ t.Errorf("stdout = %q, want no number on a device --device cannot select", stdout)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func stubOtherDiscovery(t *testing.T, others []intdevice.HIDDevice) {
|
|
|
|
|
+ t.Helper()
|
|
|
|
|
+ orig := discoverOther
|
|
|
|
|
+ discoverOther = func() ([]intdevice.HIDDevice, error) { return others, nil }
|
|
|
|
|
+ t.Cleanup(func() { discoverOther = orig })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func TestKeyboardInfoPropagatesDiscoveryError(t *testing.T) {
|
|
func TestKeyboardInfoPropagatesDiscoveryError(t *testing.T) {
|