| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- package via
- import (
- "errors"
- "strings"
- "testing"
- )
- // probeQueue scripts one answer per channel the probe visits, so a test can say
- // which channels the keyboard has.
- func probeQueue(present ...Channel) [][]byte {
- queue := make([][]byte, 0, probeChannelMax)
- for c := 1; c <= probeChannelMax; c++ {
- buf := make([]byte, 32)
- if containsChannel(present, Channel(c)) {
- buf[0] = byte(CustomGet)
- buf[3] = 160
- } else {
- buf[0] = byte(Unhandled)
- }
- buf[1] = byte(c)
- buf[2] = probeValueID
- queue = append(queue, buf)
- }
- return queue
- }
- func containsChannel(channels []Channel, want Channel) bool {
- for _, c := range channels {
- if c == want {
- return true
- }
- }
- return false
- }
- func TestDetectChannelsFindsTheAnsweredChannels(t *testing.T) {
- transport := &fakeTransport{queue: probeQueue(ChannelRgblight, ChannelRgbMatrix, ChannelAudio)}
- protocol := Protocol{handle: transport}
- got, err := protocol.DetectChannels()
- if err != nil {
- t.Fatalf("DetectChannels() error = %v", err)
- }
- want := []Channel{ChannelRgblight, ChannelRgbMatrix, ChannelAudio}
- if len(got) != len(want) {
- t.Fatalf("DetectChannels() = %v, want %v", got, want)
- }
- for i := range want {
- if got[i] != want[i] {
- t.Errorf("DetectChannels()[%d] = %d, want %d", i, got[i], want[i])
- }
- }
- }
- func TestDetectChannelsAsksBrightnessOnEveryChannelFromOneToFifteen(t *testing.T) {
- transport := &fakeTransport{queue: probeQueue()}
- protocol := Protocol{handle: transport}
- if _, err := protocol.DetectChannels(); err != nil {
- t.Fatalf("DetectChannels() error = %v", err)
- }
- if len(transport.reports) != probeChannelMax {
- t.Fatalf("requests = %d, want %d", len(transport.reports), probeChannelMax)
- }
- for i, report := range transport.reports {
- wantChannel := byte(i + 1)
- if report[0] != byte(CustomGet) {
- t.Errorf("request %d command = 0x%02x, want 0x%02x", i, report[0], CustomGet)
- }
- if report[1] != wantChannel {
- t.Errorf("request %d channel = %d, want %d", i, report[1], wantChannel)
- }
- if report[2] != probeValueID {
- t.Errorf("request %d value = 0x%02x, want 0x%02x (brightness)", i, report[2], probeValueID)
- }
- }
- }
- // A channel list missing entries is indistinguishable from a complete one, so a
- // transport failure has to abort the probe instead of shrinking it.
- func TestDetectChannelsFailsOnATransportErrorMidProbe(t *testing.T) {
- transport := &fakeTransport{
- queue: probeQueue(ChannelRgblight, ChannelRgbMatrix, ChannelAudio),
- readErr: errors.New("read: interrupted system call"),
- readErrAfter: 3,
- }
- protocol := Protocol{handle: transport}
- _, err := protocol.DetectChannels()
- if err == nil {
- t.Fatal("DetectChannels() expected an error, got nil")
- }
- if !strings.Contains(err.Error(), "channel 4") {
- t.Errorf("error = %q, want it to name the channel that failed", err)
- }
- }
- // A 0xFF that matches nothing is a valid "no such channel". Only a 0xFF whose
- // echoed channel disagrees is a desynchronised stream.
- func TestDetectChannelsAcceptsAWellFormedUnhandledAnswer(t *testing.T) {
- transport := &fakeTransport{queue: probeQueue(ChannelRgblight)}
- protocol := Protocol{handle: transport}
- got, err := protocol.DetectChannels()
- if err != nil {
- t.Fatalf("DetectChannels() error = %v", err)
- }
- if len(got) != 1 || got[0] != ChannelRgblight {
- t.Errorf("DetectChannels() = %v, want [2]", got)
- }
- }
- func TestDetectChannelsReportsNoChannelsWhenNoneArePresent(t *testing.T) {
- transport := &fakeTransport{queue: probeQueue()}
- protocol := Protocol{handle: transport}
- got, err := protocol.DetectChannels()
- if err != nil {
- t.Fatalf("DetectChannels() error = %v", err)
- }
- if len(got) != 0 {
- t.Errorf("DetectChannels() = %v, want empty", got)
- }
- }
- // effectResponse is one scripted answer to an effect get or set, carrying the
- // value the keyboard is left with.
- func effectResponse(command Message, ch Channel, value byte) []byte {
- buf := make([]byte, 32)
- buf[0] = byte(command)
- buf[1] = byte(ch)
- buf[2] = effectValueID
- buf[3] = value
- return buf
- }
- // QMK clamps rather than rejects: a mode at or above the top comes down to it.
- // A single write above the top therefore returns the top itself, which is the
- // whole reason the probe is one round trip instead of a search.
- func TestEffectTopReadsTheTopOffWhatTheFirmwareClampedTo(t *testing.T) {
- const ch = ChannelRgbMatrix
- transport := &fakeTransport{queue: [][]byte{
- effectResponse(CustomGet, ch, 12), // the effect the channel is on
- effectResponse(CustomSet, ch, 255), // the probe, echoed
- effectResponse(CustomGet, ch, 45), // what the clamp left
- effectResponse(CustomSet, ch, 12), // the restore, echoed
- }}
- protocol := Protocol{handle: transport}
- got, err := protocol.EffectTop(ch)
- if err != nil {
- t.Fatalf("EffectTop() error = %v", err)
- }
- if got != 45 {
- t.Errorf("EffectTop() = %d, want 45", got)
- }
- if len(transport.reports) != 4 {
- t.Fatalf("requests = %d, want 4 (read, probe, read back, restore)", len(transport.reports))
- }
- last := transport.reports[3]
- if last[0] != byte(CustomSet) || last[3] != 12 {
- t.Errorf("last request = %v, want the original effect 12 written back", last[:4])
- }
- }
- // A channel already sitting on its last effect answers the probe with the value
- // it held, which is the same number the clamp would give. The two cases need not
- // be told apart, so the answer is still the top.
- func TestEffectTopReportsTheTopWhenTheChannelAlreadySatOnIt(t *testing.T) {
- const ch = ChannelRgbMatrix
- transport := &fakeTransport{queue: [][]byte{
- effectResponse(CustomGet, ch, 45),
- effectResponse(CustomSet, ch, 255),
- effectResponse(CustomGet, ch, 45),
- effectResponse(CustomSet, ch, 45),
- }}
- protocol := Protocol{handle: transport}
- got, err := protocol.EffectTop(ch)
- if err != nil {
- t.Fatalf("EffectTop() error = %v", err)
- }
- if got != 45 {
- t.Errorf("EffectTop() = %d, want 45", got)
- }
- }
- // VIA reads a 0 on the effect parameter as "turn this channel off", so the probe
- // must not write one. The restore may: a channel that was on effect 0 goes back
- // to 0, which is the state it was in.
- func TestEffectTopProbesWithoutSwitchingTheChannelOff(t *testing.T) {
- if effectProbeValue == 0 {
- t.Fatal("effectProbeValue is 0, which switches the channel off instead of asking")
- }
- const ch = ChannelRgbMatrix
- transport := &fakeTransport{queue: [][]byte{
- effectResponse(CustomGet, ch, 0),
- effectResponse(CustomSet, ch, effectProbeValue),
- effectResponse(CustomGet, ch, 6),
- effectResponse(CustomSet, ch, 0),
- }}
- protocol := Protocol{handle: transport}
- if _, err := protocol.EffectTop(ch); err != nil {
- t.Fatalf("EffectTop() error = %v", err)
- }
- probe := transport.reports[1]
- if probe[0] != byte(CustomSet) || probe[2] != effectValueID {
- t.Fatalf("request 1 = %v, want a set on the effect parameter", probe[:4])
- }
- if probe[3] != effectProbeValue {
- t.Errorf("probe wrote effect %d, want %d", probe[3], effectProbeValue)
- }
- }
- // An effect that cannot be read is a channel the probe cannot restore, so it
- // stops before it writes rather than leaving the channel on the last effect.
- func TestEffectTopStopsBeforeWritingWhenTheEffectCannotBeRead(t *testing.T) {
- const ch = ChannelRgbMatrix
- transport := &fakeTransport{queue: [][]byte{effectResponse(Unhandled, ch, 0)}}
- protocol := Protocol{handle: transport}
- if _, err := protocol.EffectTop(ch); err == nil {
- t.Fatal("EffectTop() = nil error, want a failure for an effect that cannot be read")
- }
- if len(transport.reports) != 1 {
- t.Errorf("requests = %d, want only the read that failed", len(transport.reports))
- }
- }
- func TestChannelSubsystemNames(t *testing.T) {
- tests := []struct {
- channel Channel
- want string
- }{
- {ChannelBacklight, "backlight"},
- {ChannelRgblight, "rgblight"},
- {ChannelRgbMatrix, "rgb_matrix"},
- {ChannelAudio, "audio"},
- {ChannelLedMatrix, "led_matrix"},
- {Channel(9), ""},
- }
- for _, tt := range tests {
- if got := tt.channel.Subsystem(); got != tt.want {
- t.Errorf("Channel(%d).Subsystem() = %q, want %q", tt.channel, got, tt.want)
- }
- }
- }
- // A stale 0xFF — the answer to an earlier request, still in the read buffer —
- // looks exactly like absence. Reading the channel out of it is what tells the
- // two apart, and a mismatch means the request/response stream is out of step.
- func TestDetectChannelsRejectsAnUnhandledAnswerForAnotherChannel(t *testing.T) {
- queue := probeQueue(ChannelRgblight, ChannelRgbMatrix, ChannelAudio)
- // Channel 2 answers "unhandled", but for a different channel: the stream
- // is answering a request that is not this one.
- stale := make([]byte, 32)
- stale[0] = byte(Unhandled)
- stale[1] = 0x07
- stale[2] = probeValueID
- queue[1] = stale
- transport := &fakeTransport{queue: queue}
- protocol := Protocol{handle: transport}
- _, err := protocol.DetectChannels()
- if err == nil {
- t.Fatal("DetectChannels() expected an error for an answer belonging to another channel")
- }
- if !strings.Contains(err.Error(), "channel 2") {
- t.Errorf("error = %q, want it to name the channel whose answer did not match", err)
- }
- }
|