diff --git a/examples/v1alpha1/pipelineruns/clustertask-pipelinerun.yaml b/examples/v1alpha1/pipelineruns/clustertask-pipelinerun.yaml index 9c1818d73e7..c9bf0d776af 100644 --- a/examples/v1alpha1/pipelineruns/clustertask-pipelinerun.yaml +++ b/examples/v1alpha1/pipelineruns/clustertask-pipelinerun.yaml @@ -1,7 +1,7 @@ apiVersion: tekton.dev/v1alpha1 kind: ClusterTask metadata: - name: cluster-task-pipeline-4 + name: cluster-task-pipeline-4-v1alpha1 spec: steps: - name: task-two-step-one @@ -17,7 +17,7 @@ spec: tasks: - name: cluster-task-pipeline-4 taskRef: - name: cluster-task-pipeline-4 + name: cluster-task-pipeline-4-v1alpha1 kind: ClusterTask --- apiVersion: tekton.dev/v1alpha1 diff --git a/examples/v1alpha1/taskruns/clustertask.yaml b/examples/v1alpha1/taskruns/clustertask.yaml index 3e3e1f8f844..876f5a35f6d 100644 --- a/examples/v1alpha1/taskruns/clustertask.yaml +++ b/examples/v1alpha1/taskruns/clustertask.yaml @@ -1,7 +1,7 @@ apiVersion: tekton.dev/v1alpha1 kind: ClusterTask metadata: - name: clustertask + name: clustertask-v1alpha1 spec: steps: - image: ubuntu @@ -13,5 +13,5 @@ metadata: generateName: clustertask- spec: taskRef: - name: clustertask + name: clustertask-v1alpha1 kind: ClusterTask diff --git a/examples/v1alpha1/taskruns/optional-resources-with-clustertask.yaml b/examples/v1alpha1/taskruns/optional-resources-with-clustertask.yaml index 428e857e94d..53ede3d6979 100644 --- a/examples/v1alpha1/taskruns/optional-resources-with-clustertask.yaml +++ b/examples/v1alpha1/taskruns/optional-resources-with-clustertask.yaml @@ -1,7 +1,7 @@ apiVersion: tekton.dev/v1alpha1 kind: ClusterTask metadata: - name: clustertask-with-optional-resources + name: clustertask-with-optional-resources-v1alpha1 spec: inputs: resources: @@ -31,5 +31,5 @@ metadata: name: clustertask-without-resources spec: taskRef: - name: clustertask-with-optional-resources + name: clustertask-with-optional-resources-v1alpha1 kind: ClusterTask diff --git a/examples/v1beta1/taskruns/clustertask.yaml b/examples/v1beta1/taskruns/clustertask.yaml index 10972427dab..9124271adc9 100644 --- a/examples/v1beta1/taskruns/clustertask.yaml +++ b/examples/v1beta1/taskruns/clustertask.yaml @@ -1,7 +1,7 @@ apiVersion: tekton.dev/v1beta1 kind: ClusterTask metadata: - name: clustertask + name: clustertask-v1beta1 spec: steps: - image: ubuntu @@ -13,5 +13,5 @@ metadata: generateName: clustertask- spec: taskRef: - name: clustertask + name: clustertask-v1beta1 kind: ClusterTask diff --git a/examples/v1beta1/taskruns/optional-resources-with-clustertask.yaml b/examples/v1beta1/taskruns/optional-resources-with-clustertask.yaml index 1ecae87cf8d..882443c6971 100644 --- a/examples/v1beta1/taskruns/optional-resources-with-clustertask.yaml +++ b/examples/v1beta1/taskruns/optional-resources-with-clustertask.yaml @@ -1,7 +1,7 @@ apiVersion: tekton.dev/v1beta1 kind: ClusterTask metadata: - name: clustertask-with-optional-resources + name: clustertask-with-optional-resources-v1beta1 spec: params: - name: filename @@ -30,5 +30,5 @@ metadata: name: clustertask-without-resources spec: taskRef: - name: clustertask-with-optional-resources + name: clustertask-with-optional-resources-v1beta1 kind: ClusterTask diff --git a/test/README.md b/test/README.md index 5b135f929e8..0940680d7e3 100644 --- a/test/README.md +++ b/test/README.md @@ -206,9 +206,12 @@ go test -v -tags=e2e -count=1 ./test -run ^TestTaskRun To run the YAML e2e tests, run the following command: ```bash -./test/e2e-tests-yaml.sh +go test -v -count=1 -tags=examples -timeout=20m ./test/ ``` +To limit parallelism of tests, use `-parallel=n` where `n` is the number of +tests to run in parallel. + ### Running upgrade tests There are two scenarios in upgrade tests. One is to install the previous release, upgrade to the current release, and diff --git a/test/clients.go b/test/clients.go index 56822302a80..d05d17ad4e1 100644 --- a/test/clients.go +++ b/test/clients.go @@ -54,6 +54,7 @@ type clients struct { KubeClient *knativetest.KubeClient PipelineClient v1beta1.PipelineInterface + ClusterTaskClient v1beta1.ClusterTaskInterface TaskClient v1beta1.TaskInterface TaskRunClient v1beta1.TaskRunInterface PipelineRunClient v1beta1.PipelineRunInterface @@ -88,6 +89,7 @@ func newClients(t *testing.T, configPath, clusterName, namespace string) *client t.Fatalf("failed to create pipeline clientset from config file at %s: %s", configPath, err) } c.PipelineClient = cs.TektonV1beta1().Pipelines(namespace) + c.ClusterTaskClient = cs.TektonV1beta1().ClusterTasks() c.TaskClient = cs.TektonV1beta1().Tasks(namespace) c.TaskRunClient = cs.TektonV1beta1().TaskRuns(namespace) c.PipelineRunClient = cs.TektonV1beta1().PipelineRuns(namespace) diff --git a/test/e2e-common.sh b/test/e2e-common.sh index e0a6328bc33..1fba0249576 100755 --- a/test/e2e-common.sh +++ b/test/e2e-common.sh @@ -18,115 +18,6 @@ source $(git rev-parse --show-toplevel)/vendor/github.com/tektoncd/plumbing/scripts/e2e-tests.sh -function teardown() { - subheader "Tearing down Tekton Pipelines" - ko delete --ignore-not-found=true -f config/ - # teardown will be called when run against an existing cluster to cleanup before - # continuing, so we must wait for the cleanup to complete or the subsequent attempt - # to deploy to the same namespace will fail - wait_until_object_does_not_exist namespace tekton-pipelines -} - -function output_yaml_test_results() { - # If formatting fails for any reason, use yaml as a fall back. - kubectl get $1.tekton.dev -o=custom-columns-file=${REPO_ROOT_DIR}/test/columns.txt || \ - kubectl get $1.tekton.dev -oyaml -} - -function output_pods_logs() { - echo ">>> $1" - kubectl get $1.tekton.dev -o yaml - local runs=$(kubectl get $1.tekton.dev --output=jsonpath="{.items[*].metadata.name}") - set +e - for run in ${runs}; do - echo ">>>> $1 ${run}" - case "$1" in - "taskrun") - tkn taskrun logs --nocolour ${run} - ;; - "pipelinerun") - tkn pipelinerun logs --nocolour ${run} - ;; - esac - done - set -e - echo ">>>> Pods" - kubectl get pods -o yaml -} - -# Called by `fail_test` (provided by `e2e-tests.sh`) to dump info on test failure -function dump_extra_cluster_state() { - echo ">>> Pipeline controller log:" - kubectl -n tekton-pipelines logs $(get_app_pod tekton-pipelines-controller tekton-pipelines) - echo ">>> Pipeline webhook log:" - kubectl -n tekton-pipelines logs $(get_app_pod tekton-pipelines-webhook tekton-pipelines) -} - -function validate_run() { - local tests_finished=0 - for i in {1..90}; do - local finished="$(kubectl get $1.tekton.dev --output=jsonpath='{.items[*].status.conditions[*].status}')" - if [[ ! "$finished" == *"Unknown"* ]]; then - tests_finished=1 - break - fi - sleep 10 - done - - return ${tests_finished} -} - -function check_results() { - local failed=0 - results="$(kubectl get $1.tekton.dev --output=jsonpath='{range .items[*]}{.metadata.name}={.status.conditions[*].type}{.status.conditions[*].status}{" "}{end}')" - for result in ${results}; do - if [[ ! "${result,,}" == *"=succeededtrue" ]]; then - echo "ERROR: test ${result} but should be succeededtrue" - failed=1 - fi - done - - return ${failed} -} - -function create_resources() { - local resource=$1 - echo ">> Creating resources ${resource}" - - # Applying the resources, either *taskruns or * *pipelineruns except those - # in the no-ci directory - for file in $(find ${REPO_ROOT_DIR}/examples/${resource}s/ -name '*.yaml' -not -path '*/no-ci/*' | sort); do - perl -p -e 's/gcr.io\/christiewilson-catfactory/$ENV{KO_DOCKER_REPO}/g' ${file} | ko create -f - || return 1 - done -} - -function run_tests() { - local resource=$1 - - # Wait for tests to finish. - echo ">> Waiting for tests to finish for ${resource}" - if validate_run $resource; then - echo "ERROR: tests timed out" - fi - - # Check that tests passed. - echo ">> Checking test results for ${resource}" - if check_results $resource; then - echo ">> All YAML tests passed" - return 0 - fi - return 1 -} - -function run_yaml_tests() { - echo ">> Starting tests for the resource ${1}/${2}" - create_resources ${1}/${2} || fail_test "Could not create ${2}/${1} from the examples" - if ! run_tests ${2}; then - return 1 - fi - return 0 -} - function install_pipeline_crd() { echo ">> Deploying Tekton Pipelines" ko resolve -f config/ \ diff --git a/test/e2e-tests-upgrade.sh b/test/e2e-tests-upgrade.sh index cce0c60bd89..f635a860131 100755 --- a/test/e2e-tests-upgrade.sh +++ b/test/e2e-tests-upgrade.sh @@ -51,15 +51,7 @@ failed=0 go_test_e2e -timeout=20m ./test || failed=1 # Run the post-integration tests. -for test in taskrun pipelinerun; do - header "Running YAML e2e tests for ${test}s" - if ! run_yaml_tests ${test}; then - echo "ERROR: one or more YAML tests failed" - output_yaml_test_results ${test} - output_pods_logs ${test} - failed=1 - fi -done +go_test_e2e -tags=examples -timeout=20m ./test/ || failed=1 # Remove all the pipeline CRDs, and clean up the environment for next Scenario. uninstall_pipeline_crd @@ -87,15 +79,7 @@ go_test_e2e -timeout=20m ./test || failed=1 # Run the post-integration tests. We do not need to install the resources again, since # they are installed before the upgrade. We verify if they still work, after going through # the upgrade. -for test in taskrun pipelinerun; do - header "Running YAML e2e tests for ${test}s" - if ! run_tests ${test}; then - echo "ERROR: one or more YAML tests failed" - output_yaml_test_results ${test} - output_pods_logs ${test} - failed=1 - fi -done +go_test_e2e -tags=examples -timeout=20m ./test/ || failed=1 (( failed )) && fail_test diff --git a/test/e2e-tests-yaml.sh b/test/e2e-tests-yaml.sh deleted file mode 100755 index 6d2061dca9f..00000000000 --- a/test/e2e-tests-yaml.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2019 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. - -# This script calls out to scripts in tektoncd/plumbing to setup a cluster -# and deploy Tekton Pipelines to it for running integration tests. - -source $(git rev-parse --show-toplevel)/test/e2e-common.sh - -# Script entry point. - -initialize $@ - -header "Setting up environment" - -# Handle failures ourselves, so we can dump useful info. -set +o errexit -set +o pipefail - -install_pipeline_crd - -# Run the tests -failed=0 -for version in v1alpha1 v1beta1; do - for test in taskrun pipelinerun; do - header "Running YAML e2e tests for ${version} ${test}s" - if ! run_yaml_tests ${version} ${test}; then - echo "ERROR: one or more YAML tests failed" - output_yaml_test_results ${test} - output_pods_logs ${test} - failed=1 - fi - done - # Clean resources - delete_pipeline_resources - for res in services pods configmaps secrets serviceaccounts persistentvolumeclaims; do - kubectl delete --ignore-not-found=true ${res} --all - done -done - -(( failed )) && fail_test - -success diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh index c13f01308ae..9fd46940045 100755 --- a/test/e2e-tests.sh +++ b/test/e2e-tests.sh @@ -36,7 +36,7 @@ go_test_e2e -timeout=20m ./test/... || failed=1 # Run these _after_ the integration tests b/c they don't quite work all the way # and they cause a lot of noise in the logs, making it harder to debug integration # test failures. -${REPO_ROOT_DIR}/test/e2e-tests-yaml.sh --run-tests || failed=1 +go_test_e2e -tags=examples -timeout=20m ./test/ || failed=1 (( failed )) && fail_test success diff --git a/test/examples_test.go b/test/examples_test.go new file mode 100644 index 00000000000..24d89ef339e --- /dev/null +++ b/test/examples_test.go @@ -0,0 +1,221 @@ +// +build examples + +/* +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 ( + "errors" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + knativetest "knative.dev/pkg/test" +) + +var ( + pipelineRunTimeout = 10 * time.Minute +) + +const ( + DEFAULT_KO_DOCKER_REPO = `gcr.io\/christiewilson-catfactory` + DEFAULT_NAMESPACE = `namespace: default` +) + +// GetCreatedTektonCrd parses output of an external ko invocation provided as +// input, as is the kind of Tekton CRD to search for (ie. taskrun) +func GetCreatedTektonCrd(input []byte, kind string) (string, error) { + re := regexp.MustCompile(kind + `.tekton.dev\/(.+) created`) + submatch := re.FindSubmatch(input) + if submatch == nil || len(submatch) < 2 { + return "", nil + } + return string(submatch[1]), nil +} + +func waitValidatePipelineRunDone(t *testing.T, c *clients, pipelineRunName string) { + err := WaitForPipelineRunState(c, pipelineRunName, pipelineRunTimeout, Succeed(pipelineRunName), pipelineRunName) + + if err != nil { + t.Fatalf("Failed waiting for pipeline run done: %v", err) + } + return +} + +func waitValidateTaskRunDone(t *testing.T, c *clients, taskRunName string) { + // Per test basis + err := WaitForTaskRunState(c, taskRunName, Succeed(taskRunName), taskRunName) + + if err != nil { + t.Fatalf("Failed waiting for task run done: %v", err) + } + return +} + +// SubstituteEnv substitutes docker repos and bucket paths from the system +// environment for input to allow tests on local clusters. It also updates the +// namespace for ServiceAccounts so that they work under test +func SubstituteEnv(input []byte, namespace string) ([]byte, error) { + val, ok := os.LookupEnv("KO_DOCKER_REPO") + var output []byte + if ok { + re := regexp.MustCompile(DEFAULT_KO_DOCKER_REPO) + output = re.ReplaceAll(input, []byte(val)) + } else { + return nil, errors.New("KO_DOCKER_REPO is not set") + } + + re := regexp.MustCompile(DEFAULT_NAMESPACE) + output = re.ReplaceAll(output, []byte(strings.ReplaceAll(DEFAULT_NAMESPACE, "default", namespace))) + return output, nil +} + +// KoCreate wraps the ko binary and invokes `ko create` for input within +// namespace +func KoCreate(input []byte, namespace string) ([]byte, error) { + cmd := exec.Command("ko", "create", "-n", namespace, "-f", "-") + cmd.Stdin = strings.NewReader(string(input)) + + out, err := cmd.CombinedOutput() + return out, err +} + +// DeleteClusterTask removes a single clustertask by name using provided +// clientset. Test state is used for logging. DeleteClusterTask does not wait +// for the clustertask to be deleted, so it is still possible to have name +// conflicts during test +func DeleteClusterTask(t *testing.T, c *clients, name string) { + t.Logf("Deleting clustertask %s", name) + err := c.ClusterTaskClient.Delete(name, &metav1.DeleteOptions{}) + if err != nil { + t.Fatalf("Failed to delete clustertask: %v", err) + } +} + +type waitFunc func(t *testing.T, c *clients, name string) + +func exampleTest(path string, waitValidateFunc waitFunc, kind string) func(t *testing.T) { + return func(t *testing.T) { + t.Parallel() + + // Setup unique namespaces for each test so they can run in complete + // isolation + c, namespace := setup(t) + + knativetest.CleanupOnInterrupt(func() { tearDown(t, c, namespace) }, t.Logf) + defer tearDown(t, c, namespace) + + inputExample, err := ioutil.ReadFile(path) + + if err != nil { + t.Fatalf("Error reading file: %v", err) + } + + subbedInput, err := SubstituteEnv(inputExample, namespace) + if err != nil { + t.Skipf("Couldn't substitute environment: %v", err) + } + + out, err := KoCreate(subbedInput, namespace) + if err != nil { + t.Fatalf("%s Output: %s", err, out) + } + + // Parse from KoCreate for now + name, err := GetCreatedTektonCrd(out, kind) + if name == "" { + // Nothing to check from ko create, this is not a taskrun or pipeline + // run. Some examples in the directory do not directly output a TaskRun + // or PipelineRun (ie. task-result.yaml). + t.Skipf("pipelinerun or taskrun not created for %s", path) + } else if err != nil { + t.Fatalf("Failed to get created Tekton CRD of kind %s: %v", kind, err) + } + + // NOTE: If an example creates more than one clustertask, they will not all + // be cleaned up + clustertask, err := GetCreatedTektonCrd(out, "clustertask") + if clustertask != "" { + knativetest.CleanupOnInterrupt(func() { DeleteClusterTask(t, c, clustertask) }, t.Logf) + defer DeleteClusterTask(t, c, clustertask) + } else if err != nil { + t.Fatalf("Failed to get created clustertask: %v", err) + } + + waitValidateFunc(t, c, name) + } +} + +func getExamplePaths(t *testing.T, dir string) []string { + var examplePaths []string + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + t.Fatalf("couldn't walk path %s: %v", path, err) + } + // Do not append root and any other folders named "examples" + if info.Name() == "examples" && info.IsDir() { + return nil + } + if info.Name() == "no-ci" && info.IsDir() { + return filepath.SkipDir + } + if info.IsDir() == false && filepath.Ext(info.Name()) == ".yaml" { + examplePaths = append(examplePaths, path) + return nil + } + return nil + }) + if err != nil { + t.Fatalf("couldn't walk example directory %s: %v", dir, err) + } + + return examplePaths +} + +func extractTestName(baseDir string, path string) string { + re := regexp.MustCompile(baseDir + "/(.+).yaml") + submatch := re.FindSubmatch([]byte(path)) + if submatch == nil { + return path + } + return string(submatch[1]) +} + +func TestExamples(t *testing.T) { + baseDir := "../examples" + + t.Parallel() + for _, path := range getExamplePaths(t, baseDir) { + testName := extractTestName(baseDir, path) + waitValidateFunc := waitValidatePipelineRunDone + kind := "pipelinerun" + + if strings.Contains(path, "/taskruns/") { + waitValidateFunc = waitValidateTaskRunDone + kind = "taskrun" + } + + t.Run(testName, exampleTest(path, waitValidateFunc, kind)) + } +} diff --git a/test/init_test.go b/test/init_test.go index 9b5da5516da..7b279b81e9e 100644 --- a/test/init_test.go +++ b/test/init_test.go @@ -1,4 +1,4 @@ -// +build e2e +// +build e2e examples /* Copyright 2019 The Tekton Authors