feat(e2e): initial e2e test framework and runner

pull/619/head
Adam Reese 10 years ago
parent 7bc728bc05
commit e2dde20679

@ -58,6 +58,13 @@ test-unit:
test-style: test-style:
@scripts/validate-go.sh @scripts/validate-go.sh
.PHONY: test-e2e
test-e2e: TESTFLAGS += -v -tags=e2e
test-e2e: PKG = ./test/e2e
test-e2e:
@scripts/local-cluster.sh up
$(GO) test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS)
.PHONY: clean .PHONY: clean
clean: clean:
@rm -rf $(BINDIR) @rm -rf $(BINDIR)

@ -0,0 +1,83 @@
// build +e2e
package e2e
import (
"bytes"
"fmt"
"os/exec"
"regexp"
"strings"
"testing"
"time"
)
// Cmd provides helpers for command output
type Cmd struct {
t *testing.T
path string
args []string
ran bool
status error
stdout, stderr bytes.Buffer
}
func (h *Cmd) String() string {
return fmt.Sprintf("%s %s", h.path, strings.Join(h.args, " "))
}
func (h *Cmd) exec() error {
cmd := exec.Command(h.path, h.args...)
h.stdout.Reset()
h.stderr.Reset()
cmd.Stdout = &h.stdout
cmd.Stderr = &h.stderr
h.t.Logf("Executing command: %s", h)
start := time.Now()
h.status = cmd.Run()
h.t.Logf("Finished in %v", time.Since(start))
if h.stdout.Len() > 0 {
h.t.Logf("standard output:\n%s", h.stdout.String())
}
if h.stderr.Len() > 0 {
h.t.Logf("standard error: %s\n", h.stderr.String())
}
h.ran = true
return h.status
}
// Stdout returns standard output of the Cmd run as a string.
func (h *Cmd) Stdout() string {
if !h.ran {
h.t.Fatal("internal testsuite error: stdout called before run")
}
return h.stdout.String()
}
// Stderr returns standard error of the Cmd run as a string.
func (h *Cmd) Stderr() string {
if !h.ran {
h.t.Fatal("internal testsuite error: stdout called before run")
}
return h.stderr.String()
}
func (c *Cmd) Match(exp string) bool {
re := regexp.MustCompile(exp)
return re.MatchString(c.Stdout())
}
func (h *Cmd) StdoutContains(substring string) bool {
return strings.Contains(h.Stdout(), substring)
}
func (h *Cmd) StderrContains(substring string) bool {
return strings.Contains(h.Stderr(), substring)
}
func (h *Cmd) Contains(substring string) bool {
return h.StdoutContains(substring) || h.StderrContains(substring)
}

@ -0,0 +1,73 @@
// build +e2e
package e2e
import (
"testing"
"time"
)
const (
namespace = "helm"
apiProxy = "/api/v1/proxy/namespaces/" + namespace + "/services/manager-service:manager/"
)
type HelmContext struct {
t *testing.T
Path string
Host string
Timeout time.Duration
}
func NewHelmContext(t *testing.T) *HelmContext {
return &HelmContext{
t: t,
Path: "../../bin/helm",
Timeout: time.Second * 20,
}
}
func (h *HelmContext) MustRun(args ...string) *Cmd {
cmd := h.newCmd(args...)
if status := cmd.exec(); status != nil {
h.t.Errorf("helm %v failed unexpectedly: %v", args, status)
h.t.Errorf("%s", cmd.Stderr())
h.t.FailNow()
}
return cmd
}
func (h *HelmContext) Run(args ...string) *Cmd {
cmd := h.newCmd(args...)
cmd.exec()
return cmd
}
func (h *HelmContext) RunFail(args ...string) *Cmd {
cmd := h.newCmd(args...)
if status := cmd.exec(); status == nil {
h.t.Fatalf("helm unexpected to fail: %v %v", args, status)
}
return cmd
}
func (h *HelmContext) newCmd(args ...string) *Cmd {
//args = append([]string{"--host", h.Host}, args...)
return &Cmd{
t: h.t,
path: h.Path,
args: args,
}
}
func (h *HelmContext) Running() bool {
// FIXME tiller does not have a healthz endpoint
return true
//endpoint := h.Host + "healthz"
//resp, err := http.Get(endpoint)
//if err != nil {
//h.t.Errorf("Could not GET %s: %s", endpoint, err)
//}
//return resp.StatusCode == 200
}

@ -0,0 +1,78 @@
// build +e2e
package e2e
import (
"flag"
"fmt"
"math/rand"
"testing"
"time"
)
func init() {
rand.Seed(time.Now().Unix())
}
const (
timeout = 180 * time.Second
poll = 2 * time.Second
)
var (
chart = flag.String("chart", "gs://kubernetes-charts-testing/redis-2.tgz", "Chart to deploy")
tillerImage = flag.String("tiller-image", "", "The full image name of the Docker image for resourcifier.")
)
func logKubeEnv(k *KubeContext) {
config := k.Run("config", "view", "--flatten", "--minify").Stdout()
k.t.Logf("Kubernetes Environment\n%s", config)
}
func TestHelm(t *testing.T) {
kube := NewKubeContext(t)
helm := NewHelmContext(t)
logKubeEnv(kube)
if !kube.Running() {
t.Fatal("Not connected to kubernetes")
}
t.Log(kube.Version())
if !isHelmRunning(kube) {
args := []string{"init"}
if *tillerImage != "" {
args = append(args, "-i", *tillerImage)
}
helm.MustRun(args...)
err := wait(func() bool {
return isHelmRunning(kube)
})
if err != nil {
t.Fatalf("could not install helm: %s", err)
}
}
}
type conditionFunc func() bool
func wait(fn conditionFunc) error {
for start := time.Now(); time.Since(start) < timeout; time.Sleep(poll) {
if fn() {
return nil
}
}
return fmt.Errorf("Polling timeout")
}
func genName() string {
return fmt.Sprintf("e2e-%d", rand.Uint32())
}
func isHelmRunning(k *KubeContext) bool {
return k.Run("get", "pods", "--namespace=helm").StdoutContains("Running")
}

@ -0,0 +1,61 @@
// build +e2e
package e2e
import (
"strings"
"testing"
)
const defaultKubectlPath = "kubectl"
type KubeContext struct {
t *testing.T
Path string
}
func NewKubeContext(t *testing.T) *KubeContext {
return &KubeContext{
t: t,
Path: defaultKubectlPath,
}
}
func (k *KubeContext) Run(args ...string) *Cmd {
cmd := k.newCmd(args...)
cmd.exec()
return cmd
}
func (k *KubeContext) newCmd(args ...string) *Cmd {
return &Cmd{
t: k.t,
path: k.Path,
args: args,
}
}
func (k *KubeContext) getConfigValue(jsonpath string) string {
return strings.Replace(k.Run("config", "view", "--flatten=true", "--minify=true", "-o", "jsonpath="+jsonpath).Stdout(), "'", "", -1)
}
func (k *KubeContext) Cluster() string {
return k.getConfigValue("'{.clusters[0].name}'")
}
func (k *KubeContext) Server() string {
return k.getConfigValue("'{.clusters[0].cluster.server}'")
}
func (k *KubeContext) CurrentContext() string {
return k.getConfigValue("'{.current-context}'")
}
func (k *KubeContext) Running() bool {
err := k.Run("cluster-info").exec()
return err == nil
}
func (k *KubeContext) Version() string {
return k.Run("version").Stdout()
}

@ -0,0 +1,12 @@
// +build !e2e
package e2e
import (
"os"
"testing"
)
func TestMain(m *testing.M) {
os.Exit(0)
}
Loading…
Cancel
Save