153 lines
6.9 KiB
Go
153 lines
6.9 KiB
Go
|
|
package commandkit
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"reflect"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
type occurrenceInput struct{ events []string }
|
||
|
|
|
||
|
|
func occurrenceFor(name string, required bool, calls *int) Flag[occurrenceInput] {
|
||
|
|
return OccurrenceFlag(FlagMeta{Name: name, Required: required, Short: "x"}, func(raw string) (string, error) {
|
||
|
|
*calls++
|
||
|
|
if raw == "bad" {
|
||
|
|
return "", errors.New("invalid occurrence")
|
||
|
|
}
|
||
|
|
return raw, nil
|
||
|
|
}, func(input *occurrenceInput, value Supplied[string]) {
|
||
|
|
input.events = append(input.events, name+"="+value.Value)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestOccurrenceFlagsApplyInEnteredOrder(t *testing.T) {
|
||
|
|
calls, factories := 0, 0
|
||
|
|
first := occurrenceFor("first", false, &calls)
|
||
|
|
second := RenameFlag(occurrenceFor("second", false, &calls), "second")
|
||
|
|
ordinary := ValueFlag(FlagMeta{Name: "ordinary"}, strptr("default"), func(s string) (string, error) { return s, nil }, func(i *occurrenceInput, v Supplied[string]) { i.events = append(i.events, "ordinary="+v.Value) })
|
||
|
|
arg := ValueArgument(ArgumentMeta{Name: "ARG"}, false, func(s string) (string, error) { return s, nil }, func(i *occurrenceInput, v Supplied[string]) { i.events = append(i.events, "operand="+v.Value) })
|
||
|
|
var got []string
|
||
|
|
cmd := NewCommand(CommandSpec[occurrenceInput]{Name: "run", Flags: []Flag[occurrenceInput]{first, second, ordinary}, Arguments: []Argument[occurrenceInput]{arg}, NewInput: func() occurrenceInput { factories++; return occurrenceInput{} }, CheckInput: func(i occurrenceInput) error { got = append([]string(nil), i.events...); return nil }, Run: func(Invocation, occurrenceInput) Outcome { return Success() }})
|
||
|
|
app, err := New(Identity{Name: "Example"}, cmd, FlagAliasFor("run", "--other", "--second"))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
result := app.Execute(t.Context(), []string{"run", "--other=a", "--first=", "--second=b", "--ordinary=z", "tail"}, Streams{})
|
||
|
|
want := []string{"ordinary=z", "second=a", "first=", "second=b", "operand=tail"}
|
||
|
|
if result.Status != 0 || !reflect.DeepEqual(got, want) || calls != 3 || factories != 1 {
|
||
|
|
t.Fatalf("%#v events=%q calls=%d factories=%d", result, got, calls, factories)
|
||
|
|
}
|
||
|
|
result = app.Execute(t.Context(), []string{"run", "--first=bad"}, Streams{})
|
||
|
|
if result.Kind != OutcomeUsage || factories != 1 || !strings.Contains(result.Explanation.Message, "--first") {
|
||
|
|
t.Fatalf("invalid result %#v factories=%d", result, factories)
|
||
|
|
}
|
||
|
|
result = app.Execute(t.Context(), []string{"run"}, Streams{})
|
||
|
|
if result.Status != 0 || !reflect.DeepEqual(got, []string{"ordinary=default", "operand="}) {
|
||
|
|
t.Fatalf("fresh %#v, %q", result, got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
type occurrenceOuter struct{ flags FlagValues[occurrenceInput] }
|
||
|
|
|
||
|
|
func TestCapturedOccurrencesReplaySelectedOrder(t *testing.T) {
|
||
|
|
calls := 0
|
||
|
|
a := occurrenceFor("alpha", false, &calls)
|
||
|
|
b := RenameFlag(a, "beta")
|
||
|
|
missing := RenameFlag(occurrenceFor("required", true, &calls), "required")
|
||
|
|
originals := []Flag[occurrenceInput]{a, b, missing}
|
||
|
|
captured := CaptureFlags(originals, func(o *occurrenceOuter) *FlagValues[occurrenceInput] { return &o.flags })
|
||
|
|
var stored FlagValues[occurrenceInput]
|
||
|
|
cmd := NewCommand(CommandSpec[occurrenceOuter]{Name: "run", Flags: captured, Run: func(_ Invocation, i occurrenceOuter) Outcome { stored = i.flags; return Success() }})
|
||
|
|
app, err := New(Identity{Name: "Example"}, cmd, FlagAliasFor("run", "--b", "--beta"))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
result := app.Execute(t.Context(), []string{"run", "--b=1", "--alpha=2", "--beta=3", "--alpha=4"}, Streams{})
|
||
|
|
if result.Status != 0 || calls != 4 {
|
||
|
|
t.Fatalf("%#v calls=%d", result, calls)
|
||
|
|
}
|
||
|
|
var got occurrenceInput
|
||
|
|
if err := stored.ApplyTo(&got, a, b); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
want := []string{"alpha=1", "alpha=2", "alpha=3", "alpha=4"}
|
||
|
|
if !reflect.DeepEqual(got.events, want) {
|
||
|
|
t.Fatalf("events %q want %q", got.events, want)
|
||
|
|
}
|
||
|
|
var subset occurrenceInput
|
||
|
|
if err := stored.ApplyTo(&subset, b); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if !reflect.DeepEqual(subset.events, []string{"alpha=1", "alpha=3"}) || calls != 4 {
|
||
|
|
t.Fatalf("subset %q calls=%d", subset.events, calls)
|
||
|
|
}
|
||
|
|
var repeated occurrenceInput
|
||
|
|
if err := stored.ApplyTo(&repeated, a, b); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
got.events[0] = "changed"
|
||
|
|
if !reflect.DeepEqual(repeated.events, want) {
|
||
|
|
t.Fatalf("shared state %q", repeated.events)
|
||
|
|
}
|
||
|
|
if err := stored.ApplyTo(&repeated, missing); !errors.Is(err, ErrFlagInput) {
|
||
|
|
t.Fatalf("required error %v", err)
|
||
|
|
}
|
||
|
|
other := RenameFlag(a, "other")
|
||
|
|
if err := stored.ApplyTo(&repeated, other); !errors.Is(err, ErrFlagWiring) {
|
||
|
|
t.Fatalf("identity error %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestRenameFlagKeepsBindingBehavior(t *testing.T) {
|
||
|
|
parse := func(s string) (string, error) { return strings.ToUpper(s), nil }
|
||
|
|
original := ValueFlag(FlagMeta{Name: "original", Short: "o", Required: true, Completion: CompleteChoices(CompletionChoice{Value: "one"})}, strptr("default"), parse, func(i *occurrenceInput, v Supplied[string]) {
|
||
|
|
i.events = append(i.events, fmt.Sprint(v.Value, ":", v.Set))
|
||
|
|
})
|
||
|
|
renamed := RenameFlag(original, "renamed")
|
||
|
|
if renamed.binding.identity == original.binding.identity || renamed.binding.meta.Short != "" || !renamed.binding.meta.Required || !reflect.DeepEqual(renamed.binding.meta.Completion, original.binding.meta.Completion) {
|
||
|
|
t.Fatalf("renamed %#v", renamed.binding.meta)
|
||
|
|
}
|
||
|
|
value, err := renamed.binding.prepare(nil)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var input occurrenceInput
|
||
|
|
value.apply(&input)
|
||
|
|
if !reflect.DeepEqual(input.events, []string{"default:false"}) {
|
||
|
|
t.Fatalf("default %q", input.events)
|
||
|
|
}
|
||
|
|
if _, err := New(Identity{Name: "Example"}, NewCommand(CommandSpec[occurrenceInput]{Name: "run", Flags: []Flag[occurrenceInput]{original, RenameFlag(original, "bad name")}})); err == nil {
|
||
|
|
t.Fatal("invalid name accepted")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestOccurrenceRequiredAndAliasedConversion(t *testing.T) {
|
||
|
|
parseErr := errors.New("cannot parse")
|
||
|
|
builds := 0
|
||
|
|
flag := OccurrenceFlag(FlagMeta{Name: "item", Required: true}, func(raw string) (string, error) {
|
||
|
|
if raw == "bad" {
|
||
|
|
return "", parseErr
|
||
|
|
}
|
||
|
|
return raw, nil
|
||
|
|
}, func(*occurrenceInput, Supplied[string]) {})
|
||
|
|
cmd := NewCommand(CommandSpec[occurrenceInput]{Name: "run", Flags: []Flag[occurrenceInput]{flag}, NewInput: func() occurrenceInput { builds++; return occurrenceInput{} }, Run: func(Invocation, occurrenceInput) Outcome { return Success() }})
|
||
|
|
app, err := New(Identity{Name: "Example"}, cmd, FlagAliasFor("run", "--entry", "--item"))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
missing := app.Execute(t.Context(), []string{"run"}, Streams{})
|
||
|
|
if missing.Kind != OutcomeUsage || builds != 0 {
|
||
|
|
t.Fatalf("missing: %#v", missing)
|
||
|
|
}
|
||
|
|
invalid := app.Execute(t.Context(), []string{"run", "--item=ok", "--entry=bad"}, Streams{})
|
||
|
|
if invalid.Kind != OutcomeUsage || !errors.Is(invalid.Cause, parseErr) || !strings.Contains(invalid.Explanation.Message, "--entry occurrence 2") || builds != 0 {
|
||
|
|
t.Fatalf("invalid: %#v", invalid)
|
||
|
|
}
|
||
|
|
help := app.Execute(t.Context(), []string{"run", "--help"}, Streams{})
|
||
|
|
if help.Status != 0 || builds != 0 {
|
||
|
|
t.Fatalf("help: %#v", help)
|
||
|
|
}
|
||
|
|
}
|