effect_id_test.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. package main
  2. import (
  3. "strings"
  4. "testing"
  5. )
  6. // A number is an effect ID, not a name. That is the whole rule: a user typing
  7. // digits means the number, and a name is never written as digits, so the two
  8. // cannot be confused.
  9. func TestEffectTakesAnIDAsWellAsAName(t *testing.T) {
  10. tests := []struct {
  11. name string
  12. argument string
  13. wantValue uint8
  14. wantOutput string
  15. }{
  16. {"an ID the definition names", "13", 13, "Effect set to index 13"},
  17. {"the ID past the end of the definition", "46", 46, "Effect set to index 46"},
  18. {"ID 0, which two channels refuse", "0", 0, "Effect set to index 0"},
  19. }
  20. for _, tt := range tests {
  21. t.Run(tt.name, func(t *testing.T) {
  22. proto := &fakeZoneProtocol{failAt: -1}
  23. t.Cleanup(vendoredDefinitions(t))
  24. stdout, _, err := executeEffectCommand(t, proto, "Backlight", tt.argument)
  25. if err != nil {
  26. t.Fatalf("effect %s: %v", tt.argument, err)
  27. }
  28. if !strings.Contains(stdout, tt.wantOutput) {
  29. t.Errorf("stdout = %q, want it to contain %q", stdout, tt.wantOutput)
  30. }
  31. if len(proto.reports) != 1 {
  32. t.Fatalf("reports = %v, want one write", proto.reports)
  33. }
  34. if got := proto.reports[0]; got.value != tt.wantValue || got.param != 0x02 {
  35. t.Errorf("wrote value %d param 0x%02x, want %d param 0x02", got.value, got.param, tt.wantValue)
  36. }
  37. })
  38. }
  39. }
  40. // The point of taking IDs on the same command: a board with no names is still
  41. // fully drivable, because an ID is the only thing it ever needed.
  42. func TestEffectTakesAnIDOnAKeyboardWithNoNames(t *testing.T) {
  43. proto := &fakeZoneProtocol{failAt: -1}
  44. t.Cleanup(definitionFlagRestore(t))
  45. t.Cleanup(forceDefinitionsDir(t, t.TempDir()))
  46. stdout, _, err := executeEffectCommand(t, proto, "Backlight", "46")
  47. if err != nil {
  48. t.Fatalf("effect 46 without a definition: %v", err)
  49. }
  50. if len(proto.reports) != 1 || proto.reports[0].value != 46 {
  51. t.Errorf("reports = %v, want one write of 46", proto.reports)
  52. }
  53. if !strings.Contains(stdout, "Effect set to index 46") {
  54. t.Errorf("stdout = %q, want the ID form reported", stdout)
  55. }
  56. }
  57. // A number that cannot be an effect ID is a number that cannot be used, and the
  58. // message says so rather than looking for a name nobody wrote.
  59. func TestEffectSaysWhenAnIDIsOutOfRange(t *testing.T) {
  60. proto := &fakeZoneProtocol{failAt: -1}
  61. t.Cleanup(vendoredDefinitions(t))
  62. _, _, err := executeEffectCommand(t, proto, "Backlight", "300")
  63. if err == nil {
  64. t.Fatal("effect 300 = nil error, want a range error")
  65. }
  66. if !strings.Contains(err.Error(), "0-255") {
  67. t.Errorf("error = %q, want it to name the range", err)
  68. }
  69. if len(proto.reports) != 0 {
  70. t.Errorf("reports = %v, want nothing written for a value that cannot be one", proto.reports)
  71. }
  72. }
  73. // A name still resolves by name, and still goes through the alias table.
  74. func TestEffectStillTakesAName(t *testing.T) {
  75. proto := &fakeZoneProtocol{failAt: -1}
  76. t.Cleanup(vendoredDefinitions(t))
  77. stdout, _, err := executeEffectCommand(t, proto, "Backlight", "rainbow_moving_chevron")
  78. if err != nil {
  79. t.Fatalf("effect by name: %v", err)
  80. }
  81. if len(proto.reports) != 1 || proto.reports[0].value != 17 {
  82. t.Errorf("reports = %v, want one write of 17", proto.reports)
  83. }
  84. if stdout != "Effect set to \"rainbow_moving_chevron\"\n" {
  85. t.Errorf("stdout = %q, want the plain success line", stdout)
  86. }
  87. }
  88. // An unknown name is still an unknown name, and the message points at the ID form
  89. // rather than at a command that no longer exists. The ID form it points at is
  90. // `effect <zone> <index>`: the zone is the first argument and is required, so the
  91. // advice has to be an invocation that actually runs.
  92. func TestUnknownNameErrorPointsAtTheIDForm(t *testing.T) {
  93. proto := &fakeZoneProtocol{failAt: -1}
  94. t.Cleanup(vendoredDefinitions(t))
  95. _, _, err := executeEffectCommand(t, proto, "Backlight", "nonsense")
  96. if err == nil {
  97. t.Fatal("effect nonsense = nil error, want one")
  98. }
  99. if strings.Contains(err.Error(), "`mode") {
  100. t.Errorf("error = %q, want no reference to a removed command", err)
  101. }
  102. if !strings.Contains(err.Error(), "effect <zone> <index>") {
  103. t.Errorf("error = %q, want it to point at the ID form", err)
  104. }
  105. if strings.Contains(err.Error(), "`effect <index>`") {
  106. t.Errorf("error = %q, want the zone spelled out: a bare `effect <index>` is read as a channel name", err)
  107. }
  108. }
  109. // The mode command is gone: an ID goes through the command that also takes names.
  110. func TestModeCommandIsGone(t *testing.T) {
  111. root := newRootCommand()
  112. registerCommands(root)
  113. registerFlagCompletions(root)
  114. for _, c := range root.Commands() {
  115. if c.Name() == "mode" {
  116. t.Fatal("the mode command is still registered; effect takes an ID now")
  117. }
  118. }
  119. }