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

Commit 8b3f056

Browse files
committed
Initial setup for integration tests
1 parent 65fce15 commit 8b3f056

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed

Diff for: ci/integration/integration_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"cdr.dev/coder-cli/ci/tcli"
9+
)
10+
11+
func TestTCli(t *testing.T) {
12+
ctx := context.Background()
13+
14+
container := tcli.NewRunContainer(ctx, "", "test-container")
15+
16+
container.Run(ctx, "echo testing").Assert(t,
17+
tcli.Success(),
18+
tcli.StderrEmpty(),
19+
tcli.StdoutMatches("esting"),
20+
)
21+
22+
container.Run(ctx, "sleep 1.5 && echo 1>&2 stderr-message").Assert(t,
23+
tcli.Success(),
24+
tcli.StdoutEmpty(),
25+
tcli.StderrMatches("message"),
26+
tcli.DurationGreaterThan(time.Second),
27+
)
28+
}

Diff for: ci/tcli/tcli.go

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package tcli
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"os/exec"
8+
"regexp"
9+
"testing"
10+
"time"
11+
12+
"cdr.dev/slog/sloggers/slogtest/assert"
13+
"golang.org/x/xerrors"
14+
)
15+
16+
type RunContainer struct {
17+
}
18+
19+
func NewRunContainer(ctx context.Context, image, name string) *RunContainer {
20+
//exec.CommandContext(ctx, "docker", "start")
21+
// TODO: startup docker container
22+
return &RunContainer{}
23+
}
24+
25+
func (r RunContainer) Teardown() error {
26+
// TODO: teardown run environment
27+
return nil
28+
}
29+
30+
type Assertable struct {
31+
cmd string
32+
ctx context.Context
33+
}
34+
35+
func (*RunContainer) Run(ctx context.Context, cmd string) *Assertable {
36+
return &Assertable{
37+
cmd: cmd,
38+
ctx: ctx,
39+
}
40+
}
41+
42+
func (a Assertable) Assert(t *testing.T, option ...Assertion) {
43+
var cmdResult CommandResult
44+
45+
cmd := exec.CommandContext(a.ctx, "sh", "-c", a.cmd)
46+
var (
47+
stdout bytes.Buffer
48+
stderr bytes.Buffer
49+
)
50+
51+
cmd.Stdout = &stdout
52+
cmd.Stderr = &stderr
53+
54+
start := time.Now()
55+
err := cmd.Run()
56+
cmdResult.Duration = time.Since(start)
57+
58+
if exitErr, ok := err.(*exec.ExitError); ok {
59+
cmdResult.ExitCode = exitErr.ExitCode()
60+
} else if err != nil {
61+
cmdResult.ExitCode = -1
62+
} else {
63+
cmdResult.ExitCode = 0
64+
}
65+
66+
cmdResult.Stdout = stdout.Bytes()
67+
cmdResult.Stderr = stderr.Bytes()
68+
69+
for ix, o := range option {
70+
name := fmt.Sprintf("assertion_#%v", ix)
71+
if named, ok := o.(Named); ok {
72+
name = named.Name()
73+
}
74+
t.Run(name, func(t *testing.T) {
75+
err := o.Valid(cmdResult)
76+
assert.Success(t, name, err)
77+
})
78+
}
79+
}
80+
81+
type Assertion interface {
82+
Valid(r CommandResult) error
83+
}
84+
85+
type Named interface {
86+
Name() string
87+
}
88+
89+
type CommandResult struct {
90+
Stdout, Stderr []byte
91+
ExitCode int
92+
Duration time.Duration
93+
}
94+
95+
type simpleFuncAssert struct {
96+
valid func(r CommandResult) error
97+
name string
98+
}
99+
100+
func (s simpleFuncAssert) Valid(r CommandResult) error {
101+
return s.valid(r)
102+
}
103+
104+
func (s simpleFuncAssert) Name() string {
105+
return s.name
106+
}
107+
108+
func Success() Assertion {
109+
return ExitCodeIs(0)
110+
}
111+
112+
func ExitCodeIs(code int) Assertion {
113+
return simpleFuncAssert{
114+
valid: func(r CommandResult) error {
115+
if r.ExitCode != code {
116+
return xerrors.Errorf("exit code of %s expected, got %v", code, r.ExitCode)
117+
}
118+
return nil
119+
},
120+
name: fmt.Sprintf("exitcode"),
121+
}
122+
}
123+
124+
func StdoutEmpty() Assertion {
125+
return simpleFuncAssert{
126+
valid: func(r CommandResult) error {
127+
return empty("stdout", r.Stdout)
128+
},
129+
name: fmt.Sprintf("stdout-empty"),
130+
}
131+
}
132+
133+
func StderrEmpty() Assertion {
134+
return simpleFuncAssert{
135+
valid: func(r CommandResult) error {
136+
return empty("stderr", r.Stderr)
137+
},
138+
name: fmt.Sprintf("stderr-empty"),
139+
}
140+
}
141+
142+
func StdoutMatches(pattern string) Assertion {
143+
return simpleFuncAssert{
144+
valid: func(r CommandResult) error {
145+
return matches("stdout", pattern, r.Stdout)
146+
},
147+
name: fmt.Sprintf("stdout-matches"),
148+
}
149+
}
150+
151+
func StderrMatches(pattern string) Assertion {
152+
return simpleFuncAssert{
153+
valid: func(r CommandResult) error {
154+
return matches("stderr", pattern, r.Stderr)
155+
},
156+
name: fmt.Sprintf("stderr-matches"),
157+
}
158+
}
159+
160+
func CombinedMatches(pattern string) Assertion {
161+
return simpleFuncAssert{
162+
valid: func(r CommandResult) error {
163+
//stdoutValid := StdoutMatches(pattern).Valid(r)
164+
//stderrValid := StderrMatches(pattern).Valid(r)
165+
// TODO: combine errors
166+
return nil
167+
},
168+
name: fmt.Sprintf("combined-matches"),
169+
}
170+
}
171+
172+
func matches(name, pattern string, target []byte) error {
173+
ok, err := regexp.Match(pattern, target)
174+
if err != nil {
175+
return xerrors.Errorf("failed to attempt regexp match: %w", err)
176+
}
177+
if !ok {
178+
return xerrors.Errorf("expected to find pattern (%s) in %s, no match found", pattern, name)
179+
}
180+
return nil
181+
}
182+
183+
func empty(name string, a []byte) error {
184+
if len(a) > 0 {
185+
return xerrors.Errorf("expected %s to be empty, got (%s)", name, string(a))
186+
}
187+
return nil
188+
}
189+
190+
func DurationLessThan(dur time.Duration) Assertion {
191+
return simpleFuncAssert{
192+
valid: func(r CommandResult) error {
193+
if r.Duration > dur {
194+
return xerrors.Errorf("expected duration less than %s, took %s", dur.String(), r.Duration.String())
195+
}
196+
return nil
197+
},
198+
name: fmt.Sprintf("duration-lessthan"),
199+
}
200+
}
201+
202+
func DurationGreaterThan(dur time.Duration) Assertion {
203+
return simpleFuncAssert{
204+
valid: func(r CommandResult) error {
205+
if r.Duration < dur {
206+
return xerrors.Errorf("expected duration greater than %s, took %s", dur.String(), r.Duration.String())
207+
}
208+
return nil
209+
},
210+
name: fmt.Sprintf("duration-greaterthan"),
211+
}
212+
}

Diff for: go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module cdr.dev/coder-cli
33
go 1.14
44

55
require (
6+
cdr.dev/slog v1.3.0
67
cdr.dev/wsep v0.0.0-20200728013649-82316a09813f
78
github.com/fatih/color v1.9.0 // indirect
89
github.com/gorilla/websocket v1.4.1

0 commit comments

Comments
 (0)