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
Doc comments only
  • Loading branch information
cmoog committed Jul 28, 2020
commit a7ac5237504c5bc8b38b72302902ca2a34c80e5a
2 changes: 1 addition & 1 deletion ci/steps/fmt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ if [ "$CI" != "" ]; then
echo "Files need generation or are formatted incorrectly:"
git -c color.ui=always status | grep --color=no '\e\[31m'
echo "Please run the following locally:"
echo " ./ci/fmt.sh"
echo " ./ci/steps/fmt.sh"
exit 1
fi
fi
20 changes: 20 additions & 0 deletions ci/tcli/tcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import (
"golang.org/x/xerrors"
)

// RunContainer specifies a runtime container for performing command tests
type RunContainer struct {
name string
ctx context.Context
}

// ContainerConfig describes the RunContainer configuration schema for initializing a testing environment
type ContainerConfig struct {
Name string
Image string
Expand All @@ -40,6 +42,7 @@ func preflightChecks() error {
return nil
}

// NewRunContainer starts a new docker container for executing command tests
func NewRunContainer(ctx context.Context, config *ContainerConfig) (*RunContainer, error) {
if err := preflightChecks(); err != nil {
return nil, err
Expand Down Expand Up @@ -68,6 +71,7 @@ func NewRunContainer(ctx context.Context, config *ContainerConfig) (*RunContaine
}, nil
}

// Close kills and removes the command execution testing container
func (r *RunContainer) Close() error {
cmd := exec.CommandContext(r.ctx,
"sh", "-c", strings.Join([]string{
Expand All @@ -84,6 +88,7 @@ func (r *RunContainer) Close() error {
return nil
}

// Assertable describes an initialized command ready to be run and asserted against
type Assertable struct {
cmd *exec.Cmd
ctx context.Context
Expand Down Expand Up @@ -117,6 +122,7 @@ func (r *RunContainer) RunCmd(cmd *exec.Cmd) *Assertable {
}
}

// Assert runs the Assertable and
func (a Assertable) Assert(t *testing.T, option ...Assertion) {
t.Run(strings.Join(a.cmd.Args[6:], " "), func(t *testing.T) {
var cmdResult CommandResult
Expand Down Expand Up @@ -158,14 +164,19 @@ func (a Assertable) Assert(t *testing.T, option ...Assertion) {
})
}

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

// Named is an optional extension of Assertion that provides a helpful label
// to *testing.T
type Named interface {
Name() string
}

// CommandResult contains the aggregated result of a command execution
type CommandResult struct {
Stdout, Stderr []byte
ExitCode int
Expand All @@ -185,10 +196,12 @@ func (s simpleFuncAssert) Name() string {
return s.name
}

// Success asserts that the command exited with an exit code of 0
func Success() Assertion {
return ExitCodeIs(0)
}

// ExitCodeIs asserts that the command exited with the given code
func ExitCodeIs(code int) Assertion {
return simpleFuncAssert{
valid: func(r CommandResult) error {
Expand All @@ -201,6 +214,7 @@ func ExitCodeIs(code int) Assertion {
}
}

// StdoutEmpty asserts that the command did not write any data to Stdout
func StdoutEmpty() Assertion {
return simpleFuncAssert{
valid: func(r CommandResult) error {
Expand All @@ -210,6 +224,7 @@ func StdoutEmpty() Assertion {
}
}

// StderrEmpty asserts that the command did not write any data to Stderr
func StderrEmpty() Assertion {
return simpleFuncAssert{
valid: func(r CommandResult) error {
Expand All @@ -219,6 +234,7 @@ func StderrEmpty() Assertion {
}
}

// StdoutMatches asserts that Stdout contains a substring which matches the given regexp
func StdoutMatches(pattern string) Assertion {
return simpleFuncAssert{
valid: func(r CommandResult) error {
Expand All @@ -228,6 +244,7 @@ func StdoutMatches(pattern string) Assertion {
}
}

// StderrMatches asserts that Stderr contains a substring which matches the given regexp
func StderrMatches(pattern string) Assertion {
return simpleFuncAssert{
valid: func(r CommandResult) error {
Expand All @@ -237,6 +254,7 @@ func StderrMatches(pattern string) Assertion {
}
}

// CombinedMatches asserts that either Stdout or Stderr a substring which matches the given regexp
func CombinedMatches(pattern string) Assertion {
return simpleFuncAssert{
valid: func(r CommandResult) error {
Expand Down Expand Up @@ -270,6 +288,7 @@ func empty(name string, a []byte) error {
return nil
}

// DurationLessThan asserts that the command completed in less than the given duration
func DurationLessThan(dur time.Duration) Assertion {
return simpleFuncAssert{
valid: func(r CommandResult) error {
Expand All @@ -282,6 +301,7 @@ func DurationLessThan(dur time.Duration) Assertion {
}
}

// DurationGreaterThan asserts that the command completed in greater than the given duration
func DurationGreaterThan(dur time.Duration) Assertion {
return simpleFuncAssert{
valid: func(r CommandResult) error {
Expand Down
7 changes: 3 additions & 4 deletions cmd/coder/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
Expand Down Expand Up @@ -34,7 +33,7 @@ func (cmd *syncCmd) RegisterFlags(fl *pflag.FlagSet) {
}

// version returns local rsync protocol version as a string.
func (_ *syncCmd) version() string {
func rsyncVersion() string {
cmd := exec.Command("rsync", "--version")
out, err := cmd.CombinedOutput()
if err != nil {
Expand Down Expand Up @@ -93,13 +92,13 @@ func (cmd *syncCmd) Run(fl *pflag.FlagSet) {
Client: entClient,
}

localVersion := cmd.version()
localVersion := rsyncVersion()
remoteVersion, rsyncErr := s.Version()

if rsyncErr != nil {
flog.Info("Unable to determine remote rsync version. Proceeding cautiously.")
} else if localVersion != remoteVersion {
flog.Fatal(fmt.Sprintf("rsync protocol mismatch. %s.", localVersion, rsyncErr))
flog.Fatal("rsync protocol mismatch: local = %v, remote = %v", localVersion, rsyncErr)
}

for err == nil || err == sync.ErrRestartSync {
Expand Down
5 changes: 4 additions & 1 deletion internal/activity/pusher.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import (
"time"

"cdr.dev/coder-cli/internal/entclient"
"go.coder.com/flog"
"golang.org/x/time/rate"

"go.coder.com/flog"
)

const pushInterval = time.Minute
Expand All @@ -20,6 +21,7 @@ type Pusher struct {
rate *rate.Limiter
}

// NewPusher instantiates a new instance of Pusher
func NewPusher(c *entclient.Client, envID, source string) *Pusher {
return &Pusher{
envID: envID,
Expand All @@ -29,6 +31,7 @@ func NewPusher(c *entclient.Client, envID, source string) *Pusher {
}
}

// Push pushes activity, abiding by a rate limit
func (p *Pusher) Push() {
if !p.rate.Allow() {
return
Expand Down
2 changes: 2 additions & 0 deletions internal/activity/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ type activityWriter struct {
wr io.Writer
}

// Write writes to the underlying writer and tracks activity
func (w *activityWriter) Write(p []byte) (n int, err error) {
w.p.Push()
return w.wr.Write(p)
}

// Writer wraps the given writer such that all writes trigger an activity push
func (p *Pusher) Writer(wr io.Writer) io.Writer {
return &activityWriter{p: p, wr: wr}
}
5 changes: 5 additions & 0 deletions internal/config/file.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package config

// File provides convenience methods for interacting with *os.File
type File string

// Delete deletes the file
func (f File) Delete() error {
return rm(string(f))
}

// Write writes the string to the file
func (f File) Write(s string) error {
return write(string(f), 0600, []byte(s))
}

// Read reads the file to a string
func (f File) Read() (string, error) {
byt, err := read(string(f))
return string(byt), err
}

// Coder CLI configuration files
var (
Session File = "session"
URL File = "url"
Expand Down
1 change: 1 addition & 0 deletions internal/entclient/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"
)

// PushActivity pushes CLI activity to Coder
func (c Client) PushActivity(source string, envID string) error {
res, err := c.request("POST", "/api/metrics/usage/push", map[string]string{
"source": source,
Expand Down
1 change: 1 addition & 0 deletions internal/entclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/url"
)

// Client wraps the Coder HTTP API
type Client struct {
BaseURL *url.URL
Token string
Expand Down
10 changes: 6 additions & 4 deletions internal/entclient/devurl.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import (
"net/http"
)

// DelDevURL deletes the specified devurl
func (c Client) DelDevURL(envID, urlID string) error {
reqString := "/api/environments/%s/devurls/%s"
reqUrl := fmt.Sprintf(reqString, envID, urlID)
reqURL := fmt.Sprintf(reqString, envID, urlID)

res, err := c.request("DELETE", reqUrl, map[string]string{
res, err := c.request("DELETE", reqURL, map[string]string{
"environment_id": envID,
"url_id": urlID,
})
Expand All @@ -24,11 +25,12 @@ func (c Client) DelDevURL(envID, urlID string) error {
return nil
}

// UpsertDevURL upserts the specified devurl for the authenticated user
func (c Client) UpsertDevURL(envID, port, access string) error {
reqString := "/api/environments/%s/devurls"
reqUrl := fmt.Sprintf(reqString, envID)
reqURL := fmt.Sprintf(reqString, envID)

res, err := c.request("POST", reqUrl, map[string]string{
res, err := c.request("POST", reqURL, map[string]string{
"environment_id": envID,
"port": port,
"access": access,
Expand Down
4 changes: 4 additions & 0 deletions internal/entclient/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"nhooyr.io/websocket"
)

// Environment describes a Coder environment
type Environment struct {
Name string `json:"name"`
ID string `json:"id"`
}

// Envs gets the list of environments owned by the authenticated user
func (c Client) Envs(user *User, org Org) ([]Environment, error) {
var envs []Environment
err := c.requestBody(
Expand All @@ -22,6 +24,8 @@ func (c Client) Envs(user *User, org Org) ([]Environment, error) {
return envs, err
}

// DialWsep dials an environments command execution interface
// See github.com/cdr/wsep for details
func (c Client) DialWsep(ctx context.Context, env Environment) (*websocket.Conn, error) {
u := c.copyURL()
if c.BaseURL.Scheme == "https" {
Expand Down
4 changes: 4 additions & 0 deletions internal/entclient/me.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package entclient

// User describes a Coder user account
type User struct {
ID string `json:"id"`
Email string `json:"email"`
Username string `json:"username"`
}

// Me gets the details of the authenticated user
func (c Client) Me() (*User, error) {
var u User
err := c.requestBody("GET", "/api/users/me", nil, &u)
Expand All @@ -15,11 +17,13 @@ func (c Client) Me() (*User, error) {
return &u, nil
}

// SSHKey describes an SSH keypair
type SSHKey struct {
PublicKey string `json:"public_key"`
PrivateKey string `json:"private_key"`
}

// SSHKey gets the current SSH kepair of the authenticated user
func (c Client) SSHKey() (*SSHKey, error) {
var key SSHKey
err := c.requestBody("GET", "/api/users/me/sshkey", nil, &key)
Expand Down
2 changes: 2 additions & 0 deletions internal/entclient/org.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package entclient

// Org describes an Organization in Coder
type Org struct {
ID string `json:"id"`
Name string `json:"name"`
Members []User `json:"members"`
}

// Orgs gets all Organizations
func (c Client) Orgs() ([]Org, error) {
var os []Org
err := c.requestBody("GET", "/api/orgs", nil, &os)
Expand Down
1 change: 1 addition & 0 deletions internal/loginsrv/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"sync"
)

// Server waits for the login callback to send session token
type Server struct {
TokenCond *sync.Cond
Token string
Expand Down
1 change: 1 addition & 0 deletions internal/sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ func (s Sync) work(ev timedEvent) {
}
}

// ErrRestartSync describes a known error case that can be solved by re-starting the command
var ErrRestartSync = errors.New("the sync exited because it was overloaded, restart it")

// workEventGroup converges a group of events to prevent duplicate work.
Expand Down
1 change: 1 addition & 0 deletions internal/xterminal/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func ColorEnabled(fd uintptr) (bool, error) {
return terminal.IsTerminal(int(fd)), nil
}

// ResizeEvent describes the new terminal dimensions following a resize
type ResizeEvent struct {
Height, Width uint16
}
Expand Down