2026-10-19 19:31:23 +00:00
package cobra
import (
"reflect"
2026-10-20 00:29:49 +00:00
"strings"
2026-10-19 19:31:23 +00:00
"testing"
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
)
func flagGrammarTree ( ) * commandmeta . Command {
return & commandmeta . Command { Name : "app" , Children : [ ] * commandmeta . Command { {
Name : "run" , Runnable : true ,
Flags : [ ] commandmeta . Flag {
{ Name : "color" , Short : "c" } ,
{ Name : "verbose" , Short : "v" , Boolean : true } ,
{ Name : "quiet" , Short : "q" , Boolean : true } ,
{ Name : "message" , Short : "m" } ,
{ Name : "tag" , Short : "t" , Repeated : true } ,
} ,
} } }
}
func flagGrammarOptions ( ) ParseOptions {
return ParseOptions { FlagAliases : [ ] commandmeta . FlagAlias {
{ Source : "--colour" , Target : "--color" } ,
{ Source : "-C" , Target : "--color" } ,
{ Source : "-w" , Target : "--verbose" } ,
{ Source : "-z" , Target : "--quiet" } ,
{ Source : "--taggy" , Target : "-t" } ,
{ Source : "--hlep" , Target : "--help" } ,
} }
}
2026-10-26 02:55:08 +00:00
func reportingFlagTree ( ) * commandmeta . Command {
return & commandmeta . Command { Name : "app" , Children : [ ] * commandmeta . Command { {
Name : "run" , Runnable : true , CoreFlags : commandmeta . ReportingFlags ( ) ,
Flags : [ ] commandmeta . Flag { { Name : "message" } } ,
} } }
}
func reportingFlagOptions ( ) ParseOptions {
return ParseOptions { FlagAliases : [ ] commandmeta . FlagAlias {
{ Source : "-q" , Target : "--quiet" } ,
{ Source : "-v" , Target : "--verbose" } ,
{ Source : "-d" , Target : "--debug" } ,
} }
}
func TestParseReportsFlagOccurrencesInEnteredOrder ( t * testing . T ) {
result := Parse ( reportingFlagTree ( ) , [ ] string {
"run" , "--verbose=true" , "-qd" , "--progress=false" , "--message=--quiet" , "--" , "--debug" ,
} , reportingFlagOptions ( ) )
if result . Failure != nil {
t . Fatalf ( "parse failed: %s" , result . Failure . Message )
}
want := [ ] commandmeta . FlagOccurrence {
{ Name : "verbose" , Spelling : "--verbose" , RawValue : "true" } ,
{ Name : "quiet" , Spelling : "-q" , RawValue : "true" } ,
{ Name : "debug" , Spelling : "-d" , RawValue : "true" } ,
{ Name : "progress" , Spelling : "--progress" , RawValue : "false" } ,
{ Name : "message" , Spelling : "--message" , RawValue : "--quiet" } ,
}
if ! reflect . DeepEqual ( result . FlagOccurrences , want ) {
t . Fatalf ( "flag occurrences = %#v, want %#v" , result . FlagOccurrences , want )
}
if ! reflect . DeepEqual ( result . FlagValues , [ ] [ ] string { { "--quiet" } } ) || ! reflect . DeepEqual ( result . FlagSpellings , [ ] [ ] string { { "--message" } } ) {
t . Fatalf ( "consumer values or spellings changed: %#v, %#v" , result . FlagValues , result . FlagSpellings )
}
}
func TestParseFlagOccurrencesRetainOnlyConsumedPrefixOnFailure ( t * testing . T ) {
result := Parse ( reportingFlagTree ( ) , [ ] string { "run" , "--verbose" , "--unknown" , "--quiet" } , reportingFlagOptions ( ) )
if result . Failure == nil || len ( result . FlagOccurrences ) != 1 || result . FlagOccurrences [ 0 ] . Name != "verbose" {
t . Fatalf ( "unknown flag result = %#v" , result )
}
result = Parse ( reportingFlagTree ( ) , [ ] string { "run" , "-qd" , "--message" } , reportingFlagOptions ( ) )
if result . Failure == nil || len ( result . FlagOccurrences ) != 2 || result . FlagOccurrences [ 0 ] . Name != "quiet" || result . FlagOccurrences [ 1 ] . Name != "debug" {
t . Fatalf ( "missing value result = %#v" , result )
}
result = Parse ( reportingFlagTree ( ) , [ ] string { "missing" , "--quiet" } , reportingFlagOptions ( ) )
if result . Failure == nil || len ( result . FlagOccurrences ) != 0 {
t . Fatalf ( "malformed path result = %#v" , result )
}
}
func TestParseFlagOccurrenceKeepsOptionLookingValueAndConsumesTerminator ( t * testing . T ) {
result := Parse ( reportingFlagTree ( ) , [ ] string { "run" , "--message" , "--quiet" , "--" , "--verbose" } , reportingFlagOptions ( ) )
if result . Failure != nil {
t . Fatalf ( "parse failed: %s" , result . Failure . Message )
}
want := [ ] commandmeta . FlagOccurrence { { Name : "message" , Spelling : "--message" , RawValue : "--quiet" } }
if ! reflect . DeepEqual ( result . FlagOccurrences , want ) {
t . Fatalf ( "flag occurrences = %#v, want %#v" , result . FlagOccurrences , want )
}
if ! reflect . DeepEqual ( result . Operands , [ ] string { "--verbose" } ) {
t . Fatalf ( "operands = %#v" , result . Operands )
}
}
2026-10-19 19:31:23 +00:00
func TestParseCanonicalAndAliasShortGrammar ( t * testing . T ) {
for _ , test := range [ ] struct {
name string
args [ ] string
values [ ] [ ] string
spellings [ ] [ ] string
} {
{ "boolean cluster" , [ ] string { "run" , "-vq" } , [ ] [ ] string { nil , { "true" } , { "true" } , nil , nil } , [ ] [ ] string { nil , { "-v" } , { "-q" } , nil , nil } } ,
{ "aliased boolean cluster" , [ ] string { "run" , "-wz" } , [ ] [ ] string { nil , { "true" } , { "true" } , nil , nil } , [ ] [ ] string { nil , { "-w" } , { "-z" } , nil , nil } } ,
{ "value short separated" , [ ] string { "run" , "-c" , "blue" } , [ ] [ ] string { { "blue" } , nil , nil , nil , nil } , [ ] [ ] string { { "-c" } , nil , nil , nil , nil } } ,
{ "value short equals" , [ ] string { "run" , "-c=blue" } , [ ] [ ] string { { "blue" } , nil , nil , nil , nil } , [ ] [ ] string { { "-c" } , nil , nil , nil , nil } } ,
{ "value short attached" , [ ] string { "run" , "-cblue" } , [ ] [ ] string { { "blue" } , nil , nil , nil , nil } , [ ] [ ] string { { "-c" } , nil , nil , nil , nil } } ,
{ "long alias" , [ ] string { "run" , "--colour=blue" } , [ ] [ ] string { { "blue" } , nil , nil , nil , nil } , [ ] [ ] string { { "--colour" } , nil , nil , nil , nil } } ,
{ "short alias" , [ ] string { "run" , "-Cblue" } , [ ] [ ] string { { "blue" } , nil , nil , nil , nil } , [ ] [ ] string { { "-C" } , nil , nil , nil , nil } } ,
{ "cluster value consumes suffix" , [ ] string { "run" , "-vqcblue" } , [ ] [ ] string { { "blue" } , { "true" } , { "true" } , nil , nil } , [ ] [ ] string { { "-c" } , { "-v" } , { "-q" } , nil , nil } } ,
{ "cluster value consumes next" , [ ] string { "run" , "-vqc" , "blue" } , [ ] [ ] string { { "blue" } , { "true" } , { "true" } , nil , nil } , [ ] [ ] string { { "-c" } , { "-v" } , { "-q" } , nil , nil } } ,
{ "mixed canonical and alias cluster" , [ ] string { "run" , "-wqcblue" } , [ ] [ ] string { { "blue" } , { "true" } , { "true" } , nil , nil } , [ ] [ ] string { { "-c" } , { "-w" } , { "-q" } , nil , nil } } ,
{ "value takes remaining suffix" , [ ] string { "run" , "-cvq" } , [ ] [ ] string { { "vq" } , nil , nil , nil , nil } , [ ] [ ] string { { "-c" } , nil , nil , nil , nil } } ,
{ "boolean explicit false" , [ ] string { "run" , "-v=false" } , [ ] [ ] string { nil , { "false" } , nil , nil , nil } , [ ] [ ] string { nil , { "-v" } , nil , nil , nil } } ,
{ "boolean explicit false in cluster" , [ ] string { "run" , "-qv=false" } , [ ] [ ] string { nil , { "false" } , { "true" } , nil , nil } , [ ] [ ] string { nil , { "-v" } , { "-q" } , nil , nil } } ,
{ "empty canonical short" , [ ] string { "run" , "-c=" } , [ ] [ ] string { { "" } , nil , nil , nil , nil } , [ ] [ ] string { { "-c" } , nil , nil , nil , nil } } ,
{ "empty canonical long" , [ ] string { "run" , "--color=" } , [ ] [ ] string { { "" } , nil , nil , nil , nil } , [ ] [ ] string { { "--color" } , nil , nil , nil , nil } } ,
{ "empty separated short" , [ ] string { "run" , "-c" , "" } , [ ] [ ] string { { "" } , nil , nil , nil , nil } , [ ] [ ] string { { "-c" } , nil , nil , nil , nil } } ,
{ "empty aliased long" , [ ] string { "run" , "--colour=" } , [ ] [ ] string { { "" } , nil , nil , nil , nil } , [ ] [ ] string { { "--colour" } , nil , nil , nil , nil } } ,
{ "empty aliased short" , [ ] string { "run" , "-C=" } , [ ] [ ] string { { "" } , nil , nil , nil , nil } , [ ] [ ] string { { "-C" } , nil , nil , nil , nil } } ,
{ "empty boolean short" , [ ] string { "run" , "-v=" } , [ ] [ ] string { nil , { "" } , nil , nil , nil } , [ ] [ ] string { nil , { "-v" } , nil , nil , nil } } ,
} {
t . Run ( test . name , func ( t * testing . T ) {
result := Parse ( flagGrammarTree ( ) , test . args , flagGrammarOptions ( ) )
if result . Failure != nil {
t . Fatalf ( "parse failed: %s" , result . Failure . Message )
}
if ! reflect . DeepEqual ( result . FlagValues , test . values ) {
t . Errorf ( "values = %#v, want %#v" , result . FlagValues , test . values )
}
if ! reflect . DeepEqual ( result . FlagSpellings , test . spellings ) {
t . Errorf ( "spellings = %#v, want %#v" , result . FlagSpellings , test . spellings )
}
} )
}
}
func TestParseFlagAliasesPreserveValuesAndTerminators ( t * testing . T ) {
result := Parse ( flagGrammarTree ( ) , [ ] string { "run" , "--message" , "--hlep" , "--message=--hlep" , "--taggy" , "one" , "--" , "--taggy" } , flagGrammarOptions ( ) )
if result . Failure != nil {
t . Fatalf ( "parse failed: %s" , result . Failure . Message )
}
if got , want := result . FlagValues [ 3 ] , [ ] string { "--hlep" , "--hlep" } ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "message values = %#v, want %#v" , got , want )
}
if got , want := result . FlagSpellings [ 3 ] , [ ] string { "--message" , "--message" } ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "message spellings = %#v, want %#v" , got , want )
}
if got , want := result . FlagValues [ 4 ] , [ ] string { "one" } ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "tag values = %#v, want %#v" , got , want )
}
if got , want := result . Operands , [ ] string { "--taggy" } ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "operands = %#v, want %#v" , got , want )
}
}
2026-10-20 00:28:38 +00:00
func TestParseConsumesDoubleDashAsAFlagValue ( t * testing . T ) {
for _ , spelling := range [ ] string { "--color" , "--colour" , "-c" , "-C" } {
t . Run ( spelling , func ( t * testing . T ) {
result := Parse ( flagGrammarTree ( ) , [ ] string { "run" , spelling , "--" , "--taggy=one" , "--colour=red" , "--" , "--taggy=operand" } , flagGrammarOptions ( ) )
if result . Failure != nil {
t . Fatalf ( "parse failed: %s" , result . Failure . Message )
}
if ! reflect . DeepEqual ( result . FlagValues [ 0 ] , [ ] string { "--" , "red" } ) || ! reflect . DeepEqual ( result . FlagValues [ 4 ] , [ ] string { "one" } ) {
t . Errorf ( "flag values = %#v" , result . FlagValues )
}
if ! reflect . DeepEqual ( result . FlagSpellings [ 0 ] , [ ] string { spelling , "--colour" } ) || ! reflect . DeepEqual ( result . Operands , [ ] string { "--taggy=operand" } ) {
t . Errorf ( "spellings = %#v, operands = %#v" , result . FlagSpellings , result . Operands )
}
} )
}
for _ , args := range [ ] [ ] string { { "run" , "-v" , "--" , "--colour" } , { "run" , "--color" , "blue" , "--" , "--colour" } } {
result := Parse ( flagGrammarTree ( ) , args , flagGrammarOptions ( ) )
if result . Failure != nil || ! reflect . DeepEqual ( result . Operands , [ ] string { "--colour" } ) {
t . Errorf ( "Parse(%q) = %#v" , args , result )
}
}
}
2026-10-19 19:31:23 +00:00
func TestParseHelpAliasAndUnknownShortMembers ( t * testing . T ) {
result := Parse ( flagGrammarTree ( ) , [ ] string { "run" , "--hlep" } , flagGrammarOptions ( ) )
if result . Failure != nil || ! result . HelpRequested {
t . Fatalf ( "help alias result = %#v" , result )
}
result = Parse ( flagGrammarTree ( ) , [ ] string { "run" , "-vx" } , flagGrammarOptions ( ) )
if result . Failure == nil || result . Failure . Message == "" {
t . Fatalf ( "unknown short cluster member result = %#v" , result )
}
result = Parse ( flagGrammarTree ( ) , [ ] string { "run" , "--color" } , flagGrammarOptions ( ) )
if result . Failure == nil || result . Failure . Message == "" {
t . Fatalf ( "missing value result = %#v" , result )
}
result = Parse ( flagGrammarTree ( ) , [ ] string { "run" , "--colour" } , flagGrammarOptions ( ) )
if result . Failure == nil || result . Failure . Message == "" {
t . Fatalf ( "missing aliased value result = %#v" , result )
}
result = Parse ( flagGrammarTree ( ) , [ ] string { "run" , "-c" } , flagGrammarOptions ( ) )
if result . Failure == nil || result . Failure . Message == "" {
t . Fatalf ( "missing short value result = %#v" , result )
}
result = Parse ( flagGrammarTree ( ) , [ ] string { "run" , "-C" } , flagGrammarOptions ( ) )
if result . Failure == nil || result . Failure . Message == "" {
t . Fatalf ( "missing aliased short value result = %#v" , result )
}
result = Parse ( flagGrammarTree ( ) , [ ] string { "run" , "-vfalse" } , flagGrammarOptions ( ) )
if result . Failure == nil || result . Failure . Message == "" {
t . Fatalf ( "boolean suffix without equals result = %#v" , result )
}
}
func TestParseFlagAliasOccurrenceOrderAndSpellings ( t * testing . T ) {
result := Parse ( flagGrammarTree ( ) , [ ] string { "run" , "--taggy=first" , "-t" , "second" , "--taggy" , "third" } , flagGrammarOptions ( ) )
if result . Failure != nil {
t . Fatalf ( "parse failed: %s" , result . Failure . Message )
}
if got , want := result . FlagValues [ 4 ] , [ ] string { "first" , "second" , "third" } ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "tag values = %#v, want %#v" , got , want )
}
if got , want := result . FlagSpellings [ 4 ] , [ ] string { "--taggy" , "-t" , "--taggy" } ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "tag spellings = %#v, want %#v" , got , want )
}
}
2026-10-20 00:29:49 +00:00
func TestParseFailuresRetainEnteredFlagSpellings ( t * testing . T ) {
for _ , spelling := range [ ] string { "--color" , "--colour" , "-c" , "-C" } {
t . Run ( "missing/" + spelling , func ( t * testing . T ) {
result := Parse ( flagGrammarTree ( ) , [ ] string { "run" , spelling } , flagGrammarOptions ( ) )
if result . Failure == nil || ! strings . Contains ( result . Failure . Message , ": " + spelling ) {
t . Fatalf ( "missing value failure = %#v, want entered spelling %q" , result . Failure , spelling )
}
} )
}
result := Parse ( flagGrammarTree ( ) , [ ] string { "run" , "-wC" } , flagGrammarOptions ( ) )
if result . Failure == nil || ! strings . Contains ( result . Failure . Message , ": -C" ) {
t . Fatalf ( "cluster missing value failure = %#v" , result . Failure )
}
for _ , test := range [ ] struct {
args [ ] string
spelling string
} {
{ [ ] string { "--hlep=bad" } , "--hlep" } ,
{ [ ] string { "-h=bad" } , "-h" } ,
{ [ ] string { "--help=bad" } , "--help" } ,
{ [ ] string { "--hlep=bad" , "--help=true" } , "--hlep" } ,
{ [ ] string { "-h=bad" , "--hlep=true" } , "-h" } ,
{ [ ] string { "--help=true" , "--hlep=bad" } , "--hlep" } ,
} {
t . Run ( strings . Join ( test . args , " " ) , func ( t * testing . T ) {
result := Parse ( flagGrammarTree ( ) , append ( [ ] string { "run" } , test . args ... ) , flagGrammarOptions ( ) )
if result . Failure == nil || ! strings . Contains ( result . Failure . Message , "for " + test . spelling ) {
t . Fatalf ( "help failure = %#v, want entered spelling %q" , result . Failure , test . spelling )
}
} )
}
result = Parse ( flagGrammarTree ( ) , [ ] string { "run" , "--unknown" , "--colour" } , flagGrammarOptions ( ) )
if result . Failure == nil || ! strings . Contains ( result . Failure . Message , "unknown" ) || strings . Contains ( result . Failure . Message , "needs an argument" ) {
t . Fatalf ( "earlier parser failure = %#v" , result . Failure )
}
}
2026-10-26 14:37:33 +00:00
func TestFlagInterpretationStopsAtTheBackendFailurePrefix ( t * testing . T ) {
for _ , test := range [ ] struct {
name string
args [ ] string
want [ ] string
} {
{ "long" , [ ] string { "--debug" , "--unknown" } , [ ] string { "--debug" , "--unknown" } } ,
{ "short cluster" , [ ] string { "-dqz" } , [ ] string { "--debug" , "--quiet" , "-z" } } ,
} {
t . Run ( test . name , func ( t * testing . T ) {
// The later value must not be canonicalized or retained in spellings.
args := append ( append ( [ ] string ( nil ) , test . args ... ) , "--message=" + strings . Repeat ( "value" , 1024 ) , "--quiet=false" )
flags := append ( commandmeta . ReportingFlags ( ) , commandmeta . Flag { Name : "message" } )
interpreted , spellings , occurrences , failure := interpretFlagTokens ( args , flags , reportingFlagOptions ( ) . FlagAliases )
if failure != nil || ! reflect . DeepEqual ( interpreted , test . want ) || len ( spellings [ "message" ] ) != 0 {
t . Fatalf ( "interpreted=%q spellings=%v failure=%v" , interpreted , spellings , failure )
}
if len ( occurrences ) != len ( test . want ) - 1 || occurrences [ 0 ] . Name != "debug" {
t . Fatalf ( "consumed occurrences=%#v" , occurrences )
}
result := Parse ( reportingFlagTree ( ) , append ( [ ] string { "run" } , args ... ) , reportingFlagOptions ( ) )
if result . Failure == nil || ! strings . Contains ( result . Failure . Message , "unknown" ) || ! reflect . DeepEqual ( result . FlagOccurrences , occurrences ) {
t . Fatalf ( "backend result=%#v" , result )
}
} )
}
}