commands_test.go 6.3 KB

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