Skip to content

Commit

Permalink
Execute yaml examples via go tests
Browse files Browse the repository at this point in the history
In #2540 we are seeing that some yaml tests are timing out, but it's
hard to see what yaml tests are failing. This commit moves the logic out
of bash and into individual go tests - now we will run an individual go
test for each yaml example, completing all v1alpha1 before all v1beta1
and cleaning up in between. The output will still be challenging to read
since it will be interleaved, however the failures should at least
be associated with a specific yaml file.

This also makes it easier to run all tests locally, though if you
interrupt the tests you end up with your cluster in a bad state and it
might be good to update these to execute each example in a separate
namespace (in which case we could run all of v1alpha1 and v1beta1 at the
same time as well!)
  • Loading branch information
bobcatfish committed May 5, 2020
1 parent b5389c6 commit 35fc0b3
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 86 deletions.
241 changes: 241 additions & 0 deletions examples/yaml_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/*
Copyright 2020 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package test

import (
"bytes"
"fmt"
"io/ioutil"
"path"
"path/filepath"
"runtime"
"strings"
"sync"
"testing"
"time"

"os"
"os/exec"
)

const (
timeoutSeconds = 600
sleepBetween = 10
)

// cmd will run the command c with args and if input is provided, that will be piped
// into the process as input
func cmd(t *testing.T, c string, args []string, input string) (string, error) {
t.Helper()
binary, err := exec.LookPath(c)
if err != nil {
t.Fatalf("couldn't find %s, plz install", c)
}

t.Logf("Executing %s %v", binary, args)

cmd := exec.Command(binary, args...)
cmd.Env = os.Environ()

if input != "" {
cmd.Stdin = strings.NewReader(input)
}

var stderr, stdout bytes.Buffer
cmd.Stderr = &stderr
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
t.Fatalf("couldn't run command %s %v: %v, %s", c, args, err, stderr.String())
return "", err
}

return stdout.String(), nil
}

// resetState will erase all types that the yaml examples could have concievably created.
func resetState(t *testing.T) {
t.Helper()

crdTypes := []string{
"conditions.tekton.dev",
"pipelineresources.tekton.dev",
"tasks.tekton.dev",
"clustertasks.tekton.dev",
"pipelines.tekton.dev",
"taskruns.tekton.dev",
"pipelineruns.tekton.dev",
"services",
"pods",
"configmaps",
"secrets",
"serviceaccounts",
"persistentvolumeclaims",
}
for _, c := range crdTypes {
cmd(t, "kubectl", []string{"delete", "--ignore-not-found=true", c, "--all"}, "")
}
}

// getYamls will look in the directory in examples indicated by version and run for yaml files
func getYamls(t *testing.T, version, run string) []string {
t.Helper()
_, filename, _, _ := runtime.Caller(0)

// Don't read recursively; the only dir within these dirs is no-ci which doesn't
// want any tests run against it
files, err := ioutil.ReadDir(path.Join(path.Dir(filename), version, run))
if err != nil {
t.Fatalf("Couldn't read yaml files from %s/%s/%s: %v", path.Dir(filename), version, run, err)
}
yamls := []string{}
for _, f := range files {
if matches, _ := filepath.Match("*.yaml", f.Name()); matches {
yamls = append(yamls, f.Name())
}
}
return yamls
}

// replaceDockerRepo will look in the content f and replace the hard coded docker
// repo with the on provided via the KO_DOCKER_REPO environment variable
func replaceDockerRepo(t *testing.T, f string) string {
t.Helper()
r := os.Getenv("KO_DOCKER_REPO")
if r == "" {
t.Fatalf("KO_DOCKER_REPO must be set")
}
read, err := ioutil.ReadFile(f)
if err != nil {
t.Fatalf("couldnt read contents of %s: %v", f, err)
}
return strings.Replace(string(read), "gcr.io/christiewilson-catfactory", r, -1)
}

// logRun will retrieve the entire yaml of run and log it
func logRun(t *testing.T, run string) {
t.Helper()
yaml, err := cmd(t, "kubectl", []string{"get", run, "-o", "yaml"}, "")
if err == nil {
t.Logf(yaml)
}
}

// pollRun will use kubectl to query the specified run to see if it
// has completed. It will timeout after timeoutSeconds.
func pollRun(t *testing.T, run string, wg *sync.WaitGroup) {
t.Helper()
for i := 0; i < (timeoutSeconds / sleepBetween); i++ {
status, err := cmd(t, "kubectl", []string{"get", run, "--output=jsonpath={.status.conditions[*].status}"}, "")
if err != nil {
t.Fatalf("couldnt get status of %s: %v", run, err)
wg.Done()
return
}

switch status {
case "", "Unknown":
// Not finished running yet
time.Sleep(sleepBetween * time.Second)
case "True":
t.Logf("%s completed successfully", run)
wg.Done()
return
default:
t.Errorf("%s has failed with status %s", run, status)
logRun(t, run)

wg.Done()
return
}
}
t.Errorf("%s did not complete within %d seconds", run, timeoutSeconds)
logRun(t, run)
wg.Done()
}

// waitForAllRuns will use kubectl to poll all runs in runs until completed,
// failed, or timed out
func waitForAllRuns(t *testing.T, runs []string) {
t.Helper()

var wg sync.WaitGroup
for _, run := range runs {
wg.Add(1)
go pollRun(t, run, &wg)
}
wg.Wait()
}

// getRuns will look for "run" in the provided ko output to determine the names
// of any runs created
func getRuns(k string) []string {
runs := []string{}
for _, s := range strings.Split(k, "\n") {
name := strings.TrimSuffix(s, " created")
if strings.Contains(name, "run") {
runs = append(runs, name)
}
}
return runs
}

// runTests will use ko to create all yamls in the directory indicated by version
// and run, and wait for all runs (PipelineRuns and TaskRuns) created
func runTests(t *testing.T, version, run string) {
yamls := getYamls(t, version, run)
for _, yaml := range yamls {
y := yaml

t.Run(fmt.Sprintf("%s/%s", run, y), func(t *testing.T) {
t.Parallel()

t.Logf("Applying %s", y)
content := replaceDockerRepo(t, fmt.Sprintf("%s/%s/%s", version, run, y))
output, err := cmd(t, "ko", []string{"create", "-f", "-"}, content)
if err == nil {
runs := getRuns(output)

if len(runs) == 0 {
t.Fatalf("no runs were created for %s, output %s", y, output)
}

t.Logf("Waiting for created runs %v", runs)
waitForAllRuns(t, runs)
}
})
}
}

func TestYaml(t *testing.T) {
versions := []string{"v1alpha1", "v1beta1"}
runs := []string{"taskruns", "pipelineruns"}

t.Cleanup(func() { resetState(t) })
for i, version := range versions {
t.Run(version, func(t *testing.T) {
for _, run := range runs {
runTests(t, version, run)
}
})
// Since the v1alpha1 and v1beta1 tests are mostly copies of each other,
// we need to wipe them out between invocations
if i == 0 {
t.Logf("deleting all %s types between tests", version)
resetState(t)
}
}
}
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ go 1.13

require (
cloud.google.com/go v0.47.0 // indirect
cloud.google.com/go/storage v1.0.0
contrib.go.opencensus.io/exporter/stackdriver v0.12.8 // indirect
github.com/GoogleCloudPlatform/cloud-builders/gcs-fetcher v0.0.0-20191203181535-308b93ad1f39
github.com/cloudevents/sdk-go/v2 v2.0.0-preview8
github.com/ghodss/yaml v1.0.0
github.com/go-openapi/spec v0.19.4 // indirect
github.com/golang/protobuf v1.3.3 // indirect
github.com/google/go-cmp v0.4.0
github.com/google/go-containerregistry v0.0.0-20200115214256-379933c9c22b
github.com/google/go-containerregistry v0.0.0-20200310013544-4fe717a9b4cb
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/ko v0.4.1-0.20200504014251-d45c52775002 // indirect
github.com/google/uuid v1.1.1
github.com/googleapis/gnostic v0.3.1 // indirect
github.com/hashicorp/go-multierror v1.0.0
Expand All @@ -23,9 +25,8 @@ require (
github.com/markbates/inflect v1.0.4 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
github.com/onsi/ginkgo v1.10.1 // indirect
github.com/onsi/gomega v1.7.0 // indirect
github.com/pkg/errors v0.8.1
github.com/pkg/errors v0.9.1
github.com/prometheus/common v0.7.0 // indirect
github.com/shurcooL/githubv4 v0.0.0-20191102174205-af46314aec7b // indirect
github.com/tektoncd/plumbing v0.0.0-20200430135134-e53521e1d887
Expand All @@ -40,6 +41,7 @@ require (
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
golang.org/x/tools v0.0.0-20200214144324-88be01311a71 // indirect
gomodules.xyz/jsonpatch/v2 v2.1.0
google.golang.org/api v0.15.0
google.golang.org/appengine v1.6.5 // indirect
k8s.io/api v0.17.3
k8s.io/apiextensions-apiserver v0.17.3 // indirect
Expand Down
Loading

0 comments on commit 35fc0b3

Please sign in to comment.