package rolloutwords import ( "fmt" "strings" "testing" "github.com/carlosframework/platform/internal/status" ) func allWords() map[string]string { out := map[string]string{ "EventSwapping": EventSwapping, "EventSwapped": EventSwapped, "EventSwapFailed": EventSwapFailed, "EventHoldDown": EventHoldDown, "EventCohortHalted": EventCohortHalted, "EventFallbackServed": EventFallbackServed, "EventConfigPending": EventConfigPending, "EventStagingRefused": EventStagingRefused, "EventBounceInstance": EventBounceInstance, "EventBounceSidecar": EventBounceSidecar, "StateHeld": StateHeld, "StateRollingOut": StateRollingOut, "StateAsleep": StateAsleep, "StateConfigPending": StateConfigPending, "reasonUnknown": reasonUnknown, } for k, v := range causes { out["cause "+k] = v } for k, v := range reasons { out["reason "+k] = v } // Add unavailability phrases for k, v := range unavailablePhrases { out["unavailable "+k] = v } out["unavailableUnknown"] = unavailableUnknown return out } func TestNoEmDashInAnyWord(t *testing.T) { for name, s := range allWords() { if strings.ContainsRune(s, '—') { t.Errorf("%s carries an em dash: %q", name, s) } } } // The emit sites fill these verbs in this order; an approved rewording must // keep them, and carry no other verb. func TestVerbsAreWhereTheCallersPutThem(t *testing.T) { words := allWords() for name, want := range map[string]int{ "EventSwapping": 2, "EventSwapped": 2, "EventSwapFailed": 3, "EventHoldDown": 1, "EventCohortHalted": 3, "EventFallbackServed": 2, "EventConfigPending": 2, "EventStagingRefused": 1, "EventBounceInstance": 1, "EventBounceSidecar": 1, "StateHeld": 1, "StateRollingOut": 0, "StateAsleep": 1, "StateConfigPending": 0, "reasonUnknown": 1, "unavailableUnknown": 1, } { s := words[name] if strings.Count(s, "%s") != want || strings.Count(s, "%") != want { t.Errorf("%s = %q: want exactly %d %%s verbs and no other verb", name, s, want) } } for name, s := range words { if (strings.HasPrefix(name, "cause ") || strings.HasPrefix(name, "reason ") || strings.HasPrefix(name, "unavailable ")) && strings.Contains(s, "%") { t.Errorf("%s = %q: a phrase is filled into other strings and carries no verb", name, s) } } } func TestEveryCauseAndReasonHasAPhrase(t *testing.T) { for _, c := range []string{"release", "install", "retry", "restart", "config"} { if causes[c] == "" { t.Errorf("cause %q has no phrase", c) } } for _, r := range []string{status.ReasonHealth, status.ReasonSettle, status.ReasonExit, status.ReasonStaging, status.ReasonReused} { if reasons[r] == "" { t.Errorf("reason %q (plan 5's word) has no phrase", r) } } if got := Cause("future"); got != "future" { t.Errorf("Cause(future) = %q, want the word itself", got) } if got, want := Reason("future"), fmt.Sprintf(reasonUnknown, "future"); got != want { t.Errorf("Reason(future) = %q, want %q", got, want) } } func TestEveryUnavailableCodeHasAPhrase(t *testing.T) { // Declined codes for _, code := range []string{"no-cgroup-delegation", "no-generation-control", "generations-off", "host-claims-on", "cgroup-placement-failed"} { phrase := Unavailable(code) if phrase == "" { t.Errorf("Unavailable(%q) = empty string", code) } if phrase == fmt.Sprintf(unavailableUnknown, code) { t.Errorf("Unavailable(%q) fell through to unknown format", code) } if UnavailableRefuses(code) { t.Errorf("UnavailableRefuses(%q) = true, want false", code) } } // Refusing codes for _, code := range []string{"no-deploy-store", "no-release-key", "failure-book-unreadable"} { phrase := Unavailable(code) if phrase == "" { t.Errorf("Unavailable(%q) = empty string", code) } if phrase == fmt.Sprintf(unavailableUnknown, code) { t.Errorf("Unavailable(%q) fell through to unknown format", code) } if !UnavailableRefuses(code) { t.Errorf("UnavailableRefuses(%q) = false, want true", code) } } // Unknown code if got := Unavailable("future"); got != fmt.Sprintf(unavailableUnknown, "future") { t.Errorf("Unavailable(future) = %q, want %q", got, fmt.Sprintf(unavailableUnknown, "future")) } if UnavailableRefuses("future") { t.Errorf("UnavailableRefuses(future) = true, want false") } } // UnavailableDeclines is true for exactly the five codes under which a box // runs today's stop-then-start paths. A refusing code is not one, and nor is // a code this binary does not know: a reader cannot assume a newer box's // problem leaves its instances safe to wake. func TestUnavailableDeclinesOnlyTheKnownDeclinedCodes(t *testing.T) { for _, code := range []string{"no-cgroup-delegation", "no-generation-control", "generations-off", "host-claims-on", "cgroup-placement-failed"} { if !UnavailableDeclines(code) { t.Errorf("UnavailableDeclines(%q) = false, want true", code) } } for _, code := range []string{"no-deploy-store", "no-release-key", "failure-book-unreadable", "future", ""} { if UnavailableDeclines(code) { t.Errorf("UnavailableDeclines(%q) = true, want false", code) } } } func TestHostClaimsDeclineReadsAsADecline(t *testing.T) { if Unavailable("host-claims-on") != unavailableDeclined || !UnavailableDeclines("host-claims-on") || UnavailableRefuses("host-claims-on") { t.Fatal("host-claims-on must read and act as a decline") } } // A box whose arm-time test placement failed (#272) declines like the // others, in the same approved words: it adds no member-facing text. func TestPlacementFailedDeclineReadsAsADecline(t *testing.T) { if Unavailable("cgroup-placement-failed") != unavailableDeclined || !UnavailableDeclines("cgroup-placement-failed") || UnavailableRefuses("cgroup-placement-failed") { t.Fatal("cgroup-placement-failed must read and act as a decline") } }