169 lines
7.7 KiB
Go
169 lines
7.7 KiB
Go
package bonfire
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// peekCases are the global option rows shared by TestPeekGlobals and
|
|
// TestGlobalFlagsAgree. cobra marks rows whose args cobra itself accepts
|
|
// (it rejects unknown flags, so those rows are PeekGlobals-only).
|
|
var peekCases = []struct {
|
|
name string
|
|
args []string
|
|
want Globals
|
|
cobra bool
|
|
}{
|
|
{"none", []string{"migrate"}, Globals{}, true},
|
|
{"-v", []string{"-v", "migrate"}, Globals{Verbosity: 1}, true},
|
|
{"-vv", []string{"migrate", "-vv"}, Globals{Verbosity: 2}, true},
|
|
{"-vvv", []string{"-vvv", "migrate"}, Globals{Verbosity: 3}, true},
|
|
{"-vvvv capped", []string{"-vvvv", "migrate"}, Globals{Verbosity: 3}, true},
|
|
{"--verbose twice", []string{"--verbose", "migrate", "--verbose"}, Globals{Verbosity: 2}, true},
|
|
{"-v -vv capped", []string{"-v", "migrate", "-vv", "--verbose"}, Globals{Verbosity: 3}, true},
|
|
{"-q implies -n", []string{"migrate", "-q"}, Globals{Quiet: true, NoInteraction: true}, true},
|
|
{"--quiet", []string{"--quiet", "migrate"}, Globals{Quiet: true, NoInteraction: true}, true},
|
|
{"-n", []string{"migrate", "-n"}, Globals{NoInteraction: true}, true},
|
|
{"--no-interaction", []string{"--no-interaction", "migrate"}, Globals{NoInteraction: true}, true},
|
|
{"--ansi", []string{"migrate", "--ansi"}, Globals{Color: ColorAlways}, true},
|
|
{"--no-ansi", []string{"--no-ansi", "migrate"}, Globals{Color: ColorNever}, true},
|
|
{"--ansi then --no-ansi", []string{"--ansi", "migrate", "--no-ansi"}, Globals{Color: ColorNever}, true},
|
|
{"--no-ansi then --ansi", []string{"--no-ansi", "migrate", "--ansi"}, Globals{Color: ColorAlways}, true},
|
|
{"--env X", []string{"--env", "staging", "migrate"}, Globals{Env: "staging"}, true},
|
|
{"--env=X", []string{"migrate", "--env=production"}, Globals{Env: "production"}, true},
|
|
{"--env takes a dash value", []string{"migrate", "--env", "-v"}, Globals{Env: "-v"}, true},
|
|
{"last --env wins", []string{"--env=a", "migrate", "--env", "b"}, Globals{Env: "b"}, true},
|
|
{"stop at --", []string{"migrate", "--", "-v", "-q", "--env=x"}, Globals{}, true},
|
|
{"combined -qn", []string{"migrate", "-qn"}, Globals{Quiet: true, NoInteraction: true}, true},
|
|
{"combined -vq", []string{"-vq", "migrate"}, Globals{Verbosity: 1, Quiet: true, NoInteraction: true}, true},
|
|
{"combined -nvv", []string{"migrate", "-nvv"}, Globals{Verbosity: 2, NoInteraction: true}, true},
|
|
{"everything", []string{"-vv", "--env=dev", "migrate", "-n", "--no-ansi"}, Globals{Verbosity: 2, NoInteraction: true, Color: ColorNever, Env: "dev"}, true},
|
|
// IN-02: the =value forms, read with pflag's value rules.
|
|
{"--verbose=2", []string{"migrate", "--verbose=2"}, Globals{Verbosity: 2}, true},
|
|
{"-v=3", []string{"migrate", "-v=3"}, Globals{Verbosity: 3}, true},
|
|
{"-vv=1", []string{"migrate", "-vv=1"}, Globals{Verbosity: 1}, true},
|
|
{"-v --verbose=2 -v", []string{"migrate", "-v", "--verbose=2", "-v"}, Globals{Verbosity: 3}, true},
|
|
{"--quiet=true", []string{"migrate", "--quiet=true"}, Globals{Quiet: true, NoInteraction: true}, true},
|
|
{"-q --quiet=false", []string{"migrate", "-q", "--quiet=false"}, Globals{}, true},
|
|
{"-q=false", []string{"migrate", "-q=false"}, Globals{}, true},
|
|
{"--no-interaction=1", []string{"migrate", "--no-interaction=1"}, Globals{NoInteraction: true}, true},
|
|
{"-n=t", []string{"migrate", "-n=t"}, Globals{NoInteraction: true}, true},
|
|
{"-qn=false", []string{"migrate", "-qn=false"}, Globals{Quiet: true, NoInteraction: true}, true},
|
|
{"--ansi=false", []string{"migrate", "--ansi=false"}, Globals{Color: ColorAuto}, true},
|
|
{"--ansi --ansi=false", []string{"migrate", "--ansi", "--ansi=false"}, Globals{Color: ColorAuto}, true},
|
|
{"--ansi=true", []string{"migrate", "--ansi=true"}, Globals{Color: ColorAlways}, true},
|
|
{"--no-ansi=true", []string{"migrate", "--no-ansi=true"}, Globals{Color: ColorNever}, true},
|
|
{"--no-ansi --ansi=false", []string{"migrate", "--no-ansi", "--ansi=false"}, Globals{Color: ColorNever}, true},
|
|
{"unparsable values ignored", []string{"migrate", "-v", "--verbose=x", "--quiet=maybe", "--ansi=sure"}, Globals{Verbosity: 1}, false},
|
|
{"unknown flags ignored", []string{"--bogus", "-x", "migrate", "-", "--env"}, Globals{}, false},
|
|
{"--env without a value", []string{"migrate", "--env"}, Globals{}, false},
|
|
{"empty", nil, Globals{}, false},
|
|
}
|
|
|
|
// TestPeekGlobals: the tolerant pre-boot scan reads every global option
|
|
// form, caps verbosity, lets the last color flag win, stops at -- and
|
|
// never fails on unknown tokens.
|
|
func TestPeekGlobals(t *testing.T) {
|
|
for _, tc := range peekCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := PeekGlobals(tc.args); got != tc.want {
|
|
t.Errorf("PeekGlobals(%q) = %+v, want %+v", tc.args, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGlobalsArgs: Args re-serialises Globals so PeekGlobals reads them
|
|
// back unchanged.
|
|
func TestGlobalsArgs(t *testing.T) {
|
|
cases := []struct {
|
|
g Globals
|
|
want string
|
|
}{
|
|
{Globals{}, ""},
|
|
{Globals{Verbosity: 1}, "-v"},
|
|
{Globals{Verbosity: 3}, "-vvv"},
|
|
{Globals{Verbosity: 7}, "-vvv"},
|
|
{Globals{Quiet: true, NoInteraction: true}, "-q"},
|
|
{Globals{NoInteraction: true}, "-n"},
|
|
{Globals{Color: ColorAlways}, "--ansi"},
|
|
{Globals{Color: ColorNever}, "--no-ansi"},
|
|
{Globals{Env: "staging"}, "--env=staging"},
|
|
{Globals{Verbosity: 2, Quiet: true, NoInteraction: true, Color: ColorNever, Env: "dev"}, "-vv -q --no-ansi --env=dev"},
|
|
}
|
|
for _, tc := range cases {
|
|
args := tc.g.Args()
|
|
if got := strings.Join(args, " "); got != tc.want {
|
|
t.Errorf("%+v.Args() = %q, want %q", tc.g, got, tc.want)
|
|
}
|
|
if back := PeekGlobals(args); back != tc.g.normalize() {
|
|
t.Errorf("PeekGlobals(%q) = %+v, want %+v", args, back, tc.g.normalize())
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestGlobalFlagsAgree: for every row cobra accepts, the OnGlobals hook
|
|
// sees exactly the Globals PeekGlobals read before the root existed.
|
|
func TestGlobalFlagsAgree(t *testing.T) {
|
|
for _, tc := range peekCases {
|
|
if !tc.cobra {
|
|
continue
|
|
}
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var hooked []Globals
|
|
var seen Globals
|
|
cmd := Command{Name: "migrate", Description: "Run the migrations", Run: func(_ context.Context, _ Input, out Output) error {
|
|
seen = GlobalsOf(out)
|
|
return nil
|
|
}}
|
|
var buf bytes.Buffer
|
|
root, err := NewRootIO("acme", []Command{cmd}, strings.NewReader(""), &buf, &buf,
|
|
WithVersion(goldenVersion), OnGlobals(func(g Globals) { hooked = append(hooked, g) }))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if code := Execute(context.Background(), root, slices.Clone(tc.args)); code != ExitOK {
|
|
t.Fatalf("exit %d:\n%s", code, buf.String())
|
|
}
|
|
peek := PeekGlobals(tc.args)
|
|
if len(hooked) != 1 || hooked[0] != peek {
|
|
t.Errorf("hook saw %+v, PeekGlobals %+v", hooked, peek)
|
|
}
|
|
if seen != peek {
|
|
t.Errorf("GlobalsOf(out) = %+v, PeekGlobals %+v", seen, peek)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestErrorBlockUsesParsedGlobals (IN-02): once cobra has parsed the
|
|
// command's flags, the error block uses cobra's globals. In
|
|
// "migrate --name -v" the -v is the value of --name, so the block stays at
|
|
// verbosity 0 although PeekGlobals reads -v.
|
|
func TestErrorBlockUsesParsedGlobals(t *testing.T) {
|
|
plainEnv(t)
|
|
cmd := Command{
|
|
Name: "migrate",
|
|
Description: "Run the migrations",
|
|
Flags: []Flag{{Name: "name", Description: "A name"}},
|
|
Run: func(_ context.Context, in Input, _ Output) error {
|
|
if v, _ := in.Flag("name"); v != "-v" {
|
|
t.Errorf("--name = %q, want -v", v)
|
|
}
|
|
return fmt.Errorf("outer: %w", errors.New("inner"))
|
|
},
|
|
}
|
|
_, errOut, code := runGolden(t, []Command{cmd}, "migrate", "--name", "-v")
|
|
if code != ExitFailure {
|
|
t.Fatalf("exit %d, want %d\n%s", code, ExitFailure, errOut)
|
|
}
|
|
if !strings.Contains(errOut, "Run with -v for details.") || strings.Contains(errOut, "Caused by:") {
|
|
t.Errorf("block does not use the parsed verbosity 0:\n%s", errOut)
|
|
}
|
|
}
|