effect_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. func TestResolveEffectTargetsBacklightOnly(t *testing.T) {
  23. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), "rainbow_moving_chevron", impact80Channels())
  24. if err != nil {
  25. t.Fatalf("resolveEffectTargets() unexpected error: %v", err)
  26. }
  27. wantTargets := []intrgb.EffectTarget{{Channel: via.ChannelRgbMatrix, ID: 17}}
  28. if !reflect.DeepEqual(targets, wantTargets) {
  29. t.Errorf("resolveEffectTargets() targets = %v, want %v", targets, wantTargets)
  30. }
  31. wantSkipped := []string{"rgblight", "audio"}
  32. if !reflect.DeepEqual(skipped, wantSkipped) {
  33. t.Errorf("resolveEffectTargets() skipped = %v, want %v", skipped, wantSkipped)
  34. }
  35. }
  36. func TestResolveEffectTargetsBreathing(t *testing.T) {
  37. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), "breathing", impact80Channels())
  38. if err != nil {
  39. t.Fatalf("resolveEffectTargets() unexpected error: %v", err)
  40. }
  41. wantTargets := []intrgb.EffectTarget{
  42. {Channel: via.ChannelRgblight, ID: 4},
  43. {Channel: via.ChannelRgbMatrix, ID: 5},
  44. {Channel: via.ChannelAudio, ID: 4},
  45. }
  46. if !reflect.DeepEqual(targets, wantTargets) {
  47. t.Errorf("resolveEffectTargets() targets = %v, want %v", targets, wantTargets)
  48. }
  49. if len(skipped) != 0 {
  50. t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped)
  51. }
  52. }
  53. func TestResolveEffectTargetsExplicitUnsupported(t *testing.T) {
  54. cases := []struct {
  55. name string
  56. zones []via.Channel
  57. }{
  58. {"rainbow_moving_chevron", []via.Channel{via.ChannelRgblight}},
  59. {"rainbow_wave", []via.Channel{via.ChannelRgbMatrix}},
  60. {"splash", []via.Channel{via.ChannelAudio}},
  61. }
  62. for _, tc := range cases {
  63. t.Run(tc.name+string(tc.zones[0]), func(t *testing.T) {
  64. // A name the board has but the named channel does not is refused,
  65. // so this only means anything when a zone was actually named.
  66. originalZone := targetZone
  67. t.Cleanup(func() { targetZone = originalZone })
  68. targetZone = tc.zones[0].Subsystem()
  69. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), tc.name, tc.zones)
  70. if err == nil {
  71. t.Fatal("resolveEffectTargets() expected error, got nil")
  72. }
  73. if len(targets) != 0 {
  74. t.Errorf("resolveEffectTargets() targets = %v, want none", targets)
  75. }
  76. if len(skipped) != 0 {
  77. t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped)
  78. }
  79. })
  80. }
  81. }
  82. func TestResolveEffectTargetsUnknownName(t *testing.T) {
  83. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), "not_an_effect", impact80Channels())
  84. if err == nil {
  85. t.Fatal("resolveEffectTargets() expected error, got nil")
  86. }
  87. if len(targets) != 0 {
  88. t.Errorf("resolveEffectTargets() targets = %v, want none", targets)
  89. }
  90. if len(skipped) != 0 {
  91. t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped)
  92. }
  93. }
  94. func TestResolveEffectTargetsStaticCompatibility(t *testing.T) {
  95. targets, skipped, err := resolveEffectTargets(impact80Catalog(t), "static", impact80Channels())
  96. if err != nil {
  97. t.Fatalf("resolveEffectTargets() unexpected error: %v", err)
  98. }
  99. wantTargets := []intrgb.EffectTarget{
  100. {Channel: via.ChannelRgblight, ID: 5},
  101. {Channel: via.ChannelRgbMatrix, ID: 1},
  102. {Channel: via.ChannelAudio, ID: 5},
  103. }
  104. if !reflect.DeepEqual(targets, wantTargets) {
  105. t.Errorf("resolveEffectTargets() targets = %v, want %v", targets, wantTargets)
  106. }
  107. if len(skipped) != 0 {
  108. t.Errorf("resolveEffectTargets() skipped = %v, want none", skipped)
  109. }
  110. }
  111. func executeEffectCommand(t *testing.T, protocol rgbProtocol, target string, args ...string) (string, string, error) {
  112. t.Helper()
  113. originalZone := targetZone
  114. t.Cleanup(func() { targetZone = originalZone })
  115. targetZone = target
  116. t.Cleanup(stubOpenTarget(t, protocol, impact80Display(), impact80Channels(), 0x36B0, 0x309F))
  117. var stdout bytes.Buffer
  118. var stderr bytes.Buffer
  119. cmd := NewEffectCmd()
  120. cmd.SetOut(&stdout)
  121. cmd.SetErr(&stderr)
  122. cmd.SilenceErrors = true
  123. cmd.SilenceUsage = true
  124. cmd.SetArgs(args)
  125. err := cmd.Execute()
  126. return stdout.String(), stderr.String(), err
  127. }
  128. func TestEffectCommandWarnsForSkippedZones(t *testing.T) {
  129. t.Cleanup(vendoredDefinitions(t))
  130. protocol := &fakeZoneProtocol{failAt: -1}
  131. stdout, stderr, err := executeEffectCommand(t, protocol, "", "rainbow_moving_chevron")
  132. if err != nil {
  133. t.Fatalf("Execute() unexpected error: %v", err)
  134. }
  135. if !strings.Contains(stderr, "effect not supported on channel(s): rgblight, audio") {
  136. t.Errorf("stderr = %q, want skipped-zone warning", stderr)
  137. }
  138. if !strings.Contains(stdout, `Effect set to "rainbow_moving_chevron"`) {
  139. t.Errorf("stdout = %q, want success output", stdout)
  140. }
  141. if len(protocol.reports) != 1 {
  142. t.Fatalf("reports = %v, want one Backlight report", protocol.reports)
  143. }
  144. if protocol.reports[0].channel != 3 || protocol.reports[0].param != 2 || protocol.reports[0].value != 17 {
  145. t.Errorf("report = %+v, want Backlight Effect 17", protocol.reports[0])
  146. }
  147. }
  148. func TestEffectCommandWritesNothingForAnUnsupportedEffect(t *testing.T) {
  149. protocol := &fakeZoneProtocol{failAt: -1}
  150. stdout, _, err := executeEffectCommand(t, protocol, "logo", "rainbow_moving_chevron")
  151. if err == nil {
  152. t.Fatal("Execute() expected error, got nil")
  153. }
  154. if len(protocol.reports) != 0 {
  155. t.Errorf("reports = %v, want no writes before rejection", protocol.reports)
  156. }
  157. if stdout != "" {
  158. t.Errorf("stdout = %q, want no success output", stdout)
  159. }
  160. }
  161. func TestEffectCommandSuppressesSuccessAfterLaterWriteFailure(t *testing.T) {
  162. t.Cleanup(vendoredDefinitions(t))
  163. protocol := &fakeZoneProtocol{failAt: 1}
  164. stdout, _, err := executeEffectCommand(t, protocol, "", "breathing")
  165. if err == nil {
  166. t.Fatal("Execute() expected write error, got nil")
  167. }
  168. if len(protocol.reports) != 1 {
  169. t.Fatalf("reports = %v, want one report before failure", protocol.reports)
  170. }
  171. if strings.Contains(stdout, "Effect set") {
  172. t.Errorf("stdout = %q, want success output suppressed", stdout)
  173. }
  174. }