211 lines
8.1 KiB
Go
211 lines
8.1 KiB
Go
|
|
package commandkit
|
||
|
|
|
||
|
|
import (
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"gitea.maximumdirect.net/eric/CommandKit/internal/commandpath"
|
||
|
|
)
|
||
|
|
|
||
|
|
func flagAliasCommand(name string, flags ...Flag[struct{}]) Command {
|
||
|
|
return NewCommand(CommandSpec[struct{}]{
|
||
|
|
Name: name, Summary: name, Flags: flags,
|
||
|
|
Run: func(Invocation, struct{}) Outcome { return Success() },
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func stringFlag(name, short string) Flag[struct{}] {
|
||
|
|
return ValueFlag(FlagMeta{Name: name, Short: short}, (*string)(nil),
|
||
|
|
func(value string) (string, error) { return value, nil },
|
||
|
|
func(*struct{}, Supplied[string]) {},
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFlagAliasChainsUseScopedOverridesAndCanonicalEndpoints(t *testing.T) {
|
||
|
|
inspect := flagAliasCommand("inspect", stringFlag("color", "c"), stringFlag("format", "f"))
|
||
|
|
show := flagAliasCommand("show", stringFlag("color", "c"), stringFlag("format", "f"))
|
||
|
|
group := Group("Tools", "tools", show)
|
||
|
|
app, err := New(Identity{Name: "Example"},
|
||
|
|
inspect,
|
||
|
|
group,
|
||
|
|
Alias("look", "inspect"),
|
||
|
|
FlagAlias("--colour", "--hue"),
|
||
|
|
FlagAlias("--hue", "--color"),
|
||
|
|
FlagAlias("-x", "--color"),
|
||
|
|
FlagAlias("--tint", "-c"),
|
||
|
|
FlagAliasFor("Tools", "--group-help", "--help"),
|
||
|
|
FlagAliasFor("inspect", "--hue", "--format"),
|
||
|
|
FlagAliasFor("inspect", "--local", "--format"),
|
||
|
|
)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
inspectAliases := app.flagAliases[commandpath.Path{"inspect"}.Key()].bySpelling
|
||
|
|
for source, target := range map[string]string{
|
||
|
|
"--colour": "--format",
|
||
|
|
"--hue": "--format",
|
||
|
|
"-x": "--color",
|
||
|
|
"--tint": "-c",
|
||
|
|
"--local": "--format",
|
||
|
|
} {
|
||
|
|
alias, ok := inspectAliases[flagSpellingKey(capturedSpelling(source))]
|
||
|
|
if !ok || alias.target.token() != target {
|
||
|
|
t.Errorf("inspect alias %s = (%s, %v), want %s", source, alias.target.token(), ok, target)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
groupAliases := app.flagAliases[commandpath.Path{"Tools"}.Key()].bySpelling
|
||
|
|
if _, active := groupAliases[flagSpellingKey(capturedFlagSpelling{name: "colour"})]; active {
|
||
|
|
t.Error("global alias was active on a group without its destination flag")
|
||
|
|
}
|
||
|
|
if _, inherited := groupAliases[flagSpellingKey(capturedFlagSpelling{name: "local"})]; inherited {
|
||
|
|
t.Fatal("command-scoped alias was inherited by its parent group")
|
||
|
|
}
|
||
|
|
if _, inherited := app.flagAliases[commandpath.Path{"Tools", "show"}.Key()].bySpelling[flagSpellingKey(capturedFlagSpelling{name: "local"})]; inherited {
|
||
|
|
t.Fatal("command-scoped alias was inherited by a different command")
|
||
|
|
}
|
||
|
|
groupHelp := flagSpellingKey(capturedFlagSpelling{name: "group-help"})
|
||
|
|
if got := groupAliases[groupHelp].target.token(); got != "--help" {
|
||
|
|
t.Fatalf("group-scoped help alias target = %s, want --help", got)
|
||
|
|
}
|
||
|
|
if _, inherited := app.flagAliases[commandpath.Path{"Tools", "show"}.Key()].bySpelling[groupHelp]; inherited {
|
||
|
|
t.Fatal("group-scoped alias was inherited by its child command")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFlagAliasDiscoveryContextsUseOnlyGlobalHelpAliases(t *testing.T) {
|
||
|
|
show := flagAliasCommand("show")
|
||
|
|
app, err := New(Identity{Name: "Example"},
|
||
|
|
Group("Tools", "tools", show),
|
||
|
|
Alias("shortcut show", "Tools show"),
|
||
|
|
FlagAlias("--hlep", "--help"),
|
||
|
|
FlagAliasFor("Tools show", "--local-help", "--help"),
|
||
|
|
)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
globalHelp := flagSpellingKey(capturedFlagSpelling{name: "hlep"})
|
||
|
|
if got := app.flagAliases[commandpath.Path(nil).Key()].bySpelling[globalHelp].target.token(); got != "--help" {
|
||
|
|
t.Fatalf("root --hlep target = %s, want --help", got)
|
||
|
|
}
|
||
|
|
if got := app.flagAliases[commandpath.Path{"Tools"}.Key()].bySpelling[globalHelp].target.token(); got != "--help" {
|
||
|
|
t.Fatalf("group --hlep target = %s, want --help", got)
|
||
|
|
}
|
||
|
|
discovery := app.discoveryFlags[commandpath.Path{"shortcut"}.Key()].bySpelling
|
||
|
|
if got := discovery[globalHelp].target.token(); got != "--help" {
|
||
|
|
t.Fatalf("discovery --hlep target = %s, want --help", got)
|
||
|
|
}
|
||
|
|
if _, inherited := discovery[flagSpellingKey(capturedFlagSpelling{name: "local-help"})]; inherited {
|
||
|
|
t.Fatal("discovery context inherited a command-scoped alias")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFlagAliasDuplicatesAndScopesAreDeterministic(t *testing.T) {
|
||
|
|
command := flagAliasCommand("inspect", stringFlag("color", "c"), stringFlag("format", "f"))
|
||
|
|
duplicate := FlagAlias("--colour", "--color")
|
||
|
|
app, err := New(Identity{Name: "Example"}, command, flagAliasCommand("plain", stringFlag("color", "c")), duplicate, FlagAlias("--colour", "--color"),
|
||
|
|
FlagAliasFor("inspect", "--colour", "--format"))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("identical duplicates and scoped override were rejected: %v", err)
|
||
|
|
}
|
||
|
|
if got := app.flagAliases[commandpath.Path{"inspect"}.Key()].bySpelling[flagSpellingKey(capturedFlagSpelling{name: "colour"})].target.token(); got != "--format" {
|
||
|
|
t.Fatalf("scoped alias target = %s, want --format", got)
|
||
|
|
}
|
||
|
|
if got := app.flagAliases[commandpath.Path{"plain"}.Key()].bySpelling[flagSpellingKey(capturedFlagSpelling{name: "colour"})].target.token(); got != "--color" {
|
||
|
|
t.Fatalf("effective global alias target = %s, want --color", got)
|
||
|
|
}
|
||
|
|
|
||
|
|
for name, rules := range map[string][]RoutingRule{
|
||
|
|
"competing global targets": {
|
||
|
|
FlagAlias("--colour", "--color"),
|
||
|
|
FlagAlias("--colour", "--format"),
|
||
|
|
},
|
||
|
|
"competing scoped targets": {
|
||
|
|
FlagAliasFor("inspect", "--colour", "--color"),
|
||
|
|
FlagAliasFor("inspect", "--colour", "--format"),
|
||
|
|
},
|
||
|
|
"unknown scope": {FlagAliasFor("missing", "--colour", "--color")},
|
||
|
|
"missing scoped destination": {FlagAliasFor("inspect", "--colour", "--missing")},
|
||
|
|
} {
|
||
|
|
t.Run(name, func(t *testing.T) {
|
||
|
|
declarations := []Declaration{command}
|
||
|
|
for _, rule := range rules {
|
||
|
|
declarations = append(declarations, rule)
|
||
|
|
}
|
||
|
|
if _, err := New(Identity{Name: "Example"}, declarations...); err == nil {
|
||
|
|
t.Fatal("New() accepted invalid flag alias rules")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFlagAliasCyclesIdentifyParticipatingSpellings(t *testing.T) {
|
||
|
|
for name, declarations := range map[string][]Declaration{
|
||
|
|
"global": {
|
||
|
|
FlagAlias("--first", "--second"),
|
||
|
|
FlagAlias("--second", "--first"),
|
||
|
|
},
|
||
|
|
"scoped": {
|
||
|
|
flagAliasCommand("inspect"),
|
||
|
|
FlagAliasFor("inspect", "-a", "--second"),
|
||
|
|
FlagAliasFor("inspect", "--second", "-a"),
|
||
|
|
},
|
||
|
|
} {
|
||
|
|
t.Run(name, func(t *testing.T) {
|
||
|
|
_, err := New(Identity{Name: "Example"}, declarations...)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("New() accepted a flag alias cycle")
|
||
|
|
}
|
||
|
|
message := err.Error()
|
||
|
|
for _, spelling := range []string{"--first", "--second"} {
|
||
|
|
if name == "scoped" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if !strings.Contains(message, spelling) {
|
||
|
|
t.Errorf("cycle error %q does not identify %s", message, spelling)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if name == "scoped" && (!strings.Contains(message, "-a") || !strings.Contains(message, "--second")) {
|
||
|
|
t.Errorf("scoped cycle error %q does not identify both spellings", message)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestInactiveGlobalAliasesActivateAtomicallyWhenDestinationAppears(t *testing.T) {
|
||
|
|
command := flagAliasCommand("inspect", stringFlag("color", "c"))
|
||
|
|
app, err := New(Identity{Name: "Example"}, command,
|
||
|
|
FlagAlias("--color", "--missing"),
|
||
|
|
FlagAlias("--colour", "--color"),
|
||
|
|
)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
aliases := app.flagAliases[commandpath.Path{"inspect"}.Key()].bySpelling
|
||
|
|
if _, active := aliases[flagSpellingKey(capturedFlagSpelling{name: "color"})]; active {
|
||
|
|
t.Fatal("global alias with an unavailable destination became active")
|
||
|
|
}
|
||
|
|
if got := aliases[flagSpellingKey(capturedFlagSpelling{name: "colour"})].target.token(); got != "--color" {
|
||
|
|
t.Fatalf("--colour endpoint = %s, want canonical --color", got)
|
||
|
|
}
|
||
|
|
|
||
|
|
snapshot := app.routeSnapshot()
|
||
|
|
commands := len(app.commands)
|
||
|
|
rules := len(app.routingRules)
|
||
|
|
originalAlias := aliases[flagSpellingKey(capturedFlagSpelling{name: "colour"})]
|
||
|
|
if err := app.Register(flagAliasCommand("later", stringFlag("color", "c"), stringFlag("missing", ""))); err == nil {
|
||
|
|
t.Fatal("Register() accepted a newly active global source collision")
|
||
|
|
}
|
||
|
|
currentAlias := app.flagAliases[commandpath.Path{"inspect"}.Key()].bySpelling[flagSpellingKey(capturedFlagSpelling{name: "colour"})]
|
||
|
|
if app.routeSnapshot() != snapshot || len(app.commands) != commands || len(app.routingRules) != rules || currentAlias != originalAlias {
|
||
|
|
t.Fatal("failed registration changed the existing command or routing state")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func capturedSpelling(token string) capturedFlagSpelling {
|
||
|
|
issues := []error(nil)
|
||
|
|
return captureFlagSpelling("test", token, &issues)
|
||
|
|
}
|