2022-09-14 15:50:29 +00:00
|
|
|
package internal
|
|
|
|
|
|
|
|
import (
|
2022-12-09 10:47:06 +00:00
|
|
|
"bufio"
|
2022-10-10 08:27:45 +00:00
|
|
|
"bytes"
|
2022-12-09 10:47:06 +00:00
|
|
|
"context"
|
2022-09-14 15:50:29 +00:00
|
|
|
"fmt"
|
2023-06-02 14:02:18 +00:00
|
|
|
"io"
|
2022-09-14 15:50:29 +00:00
|
|
|
"math/rand"
|
|
|
|
"os"
|
2022-10-10 08:27:45 +00:00
|
|
|
"path/filepath"
|
2022-09-14 15:50:29 +00:00
|
|
|
"strings"
|
2023-06-02 14:02:18 +00:00
|
|
|
"sync"
|
2022-09-14 15:50:29 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
2022-10-10 08:27:45 +00:00
|
|
|
|
2023-05-16 16:35:39 +00:00
|
|
|
"github.com/databricks/cli/cmd/root"
|
2023-05-22 18:55:42 +00:00
|
|
|
_ "github.com/databricks/cli/cmd/version"
|
2022-10-10 08:27:45 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2023-05-26 14:02:53 +00:00
|
|
|
|
|
|
|
_ "github.com/databricks/cli/cmd/workspace"
|
2022-09-14 15:50:29 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
|
|
|
|
|
|
// GetEnvOrSkipTest proceeds with test only with that env variable
|
|
|
|
func GetEnvOrSkipTest(t *testing.T, name string) string {
|
|
|
|
value := os.Getenv(name)
|
|
|
|
if value == "" {
|
|
|
|
t.Skipf("Environment variable %s is missing", name)
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
|
|
|
// RandomName gives random name with optional prefix. e.g. qa.RandomName("tf-")
|
|
|
|
func RandomName(prefix ...string) string {
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
randLen := 12
|
|
|
|
b := make([]byte, randLen)
|
|
|
|
for i := range b {
|
|
|
|
b[i] = charset[rand.Intn(randLen)]
|
|
|
|
}
|
|
|
|
if len(prefix) > 0 {
|
|
|
|
return fmt.Sprintf("%s%s", strings.Join(prefix, ""), b)
|
|
|
|
}
|
|
|
|
return string(b)
|
|
|
|
}
|
2022-10-10 08:27:45 +00:00
|
|
|
|
2023-05-16 16:35:39 +00:00
|
|
|
// Helper for running the root command in the background.
|
2022-12-09 10:47:06 +00:00
|
|
|
// It ensures that the background goroutine terminates upon
|
|
|
|
// test completion through cancelling the command context.
|
|
|
|
type cobraTestRunner struct {
|
|
|
|
*testing.T
|
|
|
|
|
|
|
|
args []string
|
|
|
|
stdout bytes.Buffer
|
|
|
|
stderr bytes.Buffer
|
|
|
|
|
2023-06-02 14:02:18 +00:00
|
|
|
// Line-by-line output.
|
|
|
|
// Background goroutines populate these channels by reading from stdout/stderr pipes.
|
|
|
|
stdoutLines <-chan string
|
|
|
|
stderrLines <-chan string
|
|
|
|
|
2022-12-09 10:47:06 +00:00
|
|
|
errch <-chan error
|
|
|
|
}
|
|
|
|
|
2023-06-02 14:02:18 +00:00
|
|
|
func consumeLines(ctx context.Context, wg *sync.WaitGroup, r io.Reader) <-chan string {
|
|
|
|
ch := make(chan string, 1000)
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer close(ch)
|
|
|
|
defer wg.Done()
|
|
|
|
scanner := bufio.NewScanner(r)
|
|
|
|
for scanner.Scan() {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
case ch <- scanner.Text():
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return ch
|
|
|
|
}
|
|
|
|
|
2022-12-09 10:47:06 +00:00
|
|
|
func (t *cobraTestRunner) RunBackground() {
|
2023-06-02 14:02:18 +00:00
|
|
|
var stdoutR, stderrR io.Reader
|
|
|
|
var stdoutW, stderrW io.WriteCloser
|
|
|
|
stdoutR, stdoutW = io.Pipe()
|
|
|
|
stderrR, stderrW = io.Pipe()
|
2022-10-10 08:27:45 +00:00
|
|
|
root := root.RootCmd
|
2023-06-02 14:02:18 +00:00
|
|
|
root.SetOut(stdoutW)
|
|
|
|
root.SetErr(stderrW)
|
2022-12-09 10:47:06 +00:00
|
|
|
root.SetArgs(t.args)
|
|
|
|
|
|
|
|
errch := make(chan error)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
2023-06-02 14:02:18 +00:00
|
|
|
// Tee stdout/stderr to buffers.
|
|
|
|
stdoutR = io.TeeReader(stdoutR, &t.stdout)
|
|
|
|
stderrR = io.TeeReader(stderrR, &t.stderr)
|
|
|
|
|
|
|
|
// Consume stdout/stderr line-by-line.
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
t.stdoutLines = consumeLines(ctx, &wg, stdoutR)
|
|
|
|
t.stderrLines = consumeLines(ctx, &wg, stderrR)
|
|
|
|
|
2022-12-09 10:47:06 +00:00
|
|
|
// Run command in background.
|
|
|
|
go func() {
|
|
|
|
cmd, err := root.ExecuteContextC(ctx)
|
|
|
|
if err != nil {
|
|
|
|
t.Logf("Error running command: %s", err)
|
|
|
|
}
|
|
|
|
|
2023-06-02 14:02:18 +00:00
|
|
|
// Close pipes to signal EOF.
|
|
|
|
stdoutW.Close()
|
|
|
|
stderrW.Close()
|
|
|
|
|
|
|
|
// Wait for the [consumeLines] routines to finish now that
|
|
|
|
// the pipes they're reading from have closed.
|
|
|
|
wg.Wait()
|
|
|
|
|
2022-12-09 10:47:06 +00:00
|
|
|
if t.stdout.Len() > 0 {
|
|
|
|
// Make a copy of the buffer such that it remains "unread".
|
|
|
|
scanner := bufio.NewScanner(bytes.NewBuffer(t.stdout.Bytes()))
|
|
|
|
for scanner.Scan() {
|
2023-05-16 16:35:39 +00:00
|
|
|
t.Logf("[databricks stdout]: %s", scanner.Text())
|
2022-12-09 10:47:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.stderr.Len() > 0 {
|
|
|
|
// Make a copy of the buffer such that it remains "unread".
|
|
|
|
scanner := bufio.NewScanner(bytes.NewBuffer(t.stderr.Bytes()))
|
|
|
|
for scanner.Scan() {
|
2023-05-16 16:35:39 +00:00
|
|
|
t.Logf("[databricks stderr]: %s", scanner.Text())
|
2022-12-09 10:47:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset context on command for the next test.
|
|
|
|
// These commands are globals so we have to clean up to the best of our ability after each run.
|
|
|
|
// See https://github.com/spf13/cobra/blob/a6f198b635c4b18fff81930c40d464904e55b161/command.go#L1062-L1066
|
|
|
|
//lint:ignore SA1012 cobra sets the context and doesn't clear it
|
|
|
|
cmd.SetContext(nil)
|
|
|
|
|
|
|
|
// Make caller aware of error.
|
|
|
|
errch <- err
|
|
|
|
close(errch)
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Ensure command terminates upon test completion (success or failure).
|
|
|
|
t.Cleanup(func() {
|
|
|
|
// Signal termination of command.
|
|
|
|
cancel()
|
|
|
|
// Wait for goroutine to finish.
|
|
|
|
<-errch
|
|
|
|
})
|
|
|
|
|
|
|
|
t.errch = errch
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *cobraTestRunner) Run() (bytes.Buffer, bytes.Buffer, error) {
|
|
|
|
t.RunBackground()
|
|
|
|
err := <-t.errch
|
|
|
|
return t.stdout, t.stderr, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Like [require.Eventually] but errors if the underlying command has failed.
|
|
|
|
func (c *cobraTestRunner) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) {
|
|
|
|
ch := make(chan bool, 1)
|
|
|
|
|
|
|
|
timer := time.NewTimer(waitFor)
|
|
|
|
defer timer.Stop()
|
|
|
|
|
|
|
|
ticker := time.NewTicker(tick)
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
2023-06-02 14:02:18 +00:00
|
|
|
// Kick off condition check immediately.
|
|
|
|
go func() { ch <- condition() }()
|
|
|
|
|
2022-12-09 10:47:06 +00:00
|
|
|
for tick := ticker.C; ; {
|
|
|
|
select {
|
|
|
|
case err := <-c.errch:
|
|
|
|
require.Fail(c, "Command failed", err)
|
|
|
|
return
|
|
|
|
case <-timer.C:
|
|
|
|
require.Fail(c, "Condition never satisfied", msgAndArgs...)
|
|
|
|
return
|
|
|
|
case <-tick:
|
|
|
|
tick = nil
|
|
|
|
go func() { ch <- condition() }()
|
|
|
|
case v := <-ch:
|
|
|
|
if v {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
tick = ticker.C
|
|
|
|
}
|
2022-10-10 08:27:45 +00:00
|
|
|
}
|
2022-12-09 10:47:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewCobraTestRunner(t *testing.T, args ...string) *cobraTestRunner {
|
|
|
|
return &cobraTestRunner{
|
|
|
|
T: t,
|
|
|
|
args: args,
|
2022-10-10 08:27:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-09 14:41:04 +00:00
|
|
|
func RequireSuccessfulRun(t *testing.T, args ...string) (bytes.Buffer, bytes.Buffer) {
|
|
|
|
c := NewCobraTestRunner(t, args...)
|
|
|
|
stdout, stderr, err := c.Run()
|
|
|
|
require.NoError(t, err)
|
|
|
|
return stdout, stderr
|
|
|
|
}
|
|
|
|
|
2023-05-26 12:46:08 +00:00
|
|
|
func RequireErrorRun(t *testing.T, args ...string) (bytes.Buffer, bytes.Buffer, error) {
|
|
|
|
c := NewCobraTestRunner(t, args...)
|
|
|
|
stdout, stderr, err := c.Run()
|
|
|
|
require.Error(t, err)
|
|
|
|
return stdout, stderr, err
|
|
|
|
}
|
|
|
|
|
2022-10-10 08:27:45 +00:00
|
|
|
func writeFile(t *testing.T, name string, body string) string {
|
|
|
|
f, err := os.Create(filepath.Join(t.TempDir(), name))
|
|
|
|
require.NoError(t, err)
|
|
|
|
_, err = f.WriteString(body)
|
|
|
|
require.NoError(t, err)
|
|
|
|
f.Close()
|
|
|
|
return f.Name()
|
|
|
|
}
|