Skip to content

Commit

Permalink
testing: e2e testing spawns take responsibility for spawning kind clu…
Browse files Browse the repository at this point in the history
…sters
  • Loading branch information
davidmdm committed Nov 24, 2024
1 parent d0a8bdd commit fc8394d
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 93 deletions.
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ tasks:

test:
cmds:
- kind delete cluster --name=tests && kind create cluster --name=tests
# - kind delete cluster --name=tests && kind create cluster --name=tests
- go test -coverprofile cover.out -p 1 -v ./...

update-deps:
Expand Down
110 changes: 18 additions & 92 deletions cmd/atc/main_test.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"reflect"
"regexp"
"strings"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/davidmdm/ansi"

apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

backendv1 "github.com/yokecd/yoke/cmd/atc/internal/testing/apis/backend/v1"
"github.com/yokecd/yoke/internal"
"github.com/yokecd/yoke/internal/home"
"github.com/yokecd/yoke/internal/k8s"
"github.com/yokecd/yoke/internal/testutils"
"github.com/yokecd/yoke/pkg/apis/airway/v1alpha1"
"github.com/yokecd/yoke/pkg/openapi"
"github.com/yokecd/yoke/pkg/yoke"
Expand All @@ -34,29 +28,29 @@ func TestAirTrafficController(t *testing.T) {
require.NoError(t, os.RemoveAll("./test_output"))
require.NoError(t, os.MkdirAll("./test_output", 0o755))

require.NoError(t, x("kind delete clusters --all"))
require.NoError(t, x("kind create cluster --name=atc-test"))
require.NoError(t, testutils.X("kind delete clusters --all"))
require.NoError(t, testutils.X("kind create cluster --name=atc-test"))

require.NoError(t, x(
require.NoError(t, testutils.X(
"go build -o ./test_output/atc-installer.wasm ../atc-installer",
env("GOOS=wasip1", "GOARCH=wasm"),
testutils.Env("GOOS=wasip1", "GOARCH=wasm"),
))
require.NoError(t, x(
require.NoError(t, testutils.X(
"go build -o ./test_output/backend.wasm ./internal/testing/apis/backend/flight",
env("GOOS=wasip1", "GOARCH=wasm"),
testutils.Env("GOOS=wasip1", "GOARCH=wasm"),
))

require.NoError(t, x(
require.NoError(t, testutils.X(
"docker build -t yokecd/atc:test -f Dockerfile.atc .",
dir("../.."),
testutils.Dir("../.."),
))
require.NoError(t, x("kind load --name=atc-test docker-image yokecd/atc:test"))
require.NoError(t, testutils.X("kind load --name=atc-test docker-image yokecd/atc:test"))

require.NoError(t, x("docker build -t yokecd/wasmcache:test -f ./internal/testing/Dockerfile.wasmcache ../.."))
require.NoError(t, x("kind load --name=atc-test docker-image yokecd/wasmcache:test"))
require.NoError(t, testutils.X("docker build -t yokecd/wasmcache:test -f ./internal/testing/Dockerfile.wasmcache ../.."))
require.NoError(t, testutils.X("kind load --name=atc-test docker-image yokecd/wasmcache:test"))

require.NoError(t, x("docker build -t yokecd/c4ts:test -f ./internal/testing/Dockerfile.c4ts ./internal/testing"))
require.NoError(t, x("kind load --name=atc-test docker-image yokecd/c4ts:test"))
require.NoError(t, testutils.X("docker build -t yokecd/c4ts:test -f ./internal/testing/Dockerfile.c4ts ./internal/testing"))
require.NoError(t, testutils.X("kind load --name=atc-test docker-image yokecd/c4ts:test"))

client, err := k8s.NewClientFromKubeConfig(home.Kubeconfig)
require.NoError(t, err)
Expand Down Expand Up @@ -107,7 +101,7 @@ func TestAirTrafficController(t *testing.T) {
airwayTakeoffParams := yoke.TakeoffParams{
Release: "backend-airway",
Flight: yoke.FlightParams{
Input: jsonReader(v1alpha1.Airway{
Input: testutils.JsonReader(v1alpha1.Airway{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "backends.examples.com",
Expand Down Expand Up @@ -147,13 +141,13 @@ func TestAirTrafficController(t *testing.T) {
require.NoError(t, commander.Takeoff(ctx, airwayTakeoffParams))

// TODO: remove eventually code once yoke has proper support for Airway readiness.
EventuallyNoErrorf(
testutils.EventuallyNoErrorf(
t,
func() error {
return commander.Takeoff(ctx, yoke.TakeoffParams{
Release: "c4ts",
Flight: yoke.FlightParams{
Input: jsonReader(backendv1.Backend{
Input: testutils.JsonReader(backendv1.Backend{
ObjectMeta: metav1.ObjectMeta{
Name: "c4ts",
},
Expand All @@ -173,7 +167,7 @@ func TestAirTrafficController(t *testing.T) {
"failed to create backend resource",
)

EventuallyNoErrorf(
testutils.EventuallyNoErrorf(
t,
func() error {
pods, err := client.Clientset.CoreV1().Pods("default").List(ctx, metav1.ListOptions{
Expand All @@ -196,71 +190,3 @@ func TestAirTrafficController(t *testing.T) {
"failed to assert expected replica count for c4ts backend deployment",
)
}

type xoptions struct {
Env []string
Dir string
}

func env(e ...string) XOpt {
return func(opts *xoptions) {
opts.Env = e
}
}

func dir(d string) XOpt {
return func(opts *xoptions) {
opts.Dir = d
}
}

type XOpt func(*xoptions)

func x(line string, opts ...XOpt) error {
var options xoptions
for _, apply := range opts {
apply(&options)
}

args := regexp.MustCompile(`\s+`).Split(line, -1)

cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), options.Env...)
cmd.Dir = options.Dir

ansi.MakeStyle(ansi.FgCyan).Println(line)
return cmd.Run()
}

func jsonReader(value any) io.Reader {
data, err := json.Marshal(value)
if err != nil {
pr, pw := io.Pipe()
pw.CloseWithError(err)
return pr
}
return bytes.NewReader(data)
}

func EventuallyNoErrorf(t *testing.T, fn func() error, tick time.Duration, timeout time.Duration, msg string, args ...any) {
t.Helper()

var (
ticker = time.NewTimer(0)
deadline = time.Now().Add(timeout)
)

for range ticker.C {
err := fn()
if err == nil {
return
}
if time.Now().After(deadline) {
require.NoErrorf(t, err, msg, args...)
return
}
ticker.Reset(tick)
}
}
15 changes: 15 additions & 0 deletions cmd/yoke/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"math/rand/v2"
"os"
"strconv"
"strings"
"testing"
Expand All @@ -26,9 +27,23 @@ import (
"github.com/yokecd/yoke/internal"
"github.com/yokecd/yoke/internal/home"
"github.com/yokecd/yoke/internal/k8s"
"github.com/yokecd/yoke/internal/testutils"
"github.com/yokecd/yoke/pkg/yoke"
)

func TestMain(m *testing.M) {
must := func(err error) {
if err != nil {
panic(err)
}
}

must(testutils.X("kind delete clusters --all"))
must(testutils.X("kind create cluster --name=tests"))

os.Exit(m.Run())
}

var background = context.Background()

func createBasicDeployment(t *testing.T, name, namespace string) io.Reader {
Expand Down
85 changes: 85 additions & 0 deletions internal/testutils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package testutils

import (
"bytes"
"encoding/json"
"io"
"os"
"os/exec"
"regexp"
"testing"
"time"

"github.com/davidmdm/ansi"
"github.com/stretchr/testify/require"
)

var cyan = ansi.MakeStyle(ansi.FgCyan)

type xoptions struct {
Env []string
Dir string
}

func Env(e ...string) XOpt {
return func(opts *xoptions) {
opts.Env = e
}
}

func Dir(d string) XOpt {
return func(opts *xoptions) {
opts.Dir = d
}
}

type XOpt func(*xoptions)

func X(line string, opts ...XOpt) error {
var options xoptions
for _, apply := range opts {
apply(&options)
}

args := regexp.MustCompile(`\s+`).Split(line, -1)

cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), options.Env...)
cmd.Dir = options.Dir

cyan.Println(line)
return cmd.Run()
}

func JsonReader(value any) io.Reader {
data, err := json.Marshal(value)
if err != nil {
pr, pw := io.Pipe()
pw.CloseWithError(err)
return pr
}
return bytes.NewReader(data)
}

func EventuallyNoErrorf(t *testing.T, fn func() error, tick time.Duration, timeout time.Duration, msg string, args ...any) {
t.Helper()

var (
ticker = time.NewTimer(0)
deadline = time.Now().Add(timeout)
)

for range ticker.C {
err := fn()
if err == nil {
return
}
if time.Now().After(deadline) {
require.NoErrorf(t, err, msg, args...)
return
}
ticker.Reset(tick)
}
}

0 comments on commit fc8394d

Please sign in to comment.