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

Commit 433da04

Browse files
authored
Merge pull request #80 from cdr/integration-tests
Framework for integration tests
2 parents 65fce15 + 692b219 commit 433da04

File tree

27 files changed

+738
-13
lines changed

27 files changed

+738
-13
lines changed

.github/workflows/build.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
name: build
2-
32
on: [push]
43

54
jobs:
@@ -9,7 +8,7 @@ jobs:
98
- name: Checkout
109
uses: actions/checkout@v1
1110
- name: Build
12-
run: ./ci/build.sh
11+
run: ./ci/steps/build.sh
1312
- name: Upload
1413
uses: actions/upload-artifact@v2
1514
with:

.github/workflows/integration.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: integration
2+
on:
3+
push:
4+
schedule:
5+
- cron: '*/180 * * * *'
6+
7+
jobs:
8+
integration:
9+
runs-on: ubuntu-latest
10+
env:
11+
CODER_URL: ${{ secrets.CODER_URL }}
12+
CODER_EMAIL: ${{ secrets.CODER_EMAIL }}
13+
CODER_PASSWORD: ${{ secrets.CODER_PASSWORD }}
14+
steps:
15+
- uses: actions/checkout@v1
16+
- uses: actions/cache@v1
17+
with:
18+
path: ~/go/pkg/mod
19+
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
20+
restore-keys: |
21+
${{ runner.os }}-go-
22+
- uses: actions/setup-go@v2
23+
with:
24+
go-version: '^1.14'
25+
- name: go test
26+
run: go test -v ./ci/integration/...

.github/workflows/test.yaml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: test
2+
on: [push]
3+
4+
jobs:
5+
fmt:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: actions/checkout@v1
9+
- uses: actions/cache@v1
10+
with:
11+
path: ~/go/pkg/mod
12+
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
13+
restore-keys: |
14+
${{ runner.os }}-go-
15+
- name: fmt
16+
uses: ./ci/image
17+
with:
18+
args: ./ci/steps/fmt.sh
19+
lint:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v1
23+
- uses: actions/cache@v1
24+
with:
25+
path: ~/go/pkg/mod
26+
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
27+
restore-keys: |
28+
${{ runner.os }}-go-
29+
- name: lint
30+
uses: ./ci/image
31+
with:
32+
args: ./ci/steps/lint.sh
33+
test:
34+
runs-on: ubuntu-latest
35+
steps:
36+
- uses: actions/checkout@v1
37+
- uses: actions/cache@v1
38+
with:
39+
path: ~/go/pkg/mod
40+
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
41+
restore-keys: |
42+
${{ runner.os }}-go-
43+
- name: test
44+
uses: ./ci/image
45+
with:
46+
args: go test ./internal/... ./cmd/...

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
.idea
22
ci/bin
33
cmd/coder/coder
4+
ci/integration/bin
5+
ci/integration/env.sh

ci/image/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM golang:1
2+
3+
ENV GOFLAGS="-mod=readonly"
4+
ENV CI=true
5+
6+
RUN go get golang.org/x/tools/cmd/goimports
7+
RUN go get golang.org/x/lint/golint
8+
RUN go get github.com/mattn/goveralls

ci/integration/integration_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
"testing"
11+
"time"
12+
13+
"cdr.dev/coder-cli/ci/tcli"
14+
"cdr.dev/slog/sloggers/slogtest/assert"
15+
)
16+
17+
func build(path string) error {
18+
cmd := exec.Command(
19+
"sh", "-c",
20+
fmt.Sprintf("cd ../../ && go build -o %s ./cmd/coder", path),
21+
)
22+
cmd.Env = append(os.Environ(), "GOOS=linux", "CGO_ENABLED=0")
23+
24+
_, err := cmd.CombinedOutput()
25+
if err != nil {
26+
return err
27+
}
28+
return nil
29+
}
30+
31+
var binpath string
32+
33+
func init() {
34+
cwd, err := os.Getwd()
35+
if err != nil {
36+
panic(err)
37+
}
38+
39+
binpath = filepath.Join(cwd, "bin", "coder")
40+
err = build(binpath)
41+
if err != nil {
42+
panic(err)
43+
}
44+
}
45+
46+
// write session tokens to the given container runner
47+
func headlessLogin(ctx context.Context, t *testing.T, runner *tcli.ContainerRunner) {
48+
creds := login(ctx, t)
49+
cmd := exec.CommandContext(ctx, "sh", "-c", "mkdir -p ~/.config/coder && cat > ~/.config/coder/session")
50+
51+
// !IMPORTANT: be careful that this does not appear in logs
52+
cmd.Stdin = strings.NewReader(creds.token)
53+
runner.RunCmd(cmd).Assert(t,
54+
tcli.Success(),
55+
)
56+
runner.Run(ctx, fmt.Sprintf("echo -ne %s > ~/.config/coder/url", creds.url)).Assert(t,
57+
tcli.Success(),
58+
)
59+
}
60+
61+
func TestCoderCLI(t *testing.T) {
62+
t.Parallel()
63+
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
64+
defer cancel()
65+
66+
c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{
67+
Image: "codercom/enterprise-dev",
68+
Name: "coder-cli-tests",
69+
BindMounts: map[string]string{
70+
binpath: "/bin/coder",
71+
},
72+
})
73+
assert.Success(t, "new run container", err)
74+
defer c.Close()
75+
76+
c.Run(ctx, "which coder").Assert(t,
77+
tcli.Success(),
78+
tcli.StdoutMatches("/usr/sbin/coder"),
79+
tcli.StderrEmpty(),
80+
)
81+
82+
c.Run(ctx, "coder version").Assert(t,
83+
tcli.StderrEmpty(),
84+
tcli.Success(),
85+
tcli.StdoutMatches("linux"),
86+
)
87+
88+
c.Run(ctx, "coder help").Assert(t,
89+
tcli.Success(),
90+
tcli.StderrMatches("Commands:"),
91+
tcli.StderrMatches("Usage: coder"),
92+
tcli.StdoutEmpty(),
93+
)
94+
95+
headlessLogin(ctx, t, c)
96+
97+
c.Run(ctx, "coder envs").Assert(t,
98+
tcli.Success(),
99+
)
100+
101+
c.Run(ctx, "coder urls").Assert(t,
102+
tcli.Error(),
103+
)
104+
105+
c.Run(ctx, "coder sync").Assert(t,
106+
tcli.Error(),
107+
)
108+
109+
c.Run(ctx, "coder sh").Assert(t,
110+
tcli.Error(),
111+
)
112+
113+
c.Run(ctx, "coder logout").Assert(t,
114+
tcli.Success(),
115+
)
116+
117+
c.Run(ctx, "coder envs").Assert(t,
118+
tcli.Error(),
119+
)
120+
}

ci/integration/login_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package integration
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"net/http"
9+
"net/url"
10+
"os"
11+
"testing"
12+
13+
"cdr.dev/slog/sloggers/slogtest/assert"
14+
)
15+
16+
type credentials struct {
17+
url, token string
18+
}
19+
20+
func login(ctx context.Context, t *testing.T) credentials {
21+
var (
22+
email = requireEnv(t, "CODER_EMAIL")
23+
password = requireEnv(t, "CODER_PASSWORD")
24+
rawURL = requireEnv(t, "CODER_URL")
25+
)
26+
sessionToken := getSessionToken(ctx, t, email, password, rawURL)
27+
28+
return credentials{
29+
url: rawURL,
30+
token: sessionToken,
31+
}
32+
}
33+
34+
func requireEnv(t *testing.T, key string) string {
35+
value := os.Getenv(key)
36+
assert.True(t, fmt.Sprintf("%q is nonempty", key), value != "")
37+
return value
38+
}
39+
40+
type loginBuiltInAuthReq struct {
41+
Email string `json:"email"`
42+
Password string `json:"password"`
43+
}
44+
45+
type loginBuiltInAuthResp struct {
46+
SessionToken string `json:"session_token"`
47+
}
48+
49+
func getSessionToken(ctx context.Context, t *testing.T, email, password, rawURL string) string {
50+
reqbody := loginBuiltInAuthReq{
51+
Email: email,
52+
Password: password,
53+
}
54+
body, err := json.Marshal(reqbody)
55+
assert.Success(t, "marshal login req body", err)
56+
57+
u, err := url.Parse(rawURL)
58+
assert.Success(t, "parse raw url", err)
59+
u.Path = "/auth/basic/login"
60+
61+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bytes.NewReader(body))
62+
assert.Success(t, "new request", err)
63+
64+
resp, err := http.DefaultClient.Do(req)
65+
assert.Success(t, "do request", err)
66+
assert.Equal(t, "request status 201", http.StatusCreated, resp.StatusCode)
67+
68+
var tokenResp loginBuiltInAuthResp
69+
err = json.NewDecoder(resp.Body).Decode(&tokenResp)
70+
assert.Success(t, "decode response", err)
71+
72+
defer resp.Body.Close()
73+
74+
return tokenResp.SessionToken
75+
}

ci/build.sh renamed to ci/steps/build.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ mkdir -p bin
1414

1515
build(){
1616
tmpdir=$(mktemp -d)
17-
go build -ldflags "-s -w -X main.version=${tag}" -o "$tmpdir/coder" ../cmd/coder
17+
go build -ldflags "-s -w -X main.version=${tag}" -o "$tmpdir/coder" ../../cmd/coder
1818

1919
pushd "$tmpdir"
2020
tarname="coder-cli-$GOOS-$GOARCH.tar.gz"
2121
tar -czf "$tarname" coder
2222
popd
2323

24-
cp "$tmpdir/$tarname" bin
24+
cp "$tmpdir/$tarname" ../bin
2525
rm -rf "$tmpdir"
2626
}
2727

ci/steps/fmt.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
echo "Formatting..."
3+
4+
go mod tidy
5+
gofmt -w -s .
6+
goimports -w "-local=$$(go list -m)" .
7+
8+
if [ "$CI" != "" ]; then
9+
if [[ $(git ls-files --other --modified --exclude-standard) != "" ]]; then
10+
echo "Files need generation or are formatted incorrectly:"
11+
git -c color.ui=always status | grep --color=no '\e\[31m'
12+
echo "Please run the following locally:"
13+
echo " ./ci/steps/fmt.sh"
14+
exit 1
15+
fi
16+
fi

ci/steps/lint.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
3+
echo "Linting..."
4+
5+
go vet ./...
6+
golint -set_exit_status ./...

ci/tcli/doc.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Package tcli provides a framework for CLI integration testing.
2+
// Execute commands on the raw host or inside a docker container.
3+
// Define custom Assertion types to extend test functionality.
4+
package tcli

0 commit comments

Comments
 (0)