Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Initial setup for integration tests #80

Merged
merged 19 commits into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Simplify assertion type
  • Loading branch information
cmoog committed Jul 29, 2020
commit cafdd6f7eb78f4ba4ce2690b6fc1ffd2c603bcd5
27 changes: 16 additions & 11 deletions ci/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ func init() {
}
}

// write session tokens to the given container runner
func headlessLogin(ctx context.Context, t *testing.T, runner *tcli.ContainerRunner) {
creds := login(ctx, t)
cmd := exec.CommandContext(ctx, "mkdir -p ~/.config/coder && cat > ~/.config/coder/session")

// !IMPORTANT: be careful that this does not appear in logs
cmd.Stdin = strings.NewReader(creds.token)
runner.RunCmd(cmd).Assert(t,
tcli.Success(),
)
runner.Run(ctx, fmt.Sprintf("echo -ne %s > ~/.config/coder/url", creds.url)).Assert(t,
tcli.Success(),
)
}

func TestCoderCLI(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
Expand Down Expand Up @@ -77,17 +92,7 @@ func TestCoderCLI(t *testing.T) {
tcli.StdoutEmpty(),
)

creds := login(ctx, t)
cmd := exec.CommandContext(ctx, "mkdir -p ~/.config/coder && cat > ~/.config/coder/session")

// !IMPORTANT: be careful that this does not appear in logs
cmd.Stdin = strings.NewReader(creds.token)
c.RunCmd(cmd).Assert(t,
tcli.Success(),
)
c.Run(ctx, fmt.Sprintf("echo -ne %s > ~/.config/coder/url", creds.url)).Assert(t,
tcli.Success(),
)
headlessLogin(ctx, t, c)

c.Run(ctx, "coder envs").Assert(t,
tcli.Success(),
Expand Down
185 changes: 60 additions & 125 deletions ci/tcli/tcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,55 +174,47 @@ func (r *ContainerRunner) RunCmd(cmd *exec.Cmd) *Assertable {
// Assert runs the Assertable and
func (a Assertable) Assert(t *testing.T, option ...Assertion) {
slog.Helper()
var cmdResult CommandResult

var (
stdout bytes.Buffer
stderr bytes.Buffer
result CommandResult
)

a.cmd.Stdout = &stdout
a.cmd.Stderr = &stderr

start := time.Now()
err := a.cmd.Run()
cmdResult.Duration = time.Since(start)
result.Duration = time.Since(start)

if exitErr, ok := err.(*exec.ExitError); ok {
cmdResult.ExitCode = exitErr.ExitCode()
result.ExitCode = exitErr.ExitCode()
} else if err != nil {
cmdResult.ExitCode = -1
// TODO: handle this case better
result.ExitCode = -1
} else {
cmdResult.ExitCode = 0
result.ExitCode = 0
}

cmdResult.Stdout = stdout.Bytes()
cmdResult.Stderr = stderr.Bytes()
result.Stdout = stdout.Bytes()
result.Stderr = stderr.Bytes()

slogtest.Info(t, "command output",
slog.F("command", a.cmd),
slog.F("stdout", string(cmdResult.Stdout)),
slog.F("stderr", string(cmdResult.Stderr)),
slog.F("exit-code", cmdResult.ExitCode),
slog.F("duration", cmdResult.Duration),
slog.F("stdout", string(result.Stdout)),
slog.F("stderr", string(result.Stderr)),
slog.F("exit_code", result.ExitCode),
slog.F("duration", result.Duration),
)

for _, o := range option {
o.Valid(t, &cmdResult)
for _, assertion := range option {
assertion(t, &result)
}
}

// Assertion specifies an assertion on the given CommandResult.
// Pass custom Assertion types to cover special cases.
type Assertion interface {
Valid(t *testing.T, r *CommandResult)
}

// Named is an optional extension of Assertion that provides a helpful label
// to *testing.T
type Named interface {
Name() string
}
// Pass custom Assertion functions to cover special cases.
type Assertion func(t *testing.T, r *CommandResult)

// CommandResult contains the aggregated result of a command execution
type CommandResult struct {
Expand All @@ -231,20 +223,6 @@ type CommandResult struct {
Duration time.Duration
}

type simpleFuncAssert struct {
valid func(t *testing.T, r *CommandResult)
name string
}

func (s simpleFuncAssert) Valid(t *testing.T, r *CommandResult) {
slog.Helper()
s.valid(t, r)
}

func (s simpleFuncAssert) Name() string {
return s.name
}

// Success asserts that the command exited with an exit code of 0
func Success() Assertion {
slog.Helper()
Expand All @@ -253,112 +231,75 @@ func Success() Assertion {

// Error asserts that the command exited with a nonzero exit code
func Error() Assertion {
return simpleFuncAssert{
valid: func(t *testing.T, r *CommandResult) {
slog.Helper()
assert.True(t, "exit code is nonzero", r.ExitCode != 0)
},
name: fmt.Sprintf("error"),
return func(t *testing.T, r *CommandResult) {
slog.Helper()
assert.True(t, "exit code is nonzero", r.ExitCode != 0)
}
}

// ExitCodeIs asserts that the command exited with the given code
func ExitCodeIs(code int) Assertion {
return simpleFuncAssert{
valid: func(t *testing.T, r *CommandResult) {
slog.Helper()
assert.Equal(t, "exit code is as expected", code, r.ExitCode)
},
name: fmt.Sprintf("exitcode"),
return func(t *testing.T, r *CommandResult) {
slog.Helper()
assert.Equal(t, "exit code is as expected", code, r.ExitCode)
}
}

// StdoutEmpty asserts that the command did not write any data to Stdout
func StdoutEmpty() Assertion {
return simpleFuncAssert{
valid: func(t *testing.T, r *CommandResult) {
slog.Helper()
empty(t, "stdout", r.Stdout)
},
name: fmt.Sprintf("stdout-empty"),
return func(t *testing.T, r *CommandResult) {
slog.Helper()
empty(t, "stdout", r.Stdout)
}
}

// GetResult offers an escape hatch from tcli
// The pointer passed as "result" will be assigned to the command's *CommandResult
func GetResult(result **CommandResult) Assertion {
return simpleFuncAssert{
valid: func(t *testing.T, r *CommandResult) {
slog.Helper()
empty(t, "stdout", r.Stdout)
*result = r
},
name: "get-result",
return func(t *testing.T, r *CommandResult) {
slog.Helper()
*result = r
}
}

// StderrEmpty asserts that the command did not write any data to Stderr
func StderrEmpty() Assertion {
return simpleFuncAssert{
valid: func(t *testing.T, r *CommandResult) {
slog.Helper()
empty(t, "stderr", r.Stderr)
},
name: fmt.Sprintf("stderr-empty"),
return func(t *testing.T, r *CommandResult) {
slog.Helper()
empty(t, "stderr", r.Stderr)
}
}

// StdoutMatches asserts that Stdout contains a substring which matches the given regexp
func StdoutMatches(pattern string) Assertion {
return simpleFuncAssert{
valid: func(t *testing.T, r *CommandResult) {
slog.Helper()
matches(t, "stdout", pattern, r.Stdout)
},
name: fmt.Sprintf("stdout-matches"),
return func(t *testing.T, r *CommandResult) {
slog.Helper()
matches(t, "stdout", pattern, r.Stdout)
}
}

// StderrMatches asserts that Stderr contains a substring which matches the given regexp
func StderrMatches(pattern string) Assertion {
return simpleFuncAssert{
valid: func(t *testing.T, r *CommandResult) {
slog.Helper()
matches(t, "stderr", pattern, r.Stderr)
},
name: fmt.Sprintf("stderr-matches"),
}
}

// CombinedMatches asserts that either Stdout or Stderr a substring which matches the given regexp
func CombinedMatches(pattern string) Assertion {
return simpleFuncAssert{
valid: func(t *testing.T, r *CommandResult) {
slog.Helper()
StdoutMatches(pattern).Valid(t, r)
StderrMatches(pattern).Valid(t, r)
},
name: fmt.Sprintf("combined-matches"),
return func(t *testing.T, r *CommandResult) {
slog.Helper()
matches(t, "stderr", pattern, r.Stderr)
}
}

func matches(t *testing.T, name, pattern string, target []byte) {
slog.Helper()
fields := []slog.Field{
slog.F("pattern", pattern),
slog.F("target", string(target)),
slog.F("sink", name),
}

ok, err := regexp.Match(pattern, target)
if err != nil {
slogtest.Fatal(t, "failed to attempt regexp match", slog.Error(err),
slog.F("pattern", pattern),
slog.F("target", string(target)),
slog.F("sink", name),
)
slogtest.Fatal(t, "failed to attempt regexp match", append(fields, slog.Error(err))...)
}
if !ok {
slogtest.Fatal(t, "expected to find pattern, no match found",
slog.F("pattern", pattern),
slog.F("target", string(target)),
slog.F("sink", name),
)
slogtest.Fatal(t, "expected to find pattern, no match found", fields...)
}
}

Expand All @@ -371,32 +312,26 @@ func empty(t *testing.T, name string, a []byte) {

// DurationLessThan asserts that the command completed in less than the given duration
func DurationLessThan(dur time.Duration) Assertion {
return simpleFuncAssert{
valid: func(t *testing.T, r *CommandResult) {
slog.Helper()
if r.Duration > dur {
slogtest.Fatal(t, "duration longer than expected",
slog.F("expected_less_than", dur.String),
slog.F("actual", r.Duration.String()),
)
}
},
name: fmt.Sprintf("duration-lessthan"),
return func(t *testing.T, r *CommandResult) {
slog.Helper()
if r.Duration > dur {
slogtest.Fatal(t, "duration longer than expected",
slog.F("expected_less_than", dur.String),
slog.F("actual", r.Duration.String()),
)
}
}
}

// DurationGreaterThan asserts that the command completed in greater than the given duration
func DurationGreaterThan(dur time.Duration) Assertion {
return simpleFuncAssert{
valid: func(t *testing.T, r *CommandResult) {
slog.Helper()
if r.Duration < dur {
slogtest.Fatal(t, "duration shorter than expected",
slog.F("expected_greater_than", dur.String),
slog.F("actual", r.Duration.String()),
)
}
},
name: fmt.Sprintf("duration-greaterthan"),
return func(t *testing.T, r *CommandResult) {
slog.Helper()
if r.Duration < dur {
slogtest.Fatal(t, "duration shorter than expected",
slog.F("expected_greater_than", dur.String),
slog.F("actual", r.Duration.String()),
)
}
}
}