package main import ( "flag" "io" "strings" "testing" ) func quietFlagSet(name string) *flag.FlagSet { fs := flag.NewFlagSet(name, flag.ContinueOnError) fs.SetOutput(io.Discard) return fs } // Every spelling of the app flag lands in the same variable. func TestAppFlagParsesAllSpellings(t *testing.T) { for _, args := range [][]string{ {"-app", "blah"}, {"--app", "blah"}, {"-app=blah"}, {"--app=blah"}, {"-a", "blah"}, {"-a=blah"}, } { fs := quietFlagSet("t") app := appFlag(fs, "") if err := fs.Parse(args); err != nil { t.Fatalf("%v: %v", args, err) } if *app != "blah" { t.Fatalf("%v: app=%q, want blah", args, *app) } } } // The four helpers each claim exactly their ruled letter. func TestClaimedLetters(t *testing.T) { fs := quietFlagSet("t") appFlag(fs, "") channelFlag(fs, "") environmentFlag(fs, "") versionFlag(fs, "") for primary, short := range map[string]string{ "app": "a", "channel": "c", "environment": "e", "version": "v", } { if fs.Lookup(short) == nil { t.Errorf("-%s not registered for --%s", short, primary) } } } // The default-value trap: registering the alias must not reset a // non-empty primary default (StringVar assigns through the pointer). func TestAliasKeepsThePrimaryDefault(t *testing.T) { fs := quietFlagSet("t") ch := aliasedString(fs, "channel", "stable", "release channel to follow") if err := fs.Parse(nil); err != nil { t.Fatal(err) } if *ch != "stable" { t.Fatalf("default clobbered by alias registration: %q", *ch) } fs2 := quietFlagSet("t") ch2 := aliasedString(fs2, "channel", "stable", "") if err := fs2.Parse([]string{"-c", "edge"}); err != nil { t.Fatal(err) } if *ch2 != "edge" { t.Fatalf("-c did not assign: %q", *ch2) } } // An empty usage argument takes the flag's common wording. func TestHelperDefaultUsage(t *testing.T) { fs := quietFlagSet("t") appFlag(fs, "") if got := fs.Lookup("app").Usage; got != "app name" { t.Fatalf("default usage = %q", got) } fs2 := quietFlagSet("t") appFlag(fs2, "app the instance belongs to") if got := fs2.Lookup("app").Usage; got != "app the instance belongs to" { t.Fatalf("bespoke usage lost: %q", got) } } // fs.Visit reports the spelling actually typed, so asking about the // primary alone goes false when -a was given. flagWasSet must be true // for every spelling — the deploy wizard's call sites depend on it. func TestFlagWasSetSeesEverySpelling(t *testing.T) { for _, args := range [][]string{ {"-app", "blah"}, {"--app", "blah"}, {"-a", "blah"}, } { fs := quietFlagSet("t") appFlag(fs, "") if err := fs.Parse(args); err != nil { t.Fatal(err) } if !flagWasSet(fs, "app") { t.Errorf("%v: flagWasSet(app) = false", args) } } fs := quietFlagSet("t") appFlag(fs, "") fs.String("kind", "", "artifact kind") if err := fs.Parse([]string{"-a", "blah"}); err != nil { t.Fatal(err) } if flagWasSet(fs, "kind") { t.Error("kind reported set when only -a was typed") } } func printedUsage(fs *flag.FlagSet, header string) string { var b strings.Builder fs.SetOutput(&b) flagUsage(fs, header)() return b.String() } // One row per flag: the alias folds into its primary, long names render // with --, claimed single-letter primaries (-f, -n, -y) keep one dash, // and the alias never gets a row of its own. func TestFlagUsageFoldsAliases(t *testing.T) { fs := quietFlagSet("t") appFlag(fs, "") fs.Bool("f", false, "follow: poll for new lines until interrupted") fs.String("console", "", "console to work through") out := printedUsage(fs, "usage: carlos logs [flags] ") for _, want := range []string{ "usage: carlos logs [flags] ", "--app, -a ", "app name", "--console ", "\n -f ", } { if !strings.Contains(out, want) { t.Errorf("missing %q in:\n%s", want, out) } } if strings.Contains(out, "alias for") { t.Errorf("alias got its own row:\n%s", out) } if strings.Contains(out, "--f") { t.Errorf("single-letter primary rendered double-dash:\n%s", out) } } // Non-zero defaults are shown; zero-value defaults ("" / false / 0) are // noise and are not. func TestFlagUsageShowsRealDefaultsOnly(t *testing.T) { fs := quietFlagSet("t") aliasedString(fs, "channel", "stable", "release channel to follow") fs.Int("n", 0, "at most this many lines") var fields multiFlag fs.Var(&fields, "field", "only structured lines with this k=v field (repeatable)") out := printedUsage(fs, "") if !strings.Contains(out, "(default: stable)") { t.Errorf("stable default not shown:\n%s", out) } if strings.Contains(out, "default: 0") { t.Errorf("zero default shown:\n%s", out) } if strings.Contains(out, "default: []") { t.Errorf("empty repeatable default shown:\n%s", out) } } // A command that registers its own single-letter flag colliding with a // claimed letter — without the registrar — must get honest help: no fold // (the pair shares no value), and both rows printed. The fold is // recognised by the alias's marker usage, not by name alone. func TestFlagUsageDoesNotFoldAnImpostorLetter(t *testing.T) { fs := quietFlagSet("t") fs.String("version", "", "version to act on") fs.Bool("v", false, "chatty output") out := printedUsage(fs, "") if !strings.Contains(out, "--version ") { t.Errorf("--version row missing or wrongly folded:\n%s", out) } if strings.Contains(out, "--version, -v") { t.Errorf("folded -v into --version though they share no value:\n%s", out) } if !strings.Contains(out, "\n -v ") { t.Errorf("-v row hidden:\n%s", out) } }