mirror of
https://github.com/neilotoole/sq.git
synced 2024-12-18 13:41:49 +03:00
547 lines
15 KiB
Go
547 lines
15 KiB
Go
package cli_test
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/neilotoole/sq/cli"
|
|
"github.com/neilotoole/sq/cli/cobraz"
|
|
"github.com/neilotoole/sq/cli/flag"
|
|
"github.com/neilotoole/sq/cli/testrun"
|
|
"github.com/neilotoole/sq/libsq/core/lg"
|
|
"github.com/neilotoole/sq/libsq/core/lg/lgt"
|
|
"github.com/neilotoole/sq/libsq/core/options"
|
|
"github.com/neilotoole/sq/libsq/source"
|
|
"github.com/neilotoole/sq/testh"
|
|
"github.com/neilotoole/sq/testh/sakila"
|
|
"github.com/neilotoole/sq/testh/tu"
|
|
)
|
|
|
|
// testComplete is a helper for testing cobra completion.
|
|
func testComplete(tb testing.TB, from *testrun.TestRun, args ...string) completion {
|
|
tb.Helper()
|
|
var ctx context.Context
|
|
if from == nil {
|
|
ctx = lg.NewContext(context.Background(), lgt.New(tb))
|
|
} else {
|
|
ctx = from.Context
|
|
}
|
|
|
|
ctx = enableCompletionLog(ctx)
|
|
tr := testrun.New(ctx, tb, from)
|
|
args = append([]string{cobra.ShellCompRequestCmd}, args...)
|
|
|
|
err := tr.Exec(args...)
|
|
require.NoError(tb, err)
|
|
|
|
c := parseCompletion(tr)
|
|
return c
|
|
}
|
|
|
|
// parseCompletion parses the output of cobra "__complete".
|
|
// Example output:
|
|
//
|
|
// @active
|
|
// @sakila
|
|
// :4
|
|
// Completion ended with directive: ShellCompDirectiveNoFileComp
|
|
//
|
|
// The tr.T test will fail on any error.
|
|
func parseCompletion(tr *testrun.TestRun) completion {
|
|
c := completion{
|
|
stdout: tr.Out.String(),
|
|
stderr: tr.ErrOut.String(),
|
|
}
|
|
|
|
lines := strings.Split(strings.TrimSpace(c.stdout), "\n")
|
|
require.True(tr.T, len(lines) >= 1)
|
|
c.values = lines[:len(lines)-1]
|
|
|
|
result, err := strconv.Atoi(lines[len(lines)-1][1:])
|
|
require.NoError(tr.T, err)
|
|
c.result = cobra.ShellCompDirective(result)
|
|
|
|
c.directives = cobraz.ParseDirectivesLine(c.stderr)
|
|
return c
|
|
}
|
|
|
|
// completion models the result returned from the cobra "__complete" command.
|
|
type completion struct {
|
|
stdout string
|
|
stderr string
|
|
values []string
|
|
result cobra.ShellCompDirective
|
|
directives []cobra.ShellCompDirective
|
|
}
|
|
|
|
// TestCompleteFlagActiveSchema_query_cmds tests flag.ActiveSchema
|
|
// behavior for the query commands (slq, sql).
|
|
//
|
|
// See also: TestCompleteFlagActiveSchema_inspect.
|
|
func TestCompleteFlagActiveSchema_query_cmds(t *testing.T) { //nolint:tparallel
|
|
const wantDirective = cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder
|
|
|
|
testCases := []struct {
|
|
handles []string
|
|
arg string
|
|
withFlagActiveSrc string
|
|
wantContains []string
|
|
wantDirective cobra.ShellCompDirective
|
|
}{
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "saki",
|
|
wantContains: []string{"sakila."},
|
|
wantDirective: wantDirective | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "",
|
|
wantContains: []string{"public", "sakila."},
|
|
wantDirective: wantDirective | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "sakila",
|
|
wantContains: []string{"sakila."},
|
|
wantDirective: wantDirective | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "sakila.pub",
|
|
wantContains: []string{"sakila.public"},
|
|
wantDirective: wantDirective,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "pub",
|
|
wantContains: []string{"public"},
|
|
wantDirective: wantDirective,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "public",
|
|
wantContains: []string{"public"},
|
|
wantDirective: wantDirective,
|
|
},
|
|
{
|
|
handles: []string{sakila.My, sakila.Pg},
|
|
withFlagActiveSrc: sakila.Pg,
|
|
arg: "publ",
|
|
wantContains: []string{"public"},
|
|
wantDirective: wantDirective,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg, sakila.My},
|
|
withFlagActiveSrc: sakila.My,
|
|
arg: "",
|
|
wantContains: []string{"mysql", "sys", "information_schema", "sakila"},
|
|
wantDirective: wantDirective,
|
|
},
|
|
{
|
|
handles: []string{sakila.My, sakila.Pg},
|
|
withFlagActiveSrc: sakila.MS,
|
|
arg: "publ",
|
|
// Should error because sakila.MS isn't a loaded source (via "handles").
|
|
wantDirective: cobra.ShellCompDirectiveError,
|
|
},
|
|
}
|
|
|
|
for _, cmdName := range []string{"slq", "sql"} {
|
|
cmdName := cmdName
|
|
t.Run(cmdName, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for i, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tu.Name(i, tc.handles, tc.withFlagActiveSrc, tc.arg), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
th := testh.New(t)
|
|
tr := testrun.New(th.Context, t, nil)
|
|
for _, handle := range tc.handles {
|
|
tr.Add(*th.Source(handle))
|
|
}
|
|
|
|
args := []string{cmdName}
|
|
if tc.withFlagActiveSrc != "" {
|
|
args = append(args, "--"+flag.ActiveSrc, tc.withFlagActiveSrc)
|
|
}
|
|
args = append(args, "--"+flag.ActiveSchema, tc.arg)
|
|
|
|
got := testComplete(t, tr, args...)
|
|
assert.Equal(t, tc.wantDirective, got.result,
|
|
"wanted: %v\ngot : %v",
|
|
cobraz.MarshalDirective(tc.wantDirective),
|
|
cobraz.MarshalDirective(got.result))
|
|
|
|
if tc.wantDirective == cobra.ShellCompDirectiveError {
|
|
require.Empty(t, got.values)
|
|
} else {
|
|
for j := range tc.wantContains {
|
|
assert.Contains(t, got.values, tc.wantContains[j])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCompleteFlagActiveSchema_inspect tests flag.ActiveSchema
|
|
// behavior for the inspect command.
|
|
//
|
|
// See also: TestCompleteFlagActiveSchema_query_cmds.
|
|
func TestCompleteFlagActiveSchema_inspect(t *testing.T) {
|
|
tu.SkipIssueWindows(t, tu.GH372ShellCompletionWin)
|
|
t.Parallel()
|
|
|
|
const wantDirective = cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder
|
|
|
|
testCases := []struct {
|
|
handles []string
|
|
arg string
|
|
withArgActiveSrc string
|
|
wantContains []string
|
|
wantDirective cobra.ShellCompDirective
|
|
}{
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "saki",
|
|
wantContains: []string{"sakila."},
|
|
wantDirective: wantDirective | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "",
|
|
wantContains: []string{"public", "sakila."},
|
|
wantDirective: wantDirective | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "sakila",
|
|
wantContains: []string{"sakila."},
|
|
wantDirective: wantDirective | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "sakila.pub",
|
|
wantContains: []string{"sakila.public"},
|
|
wantDirective: wantDirective,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "pub",
|
|
wantContains: []string{"public"},
|
|
wantDirective: wantDirective,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg},
|
|
arg: "public",
|
|
wantContains: []string{"public"},
|
|
wantDirective: wantDirective,
|
|
},
|
|
{
|
|
handles: []string{sakila.My, sakila.Pg},
|
|
withArgActiveSrc: sakila.Pg,
|
|
arg: "publ",
|
|
wantContains: []string{"public"},
|
|
wantDirective: wantDirective,
|
|
},
|
|
{
|
|
handles: []string{sakila.Pg, sakila.My},
|
|
withArgActiveSrc: sakila.My,
|
|
arg: "",
|
|
wantContains: []string{"mysql", "sys", "information_schema", "sakila"},
|
|
wantDirective: wantDirective,
|
|
},
|
|
{
|
|
handles: []string{sakila.My, sakila.Pg},
|
|
withArgActiveSrc: sakila.MS,
|
|
arg: "publ",
|
|
// Should error because sakila.MS isn't a loaded source (via "handles").
|
|
wantDirective: cobra.ShellCompDirectiveError,
|
|
},
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tu.Name(i, tc.handles, tc.withArgActiveSrc, tc.arg), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
th := testh.New(t)
|
|
tr := testrun.New(th.Context, t, nil)
|
|
for _, handle := range tc.handles {
|
|
tr.Add(*th.Source(handle))
|
|
}
|
|
|
|
args := []string{"inspect"}
|
|
if tc.withArgActiveSrc != "" {
|
|
args = append(args, tc.withArgActiveSrc)
|
|
}
|
|
args = append(args, "--"+flag.ActiveSchema, tc.arg)
|
|
|
|
got := testComplete(t, tr, args...)
|
|
assert.Equal(t, tc.wantDirective, got.result,
|
|
"wanted: %v\ngot : %v",
|
|
cobraz.MarshalDirective(tc.wantDirective),
|
|
cobraz.MarshalDirective(got.result))
|
|
|
|
if tc.wantDirective == cobra.ShellCompDirectiveError {
|
|
require.Empty(t, got.values)
|
|
} else {
|
|
for j := range tc.wantContains {
|
|
assert.Contains(t, got.values, tc.wantContains[j])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCompleteFilterActiveGroup tests completion behavior
|
|
// wrt [cli.OptShellCompletionGroupFilter].
|
|
func TestCompleteFilterActiveGroup(t *testing.T) {
|
|
tu.SkipIssueWindows(t, tu.GH372ShellCompletionWin)
|
|
t.Parallel()
|
|
|
|
const (
|
|
prodInv = "@prod/inventory"
|
|
prodSales = "@prod/sales"
|
|
devInv = "@dev/inventory"
|
|
devSales = "@dev/sales"
|
|
)
|
|
|
|
allSrcs := []string{prodInv, prodSales, devInv, devSales}
|
|
prodSrcs := []string{prodInv, prodSales}
|
|
devSrcs := []string{devInv, devSales}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
srcs []string
|
|
activeSrc string
|
|
activeGroup string
|
|
args []string
|
|
wantEquals []string
|
|
wantDirective cobra.ShellCompDirective
|
|
}{
|
|
{
|
|
name: "src_prod_@",
|
|
srcs: allSrcs,
|
|
activeSrc: "",
|
|
activeGroup: "prod",
|
|
args: []string{"src", "@"},
|
|
wantEquals: prodSrcs,
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
},
|
|
{
|
|
name: "src_prod_@dev",
|
|
srcs: allSrcs,
|
|
activeSrc: "",
|
|
activeGroup: "prod",
|
|
args: []string{"src", "@d"},
|
|
wantEquals: devSrcs,
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
},
|
|
{
|
|
name: "src_prod_@dev_slash",
|
|
srcs: allSrcs,
|
|
activeSrc: "",
|
|
activeGroup: "prod",
|
|
args: []string{"src", "@dev/"},
|
|
wantEquals: devSrcs,
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
},
|
|
{
|
|
name: "src_dev_@",
|
|
srcs: allSrcs,
|
|
activeSrc: "",
|
|
activeGroup: "dev",
|
|
args: []string{"src", "@"},
|
|
wantEquals: devSrcs,
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
},
|
|
{
|
|
name: "inspect_prod_@",
|
|
srcs: allSrcs,
|
|
activeSrc: "",
|
|
activeGroup: "prod",
|
|
args: []string{"inspect", "@"},
|
|
wantEquals: prodSrcs,
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
name: "inspect_prod_@prod/sal",
|
|
srcs: allSrcs,
|
|
activeSrc: "",
|
|
activeGroup: "prod",
|
|
args: []string{"inspect", "@prod/sal"},
|
|
wantEquals: []string{prodSales, prodSales + ".data"},
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
},
|
|
{
|
|
name: "inspect_prod_no_active.dat",
|
|
srcs: allSrcs,
|
|
activeSrc: "",
|
|
activeGroup: "prod",
|
|
args: []string{"inspect", ".dat"},
|
|
wantEquals: []string{},
|
|
wantDirective: cobra.ShellCompDirectiveError,
|
|
},
|
|
{
|
|
name: "inspect_prod_active.dat",
|
|
srcs: allSrcs,
|
|
activeSrc: devInv,
|
|
activeGroup: "prod",
|
|
args: []string{"inspect", ".dat"},
|
|
wantEquals: []string{".data"},
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
},
|
|
{
|
|
name: "inspect_prod_root_@",
|
|
srcs: allSrcs,
|
|
activeSrc: "",
|
|
activeGroup: "prod",
|
|
args: []string{"@"},
|
|
wantEquals: prodSrcs,
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
name: "inspect_prod_root_@",
|
|
srcs: allSrcs,
|
|
activeSrc: "",
|
|
activeGroup: "prod",
|
|
args: []string{"@d"},
|
|
wantEquals: devSrcs,
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tr := testrun.New(context.Background(), t, nil).Hush()
|
|
|
|
for _, handle := range tc.srcs {
|
|
src := testh.NewActorSource(t, handle, true)
|
|
tr.Add(*src)
|
|
}
|
|
|
|
_, err := tr.Run.Config.Collection.SetActive(tc.activeSrc, false)
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, tr.Run.Config.Collection.SetActiveGroup(tc.activeGroup))
|
|
require.NoError(t, tr.Run.ConfigStore.Save(tr.Context, tr.Run.Config))
|
|
|
|
got := testComplete(t, tr, tc.args...)
|
|
assert.Equal(t, tc.wantDirective, got.result,
|
|
"wanted: %v\ngot : %v",
|
|
cobraz.MarshalDirective(tc.wantDirective),
|
|
cobraz.MarshalDirective(got.result))
|
|
|
|
if tc.wantDirective == cobra.ShellCompDirectiveError {
|
|
require.Empty(t, got.values)
|
|
} else {
|
|
require.Equal(t, tc.wantEquals, got.values)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCompleteAllCobraRequestCmds verifies that completion
|
|
// works with both cobra.ShellCompRequestCmd and
|
|
// cobra.ShellCompNoDescRequestCmd.
|
|
func TestCompleteAllCobraRequestCmds(t *testing.T) {
|
|
tu.SkipIssueWindows(t, tu.GH372ShellCompletionWin)
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
args []string
|
|
wantContains []string
|
|
wantDirective cobra.ShellCompDirective
|
|
}{
|
|
{
|
|
name: "slq_empty",
|
|
args: []string{"@"},
|
|
wantContains: []string{source.ActiveHandle, sakila.SL3},
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
name: "slq_@",
|
|
args: []string{"@"},
|
|
wantContains: []string{source.ActiveHandle, sakila.SL3},
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
name: "src_empty",
|
|
args: []string{"src", ""},
|
|
wantContains: []string{sakila.SL3},
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
},
|
|
{
|
|
name: "src_@",
|
|
args: []string{"src", "@"},
|
|
wantContains: []string{sakila.SL3},
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
},
|
|
{
|
|
name: "inspect_empty",
|
|
args: []string{"inspect", ""},
|
|
wantContains: []string{sakila.SL3},
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
{
|
|
name: "inspect_@",
|
|
args: []string{"inspect", "@"},
|
|
wantContains: []string{sakila.SL3},
|
|
wantDirective: cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace,
|
|
},
|
|
}
|
|
|
|
for _, cobraCmd := range []string{cobra.ShellCompRequestCmd, cobra.ShellCompNoDescRequestCmd} {
|
|
cobraCmd := cobraCmd
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run(cobraCmd, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
th := testh.New(t)
|
|
th.Context = options.NewContext(th.Context, options.Options{cli.OptShellCompletionLog.Key(): true})
|
|
tr := testrun.New(th.Context, t, nil)
|
|
tr.Add(*th.Source(sakila.SL3))
|
|
|
|
args := append([]string{cobraCmd}, tc.args...)
|
|
err := tr.Exec(args...)
|
|
require.NoError(t, err)
|
|
|
|
got := parseCompletion(tr)
|
|
assert.Equal(t, cobraz.MarshalDirective(tc.wantDirective), cobraz.MarshalDirective(got.result))
|
|
for j := range tc.wantContains {
|
|
assert.Contains(t, got.values, tc.wantContains[j])
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func enableCompletionLog(ctx context.Context) context.Context {
|
|
o := options.FromContext(ctx)
|
|
if o == nil {
|
|
return options.NewContext(ctx, options.Options{cli.OptShellCompletionLog.Key(): true})
|
|
}
|
|
|
|
o[cli.OptShellCompletionLog.Key()] = true
|
|
return ctx
|
|
}
|