// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package input import ( "errors" "reflect" "strings" "testing" ) type ledgerTransition struct { code uint16 down bool } type ledgerBackend struct { codes map[string]uint16 held map[uint16]bool effects []ledgerTransition resolves []string resolveError error heldError error setError func(uint16, bool) error } func (backend *ledgerBackend) Resolve(name string) (uint16, error) { backend.resolves = append(backend.resolves, name) return backend.codes[name], backend.resolveError } func (backend *ledgerBackend) Held(code uint16) (bool, error) { return backend.held[code], backend.heldError } func (backend *ledgerBackend) Set(code uint16, down bool) error { backend.effects = append(backend.effects, ledgerTransition{code, down}) // Model a backend that applies an effect before reporting uncertainty. backend.held[code] = down if backend.setError != nil { return backend.setError(code, down) } return nil } func ledgerFixture(t *testing.T) (*RemoteKeyLedger, *ledgerBackend) { t.Helper() backend := &ledgerBackend{codes: map[string]uint16{"a": 38, "q": 24, "Shift_L": 50, "Control_R": 105}, held: make(map[uint16]bool)} ledger, err := NewRemoteKeyLedger(backend) if err != nil { t.Fatal(err) } return ledger, backend } func applyLedgerKey(t *testing.T, ledger *RemoteKeyLedger, name string, down bool) { t.Helper() if err := ledger.Apply(name, down); err != nil { t.Fatal(err) } } func TestRemoteKeyLedgerRetainsDestinationThroughLayoutChange(t *testing.T) { ledger, backend := ledgerFixture(t) applyLedgerKey(t, ledger, "a", true) backend.codes["a"] = 24 applyLedgerKey(t, ledger, "a", true) // Duplicate down is idempotent. applyLedgerKey(t, ledger, "a", false) applyLedgerKey(t, ledger, "a", false) // Unowned up has no native effect. applyLedgerKey(t, ledger, "a", true) if err := ledger.Retire(); err != nil { t.Fatal(err) } want := []ledgerTransition{{38, true}, {38, false}, {24, true}, {24, false}} if !reflect.DeepEqual(backend.effects, want) || !reflect.DeepEqual(backend.resolves, []string{"a", "a"}) { t.Fatal("layout changes or duplicate transitions changed native ownership", backend.effects) } if len(ledger.held) != 0 || len(ledger.order) != 0 { t.Fatal("successful retirement retained keys") } if err := ledger.Retire(); err != nil || len(backend.effects) != len(want) { t.Fatal("successful retirement was not idempotent") } if !errors.Is(ledger.Apply("a", true), ErrRemoteKeyState) { t.Fatal("retired ledger resumed") } } func TestRemoteKeyLedgerRejectsCollisionsWithoutAdopting(t *testing.T) { for _, collision := range []string{"preexisting", "alias"} { t.Run(collision, func(t *testing.T) { ledger, backend := ledgerFixture(t) applyLedgerKey(t, ledger, "Control_R", true) if collision == "preexisting" { backend.held[38] = true } else { backend.codes["a"] = 105 } if !errors.Is(ledger.Apply("a", true), ErrRemoteKeyCollision) { t.Fatal("ambiguous key ownership accepted") } if err := ledger.Retire(); err != nil { t.Fatal(err) } if !reflect.DeepEqual(backend.effects, []ledgerTransition{{105, true}, {105, false}}) { t.Fatal("cleanup adopted/released the rejected key", backend.effects) } if collision == "preexisting" && !backend.held[38] { t.Fatal("cleanup released an unowned key") } }) } } func TestRemoteKeyLedgerFailedEffectsRetainCleanup(t *testing.T) { for _, failingDown := range []bool{true, false} { ledger, backend := ledgerFixture(t) if !failingDown { applyLedgerKey(t, ledger, "a", true) } backend.setError = func(uint16, bool) error { return errors.New("private native failure") } if err := ledger.Apply("a", failingDown); !errors.Is(err, ErrRemoteKeyBackend) || strings.Contains(err.Error(), "private") { t.Fatal("uncertain native operation did not fail with a constant error") } if ledger.held["a"] != 38 || !ledger.stopped { t.Fatal("failed effect lost its reserved destination") } if !errors.Is(ledger.Retire(), ErrRemoteKeyBackend) || ledger.held["a"] != 38 { t.Fatal("failed cleanup forgot its retryable key") } backend.codes["a"] = 99 backend.setError = nil if err := ledger.Retire(); err != nil || len(ledger.held) != 0 || backend.held[38] { t.Fatal("cleanup retry did not use the original destination", err) } if last := backend.effects[len(backend.effects)-1]; last != (ledgerTransition{38, false}) { t.Fatal("cleanup retried a new mapping", last) } } } func TestRemoteKeyLedgerPartialCleanupPreservesOrder(t *testing.T) { ledger, backend := ledgerFixture(t) for _, name := range []string{"Shift_L", "Control_R", "a", "q"} { applyLedgerKey(t, ledger, name, true) } backend.setError = func(code uint16, down bool) error { if !down && (code == 105 || code == 24) { return errors.New("fixture uncertainty") } return nil } if !errors.Is(ledger.Retire(), ErrRemoteKeyBackend) || !reflect.DeepEqual(ledger.order, []string{"Control_R", "q"}) { t.Fatal("partial cleanup did not retain original retry order") } if !reflect.DeepEqual(backend.effects[4:], []ledgerTransition{{24, false}, {38, false}, {105, false}, {50, false}}) { t.Fatal("cleanup did not attempt every owned key in reverse down order") } backend.setError = nil if err := ledger.Retire(); err != nil || !reflect.DeepEqual(backend.effects[8:], []ledgerTransition{{24, false}, {105, false}}) { t.Fatal("retry repeated successful cleanup or lost its order", err) } } func TestRemoteKeyLedgerBoundsAndUnavailableKeys(t *testing.T) { if _, err := NewRemoteKeyLedger(nil); !errors.Is(err, ErrRemoteKeyUnavailable) { t.Fatal("accepted missing backend") } var absent *RemoteKeyLedger if !errors.Is(absent.Apply("a", true), ErrRemoteKeyState) || !errors.Is(absent.Retire(), ErrRemoteKeyState) { t.Fatal("nil ledger accepted work") } for _, name := range []string{"unknown private key", "Caps_Lock", "Num_Lock", "Scroll_Lock"} { ledger, backend := ledgerFixture(t) if err := ledger.Apply(name, true); !errors.Is(err, ErrRemoteKeyUnavailable) || len(backend.effects) != 0 || len(backend.resolves) != 0 { t.Fatal("unsupported key or lock reached the native backend") } } for _, code := range []uint16{0, 7, 256, 65535} { ledger, backend := ledgerFixture(t) backend.codes["a"] = code if !errors.Is(ledger.Apply("a", true), ErrRemoteKeyUnavailable) || len(backend.effects) != 0 { t.Fatal("invalid native code produced an effect") } } ledger, backend := ledgerFixture(t) for index := range RemoteHeldKeyLimit { name := string(rune('a' + index)) if index >= 26 { name = string(rune('0' + index - 26)) } backend.codes[name] = uint16(8 + index) applyLedgerKey(t, ledger, name, true) } backend.codes["6"] = 255 if !errors.Is(ledger.Apply("6", true), ErrRemoteKeyLimit) || len(backend.effects) != RemoteHeldKeyLimit { t.Fatal("held-key overflow produced an extra effect") } if err := ledger.Retire(); err != nil || len(backend.effects) != 2*RemoteHeldKeyLimit { t.Fatal("overflow lost bounded cleanup", err) } } func TestRemoteKeyLedgerBackendFailuresArePrivate(t *testing.T) { for _, operation := range []string{"resolve", "held"} { ledger, backend := ledgerFixture(t) failure := errors.New("private backend name and path") if operation == "resolve" { backend.resolveError = failure } else { backend.heldError = failure } if err := ledger.Apply("a", true); err == nil || strings.Contains(err.Error(), "private") || len(backend.effects) != 0 || len(ledger.held) != 0 { t.Fatal("backend error leaked input or caused an effect") } } }