effect_test.go 6.0 KB

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