Cover shared debug binding composition

This commit is contained in:
2026-11-08 15:03:24 +00:00
parent 7aff296c86
commit a54ff03aef
2 changed files with 88 additions and 1 deletions

View File

@@ -1,6 +1,6 @@
# Shared debug binding implementation plan
Status: in progress; Stages 1–3 complete.
Status: in progress; Stages 1–4 complete.
Baseline: `6727d22ce6e1ac668b56a3e6d8fa5f7a06e54d88`.
Execution audience: Luna coding agent.
@@ -185,6 +185,12 @@ parse, without changing unrelated input indexes, grammar, or failure handling.
## Stage 4 — Preserve composition, defaults, and alias behavior
**Complete.** Existing capture and rename machinery retains the boolean
identity and requiredness without production changes. Focused public tests
cover true defaults, explicit false, direct and captured requiredness, both
application methods, renaming in both directions, and an alias to the local
short spelling; capture and alias tests pass offline with Go 1.27.0.
Primary files: [flag_values.go](../../flag_values.go),
[binding.go](../../binding.go), [flag_aliases.go](../../flag_aliases.go).

View File

@@ -2,6 +2,7 @@ package commandkit_test
import (
"context"
"errors"
"io"
"sync"
"testing"
@@ -122,3 +123,83 @@ func TestSharedDebugBindingAndReportingMode(t *testing.T) {
t.Fatalf("operand after terminator: %#v %#v mode=%v", got, received, mode)
}
}
func TestSharedDebugCapturedDefaultsAndRequiredness(t *testing.T) {
type inner struct{ debug commandkit.Supplied[bool] }
type outer struct{ flags commandkit.FlagValues[inner] }
for _, required := range []bool{false, true} {
flag := commandkit.BoolFlag(commandkit.FlagMeta{Name: "debug", Short: "d", Required: required}, true, func(i *inner, v commandkit.Supplied[bool]) { i.debug = v })
captured := commandkit.CaptureFlags([]commandkit.Flag[inner]{flag}, func(i *outer) *commandkit.FlagValues[inner] { return &i.flags })
var applied, supplied inner
var applyErr error
var mode commandkit.ReportingMode
cmd := commandkit.NewCommand(commandkit.CommandSpec[outer]{Name: "run", Flags: captured, Run: func(inv commandkit.Invocation, i outer) commandkit.Outcome {
mode = inv.ReportingMode()
applied = inner{}
supplied = inner{debug: commandkit.Supplied[bool]{Value: true, Set: true}}
applyErr = i.flags.ApplyTo(&applied, flag)
if err := i.flags.ApplySuppliedTo(&supplied, flag); err != nil && applyErr == nil {
applyErr = err
}
return commandkit.Success()
}})
app, err := commandkit.New(commandkit.Identity{Name: "example"}, cmd, commandkit.FlagAliasFor("run", "-x", "-d"))
if err != nil {
t.Fatal(err)
}
result := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{})
if result.Kind != commandkit.OutcomeSuccess || mode != commandkit.ReportingModeOrdinary {
t.Fatalf("omission=%#v mode=%v", result, mode)
}
if required {
if !errors.Is(applyErr, commandkit.ErrFlagInput) {
t.Fatalf("missing required: %v", applyErr)
}
} else if applyErr != nil || !applied.debug.Value || applied.debug.Set || !supplied.debug.Value || !supplied.debug.Set {
t.Fatalf("defaults=%#v %#v %v", applied, supplied, applyErr)
}
result = app.Execute(context.Background(), []string{"run", "-x=false"}, commandkit.Streams{})
if result.Kind != commandkit.OutcomeSuccess || applyErr != nil || applied.debug.Value || !applied.debug.Set || supplied.debug.Value || !supplied.debug.Set || mode != commandkit.ReportingModeOrdinary {
t.Fatalf("false=%#v %#v %#v mode=%v err=%v", result, applied, supplied, mode, applyErr)
}
}
}
func TestSharedDebugDirectRequiredAndRenamedBinding(t *testing.T) {
type input struct{ debug commandkit.Supplied[bool] }
var got input
var mode commandkit.ReportingMode
original := commandkit.BoolFlag(commandkit.FlagMeta{Name: "debug", Required: true}, true, func(i *input, v commandkit.Supplied[bool]) { i.debug = v })
required := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "required", Flags: []commandkit.Flag[input]{original}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome {
got = i
mode = inv.ReportingMode()
return commandkit.Success()
}})
renamed := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "renamed", Flags: []commandkit.Flag[input]{commandkit.RenameFlag(original, "trace")}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome {
got = i
mode = inv.ReportingMode()
return commandkit.Success()
}})
ordinary := commandkit.BoolFlag(commandkit.FlagMeta{Name: "local"}, false, func(i *input, v commandkit.Supplied[bool]) { i.debug = v })
attached := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "attached", Flags: []commandkit.Flag[input]{commandkit.RenameFlag(ordinary, "debug")}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome {
got = i
mode = inv.ReportingMode()
return commandkit.Success()
}})
app, err := commandkit.New(commandkit.Identity{Name: "example"}, required, renamed, attached)
if err != nil {
t.Fatal(err)
}
if r := app.Execute(context.Background(), []string{"required"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeUsage {
t.Fatalf("required omission=%#v", r)
}
if r := app.Execute(context.Background(), []string{"required", "--debug=false"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeSuccess || got.debug.Value || !got.debug.Set || mode != commandkit.ReportingModeOrdinary {
t.Fatalf("explicit false=%#v %#v", r, got)
}
if r := app.Execute(context.Background(), []string{"renamed", "--trace=false", "--debug"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeSuccess || got.debug.Value || !got.debug.Set || mode != commandkit.ReportingModeDebug {
t.Fatalf("renamed=%#v %#v mode=%v", r, got, mode)
}
if r := app.Execute(context.Background(), []string{"attached", "--debug"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeSuccess || !got.debug.Value || !got.debug.Set || mode != commandkit.ReportingModeDebug {
t.Fatalf("renamed to debug=%#v %#v mode=%v", r, got, mode)
}
}