commands_test.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. package main
  2. import (
  3. "errors"
  4. "reflect"
  5. "testing"
  6. intrgb "netdome.biz/paul/qmk-rgb/internal/rgb"
  7. "netdome.biz/paul/qmk-rgb/internal/via"
  8. )
  9. type commandReport struct {
  10. channel via.Channel
  11. param uint8
  12. value uint8
  13. hue uint8
  14. saturation uint8
  15. }
  16. type fakeZoneProtocol struct {
  17. reports []commandReport
  18. failAt int
  19. channels []via.Channel
  20. }
  21. // DetectChannels reports the board's three channels unless a test asks for a
  22. // different set.
  23. func (f *fakeZoneProtocol) DetectChannels() ([]via.Channel, error) {
  24. if f.channels == nil {
  25. return impact80Channels(), nil
  26. }
  27. return f.channels, nil
  28. }
  29. func (f *fakeZoneProtocol) SetValue(channel via.Channel, param, value uint8) error {
  30. if f.failAt >= 0 && f.failAt == len(f.reports) {
  31. return errors.New("write failed")
  32. }
  33. f.reports = append(f.reports, commandReport{
  34. channel: channel,
  35. param: param,
  36. value: value,
  37. })
  38. return nil
  39. }
  40. func (f *fakeZoneProtocol) SetColor(channel via.Channel, hue, saturation uint8) error {
  41. if f.failAt >= 0 && f.failAt == len(f.reports) {
  42. return errors.New("write failed")
  43. }
  44. f.reports = append(f.reports, commandReport{
  45. channel: channel,
  46. param: 4,
  47. hue: hue,
  48. saturation: saturation,
  49. })
  50. return nil
  51. }
  52. // GetValue reports the last value written to a channel, so a command that reads
  53. // back what it set sees a keyboard that accepted it. A channel that was never
  54. // written to still fails, so a read-back without a preceding set stays visible.
  55. func (f *fakeZoneProtocol) GetValue(channel via.Channel, param uint8) ([]byte, error) {
  56. for i := len(f.reports) - 1; i >= 0; i-- {
  57. if f.reports[i].channel == channel && f.reports[i].param == param {
  58. return []byte{f.reports[i].value}, nil
  59. }
  60. }
  61. return nil, errors.New("unexpected GetValue call")
  62. }
  63. func (f *fakeZoneProtocol) Close() error {
  64. return nil
  65. }
  66. func TestSelectedChannelOperations(t *testing.T) {
  67. defaultZones := []via.Channel{via.ChannelRgblight, via.ChannelRgbMatrix, via.ChannelAudio}
  68. sideZones := []via.Channel{via.ChannelAudio}
  69. operations := []struct {
  70. name string
  71. zones []via.Channel
  72. sideZones []via.Channel
  73. run func(zoneProtocol, []via.Channel) error
  74. want []commandReport
  75. wantSide []commandReport
  76. }{
  77. {
  78. name: "brightness",
  79. zones: defaultZones,
  80. sideZones: sideZones,
  81. run: func(protocol zoneProtocol, zones []via.Channel) error {
  82. return setBrightnessOnChannels(protocol, zones, 160)
  83. },
  84. want: []commandReport{
  85. {channel: 2, param: 1, value: 160},
  86. {channel: 3, param: 1, value: 160},
  87. {channel: 4, param: 1, value: 160},
  88. },
  89. wantSide: []commandReport{{channel: 4, param: 1, value: 160}},
  90. },
  91. {
  92. name: "speed",
  93. zones: defaultZones,
  94. sideZones: sideZones,
  95. run: func(protocol zoneProtocol, zones []via.Channel) error {
  96. return setSpeedChannels(protocol, zones, 2)
  97. },
  98. want: []commandReport{
  99. {channel: 2, param: 3, value: 2},
  100. {channel: 3, param: 3, value: 2},
  101. {channel: 4, param: 3, value: 2},
  102. },
  103. wantSide: []commandReport{{channel: 4, param: 3, value: 2}},
  104. },
  105. {
  106. name: "color",
  107. zones: defaultZones,
  108. sideZones: sideZones,
  109. run: func(protocol zoneProtocol, zones []via.Channel) error {
  110. return setColorOnChannels(protocol, zones, 85, 255)
  111. },
  112. want: []commandReport{
  113. {channel: 2, param: 4, hue: 85, saturation: 255},
  114. {channel: 3, param: 4, hue: 85, saturation: 255},
  115. {channel: 4, param: 4, hue: 85, saturation: 255},
  116. },
  117. wantSide: []commandReport{{channel: 4, param: 4, hue: 85, saturation: 255}},
  118. },
  119. {
  120. name: "disable",
  121. zones: defaultZones,
  122. sideZones: sideZones,
  123. run: disableLightingOnChannels,
  124. want: []commandReport{
  125. {channel: 2, param: 2, value: 0},
  126. {channel: 2, param: 1, value: 0},
  127. {channel: 3, param: 2, value: 0},
  128. {channel: 3, param: 1, value: 0},
  129. {channel: 4, param: 2, value: 0},
  130. {channel: 4, param: 1, value: 0},
  131. },
  132. wantSide: []commandReport{
  133. {channel: 4, param: 2, value: 0},
  134. {channel: 4, param: 1, value: 0},
  135. },
  136. },
  137. {
  138. name: "enable",
  139. zones: defaultZones,
  140. sideZones: sideZones,
  141. run: func(protocol zoneProtocol, channels []via.Channel) error {
  142. catalog, _ := intrgb.CatalogFor(0x36B0, 0x309F)
  143. return enableLightingOnChannels(protocol, channels, catalog)
  144. },
  145. want: []commandReport{
  146. {channel: 2, param: 2, value: 4},
  147. {channel: 2, param: 1, value: 160},
  148. {channel: 3, param: 2, value: 5},
  149. {channel: 3, param: 1, value: 160},
  150. {channel: 4, param: 2, value: 4},
  151. {channel: 4, param: 1, value: 160},
  152. },
  153. wantSide: []commandReport{
  154. {channel: 4, param: 2, value: 4},
  155. {channel: 4, param: 1, value: 160},
  156. },
  157. },
  158. {
  159. name: "mode",
  160. zones: defaultZones,
  161. sideZones: sideZones,
  162. run: func(protocol zoneProtocol, zones []via.Channel) error {
  163. return setModeOnChannels(protocol, zones, 17)
  164. },
  165. want: []commandReport{
  166. {channel: 2, param: 2, value: 17},
  167. {channel: 3, param: 2, value: 17},
  168. {channel: 4, param: 2, value: 17},
  169. },
  170. wantSide: []commandReport{{channel: 4, param: 2, value: 17}},
  171. },
  172. }
  173. for _, tc := range operations {
  174. t.Run(tc.name+"/default", func(t *testing.T) {
  175. protocol := &fakeZoneProtocol{failAt: -1}
  176. if err := tc.run(protocol, tc.zones); err != nil {
  177. t.Fatalf("operation error = %v", err)
  178. }
  179. if !reflect.DeepEqual(protocol.reports, tc.want) {
  180. t.Errorf("reports = %v, want %v", protocol.reports, tc.want)
  181. }
  182. })
  183. t.Run(tc.name+"/side", func(t *testing.T) {
  184. protocol := &fakeZoneProtocol{failAt: -1}
  185. if err := tc.run(protocol, tc.sideZones); err != nil {
  186. t.Fatalf("operation error = %v", err)
  187. }
  188. if !reflect.DeepEqual(protocol.reports, tc.wantSide) {
  189. t.Errorf("reports = %v, want %v", protocol.reports, tc.wantSide)
  190. }
  191. })
  192. }
  193. }
  194. func TestSelectedChannelStopsOnFirstError(t *testing.T) {
  195. protocol := &fakeZoneProtocol{failAt: 1}
  196. err := setBrightnessOnChannels(protocol, impact80Channels(), 160)
  197. if err == nil {
  198. t.Fatal("setBrightnessOnChannels() expected error, got nil")
  199. }
  200. if len(protocol.reports) != 1 {
  201. t.Errorf("reports = %v, want one report before failure", protocol.reports)
  202. }
  203. }
  204. // README.md promises the commands that take no arguments reject a stray token.
  205. // keyboard info declared none, so cobra accepted any number of them.
  206. func TestKeyboardInfoRejectsAStrayToken(t *testing.T) {
  207. if keyboardInfoCmd.Args == nil {
  208. t.Fatal("keyboard info declares no Args, so a stray token is accepted")
  209. }
  210. if err := keyboardInfoCmd.Args(keyboardInfoCmd, []string{"bogus"}); err == nil {
  211. t.Error("keyboard info accepted a stray token, want it rejected")
  212. }
  213. if err := keyboardInfoCmd.Args(keyboardInfoCmd, nil); err != nil {
  214. t.Errorf("keyboard info rejected no token: %v", err)
  215. }
  216. }