coder/cli/clitest/clitest.go

151 lines
3.5 KiB
Go

package clitest
import (
"archive/tar"
"bytes"
"context"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli"
"github.com/coder/coder/cli/config"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/testutil"
)
// New creates a CLI instance with a configuration pointed to a
// temporary testing directory.
func New(t *testing.T, args ...string) (*cobra.Command, config.Root) {
return NewWithSubcommands(t, cli.AGPL(), args...)
}
type logWriter struct {
prefix string
t *testing.T
}
func (l *logWriter) Write(p []byte) (n int, err error) {
trimmed := strings.TrimSpace(string(p))
if trimmed == "" {
return len(p), nil
}
l.t.Log(
l.prefix + ": " + trimmed,
)
return len(p), nil
}
func NewWithSubcommands(
t *testing.T, subcommands []*cobra.Command, args ...string,
) (*cobra.Command, config.Root) {
cmd := cli.Root(subcommands)
dir := t.TempDir()
root := config.Root(dir)
cmd.SetArgs(append([]string{"--global-config", dir}, args...))
// These can be overridden by the test.
cmd.SetOut(&logWriter{prefix: "stdout", t: t})
cmd.SetErr(&logWriter{prefix: "stderr", t: t})
return cmd, root
}
// SetupConfig applies the URL and SessionToken of the client to the config.
func SetupConfig(t *testing.T, client *codersdk.Client, root config.Root) {
err := root.Session().Write(client.SessionToken())
require.NoError(t, err)
err = root.URL().Write(client.URL.String())
require.NoError(t, err)
}
// CreateTemplateVersionSource writes the echo provisioner responses into a
// new temporary testing directory.
func CreateTemplateVersionSource(t *testing.T, responses *echo.Responses) string {
directory := t.TempDir()
f, err := ioutil.TempFile(directory, "*.tf")
require.NoError(t, err)
_ = f.Close()
data, err := echo.Tar(responses)
require.NoError(t, err)
extractTar(t, data, directory)
return directory
}
func extractTar(t *testing.T, data []byte, directory string) {
reader := tar.NewReader(bytes.NewBuffer(data))
for {
header, err := reader.Next()
if errors.Is(err, io.EOF) {
break
}
require.NoError(t, err)
if header.Name == "." || strings.Contains(header.Name, "..") {
continue
}
// #nosec
path := filepath.Join(directory, header.Name)
mode := header.FileInfo().Mode()
if mode == 0 {
mode = 0o600
}
switch header.Typeflag {
case tar.TypeDir:
err = os.MkdirAll(path, mode)
require.NoError(t, err)
case tar.TypeReg:
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, mode)
require.NoError(t, err)
// Max file size of 10MB.
_, err = io.CopyN(file, reader, (1<<20)*10)
if errors.Is(err, io.EOF) {
err = nil
}
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)
}
}
}
// Start runs the command in a goroutine and cleans it up when
// the test completed.
func Start(ctx context.Context, t *testing.T, cmd *cobra.Command) {
t.Helper()
closeCh := make(chan struct{})
deadline, hasDeadline := ctx.Deadline()
if !hasDeadline {
// We don't want to wait the full 5 minutes for a test to time out.
deadline = time.Now().Add(testutil.WaitMedium)
}
ctx, cancel := context.WithDeadline(ctx, deadline)
go func() {
defer cancel()
defer close(closeCh)
err := cmd.ExecuteContext(ctx)
if ctx.Err() == nil {
assert.NoError(t, err)
}
}()
// Don't exit test routine until server is done.
t.Cleanup(func() {
cancel()
<-closeCh
})
}