|
| 1 | +//go:build linux && !cgo |
| 2 | + |
| 3 | +package linux |
| 4 | + |
| 5 | +import ( |
| 6 | + "testing" |
| 7 | + |
| 8 | + "github.com/y3owk1n/neru/internal/derrors" |
| 9 | +) |
| 10 | + |
| 11 | +// The evdev global-hotkey listener is the Wayland substitute for OS-level |
| 12 | +// global hotkeys, and it needs cgo. In a CGO_ENABLED=0 Linux build the whole |
| 13 | +// type is a stub, so Start has to say so. |
| 14 | +// |
| 15 | +// Returning nil is the failure this pins. The only caller is |
| 16 | +// `hotkeys/linux.Manager.ensureWaylandStarted`, which reads a nil error as |
| 17 | +// "the listener is reading the keyboard" and sets waylandStarted, logging the |
| 18 | +// info line that tells the user their config keybindings are active. On a |
| 19 | +// no-cgo build nothing is reading anything: the user is told their hotkeys |
| 20 | +// work, presses one, and nothing happens — with no warning anywhere pointing |
| 21 | +// at the build. CodeNotSupported routes the same call into the warn branch |
| 22 | +// that already exists for an unreadable /dev/input. |
| 23 | +// |
| 24 | +// The rule is `internal/adapter/platform/AGENTS.md`, "Stubs are loud". |
| 25 | +// |
| 26 | +// Note where this runs: the CI Linux leg builds with CGO on, so nothing on the |
| 27 | +// gate compiles this file. `just test-linux-nocgo` does, as does any |
| 28 | +// CGO_ENABLED=0 Linux build — run it after touching the no-cgo twins. |
| 29 | +func TestGlobalHotkeyListener_StartReportsNotSupportedWithoutCgo(t *testing.T) { |
| 30 | + listener := NewGlobalHotkeyListener(nil) |
| 31 | + |
| 32 | + err := listener.Start() |
| 33 | + if err == nil { |
| 34 | + t.Fatal( |
| 35 | + "Start returned nil without cgo; the hotkey manager would report the " + |
| 36 | + "user's config keybindings active while nothing reads the keyboard", |
| 37 | + ) |
| 38 | + } |
| 39 | + |
| 40 | + if !derrors.IsNotSupported(err) { |
| 41 | + t.Errorf("Start returned %v (code %q), want CodeNotSupported", |
| 42 | + err, derrors.GetCode(err)) |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +// TestGlobalHotkeyListener_StartIsStableAcrossCalls guards against a stub that |
| 47 | +// refuses once and then changes its answer. The manager retries Start on every |
| 48 | +// registration once waylandStarted has been cleared, so a second call that |
| 49 | +// returned nil would put it back in the state above. |
| 50 | +func TestGlobalHotkeyListener_StartIsStableAcrossCalls(t *testing.T) { |
| 51 | + listener := NewGlobalHotkeyListener(nil) |
| 52 | + |
| 53 | + for i := range 3 { |
| 54 | + err := listener.Start() |
| 55 | + if !derrors.IsNotSupported(err) { |
| 56 | + t.Fatalf("Start call %d returned %v, want CodeNotSupported every time", i+1, err) |
| 57 | + } |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +// TestGlobalHotkeyListener_IsRunningStaysFalseAfterStart pins the other half of |
| 62 | +// the refusal: IsRunning and DeviceCount are what |
| 63 | +// `hotkeys/linux.Manager.HealthCheck` polls, and a stub that claimed to be |
| 64 | +// running would make the health loop stop trying to recover a listener that |
| 65 | +// does not exist. |
| 66 | +func TestGlobalHotkeyListener_IsRunningStaysFalseAfterStart(t *testing.T) { |
| 67 | + listener := NewGlobalHotkeyListener(nil) |
| 68 | + |
| 69 | + _ = listener.Start() |
| 70 | + |
| 71 | + if listener.IsRunning() { |
| 72 | + t.Error("IsRunning reported true without cgo; no evdev reader exists") |
| 73 | + } |
| 74 | + |
| 75 | + if got := listener.DeviceCount(); got != 0 { |
| 76 | + t.Errorf("DeviceCount() = %d without cgo, want 0", got) |
| 77 | + } |
| 78 | +} |
0 commit comments