| 1 | package rolloutwords |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/status" |
| 9 | ) |
| 10 | |
| 11 | func allWords() map[string]string { |
| 12 | out := map[string]string{ |
| 13 | "EventSwapping": EventSwapping, "EventSwapped": EventSwapped, "EventSwapFailed": EventSwapFailed, |
| 14 | "EventHoldDown": EventHoldDown, "EventCohortHalted": EventCohortHalted, |
| 15 | "EventFallbackServed": EventFallbackServed, "EventConfigPending": EventConfigPending, |
| 16 | "EventStagingRefused": EventStagingRefused, "EventBounceInstance": EventBounceInstance, |
| 17 | "EventBounceSidecar": EventBounceSidecar, "StateHeld": StateHeld, "StateRollingOut": StateRollingOut, |
| 18 | "StateAsleep": StateAsleep, "StateConfigPending": StateConfigPending, "reasonUnknown": reasonUnknown, |
| 19 | } |
| 20 | for k, v := range causes { |
| 21 | out["cause "+k] = v |
| 22 | } |
| 23 | for k, v := range reasons { |
| 24 | out["reason "+k] = v |
| 25 | } |
| 26 | // Add unavailability phrases |
| 27 | for k, v := range unavailablePhrases { |
| 28 | out["unavailable "+k] = v |
| 29 | } |
| 30 | out["unavailableUnknown"] = unavailableUnknown |
| 31 | return out |
| 32 | } |
| 33 | |
| 34 | func TestNoEmDashInAnyWord(t *testing.T) { |
| 35 | for name, s := range allWords() { |
| 36 | if strings.ContainsRune(s, '—') { |
| 37 | t.Errorf("%s carries an em dash: %q", name, s) |
| 38 | } |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | // The emit sites fill these verbs in this order; an approved rewording must |
| 43 | // keep them, and carry no other verb. |
| 44 | func TestVerbsAreWhereTheCallersPutThem(t *testing.T) { |
| 45 | words := allWords() |
| 46 | for name, want := range map[string]int{ |
| 47 | "EventSwapping": 2, "EventSwapped": 2, "EventSwapFailed": 3, "EventHoldDown": 1, |
| 48 | "EventCohortHalted": 3, "EventFallbackServed": 2, "EventConfigPending": 2, |
| 49 | "EventStagingRefused": 1, "EventBounceInstance": 1, "EventBounceSidecar": 1, |
| 50 | "StateHeld": 1, "StateRollingOut": 0, "StateAsleep": 1, "StateConfigPending": 0, "reasonUnknown": 1, |
| 51 | "unavailableUnknown": 1, |
| 52 | } { |
| 53 | s := words[name] |
| 54 | if strings.Count(s, "%s") != want || strings.Count(s, "%") != want { |
| 55 | t.Errorf("%s = %q: want exactly %d %%s verbs and no other verb", name, s, want) |
| 56 | } |
| 57 | } |
| 58 | for name, s := range words { |
| 59 | if (strings.HasPrefix(name, "cause ") || strings.HasPrefix(name, "reason ") || strings.HasPrefix(name, "unavailable ")) && strings.Contains(s, "%") { |
| 60 | t.Errorf("%s = %q: a phrase is filled into other strings and carries no verb", name, s) |
| 61 | } |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | func TestEveryCauseAndReasonHasAPhrase(t *testing.T) { |
| 66 | for _, c := range []string{"release", "install", "retry", "restart", "config"} { |
| 67 | if causes[c] == "" { |
| 68 | t.Errorf("cause %q has no phrase", c) |
| 69 | } |
| 70 | } |
| 71 | for _, r := range []string{status.ReasonHealth, status.ReasonSettle, status.ReasonExit, status.ReasonStaging, status.ReasonReused} { |
| 72 | if reasons[r] == "" { |
| 73 | t.Errorf("reason %q (plan 5's word) has no phrase", r) |
| 74 | } |
| 75 | } |
| 76 | if got := Cause("future"); got != "future" { |
| 77 | t.Errorf("Cause(future) = %q, want the word itself", got) |
| 78 | } |
| 79 | if got, want := Reason("future"), fmt.Sprintf(reasonUnknown, "future"); got != want { |
| 80 | t.Errorf("Reason(future) = %q, want %q", got, want) |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | func TestEveryUnavailableCodeHasAPhrase(t *testing.T) { |
| 85 | // Declined codes |
| 86 | for _, code := range []string{"no-cgroup-delegation", "no-generation-control", "generations-off", "host-claims-on", "cgroup-placement-failed"} { |
| 87 | phrase := Unavailable(code) |
| 88 | if phrase == "" { |
| 89 | t.Errorf("Unavailable(%q) = empty string", code) |
| 90 | } |
| 91 | if phrase == fmt.Sprintf(unavailableUnknown, code) { |
| 92 | t.Errorf("Unavailable(%q) fell through to unknown format", code) |
| 93 | } |
| 94 | if UnavailableRefuses(code) { |
| 95 | t.Errorf("UnavailableRefuses(%q) = true, want false", code) |
| 96 | } |
| 97 | } |
| 98 | // Refusing codes |
| 99 | for _, code := range []string{"no-deploy-store", "no-release-key", "failure-book-unreadable"} { |
| 100 | phrase := Unavailable(code) |
| 101 | if phrase == "" { |
| 102 | t.Errorf("Unavailable(%q) = empty string", code) |
| 103 | } |
| 104 | if phrase == fmt.Sprintf(unavailableUnknown, code) { |
| 105 | t.Errorf("Unavailable(%q) fell through to unknown format", code) |
| 106 | } |
| 107 | if !UnavailableRefuses(code) { |
| 108 | t.Errorf("UnavailableRefuses(%q) = false, want true", code) |
| 109 | } |
| 110 | } |
| 111 | // Unknown code |
| 112 | if got := Unavailable("future"); got != fmt.Sprintf(unavailableUnknown, "future") { |
| 113 | t.Errorf("Unavailable(future) = %q, want %q", got, fmt.Sprintf(unavailableUnknown, "future")) |
| 114 | } |
| 115 | if UnavailableRefuses("future") { |
| 116 | t.Errorf("UnavailableRefuses(future) = true, want false") |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | // UnavailableDeclines is true for exactly the five codes under which a box |
| 121 | // runs today's stop-then-start paths. A refusing code is not one, and nor is |
| 122 | // a code this binary does not know: a reader cannot assume a newer box's |
| 123 | // problem leaves its instances safe to wake. |
| 124 | func TestUnavailableDeclinesOnlyTheKnownDeclinedCodes(t *testing.T) { |
| 125 | for _, code := range []string{"no-cgroup-delegation", "no-generation-control", "generations-off", "host-claims-on", "cgroup-placement-failed"} { |
| 126 | if !UnavailableDeclines(code) { |
| 127 | t.Errorf("UnavailableDeclines(%q) = false, want true", code) |
| 128 | } |
| 129 | } |
| 130 | for _, code := range []string{"no-deploy-store", "no-release-key", "failure-book-unreadable", "future", ""} { |
| 131 | if UnavailableDeclines(code) { |
| 132 | t.Errorf("UnavailableDeclines(%q) = true, want false", code) |
| 133 | } |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | func TestHostClaimsDeclineReadsAsADecline(t *testing.T) { |
| 138 | if Unavailable("host-claims-on") != unavailableDeclined || !UnavailableDeclines("host-claims-on") || UnavailableRefuses("host-claims-on") { |
| 139 | t.Fatal("host-claims-on must read and act as a decline") |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // A box whose arm-time test placement failed (#272) declines like the |
| 144 | // others, in the same approved words: it adds no member-facing text. |
| 145 | func TestPlacementFailedDeclineReadsAsADecline(t *testing.T) { |
| 146 | if Unavailable("cgroup-placement-failed") != unavailableDeclined || !UnavailableDeclines("cgroup-placement-failed") || UnavailableRefuses("cgroup-placement-failed") { |
| 147 | t.Fatal("cgroup-placement-failed must read and act as a decline") |
| 148 | } |
| 149 | } |
| 150 | |