effect_test.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. package main
  2. import (
  3. "bytes"
  4. "path/filepath"
  5. "reflect"
  6. "strings"
  7. "testing"
  8. intrgb "netdome.biz/paul/qmk-rgb/internal/rgb"
  9. "netdome.biz/paul/qmk-rgb/internal/via"
  10. )
  11. // impact80Catalog is the catalog of the board these cases describe, which comes
  12. // from the vendored definition file: that file is where every effect name now
  13. // comes from.
  14. func impact80Catalog(t *testing.T) *intrgb.Catalog {
  15. t.Helper()
  16. def, err := intrgb.LoadDefinition(filepath.Join("..", "..", "definitions", "impact80.json"))
  17. if err != nil {
  18. t.Fatalf("LoadDefinition(definitions/impact80.json) error = %v", err)
  19. }
  20. return def.Catalog
  21. }
  22. // A name the board has but a named channel does not is refused, however many
  23. // channels can do it. `effect all` asks for every channel, so a name only one of
  24. // them has is a request the tool cannot carry out rather than one channel written
  25. // and reported as done.
  26. func TestResolveEffectTargetsRefusesANameNotEveryNamedChannelHas(t *testing.T) {
  27. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), "rainbow_moving_chevron", impact80Channels(), true)
  28. if err == nil {
  29. t.Fatal("resolveEffectTargets() expected error, got nil")
  30. }
  31. if len(targets) != 0 {
  32. t.Errorf("resolveEffectTargets() targets = %v, want none", targets)
  33. }
  34. if len(skipped) != 0 {
  35. t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped)
  36. }
  37. }
  38. // Without a channel named, a name the board does not have everywhere is skipped
  39. // with a warning, which is the shape `load` uses when it applies a whole profile.
  40. func TestResolveEffectTargetsSkipsWhenNoChannelWasNamed(t *testing.T) {
  41. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), "rainbow_moving_chevron", impact80Channels(), false)
  42. if err != nil {
  43. t.Fatalf("resolveEffectTargets() unexpected error: %v", err)
  44. }
  45. wantTargets := []intrgb.EffectTarget{{Channel: via.ChannelRgbMatrix, ID: 17}}
  46. if !reflect.DeepEqual(targets, wantTargets) {
  47. t.Errorf("resolveEffectTargets() targets = %v, want %v", targets, wantTargets)
  48. }
  49. wantSkipped := []string{"rgblight", "audio"}
  50. if !reflect.DeepEqual(skipped, wantSkipped) {
  51. t.Errorf("resolveEffectTargets() skipped = %v, want %v", skipped, wantSkipped)
  52. }
  53. }
  54. func TestResolveEffectTargetsBreathing(t *testing.T) {
  55. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), "breathing", impact80Channels(), true)
  56. if err != nil {
  57. t.Fatalf("resolveEffectTargets() unexpected error: %v", err)
  58. }
  59. wantTargets := []intrgb.EffectTarget{
  60. {Channel: via.ChannelRgblight, ID: 4},
  61. {Channel: via.ChannelRgbMatrix, ID: 5},
  62. {Channel: via.ChannelAudio, ID: 4},
  63. }
  64. if !reflect.DeepEqual(targets, wantTargets) {
  65. t.Errorf("resolveEffectTargets() targets = %v, want %v", targets, wantTargets)
  66. }
  67. if len(skipped) != 0 {
  68. t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped)
  69. }
  70. }
  71. func TestResolveEffectTargetsExplicitUnsupported(t *testing.T) {
  72. cases := []struct {
  73. name string
  74. zones []via.Channel
  75. }{
  76. {"rainbow_moving_chevron", []via.Channel{via.ChannelRgblight}},
  77. {"rainbow_wave", []via.Channel{via.ChannelRgbMatrix}},
  78. {"splash", []via.Channel{via.ChannelAudio}},
  79. }
  80. for _, tc := range cases {
  81. t.Run(tc.name+string(tc.zones[0]), func(t *testing.T) {
  82. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), tc.name, tc.zones, true)
  83. if err == nil {
  84. t.Fatal("resolveEffectTargets() expected error, got nil")
  85. }
  86. if len(targets) != 0 {
  87. t.Errorf("resolveEffectTargets() targets = %v, want none", targets)
  88. }
  89. if len(skipped) != 0 {
  90. t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped)
  91. }
  92. })
  93. }
  94. }
  95. func TestResolveEffectTargetsUnknownName(t *testing.T) {
  96. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), "not_an_effect", impact80Channels(), true)
  97. if err == nil {
  98. t.Fatal("resolveEffectTargets() expected error, got nil")
  99. }
  100. if len(targets) != 0 {
  101. t.Errorf("resolveEffectTargets() targets = %v, want none", targets)
  102. }
  103. if len(skipped) != 0 {
  104. t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped)
  105. }
  106. }
  107. func TestResolveEffectTargetsStaticCompatibility(t *testing.T) {
  108. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), "static", impact80Channels(), true)
  109. if err != nil {
  110. t.Fatalf("resolveEffectTargets() unexpected error: %v", err)
  111. }
  112. wantTargets := []intrgb.EffectTarget{
  113. {Channel: via.ChannelRgblight, ID: 5},
  114. {Channel: via.ChannelRgbMatrix, ID: 1},
  115. {Channel: via.ChannelAudio, ID: 5},
  116. }
  117. if !reflect.DeepEqual(targets, wantTargets) {
  118. t.Errorf("resolveEffectTargets() targets = %v, want %v", targets, wantTargets)
  119. }
  120. if len(skipped) != 0 {
  121. t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped)
  122. }
  123. }
  124. func executeEffectCommand(t *testing.T, protocol rgbProtocol, zone string, args ...string) (string, string, error) {
  125. t.Helper()
  126. t.Cleanup(stubOpenTarget(t, protocol, impact80Display(), impact80Channels(), 0x36B0, 0x309F))
  127. var stdout bytes.Buffer
  128. var stderr bytes.Buffer
  129. cmd := NewEffectCmd()
  130. cmd.SetOut(&stdout)
  131. cmd.SetErr(&stderr)
  132. cmd.SilenceErrors = true
  133. cmd.SilenceUsage = true
  134. // The zone is the first argument, so the effect name follows it, which is
  135. // the spelling a user would type.
  136. cmd.SetArgs(append([]string{zone}, args...))
  137. err := cmd.Execute()
  138. return stdout.String(), stderr.String(), err
  139. }
  140. // `effect all breathing` reaches every channel, because the name means the same
  141. // index on all three subsystems of this board.
  142. func TestEffectAllWritesEveryChannelTheNameIsOn(t *testing.T) {
  143. t.Cleanup(vendoredDefinitions(t))
  144. protocol := &fakeZoneProtocol{failAt: -1}
  145. stdout, _, err := executeEffectCommand(t, protocol, "all", "breathing")
  146. if err != nil {
  147. t.Fatalf("Execute() unexpected error: %v", err)
  148. }
  149. if !strings.Contains(stdout, `Effect set to "breathing"`) {
  150. t.Errorf("stdout = %q, want success output", stdout)
  151. }
  152. if len(protocol.reports) != 3 {
  153. t.Fatalf("reports = %v, want one per channel", protocol.reports)
  154. }
  155. want := []commandReport{
  156. {channel: 2, param: 2, value: 4},
  157. {channel: 3, param: 2, value: 5},
  158. {channel: 4, param: 2, value: 4},
  159. }
  160. if !reflect.DeepEqual(protocol.reports, want) {
  161. t.Errorf("reports = %v, want %v", protocol.reports, want)
  162. }
  163. }
  164. // Several zones in one argument reach exactly those channels and no others.
  165. func TestEffectWritesEveryChannelOfAZoneList(t *testing.T) {
  166. t.Cleanup(vendoredDefinitions(t))
  167. protocol := &fakeZoneProtocol{failAt: -1}
  168. _, _, err := executeEffectCommand(t, protocol, "side,logo", "breathing")
  169. if err != nil {
  170. t.Fatalf("Execute() unexpected error: %v", err)
  171. }
  172. want := []commandReport{
  173. {channel: 2, param: 2, value: 4},
  174. {channel: 4, param: 2, value: 4},
  175. }
  176. if !reflect.DeepEqual(protocol.reports, want) {
  177. t.Errorf("reports = %v, want %v", protocol.reports, want)
  178. }
  179. }
  180. func TestEffectCommandWritesNothingForAnUnsupportedEffect(t *testing.T) {
  181. protocol := &fakeZoneProtocol{failAt: -1}
  182. stdout, _, err := executeEffectCommand(t, protocol, "logo", "rainbow_moving_chevron")
  183. if err == nil {
  184. t.Fatal("Execute() expected error, got nil")
  185. }
  186. if len(protocol.reports) != 0 {
  187. t.Errorf("reports = %v, want no writes before rejection", protocol.reports)
  188. }
  189. if stdout != "" {
  190. t.Errorf("stdout = %q, want no success output", stdout)
  191. }
  192. }
  193. func TestEffectCommandSuppressesSuccessAfterLaterWriteFailure(t *testing.T) {
  194. t.Cleanup(vendoredDefinitions(t))
  195. protocol := &fakeZoneProtocol{failAt: 1}
  196. stdout, _, err := executeEffectCommand(t, protocol, "all", "breathing")
  197. if err == nil {
  198. t.Fatal("Execute() expected write error, got nil")
  199. }
  200. if len(protocol.reports) != 1 {
  201. t.Fatalf("reports = %v, want one report before failure", protocol.reports)
  202. }
  203. if strings.Contains(stdout, "Effect set") {
  204. t.Errorf("stdout = %q, want success output suppressed", stdout)
  205. }
  206. }