227 lines
8.9 KiB
Go
227 lines
8.9 KiB
Go
package cobra
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
spfcobra "github.com/spf13/cobra"
|
|
)
|
|
|
|
func TestParseRoutesBeforeOptionsAndKeepsFlagsLocal(t *testing.T) {
|
|
tree := &Command{Name: "app", Children: []*Command{
|
|
{Name: "config", Children: []*Command{
|
|
{Name: "show", Runnable: true, Flags: []Flag{{Name: "scope"}}},
|
|
}},
|
|
}}
|
|
|
|
result := Parse(tree, []string{"config", "show", "--scope", "app"}, ParseOptions{})
|
|
if result.Failure != nil {
|
|
t.Fatalf("parse failed: %s", result.Failure.Message)
|
|
}
|
|
if !reflect.DeepEqual(result.Path, []string{"config", "show"}) || !reflect.DeepEqual(result.FlagValues, [][]string{{"app"}}) {
|
|
t.Fatalf("selected result = %#v", result)
|
|
}
|
|
|
|
result = Parse(tree, []string{"config", "--scope", "app", "show"}, ParseOptions{})
|
|
if result.Failure == nil {
|
|
t.Fatal("accepted a child flag before the complete command path")
|
|
}
|
|
}
|
|
|
|
func TestParseRequiresExactCommandPathWhenPrefixMatchingIsEnabled(t *testing.T) {
|
|
previous := spfcobra.EnablePrefixMatching
|
|
spfcobra.EnablePrefixMatching = true
|
|
t.Cleanup(func() { spfcobra.EnablePrefixMatching = previous })
|
|
|
|
tree := &Command{Name: "app", Children: []*Command{{Name: "config", Runnable: true}}}
|
|
result := Parse(tree, []string{"con"}, ParseOptions{})
|
|
if result.Failure == nil {
|
|
t.Fatal("accepted a prefixed command path")
|
|
}
|
|
}
|
|
|
|
func TestParseInterspersedOperandsBooleanFlagsAndTerminator(t *testing.T) {
|
|
tree := &Command{Name: "app", Children: []*Command{
|
|
{Name: "restore", Runnable: true, Flags: []Flag{
|
|
{Name: "enabled", Boolean: true},
|
|
{Name: "tag", Repeated: true},
|
|
}},
|
|
}}
|
|
result := Parse(tree, []string{"restore", "before", "--enabled", "--tag", "a,b", "--tag=c", "--", "--literal"}, ParseOptions{})
|
|
if result.Failure != nil {
|
|
t.Fatalf("parse failed: %s", result.Failure.Message)
|
|
}
|
|
if !reflect.DeepEqual(result.FlagValues, [][]string{{"true"}, {"a,b", "c"}}) {
|
|
t.Fatalf("flag values = %#v", result.FlagValues)
|
|
}
|
|
if !reflect.DeepEqual(result.Operands, []string{"before", "--literal"}) {
|
|
t.Fatalf("operands = %#v", result.Operands)
|
|
}
|
|
|
|
result = Parse(tree, []string{"restore", "--enabled=false"}, ParseOptions{})
|
|
if result.Failure != nil || !reflect.DeepEqual(result.FlagValues[0], []string{"false"}) {
|
|
t.Fatalf("explicit false = %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestParseHelpIsLocalAndFalseDoesNotRequestHelp(t *testing.T) {
|
|
tree := &Command{Name: "app", Children: []*Command{
|
|
{Name: "run", Runnable: true, Flags: []Flag{{Name: "required", Required: true}}},
|
|
}}
|
|
result := Parse(tree, []string{"run", "--help"}, ParseOptions{})
|
|
if result.Failure != nil || !result.HelpRequested {
|
|
t.Fatalf("help parse = %#v", result)
|
|
}
|
|
result = Parse(tree, []string{"run", "--help=false"}, ParseOptions{})
|
|
if result.Failure != nil || result.HelpRequested {
|
|
t.Fatalf("false help parse = %#v", result)
|
|
}
|
|
result = Parse(tree, []string{"run", "--help=1"}, ParseOptions{})
|
|
if result.Failure != nil || !result.HelpRequested {
|
|
t.Fatalf("strconv boolean help parse = %#v", result)
|
|
}
|
|
result = Parse(tree, []string{"run", "--help=invalid", "--help"}, ParseOptions{})
|
|
if result.Failure == nil {
|
|
t.Fatal("accepted an invalid earlier help occurrence")
|
|
}
|
|
}
|
|
|
|
func TestParseRunnableParentAndGroupUnknownCommand(t *testing.T) {
|
|
tree := &Command{Name: "app", Children: []*Command{
|
|
{Name: "deploy", Runnable: true, Flags: []Flag{{Name: "scope"}}, Children: []*Command{{Name: "now", Runnable: true}}},
|
|
{Name: "config", Children: []*Command{{Name: "show", Runnable: true}}},
|
|
}}
|
|
result := Parse(tree, []string{"deploy", "target"}, ParseOptions{})
|
|
if result.Failure != nil || !reflect.DeepEqual(result.Path, []string{"deploy"}) || !reflect.DeepEqual(result.Operands, []string{"target"}) {
|
|
t.Fatalf("runnable parent result = %#v", result)
|
|
}
|
|
result = Parse(tree, []string{"deploy", "--scope", "local"}, ParseOptions{})
|
|
if result.Failure != nil || !reflect.DeepEqual(result.FlagValues, [][]string{{"local"}}) {
|
|
t.Fatalf("runnable parent local flag = %#v", result)
|
|
}
|
|
result = Parse(tree, []string{"deploy", "now"}, ParseOptions{})
|
|
if result.Failure != nil || !reflect.DeepEqual(result.Path, []string{"deploy", "now"}) {
|
|
t.Fatalf("nested runnable child = %#v", result)
|
|
}
|
|
result = Parse(tree, []string{"config", "mystery"}, ParseOptions{})
|
|
if result.Failure == nil {
|
|
t.Fatal("accepted an unknown trailing command beneath a group")
|
|
}
|
|
}
|
|
|
|
func TestParseReturnsNeutralFailuresAndFreshState(t *testing.T) {
|
|
tree := &Command{Name: "app", Children: []*Command{
|
|
{Name: "run", Runnable: true, Flags: []Flag{{Name: "value"}}},
|
|
}}
|
|
options := ParseOptions{Context: context.Background(), In: strings.NewReader("unused"), Out: io.Discard, Err: io.Discard}
|
|
first := Parse(tree, []string{"run", "--value=old"}, options)
|
|
second := Parse(tree, []string{"run"}, options)
|
|
if first.Failure != nil || second.Failure != nil {
|
|
t.Fatalf("parse results = %#v, %#v", first, second)
|
|
}
|
|
if !reflect.DeepEqual(first.FlagValues, [][]string{{"old"}}) || !reflect.DeepEqual(second.FlagValues, [][]string{nil}) {
|
|
t.Fatalf("state leaked between parses: %#v then %#v", first.FlagValues, second.FlagValues)
|
|
}
|
|
failed := Parse(tree, []string{"run", "--unknown"}, options)
|
|
if failed.Failure == nil || failed.Failure.Message == "" {
|
|
t.Fatalf("unknown flag failure = %#v", failed.Failure)
|
|
}
|
|
}
|
|
|
|
func TestSelectedPathsResolveThroughBackendParents(t *testing.T) {
|
|
root := &Command{Name: "app"}
|
|
for index := 0; index < 128; index++ {
|
|
root.Children = append(root.Children, &Command{Name: fmt.Sprintf("wide%03d", index), Runnable: true})
|
|
}
|
|
path := make([]string, 24)
|
|
current := root
|
|
for index := range path {
|
|
path[index] = fmt.Sprintf("deep%02d", index)
|
|
child := &Command{Name: path[index]}
|
|
if index == len(path)-1 {
|
|
child.Runnable = true
|
|
}
|
|
current.Children = append(current.Children, child)
|
|
current = child
|
|
}
|
|
|
|
result := Parse(root, path, ParseOptions{})
|
|
if result.Failure != nil || !reflect.DeepEqual(result.Path, path) {
|
|
t.Fatalf("deep parse path = %#v", result)
|
|
}
|
|
rootResult := Parse(root, nil, ParseOptions{})
|
|
if rootResult.Failure != nil || len(rootResult.Path) != 0 {
|
|
t.Fatalf("root parse path = %#v", rootResult)
|
|
}
|
|
|
|
help, failure := RenderHelp(root, path)
|
|
if failure != nil || !strings.Contains(help, path[len(path)-1]) {
|
|
t.Fatalf("deep help = %q, %v", help, failure)
|
|
}
|
|
if _, failure := RenderHelp(root, []string{"wide128"}); failure == nil {
|
|
t.Fatal("help accepted an unknown sibling")
|
|
}
|
|
line, failure := UsageLine(root, path)
|
|
if failure != nil || !strings.Contains(line, path[len(path)-1]) {
|
|
t.Fatalf("deep usage line = %q, %v", line, failure)
|
|
}
|
|
if _, failure := UsageLine(root, []string{"wide128"}); failure == nil {
|
|
t.Fatal("usage accepted an unknown sibling")
|
|
}
|
|
if _, failure := RenderHelp(root, nil); failure != nil {
|
|
t.Fatalf("root help failed: %v", failure)
|
|
}
|
|
if _, failure := UsageLine(root, nil); failure != nil {
|
|
t.Fatalf("root usage failed: %v", failure)
|
|
}
|
|
}
|
|
|
|
func TestSessionReusesTreeForParseHelpAndUsageWithFreshState(t *testing.T) {
|
|
tree := &Command{Name: "app", Children: []*Command{{Name: "run", Runnable: true, Flags: []Flag{{Name: "value"}}}}}
|
|
var output, errors bytes.Buffer
|
|
session := Prepare(tree, ParseOptions{Context: context.Background(), Out: &output, Err: &errors})
|
|
first := session.Parse([]string{"run", "--value=old"})
|
|
if first.Failure != nil || !reflect.DeepEqual(first.FlagValues, [][]string{{"old"}}) {
|
|
t.Fatalf("first session parse = %#v", first)
|
|
}
|
|
help, failure := session.RenderHelp([]string{"run"})
|
|
if failure != nil || !strings.Contains(help, "run") {
|
|
t.Fatalf("session help = %q, %v", help, failure)
|
|
}
|
|
line, failure := session.UsageLine([]string{"run"})
|
|
if failure != nil || !strings.Contains(line, "run") {
|
|
t.Fatalf("session usage = %q, %v", line, failure)
|
|
}
|
|
if output.Len() != 0 || errors.Len() != 0 {
|
|
t.Fatalf("help escaped session delivery control: out=%q err=%q", output.String(), errors.String())
|
|
}
|
|
|
|
var secondOutput, secondErrors bytes.Buffer
|
|
second := Prepare(tree, ParseOptions{Context: context.Background(), Out: &secondOutput, Err: &secondErrors})
|
|
secondResult := second.Parse([]string{"run"})
|
|
if secondResult.Failure != nil || !reflect.DeepEqual(secondResult.FlagValues, [][]string{nil}) {
|
|
t.Fatalf("fresh session state = %#v", secondResult)
|
|
}
|
|
if output.Len() != 0 || errors.Len() != 0 || secondOutput.Len() != 0 || secondErrors.Len() != 0 {
|
|
t.Fatal("prepared session streams were unexpectedly used")
|
|
}
|
|
}
|
|
|
|
func TestMissingSessionTreeReturnsNeutralFailures(t *testing.T) {
|
|
session := Prepare(nil, ParseOptions{})
|
|
if result := session.Parse(nil); result.Failure == nil || result.Failure.Message != "command tree is nil" {
|
|
t.Fatalf("missing-tree parse = %#v", result)
|
|
}
|
|
if _, failure := session.RenderHelp(nil); failure == nil || failure.Message != "command tree is nil" {
|
|
t.Fatalf("missing-tree help failure = %#v", failure)
|
|
}
|
|
if _, failure := session.UsageLine(nil); failure == nil || failure.Message != "command tree is nil" {
|
|
t.Fatalf("missing-tree usage failure = %#v", failure)
|
|
}
|
|
}
|