mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 02:44:59 +00:00
fbsql integration test framework (#2308)
* stub out a test framework for fbsql * Introduce fbsql integration test framework. This also adds support for `pset location` to set the geo location (i.e. time zone) in which timestamps should be displayed. And it adds support for comments (lines starting with `--`) in the line reader/splitter. * Replace Stdin and Stderr with setters and un-export them
This commit is contained in:
parent
cd32cd7696
commit
37ee6ea482
26 changed files with 990 additions and 25 deletions
15
cli/Makefile
Normal file
15
cli/Makefile
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
.PHONY: test testv test-integration testv-integration
|
||||
|
||||
GO=go
|
||||
|
||||
test:
|
||||
$(GO) test ./... -short
|
||||
|
||||
testv:
|
||||
$(GO) test -v ./... -short
|
||||
|
||||
test-integration:
|
||||
$(GO) test . -count 1 -timeout 20m -run TestCLIIntegration/$(RUN)
|
||||
|
||||
testv-integration:
|
||||
$(GO) test -v . -count 1 -timeout 20m -run TestCLIIntegration/$(RUN)
|
||||
41
cli/cli.go
41
cli/cli.go
|
|
@ -56,9 +56,9 @@ type Command struct {
|
|||
|
||||
Queryer Queryer `json:"-"`
|
||||
|
||||
Stdin io.ReadCloser `json:"-"`
|
||||
Stdout io.Writer `json:"-"`
|
||||
Stderr io.Writer `json:"-"`
|
||||
stdin io.ReadCloser `json:"-"`
|
||||
stdout io.Writer `json:"-"`
|
||||
stderr io.Writer `json:"-"`
|
||||
|
||||
// output is where actual results are written. This might point to stdout,
|
||||
// or to a file, based on the current configuration.
|
||||
|
|
@ -116,9 +116,9 @@ func NewCommand(logdest logger.Logger) *Command {
|
|||
splitter: newSplitter(newReplacer(variables)),
|
||||
workingDir: newWorkingDir(),
|
||||
|
||||
Stdin: Stdin,
|
||||
Stdout: Stdout,
|
||||
Stderr: Stderr,
|
||||
stdin: Stdin,
|
||||
stdout: Stdout,
|
||||
stderr: Stderr,
|
||||
|
||||
output: Stdout,
|
||||
writeOptions: defaultWriteOptions(),
|
||||
|
|
@ -129,6 +129,23 @@ func NewCommand(logdest logger.Logger) *Command {
|
|||
}
|
||||
}
|
||||
|
||||
// SetStdin sets stdin. This is useful for initial configuration in tests.
|
||||
func (cmd *Command) SetStdin(rc io.ReadCloser) {
|
||||
cmd.stdin = rc
|
||||
}
|
||||
|
||||
// SetStdout sets both stdout and output to the value provided. This is useful
|
||||
// for initial configuration in tests.
|
||||
func (cmd *Command) SetStdout(w io.Writer) {
|
||||
cmd.stdout = w
|
||||
cmd.output = w
|
||||
}
|
||||
|
||||
// SetStderr sets stderr. This is useful for initial configuration in tests.
|
||||
func (cmd *Command) SetStderr(w io.Writer) {
|
||||
cmd.stderr = w
|
||||
}
|
||||
|
||||
// Run is the main entry-point to the CLI.
|
||||
func (cmd *Command) Run(ctx context.Context) error {
|
||||
if err := cmd.run(ctx); err != nil {
|
||||
|
|
@ -209,9 +226,9 @@ func (cmd *Command) run(ctx context.Context) error {
|
|||
HistoryLimit: 100000,
|
||||
DisableAutoSaveHistory: true,
|
||||
|
||||
Stdin: cmd.Stdin,
|
||||
Stdout: cmd.Stdout,
|
||||
Stderr: cmd.Stderr,
|
||||
Stdin: cmd.stdin,
|
||||
Stdout: cmd.stdout,
|
||||
Stderr: cmd.stderr,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting readline")
|
||||
|
|
@ -376,7 +393,7 @@ func (cmd *Command) executeAndWriteQuery(qry query) error {
|
|||
}
|
||||
return errors.Wrap(err, "making query")
|
||||
}
|
||||
if err := writeTable(queryResponse, cmd.writeOptions, cmd.output, cmd.Stdout, cmd.Stderr); err != nil {
|
||||
if err := writeTable(queryResponse, cmd.writeOptions, cmd.output, cmd.stdout, cmd.stderr); err != nil {
|
||||
return errors.Wrap(err, "writing out response")
|
||||
}
|
||||
|
||||
|
|
@ -421,7 +438,7 @@ func (n *nopPrinter) Errorf(format string, a ...any) {}
|
|||
// Printf is a helper method which sends the given payload to stdout.
|
||||
func (cmd *Command) Printf(format string, a ...any) {
|
||||
out := fmt.Sprintf(format, a...)
|
||||
cmd.Stdout.Write([]byte(out))
|
||||
cmd.stdout.Write([]byte(out))
|
||||
}
|
||||
|
||||
// Outputf is a helper method which sends the given payload to output.
|
||||
|
|
@ -433,7 +450,7 @@ func (cmd *Command) Outputf(format string, a ...any) {
|
|||
// Errorf is a helper method which sends the given payload to stderr.
|
||||
func (cmd *Command) Errorf(format string, a ...any) {
|
||||
out := fmt.Sprintf(format, a...)
|
||||
cmd.Stderr.Write([]byte(out))
|
||||
cmd.stderr.Write([]byte(out))
|
||||
}
|
||||
|
||||
func (cmd *Command) setupHistory() {
|
||||
|
|
|
|||
255
cli/cli_integration_test.go
Normal file
255
cli/cli_integration_test.go
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
package cli_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/featurebasedb/featurebase/v3/cli"
|
||||
"github.com/featurebasedb/featurebase/v3/dax/server/test"
|
||||
"github.com/featurebasedb/featurebase/v3/logger"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCLIIntegration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("Stubbed Framework", func(t *testing.T) {
|
||||
mc := test.MustRunManagedCommand(t)
|
||||
defer mc.Close()
|
||||
|
||||
addr := mc.Address()
|
||||
|
||||
capture := newCapture(t)
|
||||
|
||||
comparer := newComparer(t)
|
||||
comparer.run()
|
||||
|
||||
fbsql := cli.NewCommand(logger.StderrLogger)
|
||||
fbsql.SetStdin(capture)
|
||||
fbsql.SetStdout(comparer)
|
||||
fbsql.SetStderr(comparer)
|
||||
|
||||
fbsql.Config = &cli.Config{
|
||||
Host: addr.Host(),
|
||||
Port: fmt.Sprintf("%d", addr.Port()),
|
||||
}
|
||||
|
||||
// Run fbsql in a goroutine so we can continue to send it commands
|
||||
// below.
|
||||
didQuit := make(chan struct{})
|
||||
go func() {
|
||||
require.NoError(t, fbsql.Run(ctx))
|
||||
close(didQuit)
|
||||
}()
|
||||
|
||||
// testFiles reference files located in the cli/testdata directory. All
|
||||
// tests should be placed there; other than adding another test file to
|
||||
// this list, you probably shouldn't be editing this file unless you are
|
||||
// trying to modify the way the test framework itself works.
|
||||
testFiles := []string{
|
||||
"setup",
|
||||
"database",
|
||||
"table",
|
||||
// the tests below may be dependent on the previous tests, which do
|
||||
// setup and some shared database and table creation.
|
||||
"query_buffer",
|
||||
// meta commands
|
||||
"meta_bang",
|
||||
"meta_cd",
|
||||
"meta_echo",
|
||||
"meta_file",
|
||||
"meta_pset_border",
|
||||
"meta_pset_expanded",
|
||||
"meta_pset_tuples_only",
|
||||
"meta_include",
|
||||
"meta_output",
|
||||
"meta_set",
|
||||
"meta_timing",
|
||||
"meta_write",
|
||||
}
|
||||
|
||||
for _, testFile := range testFiles {
|
||||
t.Run(testFile, func(t *testing.T) {
|
||||
f, err := os.Open("testdata/" + testFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
var lineNo int
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
lineNo++
|
||||
|
||||
// Empty lines and comments (//) are ignored.
|
||||
if line == "" {
|
||||
continue
|
||||
} else if strings.HasPrefix(line, "//") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
|
||||
switch parts[0] {
|
||||
case "SEND":
|
||||
v := ""
|
||||
if len(parts) == 2 {
|
||||
v = parts[1]
|
||||
}
|
||||
capture.sendLine(v)
|
||||
case "EXPECT":
|
||||
v := ""
|
||||
if len(parts) == 2 {
|
||||
v = parts[1]
|
||||
}
|
||||
comparer.expectLine(v, testFile, lineNo)
|
||||
case "EXPECTCOMP":
|
||||
if len(parts) == 2 {
|
||||
comps := strings.SplitN(parts[1], ":", 2)
|
||||
v := ""
|
||||
if len(comps) == 2 {
|
||||
v = comps[1]
|
||||
}
|
||||
comparer.expectLineComp(comparator(comps[0]), v, testFile, lineNo)
|
||||
} else {
|
||||
t.Errorf("unexpected line: %s[%d]:%s", testFile, lineNo, line)
|
||||
}
|
||||
default:
|
||||
t.Errorf("unexpected line: %s[%d]:%s", testFile, lineNo, line)
|
||||
}
|
||||
}
|
||||
require.NoError(t, scanner.Err())
|
||||
})
|
||||
}
|
||||
|
||||
// End with quit to ensure that fbsql closes without error.
|
||||
capture.sendLine(`\q`)
|
||||
|
||||
// Ensure fbsql quits cleanly.
|
||||
select {
|
||||
case <-didQuit:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("expected fbsql to quit")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// compare is used to compare fbsql output written to its Stdout with expected
|
||||
// lines.
|
||||
type comparer struct {
|
||||
t *testing.T
|
||||
out chan byte
|
||||
outline chan []byte
|
||||
exp chan []byte
|
||||
}
|
||||
|
||||
func newComparer(t *testing.T) *comparer {
|
||||
return &comparer{
|
||||
t: t,
|
||||
out: make(chan byte, 1024),
|
||||
outline: make(chan []byte, 128),
|
||||
exp: make(chan []byte, 1024),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *comparer) run() {
|
||||
// Read bytes off output, and for every line (designated by a line feed "\n"),
|
||||
// push the line onto the outline channel.
|
||||
go func() {
|
||||
var line []byte
|
||||
for {
|
||||
b := <-c.out
|
||||
if b == byte('\n') {
|
||||
c.outline <- line
|
||||
line = []byte{}
|
||||
continue
|
||||
}
|
||||
line = append(line, b)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
type comparator string
|
||||
|
||||
const (
|
||||
compEquals = "Equals"
|
||||
compHasPrefix = "HasPrefix"
|
||||
compWithFormat = "WithFormat"
|
||||
)
|
||||
|
||||
// expectLine is a convenience method which calls expectLineComp with the compEq
|
||||
// comparator and the given line.
|
||||
func (c *comparer) expectLine(line string, fileName string, lineNo int) {
|
||||
c.expectLineComp(compEquals, line, fileName, lineNo)
|
||||
}
|
||||
|
||||
// expectLineComp reads the next line from the outline channel and compares it
|
||||
// with the given `line`. A comparator can be provided to inform how the lines
|
||||
// should be compared (for example, the compHasPrefix comparator will just
|
||||
// compare the beginning part of the outline).
|
||||
func (c *comparer) expectLineComp(comp comparator, line string, fileName string, lineNo int) {
|
||||
var outline []byte
|
||||
select {
|
||||
case outline = <-c.outline:
|
||||
case <-time.After(10 * time.Second):
|
||||
// TODO(tlt): this is 10 seconds to account for the fb_views creation on
|
||||
// a local mac. This should really be something like 2 seconds. Put this
|
||||
// back to 2 once fb_views issue is addressed.
|
||||
c.t.Fatalf("expected output line %s[%d]: >%s<", fileName, lineNo, line)
|
||||
}
|
||||
|
||||
// msg is included in any require which fails.
|
||||
msg := []interface{}{"exp: %s[%d], got: >%s<", fileName, lineNo, outline}
|
||||
|
||||
switch comp {
|
||||
case compEquals:
|
||||
require.Equal(c.t, []byte(line), outline, msg...)
|
||||
case compHasPrefix:
|
||||
require.True(c.t, strings.HasPrefix(string(outline), line), msg...)
|
||||
case compWithFormat:
|
||||
require.True(c.t, compareByteSlices(outline, []byte(line)), msg...)
|
||||
default:
|
||||
c.t.Fatalf("invalid comparator: %s", comp)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *comparer) Write(b []byte) (n int, err error) {
|
||||
for i := range b {
|
||||
c.out <- b[i]
|
||||
}
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
// compareByteSlices compares a byte slice s with another byte slice format and
|
||||
// returns true if they are the same. It will accept underscore as a
|
||||
// single-character wildcard anywhere in slice format.
|
||||
func compareByteSlices(s, format []byte) bool {
|
||||
// Replace some helpers in format before comparing.
|
||||
f := string(format)
|
||||
f = strings.ReplaceAll(f, `{uuid}`, `________-____-____-____-____________`)
|
||||
f = strings.ReplaceAll(f, `{timestamp}`, `____-__-__T__:__:__Z`)
|
||||
format = []byte(f)
|
||||
|
||||
if len(s) != len(format) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range s {
|
||||
if format[i] == '_' {
|
||||
continue
|
||||
}
|
||||
if s[i] != format[i] {
|
||||
// log.Printf("DEBUG: characters differ: (%d): '%v' != '%v'", i, s[i], format[i])
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -22,8 +22,8 @@ func TestCLI(t *testing.T) {
|
|||
capture := newCapture(t)
|
||||
|
||||
cli := cli.NewCommand(logger.StderrLogger)
|
||||
cli.Stdin = capture
|
||||
cli.Stdout = capture
|
||||
cli.SetStdin(capture)
|
||||
cli.SetStdout(capture)
|
||||
cli.Queryer = capture
|
||||
|
||||
go func() {
|
||||
|
|
@ -74,7 +74,7 @@ var _ cli.Queryer = (*capture)(nil)
|
|||
|
||||
// capture implements the various CLI interfaces in order to capture test input
|
||||
// and submit it as though that input were being read from the command line. It
|
||||
// also captures calls made to the Queryer.Query method and ensures the sql the
|
||||
// also captures calls made to the Queryer.Query method and ensures the sql they
|
||||
// contain is expected.
|
||||
type capture struct {
|
||||
t *testing.T
|
||||
|
|
|
|||
|
|
@ -65,6 +65,6 @@ func (cmd *Command) newKafkaRunner(cfgFile string) (*kafka.Runner, error) {
|
|||
return kafka.NewRunner(
|
||||
idkCfg,
|
||||
batch.NewSQLBatcher(cmd, flds),
|
||||
cmd.Stderr,
|
||||
cmd.stderr,
|
||||
), nil
|
||||
}
|
||||
|
|
|
|||
50
cli/meta.go
50
cli/meta.go
|
|
@ -81,7 +81,7 @@ func (m *metaBang) execute(cmd *Command) (responseAction, error) {
|
|||
}
|
||||
c := exec.Command(m.args[0])
|
||||
c.Args = m.args
|
||||
c.Stdout = cmd.Stdout
|
||||
c.Stdout = cmd.stdout
|
||||
err := c.Run()
|
||||
return actionNone, errors.Wrap(err, "running bang command")
|
||||
}
|
||||
|
|
@ -192,7 +192,7 @@ func newMetaEcho(args []string) *metaEcho {
|
|||
}
|
||||
|
||||
func (m *metaEcho) execute(cmd *Command) (responseAction, error) {
|
||||
return echo(m.args, cmd.Stdout)
|
||||
return echo(m.args, cmd.stdout)
|
||||
}
|
||||
|
||||
func echo(args []string, w io.Writer) (responseAction, error) {
|
||||
|
|
@ -335,7 +335,7 @@ Informational
|
|||
|
||||
Formatting
|
||||
\pset [NAME [VALUE]] set table output option
|
||||
(border|expanded|tuples_only)
|
||||
(border|expanded|location|tuples_only)
|
||||
\t [on|off] show only rows
|
||||
\x [on|off] toggle expanded output
|
||||
|
||||
|
|
@ -491,6 +491,37 @@ func (m *metaListViews) execute(cmd *Command) (responseAction, error) {
|
|||
return actionReset, nil
|
||||
}
|
||||
|
||||
// ////////////////////////////////////////////////////////////////////////////
|
||||
// location (sub-command of pset)
|
||||
// ////////////////////////////////////////////////////////////////////////////
|
||||
type metaLocation struct {
|
||||
args []string
|
||||
}
|
||||
|
||||
func newMetaLocation(args []string) *metaLocation {
|
||||
return &metaLocation{
|
||||
args: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *metaLocation) execute(cmd *Command) (responseAction, error) {
|
||||
switch len(m.args) {
|
||||
case 0:
|
||||
// pass
|
||||
case 1:
|
||||
loc, err := time.LoadLocation(m.args[0])
|
||||
if err != nil {
|
||||
return actionNone, errors.Wrapf(err, "loading location: %s", m.args[0])
|
||||
}
|
||||
cmd.writeOptions.location = loc
|
||||
default:
|
||||
return actionNone, errors.Errorf("meta command 'location' takes zero or one argument")
|
||||
}
|
||||
|
||||
cmd.Printf("Location is %s.\n", cmd.writeOptions.location)
|
||||
return actionNone, nil
|
||||
}
|
||||
|
||||
// ////////////////////////////////////////////////////////////////////////////
|
||||
// org
|
||||
// ////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -542,7 +573,7 @@ func (m *metaOutput) execute(cmd *Command) (responseAction, error) {
|
|||
}
|
||||
|
||||
// Set cmd.output to cmd.Stdout.
|
||||
cmd.output = cmd.Stdout
|
||||
cmd.output = cmd.stdout
|
||||
|
||||
return actionNone, nil
|
||||
|
||||
|
|
@ -603,12 +634,14 @@ func (m *metaPSet) print(cmd *Command) {
|
|||
|
||||
fmt := `border %d
|
||||
expanded %s
|
||||
location %s
|
||||
tuples_only %s
|
||||
`
|
||||
|
||||
cmd.Printf(fmt,
|
||||
cmd.writeOptions.border,
|
||||
onOff(cmd.writeOptions.expanded),
|
||||
cmd.writeOptions.location,
|
||||
onOff(cmd.writeOptions.tuplesOnly),
|
||||
)
|
||||
|
||||
|
|
@ -627,6 +660,9 @@ func (m *metaPSet) execute(cmd *Command) (responseAction, error) {
|
|||
case "expanded", "x":
|
||||
sub := newMetaExpanded(m.args[1:])
|
||||
return sub.execute(cmd)
|
||||
case "location":
|
||||
sub := newMetaLocation(m.args[1:])
|
||||
return sub.execute(cmd)
|
||||
case "tuples_only", "t":
|
||||
sub := newMetaTuplesOnly(m.args[1:])
|
||||
return sub.execute(cmd)
|
||||
|
|
@ -678,7 +714,7 @@ func newMetaReset() *metaReset {
|
|||
}
|
||||
|
||||
func (m *metaReset) execute(cmd *Command) (responseAction, error) {
|
||||
cmd.Printf(cmd.buffer.reset())
|
||||
cmd.Printf(cmd.buffer.reset() + "\n")
|
||||
return actionReset, nil
|
||||
}
|
||||
|
||||
|
|
@ -746,7 +782,7 @@ func (m *metaTiming) execute(cmd *Command) (responseAction, error) {
|
|||
case "off":
|
||||
cmd.writeOptions.timing = false
|
||||
default:
|
||||
return actionNone, errors.Errorf("unrecognized value \"%s\" for \"\timing\": Boolean expected", m.args[0])
|
||||
return actionNone, errors.Errorf("unrecognized value \"%s\" for \"\\timing\": Boolean expected", m.args[0])
|
||||
}
|
||||
default:
|
||||
return actionNone, errors.Errorf("meta command 'timing' takes zero or one argument")
|
||||
|
|
@ -843,7 +879,7 @@ func newMetaWarn(args []string) *metaWarn {
|
|||
}
|
||||
|
||||
func (m *metaWarn) execute(cmd *Command) (responseAction, error) {
|
||||
return echo(m.args, cmd.Stderr)
|
||||
return echo(m.args, cmd.stderr)
|
||||
}
|
||||
|
||||
// ////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@ func newSplitter(r *replacer) *splitter {
|
|||
// 2- [metaCommands...]: "\! pwd \q"
|
||||
// 3- [queryParts...][metaCommands...]: "select * from \i file.sql"
|
||||
func (s *splitter) split(line string) ([]queryPart, []metaCommand, error) {
|
||||
// Look for a meta command
|
||||
// Look for a comment line.
|
||||
if strings.HasPrefix(line, "--") {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Look for a meta command.
|
||||
parts := strings.SplitN(line, `\`, 2)
|
||||
|
||||
switch len(parts) {
|
||||
|
|
|
|||
45
cli/testdata/database
vendored
Normal file
45
cli/testdata/database
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Show databases now that we have set org.
|
||||
SEND:SHOW DATABASES;
|
||||
EXPECT:+-----+------+-------+------------+------------+------------+-------+-------------+
|
||||
EXPECT:| _id | name | owner | updated_by | created_at | updated_at | units | description |
|
||||
EXPECT:+-----+------+-------+------------+------------+------------+-------+-------------+
|
||||
EXPECT:+-----+------+-------+------------+------------+------------+-------+-------------+
|
||||
EXPECT:
|
||||
|
||||
// Create db1.
|
||||
SEND:CREATE DATABASE db1 WITH UNITS 1;
|
||||
EXPECT:
|
||||
|
||||
// List databases via SHOW DATABASES.
|
||||
SEND:SHOW DATABASES;
|
||||
EXPECT:+--------------------------------------+------+-------+------------+----------------------+----------------------+-------+-------------+
|
||||
EXPECT:| _id | name | owner | updated_by | created_at | updated_at | units | description |
|
||||
EXPECT:+--------------------------------------+------+-------+------------+----------------------+----------------------+-------+-------------+
|
||||
EXPECTCOMP:WithFormat:| {uuid} | db1 | | | {timestamp} | {timestamp} | 1 | |
|
||||
EXPECT:+--------------------------------------+------+-------+------------+----------------------+----------------------+-------+-------------+
|
||||
EXPECT:
|
||||
|
||||
// List databases via SHOW DATABASES.
|
||||
SEND:\l
|
||||
EXPECT:+--------------------------------------+------+-------+------------+----------------------+----------------------+-------+-------------+
|
||||
EXPECT:| _id | name | owner | updated_by | created_at | updated_at | units | description |
|
||||
EXPECT:+--------------------------------------+------+-------+------------+----------------------+----------------------+-------+-------------+
|
||||
EXPECTCOMP:WithFormat:| {uuid} | db1 | | | {timestamp} | {timestamp} | 1 | |
|
||||
EXPECT:+--------------------------------------+------+-------+------------+----------------------+----------------------+-------+-------------+
|
||||
EXPECT:
|
||||
|
||||
// Check database connection.
|
||||
SEND:\c
|
||||
EXPECT:You are not connected to a database.
|
||||
|
||||
// Try connecting to an invalid database.
|
||||
SEND:\c invalid
|
||||
EXPECT:executing meta command: invalid database: invalid
|
||||
|
||||
// Try connecting with too many arguments.
|
||||
SEND:\c db1 extra
|
||||
EXPECT:executing meta command: meta command 'connect' takes zero or one argument
|
||||
|
||||
// Connect to a database.
|
||||
SEND:\c db1
|
||||
EXPECTCOMP:WithFormat:You are now connected to database "db1" ({uuid}).
|
||||
10
cli/testdata/famous.csv
vendored
Normal file
10
cli/testdata/famous.csv
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
"Id", "Name", "Short description", "Gender", "Country", "Occupation", "Birth year", "Death year", "Manner of death", "Age of death"
|
||||
1, "George Washington", "1st president of the United States (1732–1799)", "Male", "United States of America; Kingdom of Great Britain", "Politician", "1732", "1799", "natural causes", "67"
|
||||
2, "Douglas Adams", "English writer and humorist", "Male", "United Kingdom", "Artist", "1952", "2001", "natural causes", "49"
|
||||
3, "Abraham Lincoln", "16th president of the United States (1809-1865)", "Male", "United States of America", "Politician", "1809", "1865", "homicide", "56"
|
||||
4, "Wolfgang Amadeus Mozart", "Austrian composer of the Classical period", "Male", "Archduchy of Austria; Archbishopric of Salzburg", "Artist", "1756", "1791", "0", "35"
|
||||
5, "Ludwig van Beethoven", "German classical and romantic composer", "Male", "Holy Roman Empire; Austrian Empire", "Artist", "1770", "1827", "0", "57"
|
||||
6, "Jean-François Champollion", "French classical scholar", "Male", "Kingdom of France; First French Empire", "Egyptologist", "1790", "1832", "natural causes", "42"
|
||||
7, "Paul Morand", "French writer", "Male", "France", "Artist", "1888", "1976", "0", "88"
|
||||
8, "Claude Monet", "French impressionist painter (1840-1926)", "Male", "France", "Artist", "1840", "1926", "natural causes", "86"
|
||||
|
8
cli/testdata/meta_bang
vendored
Normal file
8
cli/testdata/meta_bang
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
SEND:\! echo 'foo'
|
||||
EXPECT:foo
|
||||
|
||||
SEND:\! echo "foo"
|
||||
EXPECT:"foo"
|
||||
|
||||
SEND:\!
|
||||
EXPECT:executing meta command: meta command '!' requires at least one argument
|
||||
17
cli/testdata/meta_cd
vendored
Normal file
17
cli/testdata/meta_cd
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Make a directory so we can test \cd'ing into it.
|
||||
SEND:\! mkdir cli-test-dir
|
||||
SEND:\cd cli-test-dir
|
||||
SEND:\cd ..
|
||||
SEND:\! rmdir cli-test-dir
|
||||
|
||||
// TODO(tlt): before we do this, we should implement the ability to execute
|
||||
// commands in a \set like:
|
||||
// \set homedir `pwd`
|
||||
// then we can store what directory we're in so we can move back to it
|
||||
// at the end of the test
|
||||
// Switch to home directory.
|
||||
// SEND:\cd
|
||||
|
||||
// Expect error on extra argument to \cd.
|
||||
SEND:\cd dir extra
|
||||
EXPECT:executing meta command: meta command 'cd' takes zero or one argument
|
||||
6
cli/testdata/meta_echo
vendored
Normal file
6
cli/testdata/meta_echo
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
SEND:\echo
|
||||
EXPECT:
|
||||
|
||||
// Simple \echo.
|
||||
SEND:\echo foo bar
|
||||
EXPECT:foo bar
|
||||
70
cli/testdata/meta_file
vendored
Normal file
70
cli/testdata/meta_file
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Create a table.
|
||||
SEND:CREATE TABLE famous (
|
||||
SEND: _id ID,
|
||||
SEND: name STRING,
|
||||
SEND: description STRING,
|
||||
SEND: gender STRING,
|
||||
SEND: country STRING,
|
||||
SEND: occupation STRING,
|
||||
SEND: birth_year INT min -32767 max 32767,
|
||||
SEND: death_year INT min -32767 max 32767,
|
||||
SEND: death_manner STRING,
|
||||
SEND: birth_age INT min -32767 max 32767
|
||||
SEND:);
|
||||
EXPECT:
|
||||
|
||||
// Open bulk insert.
|
||||
SEND:BULK INSERT
|
||||
SEND:INTO famous (_id, name, description, gender, country, occupation,
|
||||
SEND: birth_year, death_year, death_manner, birth_age )
|
||||
SEND:MAP(0 INT,
|
||||
SEND:1 STRING,
|
||||
SEND:2 STRING,
|
||||
SEND:3 STRING,
|
||||
SEND:4 STRING,
|
||||
SEND:5 STRING,
|
||||
SEND:6 INT,
|
||||
SEND:7 INT,
|
||||
SEND:8 STRING,
|
||||
SEND:9 INT )
|
||||
SEND:FROM
|
||||
SEND: x'
|
||||
|
||||
// Call \file
|
||||
SEND:\file testdata/famous.csv
|
||||
|
||||
// Close bulk insert.
|
||||
SEND:'
|
||||
SEND:WITH
|
||||
SEND: BATCHSIZE 100000
|
||||
SEND: FORMAT 'CSV'
|
||||
SEND: INPUT 'STREAM'
|
||||
SEND: HEADER_ROW;
|
||||
EXPECT:
|
||||
|
||||
// Query table to ensure we have data.
|
||||
SEND:SELECT * FROM famous;
|
||||
EXPECT:+-----+---------------------------+-------------------------------------------------+--------+----------------------------------------------------+--------------+------------+------------+----------------+-----------+
|
||||
EXPECT:| _id | name | description | gender | country | occupation | birth_year | death_year | death_manner | birth_age |
|
||||
EXPECT:+-----+---------------------------+-------------------------------------------------+--------+----------------------------------------------------+--------------+------------+------------+----------------+-----------+
|
||||
EXPECT:| 1 | George Washington | 1st president of the United States (1732–1799) | Male | United States of America; Kingdom of Great Britain | Politician | 1732 | 1799 | natural causes | 67 |
|
||||
EXPECT:| 2 | Douglas Adams | English writer and humorist | Male | United Kingdom | Artist | 1952 | 2001 | natural causes | 49 |
|
||||
EXPECT:| 3 | Abraham Lincoln | 16th president of the United States (1809-1865) | Male | United States of America | Politician | 1809 | 1865 | homicide | 56 |
|
||||
EXPECT:| 4 | Wolfgang Amadeus Mozart | Austrian composer of the Classical period | Male | Archduchy of Austria; Archbishopric of Salzburg | Artist | 1756 | 1791 | 0 | 35 |
|
||||
EXPECT:| 5 | Ludwig van Beethoven | German classical and romantic composer | Male | Holy Roman Empire; Austrian Empire | Artist | 1770 | 1827 | 0 | 57 |
|
||||
EXPECT:| 6 | Jean-François Champollion | French classical scholar | Male | Kingdom of France; First French Empire | Egyptologist | 1790 | 1832 | natural causes | 42 |
|
||||
EXPECT:| 7 | Paul Morand | French writer | Male | France | Artist | 1888 | 1976 | 0 | 88 |
|
||||
EXPECT:| 8 | Claude Monet | French impressionist painter (1840-1926) | Male | France | Artist | 1840 | 1926 | natural causes | 86 |
|
||||
EXPECT:+-----+---------------------------+-------------------------------------------------+--------+----------------------------------------------------+--------------+------------+------------+----------------+-----------+
|
||||
EXPECT:
|
||||
|
||||
// TODO(tlt): dropping the table seems to cause problems.
|
||||
// Drop the table.
|
||||
//SEND:DROP TABLE famous;
|
||||
|
||||
// Ensure that invalid aruments (none or too many) return an error.
|
||||
SEND:\file
|
||||
EXPECT:executing meta command: meta command 'file' requires exactly one argument
|
||||
SEND:\file filename extra
|
||||
EXPECT:executing meta command: meta command 'file' requires exactly one argument
|
||||
|
||||
24
cli/testdata/meta_include
vendored
Normal file
24
cli/testdata/meta_include
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Include with no argument should error.
|
||||
SEND:\i
|
||||
EXPECT:executing meta command: meta command 'include' requires exactly one argument
|
||||
|
||||
// Include with too many arguments should error.
|
||||
SEND:\include testdata/people.sql extra
|
||||
EXPECT:executing meta command: meta command 'include' requires exactly one argument
|
||||
|
||||
// Invalid file should error.
|
||||
SEND:\include invalid.file
|
||||
EXPECT:executing meta command: opening file: invalid.file: open invalid.file: no such file or directory
|
||||
|
||||
SEND:\include testdata/people.sql
|
||||
EXPECT:
|
||||
EXPECT:
|
||||
EXPECT:+-----+------+-----+
|
||||
EXPECT:| _id | name | age |
|
||||
EXPECT:+-----+------+-----+
|
||||
EXPECT:| 1 | Amy | 42 |
|
||||
EXPECT:| 2 | Bob | 27 |
|
||||
EXPECT:| 3 | Carl | 33 |
|
||||
EXPECT:+-----+------+-----+
|
||||
EXPECT:
|
||||
EXPECT:mix in a meta command
|
||||
67
cli/testdata/meta_output
vendored
Normal file
67
cli/testdata/meta_output
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
SEND:SELECT * FROM users;
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| _id | name | age |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| 1 | Anne | 38 |
|
||||
EXPECT:| 2 | Bill | 23 |
|
||||
EXPECT:| 3 | Cindy | 64 |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:
|
||||
|
||||
// Redirect output to a file.
|
||||
SEND:\o test-output-file
|
||||
SEND:SELECT * FROM users;
|
||||
|
||||
// Ensure the output went to the file.
|
||||
SEND:\! cat test-output-file
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| _id | name | age |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| 1 | Anne | 38 |
|
||||
EXPECT:| 2 | Bill | 23 |
|
||||
EXPECT:| 3 | Cindy | 64 |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:
|
||||
|
||||
// Let's test some qecho stuff here while we're at it.
|
||||
SEND:\qecho string with "double quotes"
|
||||
SEND:\qecho -n one
|
||||
SEND:\qecho -n two
|
||||
SEND:\qecho three
|
||||
SEND:\qecho four
|
||||
|
||||
SEND:\! cat test-output-file
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| _id | name | age |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| 1 | Anne | 38 |
|
||||
EXPECT:| 2 | Bill | 23 |
|
||||
EXPECT:| 3 | Cindy | 64 |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:
|
||||
EXPECT:string with "double quotes"
|
||||
EXPECT:onetwothree
|
||||
EXPECT:four
|
||||
|
||||
// And \warn messages should still go to stderr, not the file.
|
||||
SEND:\warn a warning string
|
||||
EXPECT:a warning string
|
||||
|
||||
// Remove the file.
|
||||
SEND:\! rm test-output-file
|
||||
|
||||
// Set the output back to stdout.
|
||||
SEND:\o
|
||||
SEND:SELECT * FROM users;
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| _id | name | age |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| 1 | Anne | 38 |
|
||||
EXPECT:| 2 | Bill | 23 |
|
||||
EXPECT:| 3 | Cindy | 64 |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:
|
||||
|
||||
// Ensure extra arguments to \output causes an error.
|
||||
SEND:\o filename extra
|
||||
EXPECT:executing meta command: meta command 'output' takes zero or one argument
|
||||
52
cli/testdata/meta_pset_border
vendored
Normal file
52
cli/testdata/meta_pset_border
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
SEND:\pset border 2
|
||||
EXPECT:Border style is 2.
|
||||
|
||||
SEND:SELECT 1 as foo, 'baz' as bar;
|
||||
EXPECT:+-----+-----+
|
||||
EXPECT:| foo | bar |
|
||||
EXPECT:+-----+-----+
|
||||
EXPECT:| 1 | baz |
|
||||
EXPECT:+-----+-----+
|
||||
EXPECT:
|
||||
|
||||
SEND:\pset border
|
||||
EXPECT:Border style is 2.
|
||||
|
||||
SEND:\pset border 999
|
||||
EXPECT:Border style is 0.
|
||||
|
||||
SEND:\pset border 1
|
||||
EXPECT:Border style is 1.
|
||||
|
||||
SEND:SELECT 1 as foo, 'baz' as bar;
|
||||
EXPECT: foo | bar
|
||||
EXPECT:-----+-----
|
||||
EXPECT: 1 | baz
|
||||
EXPECT:
|
||||
|
||||
SEND:\pset border 2
|
||||
EXPECT:Border style is 2.
|
||||
|
||||
SEND:SELECT 1 as foo, 'baz' as bar;
|
||||
EXPECT:+-----+-----+
|
||||
EXPECT:| foo | bar |
|
||||
EXPECT:+-----+-----+
|
||||
EXPECT:| 1 | baz |
|
||||
EXPECT:+-----+-----+
|
||||
EXPECT:
|
||||
|
||||
SEND:\pset border 0
|
||||
EXPECT:Border style is 0.
|
||||
|
||||
SEND:SELECT 1 as foo, 'baz' as bar;
|
||||
EXPECT:foo bar
|
||||
EXPECT:--- ---
|
||||
EXPECT: 1 baz
|
||||
EXPECT:
|
||||
|
||||
SEND:\pset border 1 extra
|
||||
EXPECT:executing meta command: meta command 'pset' takes zero, one, or two arguments
|
||||
|
||||
// Set border back to the testing default.
|
||||
SEND:\pset border 2
|
||||
EXPECT:Border style is 2.
|
||||
35
cli/testdata/meta_pset_expanded
vendored
Normal file
35
cli/testdata/meta_pset_expanded
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Set to off.
|
||||
SEND:\pset expanded off
|
||||
EXPECT:Expanded display is off.
|
||||
|
||||
// Set to on.
|
||||
SEND:\pset expanded on
|
||||
EXPECT:Expanded display is on.
|
||||
|
||||
// Toggle to off.
|
||||
SEND:\pset expanded
|
||||
EXPECT:Expanded display is off.
|
||||
|
||||
// Toggle to on.
|
||||
SEND:\pset expanded
|
||||
EXPECT:Expanded display is on.
|
||||
|
||||
// Set to something invalid.
|
||||
SEND:\pset expanded invalid
|
||||
EXPECT:executing meta command: unrecognized value "invalid" for "expanded": Boolean expected
|
||||
|
||||
// Ensure expanded shows results vertically.
|
||||
SEND:SELECT 1 as foo, 'baz' as bar;
|
||||
EXPECT:+-----+-----+
|
||||
EXPECT:| foo | 1 |
|
||||
EXPECT:| bar | baz |
|
||||
EXPECT:+-----+-----+
|
||||
EXPECT:
|
||||
|
||||
// Set back to off as we started.
|
||||
SEND:\pset expanded off
|
||||
EXPECT:Expanded display is off.
|
||||
|
||||
// make sure the \x meta-command returns expected errors
|
||||
SEND:\x on extra
|
||||
EXPECT:executing meta command: meta command 'expanded' takes zero or one argument
|
||||
34
cli/testdata/meta_pset_tuples_only
vendored
Normal file
34
cli/testdata/meta_pset_tuples_only
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Set to off.
|
||||
SEND:\pset tuples_only off
|
||||
EXPECT:Tuples only is off.
|
||||
|
||||
// Set to on.
|
||||
SEND:\pset tuples_only on
|
||||
EXPECT:Tuples only is on.
|
||||
|
||||
// Toggle to off.
|
||||
SEND:\pset tuples_only
|
||||
EXPECT:Tuples only is off.
|
||||
|
||||
// Toggle to on.
|
||||
SEND:\pset tuples_only
|
||||
EXPECT:Tuples only is on.
|
||||
|
||||
// Set to something invalid.
|
||||
SEND:\pset tuples_only invalid
|
||||
EXPECT:executing meta command: unrecognized value "invalid" for "tuples_only": Boolean expected
|
||||
|
||||
// Ensure tuples_only shows only tuples.
|
||||
SEND:SELECT 1 as foo, 'baz' as bar;
|
||||
EXPECT:+---+-----+
|
||||
EXPECT:| 1 | baz |
|
||||
EXPECT:+---+-----+
|
||||
EXPECT:
|
||||
|
||||
// Set back to off as we started.
|
||||
SEND:\pset tuples_only off
|
||||
EXPECT:Tuples only is off.
|
||||
|
||||
// make sure the \t meta-command returns expected errors
|
||||
SEND:\t off extra
|
||||
EXPECT:executing meta command: meta command 'tuples_only' takes zero or one argument
|
||||
31
cli/testdata/meta_set
vendored
Normal file
31
cli/testdata/meta_set
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
SEND:\set
|
||||
|
||||
SEND:\set var1 foo
|
||||
SEND:\set
|
||||
EXPECT:var1 = 'foo'
|
||||
|
||||
SEND:\set var2 bar
|
||||
SEND:\set
|
||||
EXPECT:var1 = 'foo'
|
||||
EXPECT:var2 = 'bar'
|
||||
|
||||
SEND:\set var3 zoo
|
||||
SEND:\set
|
||||
EXPECT:var1 = 'foo'
|
||||
EXPECT:var2 = 'bar'
|
||||
EXPECT:var3 = 'zoo'
|
||||
|
||||
SEND:\unset
|
||||
EXPECT:\unset: missing required argument
|
||||
|
||||
SEND:\unset non-existent-key
|
||||
|
||||
SEND:\unset var1
|
||||
SEND:\set
|
||||
EXPECT:var2 = 'bar'
|
||||
EXPECT:var3 = 'zoo'
|
||||
|
||||
SEND:\unset var2 extra
|
||||
EXPECT:\unset: extra argument "extra" ignored
|
||||
SEND:\set
|
||||
EXPECT:var3 = 'zoo'
|
||||
50
cli/testdata/meta_timing
vendored
Normal file
50
cli/testdata/meta_timing
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Start by ensuring timing is off.
|
||||
SEND:\timing off
|
||||
EXPECT:Timing is off.
|
||||
|
||||
// Set timing on.
|
||||
SEND:\timing on
|
||||
EXPECT:Timing is on.
|
||||
|
||||
// Toggle timing.
|
||||
SEND:\timing
|
||||
EXPECT:Timing is off.
|
||||
|
||||
// Toggle timing again.
|
||||
SEND:\timing
|
||||
EXPECT:Timing is on.
|
||||
|
||||
// Send extra argument to \timing.
|
||||
SEND:\timing on extra
|
||||
EXPECT:executing meta command: meta command 'timing' takes zero or one argument
|
||||
|
||||
|
||||
SEND:SELECT * from users;
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| _id | name | age |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| 1 | Anne | 38 |
|
||||
EXPECT:| 2 | Bill | 23 |
|
||||
EXPECT:| 3 | Cindy | 64 |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:
|
||||
EXPECTCOMP:HasPrefix:Execution time:
|
||||
|
||||
// Turn timing back off.
|
||||
SEND:\timing off
|
||||
EXPECT:Timing is off.
|
||||
|
||||
// Ensure we don't get timing.
|
||||
SEND:SELECT * from users;
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| _id | name | age |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| 1 | Anne | 38 |
|
||||
EXPECT:| 2 | Bill | 23 |
|
||||
EXPECT:| 3 | Cindy | 64 |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:
|
||||
|
||||
// Ensure an invalid timing value returns an error.
|
||||
SEND:\timing invalid
|
||||
EXPECT:executing meta command: unrecognized value "invalid" for "\timing": Boolean expected
|
||||
24
cli/testdata/meta_write
vendored
Normal file
24
cli/testdata/meta_write
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Make sure there's something in the query buffer.
|
||||
// This is left unterminated because we don't need to execute the query;
|
||||
// we just need there to be something in the buffer.
|
||||
SEND:SELECT * FROM invalid-table
|
||||
SEND:\write query-buffer-contents
|
||||
|
||||
// Reset the buffer.
|
||||
SEND:\r
|
||||
EXPECT:Query buffer reset (cleared).
|
||||
|
||||
// Read from the file.
|
||||
SEND:\! cat query-buffer-contents
|
||||
EXPECT:SELECT * FROM invalid-table
|
||||
|
||||
// Remove the file.
|
||||
SEND:\! rm query-buffer-contents
|
||||
|
||||
// Send \write with no arguments.
|
||||
SEND:\write
|
||||
EXPECT:\w: missing required argument
|
||||
|
||||
// Send \write with extra arguments.
|
||||
SEND:\write filename extra
|
||||
EXPECT:executing meta command: meta command 'w' exactly one argument
|
||||
12
cli/testdata/people.sql
vendored
Normal file
12
cli/testdata/people.sql
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
-- Create a table.
|
||||
create table people (_id id, name string, age int);
|
||||
|
||||
-- Insert some values.
|
||||
insert into people values (1, 'Amy', 42), (2, 'Bob', 27), (3, 'Carl', 33);
|
||||
|
||||
-- Get all rows from the table.
|
||||
select * from people;
|
||||
|
||||
-- Mix in a meta-command to show that both are supported
|
||||
-- in the include file.
|
||||
\echo mix in a meta command
|
||||
40
cli/testdata/query_buffer
vendored
Normal file
40
cli/testdata/query_buffer
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
SEND:select 1 as foo;
|
||||
EXPECT:+-----+
|
||||
EXPECT:| foo |
|
||||
EXPECT:+-----+
|
||||
EXPECT:| 1 |
|
||||
EXPECT:+-----+
|
||||
EXPECT:
|
||||
|
||||
SEND:\p
|
||||
EXPECT:select 1 as foo;
|
||||
|
||||
SEND:select 2
|
||||
SEND:\p
|
||||
EXPECT:select 2
|
||||
SEND:\r
|
||||
EXPECT:Query buffer reset (cleared).
|
||||
|
||||
SEND:\p
|
||||
EXPECT:select 1 as foo;
|
||||
|
||||
SEND:select 3
|
||||
SEND:\p
|
||||
EXPECT:select 3
|
||||
|
||||
SEND:as foo
|
||||
SEND:\p
|
||||
EXPECT:select 3
|
||||
EXPECT:as foo
|
||||
|
||||
SEND:;
|
||||
EXPECT:+-----+
|
||||
EXPECT:| foo |
|
||||
EXPECT:+-----+
|
||||
EXPECT:| 3 |
|
||||
EXPECT:+-----+
|
||||
EXPECT:
|
||||
|
||||
SEND:\p
|
||||
EXPECT:select 3
|
||||
EXPECT:as foo;
|
||||
50
cli/testdata/setup
vendored
Normal file
50
cli/testdata/setup
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Startup splash.
|
||||
EXPECT:FeatureBase CLI ()
|
||||
EXPECT:Type "\q" to quit.
|
||||
EXPECT:Detected on-prem, serverless deployment.
|
||||
EXPECTCOMP:HasPrefix:Host: http://localhost:
|
||||
EXPECT:You are not connected to a database.
|
||||
|
||||
// Show databases.
|
||||
SEND:SHOW DATABASES;
|
||||
EXPECT:Organization required. Use \org to set an organization.
|
||||
|
||||
// Get current org.
|
||||
SEND:\org
|
||||
EXPECT:You have not set an organization.
|
||||
|
||||
// Set org.
|
||||
SEND:\org acme
|
||||
EXPECT:You have set organization "acme".
|
||||
|
||||
// Try to set org with too many arguments.
|
||||
SEND:\org acme extra
|
||||
EXPECT:executing meta command: meta command 'org' takes zero or one argument
|
||||
|
||||
// Set location to UTC so that expected timestamp size is consistent.
|
||||
// Without this, a test running locally in may have a timestamp that
|
||||
// ends in a timezone offset such as `-06:00`, while one running as UTC
|
||||
// will have `Z`. Since these string lengths differ, our generic
|
||||
// {timestamp} comparison will fail.
|
||||
SEND:\pset location UTC
|
||||
EXPECT:Location is UTC.
|
||||
|
||||
// Set an invalid location.
|
||||
SEND:\pset location invalid
|
||||
EXPECT:executing meta command: loading location: invalid: unknown time zone invalid
|
||||
|
||||
// Try to set location with too many arguments.
|
||||
SEND:\pset location UTC extra
|
||||
EXPECT:executing meta command: meta command 'pset' takes zero, one, or two arguments
|
||||
|
||||
// Set border to 2 for testing because it makes it easier to visually see
|
||||
// what the tests are expecting (because lines don't end in spaces).
|
||||
SEND:\pset border 2
|
||||
EXPECT:Border style is 2.
|
||||
|
||||
// Check the state of pset.
|
||||
SEND:\pset
|
||||
EXPECT:border 2
|
||||
EXPECT:expanded off
|
||||
EXPECT:location UTC
|
||||
EXPECT:tuples_only off
|
||||
60
cli/testdata/table
vendored
Normal file
60
cli/testdata/table
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Show tables for database using SHOW TABLES.
|
||||
SEND:SHOW TABLES;
|
||||
EXPECT:+-------------------------+-------------------------+-------+------------+----------------------+----------------------+-------+------------+-------------+
|
||||
EXPECT:| _id | name | owner | updated_by | created_at | updated_at | keys | space_used | description |
|
||||
EXPECT:+-------------------------+-------------------------+-------+------------+----------------------+----------------------+-------+------------+-------------+
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECT:+-------------------------+-------------------------+-------+------------+----------------------+----------------------+-------+------------+-------------+
|
||||
EXPECT:
|
||||
|
||||
// Show tables for database using \dt.
|
||||
SEND:\dt
|
||||
EXPECT:+-------------------------+-------------------------+-------+------------+----------------------+----------------------+-------+------------+-------------+
|
||||
EXPECT:| _id | name | owner | updated_by | created_at | updated_at | keys | space_used | description |
|
||||
EXPECT:+-------------------------+-------------------------+-------+------------+----------------------+----------------------+-------+------------+-------------+
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECT:+-------------------------+-------------------------+-------+------------+----------------------+----------------------+-------+------------+-------------+
|
||||
EXPECT:
|
||||
|
||||
// Create a table. That can be used for general testing.
|
||||
SEND:CREATE TABLE users (_id id, name string, age int);
|
||||
EXPECT:
|
||||
SEND:INSERT INTO users VALUES (1, 'Anne', 38), (2, 'Bill', 23), (3, 'Cindy', 64);
|
||||
EXPECT:
|
||||
|
||||
|
||||
// Show tables for database to get the newly created table.
|
||||
SEND:\dt
|
||||
EXPECT:+-------------------------+-------------------------+-------+------------+----------------------+----------------------+-------+------------+-------------+
|
||||
EXPECT:| _id | name | owner | updated_by | created_at | updated_at | keys | space_used | description |
|
||||
EXPECT:+-------------------------+-------------------------+-------+------------+----------------------+----------------------+-------+------------+-------------+
|
||||
EXPECTCOMP:WithFormat:| users | users | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECTCOMP:WithFormat:| fb_____________________ | fb_____________________ | | | {timestamp} | {timestamp} | false | 0 | |
|
||||
EXPECT:+-------------------------+-------------------------+-------+------------+----------------------+----------------------+-------+------------+-------------+
|
||||
EXPECT:
|
||||
|
||||
// We don't select from users until AFTER we check SHOW TABLES above because
|
||||
// running this creates the fb_views sytem table which has a description.
|
||||
// And it's annoying to mask out all of the description fields because we
|
||||
// don't know which row fb_views will fall into.
|
||||
SEND:SELECT * from users;
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| _id | name | age |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:| 1 | Anne | 38 |
|
||||
EXPECT:| 2 | Bill | 23 |
|
||||
EXPECT:| 3 | Cindy | 64 |
|
||||
EXPECT:+-----+-------+-----+
|
||||
EXPECT:
|
||||
|
|
@ -18,6 +18,7 @@ type writeOptions struct {
|
|||
expanded bool
|
||||
timing bool
|
||||
tuplesOnly bool
|
||||
location *time.Location
|
||||
}
|
||||
|
||||
func defaultWriteOptions() *writeOptions {
|
||||
|
|
@ -26,6 +27,7 @@ func defaultWriteOptions() *writeOptions {
|
|||
expanded: false,
|
||||
timing: false,
|
||||
tuplesOnly: false,
|
||||
location: time.Local,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +103,7 @@ func writeTable(r *featurebase.WireQueryResponse, format *writeOptions, qOut io.
|
|||
case nil:
|
||||
row[i] = nullValue
|
||||
case time.Time:
|
||||
row[i] = v.Format(time.RFC3339Nano)
|
||||
row[i] = v.In(format.location).Format(time.RFC3339Nano)
|
||||
}
|
||||
}
|
||||
t.AppendRow(table.Row(row))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue