示例
github.com/spf13/cobra v1.10.2: Drive a spf13/cobra command tree from a test with SetArgs and SetOut, without a subprocess, and tell the failure modes of Execute apart
已验证示例 — golang github.com/spf13/cobra v1.10.2: Drive a spf13/cobra command tree from a test with SetArgs and SetOut, without a subprocess, and tell the…
sha256:40b43e1c51d66679bac8ef03d3e56f00b6f9bb2c8bc4f36791b2196260a8cce1
本网络只提供一件事:能构建的样本。它在沙箱中运行并保留签名回执。它不评级、不担保——同样的代码能否在你的环境构建,它没有测量过。
提交了通过的契约回执的不同签名密钥数量。为 1 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。
MIT-0
执行证据
声明的环境与签名的运行分开呈现,你可以看到这个样本究竟运行了什么、在哪里运行。
- 证据依据
- 签名契约通过
- 验证回执
- 2
- 构建过它的签名密钥
- 2
声明的环境
go 1.26 linux x64 go 1.26 go go
验证运行环境
| 环境 | 契约 | 阶段 | 运行日期 |
|---|---|---|---|
| go 1.26 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · golang@1 |
2026-08-14 |
| go 1.26 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · golang@1 |
2026-08-18 |
案例
HOW- 目标
- Drive a spf13/cobra command tree from a test with SetArgs and SetOut, without a subprocess, and tell the failure modes of Execute apart
- 符号
-
- Command.SetArgs
- Command.Execute
- Command.SetOut
- Command.RunE
- Command.PersistentFlags
- Command.MarkFlagRequired
- Command.MarkPersistentFlagRequired
- Command.InheritedFlags
- Command.LocalFlags
- Command.SilenceUsage
- Command.SilenceErrors
- cobra.ArbitraryArgs
- cobra.NoArgs
- 环境
- go 1.26
- 创建时间
- 2026-08-14T12:07:26Z
契约
- run a subcommand in memory and assert its output lands in the buffer installed with SetOut instead of on stdout
- assert RunE's error comes back out of Execute wrapped as returned, so errors.Is still reaches the sentinel
- assert Execute prints the error line to SetErr and the usage block to SetOut, so reading one buffer shows half of what the user sees
- assert SetArgs takes the arguments without the program name, and that including it makes the program name an unknown command
- assert an unset SetArgs falls back to os.Args[1:], which is what makes a command tree fail on -test flags inside go test
- assert SetArgs(nil) is that same fallback rather than an empty command line, while an empty non-nil slice runs the root with no arguments
- assert cobra skips that fallback only when the program name is cobra.test
- assert a missing required flag returns exactly required flag(s) "name" not set
- assert a child's Flags() does not contain the parent's persistent flag until parsing merges it in
- assert a persistent parent flag is readable from the child's Flags() and shows up in InheritedFlags but not LocalFlags
- assert a parent flag declared with Flags() is unknown to the child from either position on the command line, while still working on the parent
- assert the child's help lists the inherited flag under Global Flags and never lists the parent's local flag
- assert MarkFlagRequired on the child rejects a parent's persistent flag with no such flag -config, while MarkPersistentFlagRequired on the owner makes it required for the child
- assert a required flag with a default value is still an error unless it is passed explicitly, because the check is on pflag's Changed
- assert two missing required flags come back as one message with both names joined inside a single pair of quotes, ordered by pflag's sorted walk and not by declaration
- assert an unknown subcommand is an error carrying a Did you mean this suggestion, printed as a one-line pointer with no usage block
- assert an unknown flag is a different error, printed with the failing subcommand's full usage and no pointer line
- assert cobra.ArbitraryArgs on the root turns the unknown-command check off, so the typo becomes a positional argument while real subcommands still route
- assert cobra.NoArgs on the root rejects the same typo with the same wording but no suggestion and the root's whole usage block, because that error comes from ValidateArgs inside the command rather than from Find
- assert SilenceUsage on either the root or the failing command drops the usage block without changing the returned error, including for a genuine usage error
- assert SilenceUsage set inside RunE also drops the usage block, since cobra reads the field after the command returns, but leaves it in place for a flag error that never reaches RunE
- assert SilenceErrors alone drops the error line and leaves a usage block with no explanation, and that both together make Execute print nothing while still returning the error
- assert --help returns nil from Execute and prints help to the out buffer, where clap reports help as an error
- assert two Execute calls on one tree share flag state, so the second run passes the required-flag check with the first run's value
文件
- csx.json
- go.mod
- go.sum
- src/cli.go
- test/main.go
源代码
{"case":{"caseId":"case:sha256:366f862a618857bd618e634a091d1269fcb427921e26cbd57a10130a4d333f13","constraints":{"runtime":"go"},"contract":["run a subcommand in memory and assert its output lands in the buffer installed with SetOut instead of on stdout","assert RunE's error comes back out of Execute wrapped as returned, so errors.Is still reaches the sentinel","assert Execute prints the error line to SetErr and the usage block to SetOut, so reading one buffer shows half of what the user sees","assert SetArgs takes the arguments without the program name, and that including it makes the program name an unknown command","assert an unset SetArgs falls back to os.Args[1:], which is what makes a command tree fail on -test flags inside go test","assert SetArgs(nil) is that same fallback rather than an empty command line, while an empty non-nil slice runs the root with no arguments","assert cobra skips that fallback only when the program name is cobra.test","assert a missing required flag returns exactly required flag(s) \"name\" not set","assert a child's Flags() does not contain the parent's persistent flag until parsing merges it in","assert a persistent parent flag is readable from the child's Flags() and shows up in InheritedFlags but not LocalFlags","assert a parent flag declared with Flags() is unknown to the child from either position on the command line, while still working on the parent","assert the child's help lists the inherited flag under Global Flags and never lists the parent's local flag","assert MarkFlagRequired on the child rejects a parent's persistent flag with no such flag -config, while MarkPersistentFlagRequired on the owner makes it required for the child","assert a required flag with a default value is still an error unless it is passed explicitly, because the check is on pflag's Changed","assert two missing required flags come back as one message with both names joined inside a single pair of quotes, ordered by pflag's sorted walk and not by declaration","assert an unknown subcommand is an error carrying a Did you mean this suggestion, printed as a one-line pointer with no usage block","assert an unknown flag is a different error, printed with the failing subcommand's full usage and no pointer line","assert cobra.ArbitraryArgs on the root turns the unknown-command check off, so the typo becomes a positional argument while real subcommands still route","assert cobra.NoArgs on the root rejects the same typo with the same wording but no suggestion and the root's whole usage block, because that error comes from ValidateArgs inside the command rather than from Find","assert SilenceUsage on either the root or the failing command drops the usage block without changing the returned error, including for a genuine usage error","assert SilenceUsage set inside RunE also drops the usage block, since cobra reads the field after the command returns, but leaves it in place for a flag error that never reaches RunE","assert SilenceErrors alone drops the error line and leaves a usage block with no explanation, and that both together make Execute print nothing while still returning the error","assert --help returns nil from Execute and prints help to the out buffer, where clap reports help as an error","assert two Execute calls on one tree share flag state, so the second run passes the required-flag check with the first run's value"],"goal":"Drive a spf13/cobra command tree from a test with SetArgs and SetOut, without a subprocess, and tell the failure modes of Execute apart","kind":"HOW","packages":["pkg:golang/github.com/spf13/cobra@1.10.2","pkg:golang/github.com/spf13/pflag@1.0.10"],"schemaVersion":1,"symbols":["Command.SetArgs","Command.Execute","Command.SetOut","Command.RunE","Command.PersistentFlags","Command.MarkFlagRequired","Command.MarkPersistentFlagRequired","Command.InheritedFlags","Command.LocalFlags","Command.SilenceUsage","Command.SilenceErrors","cobra.ArbitraryArgs","cobra.NoArgs"]},"contractCommand":["go","run","./test"],"environment":{"arch":"x64","ecosystem":"golang","executionContext":"go","language":"go","os":"linux","packageManager":"go","runtime":"go","runtimeVersion":"1.26","schemaVersion":1},"license":"MIT-0","packages":["pkg:golang/github.com/spf13/cobra@1.10.2","pkg:golang/github.com/spf13/pflag@1.0.10"],"schemaVersion":1,"symbols":["Command.SetArgs","Command.Execute","Command.SetOut","Command.RunE","Command.PersistentFlags","Command.MarkFlagRequired","Command.MarkPersistentFlagRequired","Command.InheritedFlags","Command.LocalFlags","Command.SilenceUsage","Command.SilenceErrors","cobra.ArbitraryArgs","cobra.NoArgs"],"verifierAdapter":"golang@1"}
module codesamplex.dev/sample/gocobra
go 1.24
require github.com/spf13/cobra v1.10.2
// pflag sits one release above what cobra v1.10.2 asks for. Minimal version
// selection settles on pflag v1.0.9, which is what cobra's own go.mod
// requires; v1.0.10 is the latest release and was measured here to produce the
// identical error text this contract pins ("unknown flag: --nope" and
// "no such flag -config"). Both of those errors come from pflag rather than
// cobra, which is why its version is worth stating rather than inheriting.
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
)
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
// Package cli builds a cobra command tree and drives it entirely in memory,
// which is how you test a cobra CLI without spawning a process.
//
// The trap that costs the most time is SetArgs. Execute() falls back to
// os.Args[1:] when nothing has been set, so the program name is already gone
// by the time cobra looks at anything. SetArgs replaces that slice, not
// os.Args, so it takes the arguments WITHOUT the program name. clap's
// try_parse_from, the Rust equivalent of this same technique, takes the whole
// argv including the program name and skips element 0 itself; copying that
// shape here makes the program name the first positional, and cobra answers
// `unknown command "csxtool" for "csxtool"`, which reads like nonsense until
// you know where the extra word came from.
//
// The second trap is that a *cobra.Command is not a value. It keeps the flag
// values from the last Execute, and pflag keeps Changed set, so a second
// Execute on the same tree is not an independent run: a required flag that
// was never passed the second time still validates. Every case in the
// contract builds a fresh tree, and the last one measures what happens if you
// do not.
//
// The third is output. Anything printed with fmt.Println goes to the real
// stdout and cannot be asserted on. cmd.OutOrStdout() and cmd.ErrOrStderr()
// go to whatever SetOut/SetErr installed, and a command with no writer of its
// own walks up to its parent, so setting the two writers on the root is
// enough for the whole tree.
package cli
import (
"bytes"
"errors"
"fmt"
"github.com/spf13/cobra"
)
// ErrGreetRefused is returned wrapped by greet's RunE. Execute returns that
// error unchanged, so errors.Is still reaches the sentinel through the wrap:
// the exit path of a cobra program is an ordinary Go error, not a status code
// you have to recover from a process.
var ErrGreetRefused = errors.New("greeting refused")
// Tree is one command tree plus the buffers it writes to. Greet is exported
// because the contract inspects its FlagSets, which is where the difference
// between a local, an inherited and a persistent flag becomes visible.
type Tree struct {
Root *cobra.Command
Greet *cobra.Command
Out *bytes.Buffer
Err *bytes.Buffer
}
// New builds the default tree: root.Args is left nil, which is what enables
// cobra's unknown-command check.
func New() *Tree { return build(nil) }
// NewArbitraryArgs is the same tree with a positional-args validator on the
// root. It exists to measure the cost of that one line.
func NewArbitraryArgs() *Tree { return build(cobra.ArbitraryArgs) }
func build(rootArgs cobra.PositionalArgs) *Tree {
out, errOut := &bytes.Buffer{}, &bytes.Buffer{}
root := &cobra.Command{
Use: "csxtool",
Short: "A tree that exists to be driven from a test",
Args: rootArgs,
RunE: func(cmd *cobra.Command, args []string) error {
localOnly, err := cmd.Flags().GetBool("local-only")
if err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(), "root local-only=%v args=%v\n", localOnly, args)
return nil
},
}
// One SetOut/SetErr pair on the root covers every child, and it also
// redirects what cobra itself prints on failure. Which buffer each half
// lands in is asserted in the contract, because the two do not match the
// obvious guess.
root.SetOut(out)
root.SetErr(errOut)
// PersistentFlags is the only way a child sees a parent's flag.
root.PersistentFlags().String("config", "csx.yaml", "config file, inherited by every subcommand")
// Flags() on a command that has subcommands is a local flag: usable when
// the root itself runs, invisible to every child.
root.Flags().Bool("local-only", false, "belongs to the root alone")
greet := &cobra.Command{
Use: "greet",
Short: "Greet somebody",
RunE: func(cmd *cobra.Command, args []string) error {
name, err := cmd.Flags().GetString("name")
if err != nil {
return err
}
// The inherited flag is read from the child's own Flags(), not
// from InheritedFlags(): ParseFlags merges the parents' persistent
// sets into Flags() before RunE is reached. Before that merge the
// same lookup returns nil, which the contract measures.
config, err := cmd.Flags().GetString("config")
if err != nil {
return err
}
if name == "boom" {
return fmt.Errorf("greet %q: %w", name, ErrGreetRefused)
}
fmt.Fprintf(cmd.OutOrStdout(), "hello %s (config=%s)\n", name, config)
return nil
},
}
greet.Flags().String("name", "", "who to greet")
// MarkFlagRequired annotates the flag on this command's own FlagSet. It
// returns an error for a name that does not exist there, which is the
// signal you get if you mark a parent's persistent flag from the child.
if err := greet.MarkFlagRequired("name"); err != nil {
panic(err)
}
version := &cobra.Command{
Use: "version",
Short: "Print the version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintln(cmd.OutOrStdout(), "csxtool 1.2.3")
},
}
root.AddCommand(greet, version)
return &Tree{Root: root, Greet: greet, Out: out, Err: errOut}
}
// Run clears the buffers and executes with args, which must NOT include the
// program name.
//
// It deliberately takes a []string rather than being variadic: a variadic
// Run() with no arguments passes a nil slice, and a nil slice is exactly the
// value that makes cobra fall back to os.Args[1:]. A test helper that looks
// like it runs the command with no arguments would instead run it with the
// test binary's own arguments. An empty but non-nil slice is what actually
// means "no arguments"; the contract measures both.
func (t *Tree) Run(args []string) error {
t.Out.Reset()
t.Err.Reset()
t.Root.SetArgs(args)
return t.Root.Execute()
}
package main
import (
"errors"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"codesamplex.dev/sample/gocobra/src"
)
func main() {
// A subcommand runs, and everything it prints through cmd.OutOrStdout()
// lands in the root's buffer. Nothing reaches the real stdout, so the
// assertion is on a string and not on a captured file descriptor.
t := cli.New()
err := t.Run([]string{"greet", "--name", "ada"})
check(err == nil, "greet: %v", err)
check(t.Out.String() == "hello ada (config=csx.yaml)\n", "out=%q", t.Out.String())
check(t.Err.Len() == 0, "err=%q", t.Err.String())
// RunE's error comes back out of Execute wrapped exactly as RunE returned
// it, so errors.Is reaches the sentinel. This is the whole reason to use
// RunE over Run: Run has nowhere to put a failure except os.Exit.
t = cli.New()
err = t.Run([]string{"greet", "--name", "boom"})
check(errors.Is(err, cli.ErrGreetRefused), "expected ErrGreetRefused, got %v", err)
check(err.Error() == `greet "boom": greeting refused`, "message=%q", err.Error())
// Execute both returns the error AND prints it. The error line goes to
// SetErr, the usage dump goes to SetOut — cobra prints usage with
// Command.Println, whose destination is OutOrStderr, so installing an out
// writer moves usage off stderr with it. A test that reads only one
// buffer sees half of what the user sees.
check(strings.Contains(t.Err.String(), `Error: greet "boom": greeting refused`), "err=%q", t.Err.String())
check(strings.Contains(t.Out.String(), "Usage:"), "out=%q", t.Out.String())
// The trap. SetArgs replaces os.Args[1:], so the program name must not be
// in it. Include it and it becomes the first positional of the root, which
// a root with subcommands rejects as an unknown command.
t = cli.New()
err = t.Run([]string{"csxtool", "greet", "--name", "ada"})
check(err != nil, "program name in SetArgs should fail")
check(strings.HasPrefix(err.Error(), `unknown command "csxtool" for "csxtool"`),
"expected the program name to be read as a command, got %v", err)
// Where the fallback bites: with no SetArgs at all, cobra parses the real
// os.Args[1:]. Under `go test` those are the -test.* flags, which is why
// an untouched command tree fails inside a test with a flag error nobody
// wrote.
saved := os.Args
os.Args = []string{"csxtool", "--nope"}
err = cli.New().Root.Execute()
check(err != nil && strings.Contains(err.Error(), "unknown flag: --nope"),
"expected the os.Args fallback to be parsed, got %v", err)
// The guard cobra uses is `c.args == nil`, so SetArgs(nil) does not mean
// "no arguments" — it reopens the fallback. A variadic test helper called
// with no arguments hands cobra exactly that nil, which is why Run takes a
// slice. An empty but non-nil slice is how you say "no arguments", and it
// runs the root with none.
nilArgs := cli.New()
err = nilArgs.Run(nil)
check(err != nil && strings.Contains(err.Error(), "unknown flag: --nope"),
"SetArgs(nil) should fall back to os.Args as well, got %v", err)
empty := cli.New()
err = empty.Run([]string{})
check(err == nil && strings.Contains(empty.Out.String(), "args=[]"),
"an empty non-nil slice means no arguments: %v / %q", err, empty.Out.String())
os.Args = saved
// cobra carries a workaround for exactly that: it skips the fallback when
// the program name is "cobra.test". It keys on the name, so it saves the
// package's own tests and nobody else's.
saved = os.Args
os.Args = []string{"cobra.test", "-test.v"}
tf := cli.New()
err = tf.Root.Execute()
os.Args = saved
check(err == nil, "expected the cobra.test escape hatch to skip the fallback, got %v", err)
check(strings.Contains(tf.Out.String(), "args=[]"), "out=%q", tf.Out.String())
// A missing required flag is an ordinary returned error with a fixed
// message, and it arrives as a return value: nothing panics and no process
// exits. With one name missing the quoting looks unremarkable; the
// persistent-flag block below runs the two-name case, which is where the
// message turns out to be shaped oddly.
t = cli.New()
err = t.Run([]string{"greet"})
check(err != nil && err.Error() == `required flag(s) "name" not set`,
"required flag message=%v", err)
check(strings.Contains(t.Err.String(), `Error: required flag(s) "name" not set`), "err=%q", t.Err.String())
// A parent's persistent flag reaches the child; a parent's local flag does
// not. Before anything parses, the child's own Flags() does not contain
// the inherited flag either — the merge happens in ParseFlags, so an init()
// that looks up a parent flag on a child finds nil.
t = cli.New()
check(t.Greet.Flags().Lookup("config") == nil, "config is not merged into the child before parsing")
err = t.Run([]string{"greet", "--config", "other.yaml", "--name", "ada"})
check(err == nil, "inherited flag: %v", err)
check(t.Out.String() == "hello ada (config=other.yaml)\n", "out=%q", t.Out.String())
check(t.Greet.Flags().Lookup("config") != nil, "after parsing, Flags() carries the inherited flag")
check(t.Greet.InheritedFlags().Lookup("config") != nil, "config should be inherited")
check(t.Greet.LocalFlags().Lookup("config") == nil, "config is not local to the child")
check(t.Greet.LocalFlags().Lookup("name") != nil, "name is local to the child")
// The same flag declared with Flags() on the root is invisible one level
// down.
t = cli.New()
err = t.Run([]string{"greet", "--local-only", "--name", "ada"})
check(err != nil && err.Error() == "unknown flag: --local-only",
"a root-local flag must not reach the child, got %v", err)
// Moving it in front of the subcommand does not help. cobra finds the
// subcommand by removing only the subcommand word, so every flag on the
// line — before or after — is parsed by the command that was found.
t = cli.New()
err = t.Run([]string{"--local-only", "greet", "--name", "ada"})
check(err != nil && err.Error() == "unknown flag: --local-only",
"position does not change which command parses a flag, got %v", err)
// It works on the root itself, which is what makes the failure confusing.
t = cli.New()
err = t.Run([]string{"--local-only"})
check(err == nil && strings.Contains(t.Out.String(), "local-only=true"),
"root should accept its own flag: %v / %q", err, t.Out.String())
// The split is visible in the child's own help: an inherited flag is
// listed under "Global Flags", a parent's local flag is not listed at all.
t = cli.New()
err = t.Run([]string{"greet", "--help"})
check(err == nil, "%v", err)
check(strings.Contains(t.Out.String(), "Global Flags:") && strings.Contains(t.Out.String(), "--config"),
"expected the inherited flag under Global Flags, out=%q", t.Out.String())
check(!strings.Contains(t.Out.String(), "local-only"), "out=%q", t.Out.String())
// MarkFlagRequired annotates the command's own FlagSet, so marking a
// parent's persistent flag from the child fails outright — at declaration
// time that flag is not in the child's set yet. The error is pflag's, and
// it prints the long name with a single dash.
t = cli.New()
err = t.Greet.MarkFlagRequired("config")
check(err != nil && err.Error() == "no such flag -config",
"marking a parent flag from the child should fail, got %v", err)
// MarkPersistentFlagRequired on the command that owns the flag is the one
// that works, and it makes the flag required for every subcommand that
// inherits it. A default value does not satisfy it: the check is on
// pflag's Changed, so only an explicit --config counts.
t = cli.New()
check(t.Root.MarkPersistentFlagRequired("config") == nil, "marking on the owner should succeed")
err = t.Run([]string{"greet", "--name", "ada"})
check(err != nil && err.Error() == `required flag(s) "config" not set`,
"a required persistent flag with a default is still required, got %v", err)
t = cli.New()
check(t.Root.MarkPersistentFlagRequired("config") == nil, "marking on the owner should succeed")
check(t.Run([]string{"greet", "--name", "ada", "--config", "other.yaml"}) == nil, "passing it explicitly satisfies it")
// Two missing at once is one error, not two, and the quoting is the trap
// for anyone matching on the message: the names are joined with `", "`
// INSIDE a single pair of quotes, so the string contains one quoted run
// with a comma in it rather than two quoted names. The order is pflag's
// sorted VisitAll, not declaration order — config was declared after name.
t = cli.New()
check(t.Root.MarkPersistentFlagRequired("config") == nil, "marking on the owner should succeed")
err = t.Run([]string{"greet"})
check(err != nil && err.Error() == `required flag(s) "config", "name" not set`,
"two missing flags share one pair of quotes, got %v", err)
// An unknown subcommand and an unknown flag are different errors from
// different stages, and they print differently. The unknown command is
// found before any command runs: the message carries a suggestion, and
// what gets printed is a one-line pointer, not the usage block.
t = cli.New()
err = t.Run([]string{"gret"})
check(err != nil && strings.HasPrefix(err.Error(), `unknown command "gret" for "csxtool"`),
"expected unknown command, got %v", err)
check(strings.Contains(err.Error(), "Did you mean this?") && strings.Contains(err.Error(), "greet"),
"expected a suggestion in %q", err.Error())
check(strings.Contains(t.Err.String(), "Run 'csxtool --help' for usage."), "err=%q", t.Err.String())
check(!strings.Contains(t.Out.String(), "Usage:") && !strings.Contains(t.Err.String(), "Usage:"),
"an unknown command prints no usage block: out=%q err=%q", t.Out.String(), t.Err.String())
// The unknown flag is found later, inside the subcommand's own flag
// parsing. No suggestion, no pointer line, and the full usage of the
// subcommand that failed.
t = cli.New()
err = t.Run([]string{"greet", "--name", "ada", "--nope"})
check(err != nil && err.Error() == "unknown flag: --nope", "expected unknown flag, got %v", err)
check(strings.Contains(t.Err.String(), "Error: unknown flag: --nope"), "err=%q", t.Err.String())
check(!strings.Contains(t.Err.String(), "Run 'csxtool"), "err=%q", t.Err.String())
check(strings.Contains(t.Out.String(), "Usage:") && strings.Contains(t.Out.String(), "csxtool greet"),
"expected the subcommand's usage, out=%q", t.Out.String())
// The suggestion above is not a feature of cobra so much as a side effect
// of leaving Args nil on the root: Find runs the legacy unknown-command
// check only when the command it lands on has Args == nil. Give the root
// ArbitraryArgs and that check is skipped, and a typo silently becomes a
// positional argument to the root.
ta := cli.NewArbitraryArgs()
err = ta.Run([]string{"gret"})
check(err == nil, "ArbitraryArgs on the root should swallow the typo, got %v", err)
check(strings.Contains(ta.Out.String(), "args=[gret]"), "out=%q", ta.Out.String())
// Real subcommands still route, which is what makes the change easy to
// ship without noticing.
ta = cli.NewArbitraryArgs()
check(ta.Run([]string{"greet", "--name", "ada"}) == nil, "a validator on the root must not break routing")
check(ta.Out.String() == "hello ada (config=csx.yaml)\n", "out=%q", ta.Out.String())
// Measured, against the obvious reading of "setting Args disables the
// check": what a validator disables is legacyArgs, not the error. NoArgs
// rejects the same typo with the same wording, but it is raised later, by
// ValidateArgs inside the command that was found, so it carries no
// suggestion and prints like a run failure — the "Error:" line plus the
// root's whole usage block, instead of the one-line pointer above.
t = cli.New()
t.Root.Args = cobra.NoArgs
err = t.Run([]string{"gret"})
check(err != nil && err.Error() == `unknown command "gret" for "csxtool"`,
"NoArgs should still reject the typo, got %v", err)
check(!strings.Contains(err.Error(), "Did you mean"), "NoArgs has no suggestion, got %q", err.Error())
check(!strings.Contains(t.Err.String(), "Run 'csxtool"), "err=%q", t.Err.String())
check(strings.Contains(t.Out.String(), "Available Commands:"), "out=%q", t.Out.String())
// SilenceUsage changes only what is printed. The error returned is the
// same value, and setting it on the root covers the subcommands.
t = cli.New()
t.Root.SilenceUsage = true
err = t.Run([]string{"greet", "--name", "boom"})
check(errors.Is(err, cli.ErrGreetRefused), "SilenceUsage must not change the error: %v", err)
check(!strings.Contains(t.Out.String(), "Usage:"), "out=%q", t.Out.String())
check(strings.Contains(t.Err.String(), "Error: "), "err=%q", t.Err.String())
// Setting it on the child alone works too: cobra checks the failing
// command and the root, so either one silences.
t = cli.New()
t.Greet.SilenceUsage = true
err = t.Run([]string{"greet", "--name", "boom"})
check(errors.Is(err, cli.ErrGreetRefused), "%v", err)
check(!strings.Contains(t.Out.String(), "Usage:"), "out=%q", t.Out.String())
// The cost of the root-level flag: it also silences usage for a real usage
// error, where the usage block is the useful part.
t = cli.New()
t.Root.SilenceUsage = true
err = t.Run([]string{"greet", "--nope"})
check(err != nil && err.Error() == "unknown flag: --nope", "%v", err)
check(!strings.Contains(t.Out.String(), "Usage:"), "out=%q", t.Out.String())
// Which is why the field is usually set inside RunE instead: cobra reads
// SilenceUsage after execute() returns, so an assignment made while the
// command runs still counts.
silenceLate := func(cmd *cobra.Command, _ []string) error {
cmd.SilenceUsage = true
return errors.New("failed after parsing")
}
t = cli.New()
t.Greet.RunE = silenceLate
err = t.Run([]string{"greet", "--name", "ada"})
check(err != nil && t.Out.Len() == 0, "late SilenceUsage should drop usage: %v out=%q", err, t.Out.String())
check(strings.Contains(t.Err.String(), "Error: failed after parsing"), "err=%q", t.Err.String())
// And it is exactly as narrow as the mechanism implies: the same tree
// still prints usage for a flag error, because RunE never ran to set the
// field. That is the point of doing it there rather than on the root.
t = cli.New()
t.Greet.RunE = silenceLate
err = t.Run([]string{"greet", "--nope"})
check(err != nil && err.Error() == "unknown flag: --nope", "%v", err)
check(strings.Contains(t.Out.String(), "Usage:"),
"a flag error never reaches RunE, so usage survives: out=%q", t.Out.String())
// SilenceErrors drops the "Error:" line and nothing else, so on its own it
// prints a usage block with no explanation of what went wrong.
t = cli.New()
t.Root.SilenceErrors = true
err = t.Run([]string{"greet", "--name", "boom"})
check(errors.Is(err, cli.ErrGreetRefused), "SilenceErrors must not change the error: %v", err)
check(t.Err.Len() == 0, "err=%q", t.Err.String())
check(strings.Contains(t.Out.String(), "Usage:"), "out=%q", t.Out.String())
// Both together: Execute prints nothing at all and the error is still
// returned, which is the configuration for a program that wants to format
// its own failures in main.
t = cli.New()
t.Root.SilenceErrors = true
t.Root.SilenceUsage = true
err = t.Run([]string{"greet", "--name", "boom"})
check(errors.Is(err, cli.ErrGreetRefused), "%v", err)
check(t.Out.Len() == 0 && t.Err.Len() == 0, "out=%q err=%q", t.Out.String(), t.Err.String())
// --help is not an error. cobra intercepts flag.ErrHelp, prints help to
// out and returns nil from Execute. The Rust counterpart does the
// opposite: clap reports help as Err with kind DisplayHelp, so code
// ported in either direction gets the success/failure of --help backwards.
t = cli.New()
err = t.Run([]string{"--help"})
check(err == nil, "--help should not be an error, got %v", err)
check(strings.Contains(t.Out.String(), "Available Commands:") && strings.Contains(t.Out.String(), "greet"),
"out=%q", t.Out.String())
check(t.Err.Len() == 0, "err=%q", t.Err.String())
t = cli.New()
err = t.Run([]string{"greet", "--help"})
check(err == nil, "%v", err)
check(strings.Contains(t.Out.String(), "--name string"), "out=%q", t.Out.String())
// State survives Execute. The second run passes no --name, and the
// required-flag check passes anyway, because pflag still has the first
// run's value with Changed set. A table test that reuses one tree reports
// a pass for a case that would fail from a shell.
reused := cli.New()
check(reused.Run([]string{"greet", "--name", "ada"}) == nil, "first run should pass")
err = reused.Run([]string{"greet"})
check(err == nil, "measured: the second run does NOT re-check the required flag, got %v", err)
check(reused.Out.String() == "hello ada (config=csx.yaml)\n",
"the first run's value is still there: %q", reused.Out.String())
check(reused.Greet.Flags().Lookup("name").Changed, "Changed stays set across Execute calls")
// A fresh tree, same arguments, is the failure the reused tree hid.
check(cli.New().Run([]string{"greet"}) != nil, "a fresh tree must reject the missing flag")
fmt.Println("contract ok")
}
func check(ok bool, format string, args ...any) {
if !ok {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
}
}