channel_test.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. package via
  2. import (
  3. "errors"
  4. "strings"
  5. "testing"
  6. )
  7. // probeQueue scripts one answer per channel the probe visits, so a test can say
  8. // which channels the keyboard has.
  9. func probeQueue(present ...Channel) [][]byte {
  10. queue := make([][]byte, 0, probeChannelMax)
  11. for c := 1; c <= probeChannelMax; c++ {
  12. buf := make([]byte, 32)
  13. if containsChannel(present, Channel(c)) {
  14. buf[0] = byte(CustomGet)
  15. buf[3] = 160
  16. } else {
  17. buf[0] = byte(Unhandled)
  18. }
  19. buf[1] = byte(c)
  20. buf[2] = probeValueID
  21. queue = append(queue, buf)
  22. }
  23. return queue
  24. }
  25. func containsChannel(channels []Channel, want Channel) bool {
  26. for _, c := range channels {
  27. if c == want {
  28. return true
  29. }
  30. }
  31. return false
  32. }
  33. func TestDetectChannelsFindsTheAnsweredChannels(t *testing.T) {
  34. transport := &fakeTransport{queue: probeQueue(ChannelRgblight, ChannelRgbMatrix, ChannelAudio)}
  35. protocol := Protocol{handle: transport}
  36. got, err := protocol.DetectChannels()
  37. if err != nil {
  38. t.Fatalf("DetectChannels() error = %v", err)
  39. }
  40. want := []Channel{ChannelRgblight, ChannelRgbMatrix, ChannelAudio}
  41. if len(got) != len(want) {
  42. t.Fatalf("DetectChannels() = %v, want %v", got, want)
  43. }
  44. for i := range want {
  45. if got[i] != want[i] {
  46. t.Errorf("DetectChannels()[%d] = %d, want %d", i, got[i], want[i])
  47. }
  48. }
  49. }
  50. func TestDetectChannelsAsksBrightnessOnEveryChannelFromOneToFifteen(t *testing.T) {
  51. transport := &fakeTransport{queue: probeQueue()}
  52. protocol := Protocol{handle: transport}
  53. if _, err := protocol.DetectChannels(); err != nil {
  54. t.Fatalf("DetectChannels() error = %v", err)
  55. }
  56. if len(transport.reports) != probeChannelMax {
  57. t.Fatalf("requests = %d, want %d", len(transport.reports), probeChannelMax)
  58. }
  59. for i, report := range transport.reports {
  60. wantChannel := byte(i + 1)
  61. if report[0] != byte(CustomGet) {
  62. t.Errorf("request %d command = 0x%02x, want 0x%02x", i, report[0], CustomGet)
  63. }
  64. if report[1] != wantChannel {
  65. t.Errorf("request %d channel = %d, want %d", i, report[1], wantChannel)
  66. }
  67. if report[2] != probeValueID {
  68. t.Errorf("request %d value = 0x%02x, want 0x%02x (brightness)", i, report[2], probeValueID)
  69. }
  70. }
  71. }
  72. // A channel list missing entries is indistinguishable from a complete one, so a
  73. // transport failure has to abort the probe instead of shrinking it.
  74. func TestDetectChannelsFailsOnATransportErrorMidProbe(t *testing.T) {
  75. transport := &fakeTransport{
  76. queue: probeQueue(ChannelRgblight, ChannelRgbMatrix, ChannelAudio),
  77. readErr: errors.New("read: interrupted system call"),
  78. readErrAfter: 3,
  79. }
  80. protocol := Protocol{handle: transport}
  81. _, err := protocol.DetectChannels()
  82. if err == nil {
  83. t.Fatal("DetectChannels() expected an error, got nil")
  84. }
  85. if !strings.Contains(err.Error(), "channel 4") {
  86. t.Errorf("error = %q, want it to name the channel that failed", err)
  87. }
  88. }
  89. // A 0xFF that matches nothing is a valid "no such channel". Only a 0xFF whose
  90. // echoed channel disagrees is a desynchronised stream.
  91. func TestDetectChannelsAcceptsAWellFormedUnhandledAnswer(t *testing.T) {
  92. transport := &fakeTransport{queue: probeQueue(ChannelRgblight)}
  93. protocol := Protocol{handle: transport}
  94. got, err := protocol.DetectChannels()
  95. if err != nil {
  96. t.Fatalf("DetectChannels() error = %v", err)
  97. }
  98. if len(got) != 1 || got[0] != ChannelRgblight {
  99. t.Errorf("DetectChannels() = %v, want [2]", got)
  100. }
  101. }
  102. func TestDetectChannelsReportsNoChannelsWhenNoneArePresent(t *testing.T) {
  103. transport := &fakeTransport{queue: probeQueue()}
  104. protocol := Protocol{handle: transport}
  105. got, err := protocol.DetectChannels()
  106. if err != nil {
  107. t.Fatalf("DetectChannels() error = %v", err)
  108. }
  109. if len(got) != 0 {
  110. t.Errorf("DetectChannels() = %v, want empty", got)
  111. }
  112. }
  113. // effectResponse is one scripted answer to an effect get or set, carrying the
  114. // value the keyboard is left with.
  115. func effectResponse(command Message, ch Channel, value byte) []byte {
  116. buf := make([]byte, 32)
  117. buf[0] = byte(command)
  118. buf[1] = byte(ch)
  119. buf[2] = effectValueID
  120. buf[3] = value
  121. return buf
  122. }
  123. // QMK clamps rather than rejects: a mode at or above the top comes down to it.
  124. // A single write above the top therefore returns the top itself, which is the
  125. // whole reason the probe is one round trip instead of a search.
  126. func TestEffectTopReadsTheTopOffWhatTheFirmwareClampedTo(t *testing.T) {
  127. const ch = ChannelRgbMatrix
  128. transport := &fakeTransport{queue: [][]byte{
  129. effectResponse(CustomGet, ch, 12), // the effect the channel is on
  130. effectResponse(CustomSet, ch, 255), // the probe, echoed
  131. effectResponse(CustomGet, ch, 45), // what the clamp left
  132. effectResponse(CustomSet, ch, 12), // the restore, echoed
  133. }}
  134. protocol := Protocol{handle: transport}
  135. got, err := protocol.EffectTop(ch)
  136. if err != nil {
  137. t.Fatalf("EffectTop() error = %v", err)
  138. }
  139. if got != 45 {
  140. t.Errorf("EffectTop() = %d, want 45", got)
  141. }
  142. if len(transport.reports) != 4 {
  143. t.Fatalf("requests = %d, want 4 (read, probe, read back, restore)", len(transport.reports))
  144. }
  145. last := transport.reports[3]
  146. if last[0] != byte(CustomSet) || last[3] != 12 {
  147. t.Errorf("last request = %v, want the original effect 12 written back", last[:4])
  148. }
  149. }
  150. // A channel already sitting on its last effect answers the probe with the value
  151. // it held, which is the same number the clamp would give. The two cases need not
  152. // be told apart, so the answer is still the top.
  153. func TestEffectTopReportsTheTopWhenTheChannelAlreadySatOnIt(t *testing.T) {
  154. const ch = ChannelRgbMatrix
  155. transport := &fakeTransport{queue: [][]byte{
  156. effectResponse(CustomGet, ch, 45),
  157. effectResponse(CustomSet, ch, 255),
  158. effectResponse(CustomGet, ch, 45),
  159. effectResponse(CustomSet, ch, 45),
  160. }}
  161. protocol := Protocol{handle: transport}
  162. got, err := protocol.EffectTop(ch)
  163. if err != nil {
  164. t.Fatalf("EffectTop() error = %v", err)
  165. }
  166. if got != 45 {
  167. t.Errorf("EffectTop() = %d, want 45", got)
  168. }
  169. }
  170. // VIA reads a 0 on the effect parameter as "turn this channel off", so the probe
  171. // must not write one. The restore may: a channel that was on effect 0 goes back
  172. // to 0, which is the state it was in.
  173. func TestEffectTopProbesWithoutSwitchingTheChannelOff(t *testing.T) {
  174. if effectProbeValue == 0 {
  175. t.Fatal("effectProbeValue is 0, which switches the channel off instead of asking")
  176. }
  177. const ch = ChannelRgbMatrix
  178. transport := &fakeTransport{queue: [][]byte{
  179. effectResponse(CustomGet, ch, 0),
  180. effectResponse(CustomSet, ch, effectProbeValue),
  181. effectResponse(CustomGet, ch, 6),
  182. effectResponse(CustomSet, ch, 0),
  183. }}
  184. protocol := Protocol{handle: transport}
  185. if _, err := protocol.EffectTop(ch); err != nil {
  186. t.Fatalf("EffectTop() error = %v", err)
  187. }
  188. probe := transport.reports[1]
  189. if probe[0] != byte(CustomSet) || probe[2] != effectValueID {
  190. t.Fatalf("request 1 = %v, want a set on the effect parameter", probe[:4])
  191. }
  192. if probe[3] != effectProbeValue {
  193. t.Errorf("probe wrote effect %d, want %d", probe[3], effectProbeValue)
  194. }
  195. }
  196. // An effect that cannot be read is a channel the probe cannot restore, so it
  197. // stops before it writes rather than leaving the channel on the last effect.
  198. func TestEffectTopStopsBeforeWritingWhenTheEffectCannotBeRead(t *testing.T) {
  199. const ch = ChannelRgbMatrix
  200. transport := &fakeTransport{queue: [][]byte{effectResponse(Unhandled, ch, 0)}}
  201. protocol := Protocol{handle: transport}
  202. if _, err := protocol.EffectTop(ch); err == nil {
  203. t.Fatal("EffectTop() = nil error, want a failure for an effect that cannot be read")
  204. }
  205. if len(transport.reports) != 1 {
  206. t.Errorf("requests = %d, want only the read that failed", len(transport.reports))
  207. }
  208. }
  209. func TestChannelSubsystemNames(t *testing.T) {
  210. tests := []struct {
  211. channel Channel
  212. want string
  213. }{
  214. {ChannelBacklight, "backlight"},
  215. {ChannelRgblight, "rgblight"},
  216. {ChannelRgbMatrix, "rgb_matrix"},
  217. {ChannelAudio, "audio"},
  218. {ChannelLedMatrix, "led_matrix"},
  219. {Channel(9), ""},
  220. }
  221. for _, tt := range tests {
  222. if got := tt.channel.Subsystem(); got != tt.want {
  223. t.Errorf("Channel(%d).Subsystem() = %q, want %q", tt.channel, got, tt.want)
  224. }
  225. }
  226. }
  227. // A stale 0xFF — the answer to an earlier request, still in the read buffer —
  228. // looks exactly like absence. Reading the channel out of it is what tells the
  229. // two apart, and a mismatch means the request/response stream is out of step.
  230. func TestDetectChannelsRejectsAnUnhandledAnswerForAnotherChannel(t *testing.T) {
  231. queue := probeQueue(ChannelRgblight, ChannelRgbMatrix, ChannelAudio)
  232. // Channel 2 answers "unhandled", but for a different channel: the stream
  233. // is answering a request that is not this one.
  234. stale := make([]byte, 32)
  235. stale[0] = byte(Unhandled)
  236. stale[1] = 0x07
  237. stale[2] = probeValueID
  238. queue[1] = stale
  239. transport := &fakeTransport{queue: queue}
  240. protocol := Protocol{handle: transport}
  241. _, err := protocol.DetectChannels()
  242. if err == nil {
  243. t.Fatal("DetectChannels() expected an error for an answer belonging to another channel")
  244. }
  245. if !strings.Contains(err.Error(), "channel 2") {
  246. t.Errorf("error = %q, want it to name the channel whose answer did not match", err)
  247. }
  248. }