brightness_verify_test.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package main
  2. import (
  3. "bytes"
  4. "errors"
  5. "strings"
  6. "testing"
  7. intrgb "netdome.biz/paul/qmk-rgb/internal/rgb"
  8. "netdome.biz/paul/qmk-rgb/internal/via"
  9. )
  10. // verifyingProtocol records writes and reports what the keyboard actually
  11. // applied, which on real hardware is not always the requested value.
  12. type verifyingProtocol struct {
  13. reports []commandReport
  14. applied map[via.Channel]uint8
  15. channels []via.Channel
  16. getErr error
  17. getCalls int
  18. }
  19. // impact80Display is the channel naming the Impact 80's definition file
  20. // supplies, so summary lines name logo, backlight and side.
  21. func impact80Display() map[uint16]string {
  22. return map[uint16]string{2: "logo", 3: "backlight", 4: "side"}
  23. }
  24. func impact80Channels() []via.Channel {
  25. return []via.Channel{via.ChannelRgblight, via.ChannelRgbMatrix, via.ChannelAudio}
  26. }
  27. // DetectChannels reports the three channels of the board under test unless a
  28. // test asks for a different set.
  29. func (f *verifyingProtocol) DetectChannels() ([]via.Channel, error) {
  30. if f.channels == nil {
  31. return []via.Channel{via.ChannelRgblight, via.ChannelRgbMatrix, via.ChannelAudio}, nil
  32. }
  33. return f.channels, nil
  34. }
  35. func (f *verifyingProtocol) SetValue(channel via.Channel, param, value uint8) error {
  36. f.reports = append(f.reports, commandReport{channel: channel, param: param, value: value})
  37. return nil
  38. }
  39. func (f *verifyingProtocol) SetColor(via.Channel, uint8, uint8) error { return nil }
  40. func (f *verifyingProtocol) GetValue(channel via.Channel, param uint8) ([]byte, error) {
  41. f.getCalls++
  42. if f.getErr != nil {
  43. return nil, f.getErr
  44. }
  45. // The color value ID is two bytes; a one byte answer would make every
  46. // caller that reads a colour fail for the wrong reason.
  47. if param == uint8(intrgb.ColorValue) {
  48. return []byte{f.applied[channel], 255}, nil
  49. }
  50. return []byte{f.applied[channel]}, nil
  51. }
  52. func (f *verifyingProtocol) Close() error { return nil }
  53. func appliedFor(p *verifyingProtocol, channel via.Channel) uint8 { return p.applied[channel] }
  54. func TestSetBrightnessVerifiedReportsAppliedValue(t *testing.T) {
  55. tests := []struct {
  56. name string
  57. requested uint8
  58. applied uint8
  59. wantWarning bool
  60. }{
  61. {"keyboard honours the value", 100, 100, false},
  62. {"keyboard clamps to 160", 200, 160, true},
  63. {"keyboard scales up", 100, 159, true},
  64. {"both zones clamp", 255, 160, true},
  65. }
  66. for _, tt := range tests {
  67. t.Run(tt.name, func(t *testing.T) {
  68. proto := &verifyingProtocol{
  69. applied: map[via.Channel]uint8{
  70. via.ChannelRgblight: tt.applied,
  71. via.ChannelRgbMatrix: tt.applied,
  72. via.ChannelAudio: tt.applied,
  73. },
  74. }
  75. zones := []via.Channel{via.ChannelRgblight}
  76. results, err := setBrightnessVerified(proto, zones, impact80Display(), tt.requested)
  77. if err != nil {
  78. t.Fatalf("setBrightnessVerified() error = %v", err)
  79. }
  80. if len(results) != 1 {
  81. t.Fatalf("results = %d entries, want 1", len(results))
  82. }
  83. got := results[0]
  84. if got.Name != "logo" {
  85. t.Errorf("results[0].Name = %q, want %q", got.Name, "logo")
  86. }
  87. if got.Requested != tt.requested {
  88. t.Errorf("results[0].Requested = %d, want %d", got.Requested, tt.requested)
  89. }
  90. if got.Applied != tt.applied {
  91. t.Errorf("results[0].Applied = %d, want %d", got.Applied, tt.applied)
  92. }
  93. if got.Mismatch() != tt.wantWarning {
  94. t.Errorf("results[0].Mismatch() = %t, want %t", got.Mismatch(), tt.wantWarning)
  95. }
  96. })
  97. }
  98. }
  99. // The read-back must happen once per written zone, on the right channel.
  100. func TestSetBrightnessVerifiedReadsBackEveryZone(t *testing.T) {
  101. proto := &verifyingProtocol{
  102. applied: map[via.Channel]uint8{
  103. via.ChannelRgblight: 160,
  104. via.ChannelRgbMatrix: 255,
  105. via.ChannelAudio: 160,
  106. },
  107. }
  108. results, err := setBrightnessVerified(proto, impact80Channels(), impact80Display(), 200)
  109. if err != nil {
  110. t.Fatalf("setBrightnessVerified() error = %v", err)
  111. }
  112. if len(results) != 3 {
  113. t.Fatalf("results = %d entries, want 3", len(results))
  114. }
  115. if proto.getCalls != 3 {
  116. t.Errorf("GetValue calls = %d, want 3", proto.getCalls)
  117. }
  118. // A set that fails must not be reported as applied.
  119. if got := appliedFor(proto, via.ChannelRgblight); got != 160 {
  120. t.Errorf("unexpected applied value %d", got)
  121. }
  122. want := map[string]uint8{
  123. "logo": 160,
  124. "backlight": 255,
  125. "side": 160,
  126. }
  127. for _, r := range results {
  128. if r.Applied != want[r.Name] {
  129. t.Errorf("%s applied = %d, want %d", r.Name, r.Applied, want[r.Name])
  130. }
  131. if !r.Mismatch() {
  132. t.Errorf("%s: Mismatch() = false, want true (requested 200)", r.Name)
  133. }
  134. }
  135. }
  136. // A failed read-back must not be silently reported as success.
  137. func TestSetBrightnessVerifiedPropagatesReadError(t *testing.T) {
  138. proto := &verifyingProtocol{getErr: errors.New("read timeout")}
  139. if _, err := setBrightnessVerified(proto, []via.Channel{via.ChannelRgblight}, impact80Display(), 100); err == nil {
  140. t.Fatal("setBrightnessVerified() expected read error, got nil")
  141. }
  142. }
  143. func TestBrightnessCommandStaysQuietWhenApplied(t *testing.T) {
  144. proto := &verifyingProtocol{applied: map[via.Channel]uint8{via.ChannelRgblight: 200}}
  145. t.Cleanup(impact80Target(t, proto))
  146. var out, errOut bytes.Buffer
  147. cmd := NewBrightnessCmd()
  148. cmd.SetOut(&out)
  149. cmd.SetErr(&errOut)
  150. cmd.SetArgs([]string{"200"})
  151. if err := cmd.Execute(); err != nil {
  152. t.Fatalf("brightness returned error: %v", err)
  153. }
  154. if errOut.Len() != 0 {
  155. t.Errorf("stderr = %q, want no warning when the keyboard applied the requested value", errOut.String())
  156. }
  157. if !strings.Contains(out.String(), "200") {
  158. t.Errorf("stdout = %q, want the applied value reported", out.String())
  159. }
  160. }