From 4041d609dd47adfeedf3e72c4aa32fd50d87d520 Mon Sep 17 00:00:00 2001 From: Jefferson Ramos Date: Mon, 5 Sep 2022 10:08:22 -0300 Subject: [PATCH] test: oncluster build initial e2e set of tests (#1193) --- .../workflows/test-e2e-oncluster-runtime.yaml | 29 ++++ .github/workflows/test-e2e-oncluster.yaml | 29 ++++ Makefile | 3 + hack/tekton.sh | 50 ++++++ hack/test-integration-podman.sh | 2 +- test/README.md | 68 +++++++++ test/_common/cmd.go | 115 ++++++++++++++ test/_common/gitserver.go | 106 +++++++++++++ test/_common/knfunc.go | 30 ++++ test/_common/shell.go | 18 +++ test/_e2e/cleaner.go | 29 ++++ test/_e2e/cmd_deploy_test.go | 26 ---- test/_e2e/knative.go | 12 +- .../{ready_check_test.go => ready_check.go} | 4 +- test/_e2e/update_test.go | 4 +- test/_oncluster/asserts.go | 35 +++++ test/_oncluster/git_helper.go | 67 ++++++++ test/_oncluster/nodejs_helper.go | 23 +++ test/_oncluster/scenario_basic_test.go | 71 +++++++++ test/_oncluster/scenario_context-dir_test.go | 59 ++++++++ .../scenario_from-cli-local_test.go | 36 +++++ test/_oncluster/scenario_from-cli_test.go | 143 ++++++++++++++++++ test/_oncluster/scenario_revision_test.go | 121 +++++++++++++++ test/_oncluster/scenario_runtime_test.go | 69 +++++++++ test/_oncluster/tekton.go | 96 ++++++++++++ test/e2e_oncluster_tests.sh | 58 +++++++ test/gitserver.sh | 49 ++++++ 27 files changed, 1319 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/test-e2e-oncluster-runtime.yaml create mode 100644 .github/workflows/test-e2e-oncluster.yaml create mode 100755 hack/tekton.sh create mode 100644 test/README.md create mode 100644 test/_common/cmd.go create mode 100644 test/_common/gitserver.go create mode 100644 test/_common/knfunc.go create mode 100644 test/_common/shell.go create mode 100644 test/_e2e/cleaner.go rename test/_e2e/{ready_check_test.go => ready_check.go} (85%) create mode 100644 test/_oncluster/asserts.go create mode 100644 test/_oncluster/git_helper.go create mode 100644 test/_oncluster/nodejs_helper.go create mode 100644 test/_oncluster/scenario_basic_test.go create mode 100644 test/_oncluster/scenario_context-dir_test.go create mode 100644 test/_oncluster/scenario_from-cli-local_test.go create mode 100644 test/_oncluster/scenario_from-cli_test.go create mode 100644 test/_oncluster/scenario_revision_test.go create mode 100644 test/_oncluster/scenario_runtime_test.go create mode 100644 test/_oncluster/tekton.go create mode 100755 test/e2e_oncluster_tests.sh create mode 100755 test/gitserver.sh diff --git a/.github/workflows/test-e2e-oncluster-runtime.yaml b/.github/workflows/test-e2e-oncluster-runtime.yaml new file mode 100644 index 00000000..8355fd4b --- /dev/null +++ b/.github/workflows/test-e2e-oncluster-runtime.yaml @@ -0,0 +1,29 @@ +name: Func E2E OnCluster RT Test + +on: [pull_request] + +jobs: + test: + name: On Cluster RT Test + strategy: + matrix: + go: [1.17.x] + os: ["ubuntu-latest"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - name: Install Binaries + run: ./hack/binaries.sh + - name: Allocate Cluster + run: ./hack/allocate.sh + - name: Deploy Tekton + run: ./hack/tekton.sh + - name: Deploy Test Git Server + run: ./test/gitserver.sh + - name: E2E On Cluster Test (Runtimes) + env: + TEST_TAGS: runtime + run: make && make test-e2e-on-cluster diff --git a/.github/workflows/test-e2e-oncluster.yaml b/.github/workflows/test-e2e-oncluster.yaml new file mode 100644 index 00000000..d9cc660f --- /dev/null +++ b/.github/workflows/test-e2e-oncluster.yaml @@ -0,0 +1,29 @@ +name: Func E2E OnCluster Test + +on: [pull_request] + +jobs: + test: + name: On Cluster Test + strategy: + matrix: + go: [1.17.x] + os: ["ubuntu-latest"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - name: Install Binaries + run: ./hack/binaries.sh + - name: Allocate Cluster + run: ./hack/allocate.sh + - name: Deploy Tekton + run: ./hack/tekton.sh + - name: Deploy Test Git Server + run: ./test/gitserver.sh + - name: E2E On Cluster Test + env: + E2E_RUNTIMES: "" + run: make && make test-e2e-on-cluster diff --git a/Makefile b/Makefile index 7e3f36a0..303d8da9 100644 --- a/Makefile +++ b/Makefile @@ -149,6 +149,9 @@ test-e2e: ## Run end-to-end tests using an available cluster. test-e2e-runtime: ## Run end-to-end lifecycle tests using an available cluster for a single runtime. ./test/e2e_lifecycle_tests.sh $(runtime) +test-e2e-on-cluster: ## Run end-to-end on-cluster build tests using an available cluster. + ./test/e2e_oncluster_tests.sh + ###################### ##@ Release Artifacts ###################### diff --git a/hack/tekton.sh b/hack/tekton.sh new file mode 100755 index 00000000..824b5b4f --- /dev/null +++ b/hack/tekton.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +# 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 +# +# https://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. + +# +# Install Tekton and required tasks in the cluster +# + +set -o errexit +set -o nounset +set -o pipefail + +export TERM="${TERM:-dumb}" + +tekton_release="previous/v0.38.3" +git_clone_release="0.4" +source_path="https://raw.githubusercontent.com/knative-sandbox/kn-plugin-func/main" +namespace="${NAMESPACE:-default}" + +tekton() { + echo "Installing Tekton..." + kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/${tekton_release}/release.yaml + sleep 10 + kubectl wait pod --for=condition=Ready --timeout=180s -n tekton-pipelines -l "app=tekton-pipelines-controller" + + kubectl create clusterrolebinding ${namespace}:knative-serving-namespaced-admin \ + --clusterrole=knative-serving-namespaced-admin --serviceaccount=${namespace}:default +} + +tekton_tasks() { + echo "Creating Pipeline tasks..." + kubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/master/task/git-clone/${git_clone_release}/git-clone.yaml + kubectl apply -f ${source_path}/pipelines/resources/tekton/task/func-buildpacks/0.1/func-buildpacks.yaml + kubectl apply -f ${source_path}/pipelines/resources/tekton/task/func-deploy/0.1/func-deploy.yaml +} + +tekton +tekton_tasks + +echo Done diff --git a/hack/test-integration-podman.sh b/hack/test-integration-podman.sh index 3aefb78c..151f60b8 100755 --- a/hack/test-integration-podman.sh +++ b/hack/test-integration-podman.sh @@ -17,7 +17,7 @@ podman_pid=$! DOCKER_HOST="unix://$(podman info -f '{{.Host.RemoteSocket.Path}}' 2> /dev/null)" export DOCKER_HOST -go test -tags integration ./... -v +go test -test.timeout=15m -tags integration ./... -v e=$? kill -TERM "$podman_pid" > /dev/null 2>&1 diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..a18edc1f --- /dev/null +++ b/test/README.md @@ -0,0 +1,68 @@ +# Functions E2E Test + +## Lifecycle tests + +Lifecycle tests exercises the most important phases of a function lifecycle starting from +creation, going thru to build, deployment, execution and then deletion (CRUD operations). +It runs func commands such as `create`, `deploy`, `list` and `delete` for a language +runtime using both default `http` and `cloudevents` templates. + +## Extended tests + +Extended tests performs additional tests on `func` such as templates, config envs, volumes, labels and +other scenarios. + +## On Cluster Builds tests + +On cluster builds e2e tests exercises functions built directly on cluster. +The tests are organized per scenarios under `./_oncluster` folder. + +### Pre-requisites + +Prior to run On Cluster builds e2e tests ensure you are connected to +a Kubernetes Cluster with the following deployed: + +- Knative Serving +- Tekton +- Tekton Tasks listed [here](../docs/reference/on_cluster_build.md) +- Embedded Git Server (`func-git`) used by tests + +For your convenience you can run the following script to setup Tekton and required Tasks: +``` +$ ../hack/tekton.sh +``` + +To install the Git Server required by tests, run: +``` +$ ./gitserver.sh +``` + +#### Running all the Tests on KinD + +The below instructions will run all the tests on KinD using an **ephemeral** container registry. +``` +# Pre-Reqs +./hack/allocate.sh +./hack/tekton.sh +./test/gitserver.sh +make build + +# Run tests +./test/e2e_oncluter_tests.sh +``` + +#### Running "runtime" only scenario + +You can run only e2e tests to exercise a given language/runtime, for example *python* + +``` +env E2E_RUNTIMES=python TEST_TAGS=runtime ./test/e2e_oncluster_test.sh +``` + +#### Running tests except "runtime" ones + +You can run most of on cluster builds e2e scenarios, except the language/runtime specific +ones, by running: +``` +env E2E_RUNTIMES="" ./test/e2e_oncluster_test.sh +``` diff --git a/test/_common/cmd.go b/test/_common/cmd.go new file mode 100644 index 00000000..00a82343 --- /dev/null +++ b/test/_common/cmd.go @@ -0,0 +1,115 @@ +package common + +import ( + "bytes" + "os" + "os/exec" + "strings" + "testing" +) + +type TestExecCmd struct { + + // binary to invoke + // Example: "func", "kn", "kubectl", "/usr/bin/sh" + Binary string + + // Binary args to append before actual args. Examples: + // when 'kn' binary binaryArgs should be ["func"] + BinaryArgs []string + + // Run commands from Dir + SourceDir string + + // Indicates shell should dump command line args during execution + ShouldDumpCmdLine bool + + // Indicates shell should dump + ShouldDumpOnSuccess bool + + // Fail Test on Error + ShouldFailOnError bool + + // Environment variable to be used with the command + Env []string + + // Optional function to be used to dump stdout command results + DumpLogger func(out string) + + // Boolean + T *testing.T +} + +// TestExecCmdResult stored command result +type TestExecCmdResult struct { + Stdout string + Stderr string + Error error +} + +func (f *TestExecCmd) WithEnv(envKey string, envValue string) *TestExecCmd { + env := envKey + "=" + envValue + f.Env = append(f.Env, env) + return f +} + +func (f *TestExecCmd) FromDir(dir string) *TestExecCmd { + f.SourceDir = dir + return f +} + +func (f *TestExecCmd) Run(oneArgs string) TestExecCmdResult { + args := strings.Split(oneArgs, " ") + return f.Exec(args...) +} + +// Exec invokes go exec library and runs a shell command combining the binary args with args from method signature +func (f *TestExecCmd) Exec(args ...string) TestExecCmdResult { + finalArgs := f.BinaryArgs + if finalArgs == nil { + finalArgs = args + } else if args != nil { + finalArgs = append(finalArgs, args...) + } + + if f.ShouldDumpCmdLine { + f.T.Log(f.Binary, strings.Join(finalArgs, " ")) + } + + var stderr bytes.Buffer + var stdout bytes.Buffer + + cmd := exec.Command(f.Binary, finalArgs...) + cmd.Stderr = &stderr + cmd.Stdout = &stdout + if f.SourceDir != "" { + cmd.Dir = f.SourceDir + } + cmd.Env = append(os.Environ(), f.Env...) + err := cmd.Run() + + result := TestExecCmdResult{ + Stdout: stdout.String(), + Stderr: stderr.String(), + Error: err, + } + + if err == nil && f.ShouldDumpOnSuccess { + if result.Stdout != "" { + if f.DumpLogger != nil { + f.DumpLogger(result.Stdout) + } else { + f.T.Logf("%v", result.Stdout) + } + } + } + if err != nil { + f.T.Log(err.Error()) + f.T.Log(result.Stderr) + if f.ShouldFailOnError { + f.T.Fail() + } + } + + return result +} diff --git a/test/_common/gitserver.go b/test/_common/gitserver.go new file mode 100644 index 00000000..b52737b9 --- /dev/null +++ b/test/_common/gitserver.go @@ -0,0 +1,106 @@ +package common + +import ( + "context" + + "knative.dev/kn-plugin-func/k8s" + e2e "knative.dev/kn-plugin-func/test/_e2e" + + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +func GetGitServer(T *testing.T) GitProvider { + gitTestServer := GitTestServerProvider{} + gitTestServer.Init(T) + return &gitTestServer +} + +type GitRemoteRepo struct { + RepoName string + ExternalCloneURL string + ClusterCloneURL string +} + +type GitProvider interface { + Init(T *testing.T) + CreateRepository(repoName string) *GitRemoteRepo + DeleteRepository(repoName string) +} + +// ------------------------------------------------------ +// Git Server on Kubernetes as Knative Service (func-git) +// ------------------------------------------------------ + +type GitTestServerProvider struct { + PodName string + ServiceUrl string + Kubectl *TestExecCmd + t *testing.T +} + +func (g *GitTestServerProvider) Init(T *testing.T) { + + g.t = T + if g.PodName == "" { + config, err := k8s.GetClientConfig().ClientConfig() + if err != nil { + T.Fatal(err.Error()) + } + clientSet, err := kubernetes.NewForConfig(config) + if err != nil { + T.Fatal(err.Error()) + } + ctx := context.Background() + + namespace, _, _ := k8s.GetClientConfig().Namespace() + podList, err := clientSet.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: "serving.knative.dev/service=func-git", + }) + if err != nil { + T.Fatal(err.Error()) + } + for _, pod := range podList.Items { + g.PodName = pod.Name + } + } + + if g.ServiceUrl == "" { + // Get Route Name + g.ServiceUrl = e2e.GetKnativeServiceUrl(T, "func-git") + } + + if g.Kubectl == nil { + g.Kubectl = &TestExecCmd{ + Binary: "kubectl", + ShouldDumpCmdLine: true, + ShouldDumpOnSuccess: true, + T: T, + } + } + T.Logf("Initialized HTTP Func Git Server: Server URL = %v Pod Name = %v\n", g.ServiceUrl, g.PodName) +} + +func (g *GitTestServerProvider) CreateRepository(repoName string) *GitRemoteRepo { + // kubectl exec $podname -c user-container -- git-repo create $reponame + cmdResult := g.Kubectl.Exec("exec", g.PodName, "-c", "user-container", "--", "git-repo", "create", repoName) + if !strings.Contains(cmdResult.Stdout, "created") { + g.t.Fatal("unable to create git bare repository " + repoName) + } + gitRepo := &GitRemoteRepo{ + RepoName: repoName, + ExternalCloneURL: g.ServiceUrl + "/" + repoName + ".git", + ClusterCloneURL: "http://func-git.default.svc.cluster.local/" + repoName + ".git", + } + return gitRepo +} + +func (g *GitTestServerProvider) DeleteRepository(repoName string) { + cmdResult := g.Kubectl.Exec("exec", g.PodName, "-c", "user-container", "--", "git-repo", "delete", repoName) + if !strings.Contains(cmdResult.Stdout, "deleted") { + g.t.Fatal("unable to delete git bare repository " + repoName) + } +} diff --git a/test/_common/knfunc.go b/test/_common/knfunc.go new file mode 100644 index 00000000..ca1d2ae8 --- /dev/null +++ b/test/_common/knfunc.go @@ -0,0 +1,30 @@ +package common + +import ( + "testing" + + e2e "knative.dev/kn-plugin-func/test/_e2e" +) + +func NewKnFuncShellCli(t *testing.T) *TestExecCmd { + knFunc := TestExecCmd{} + knFunc.T = t + + if e2e.IsUseKnFunc() { + knFunc.Binary = "kn" + knFunc.BinaryArgs = []string{"func"} + } else { + knFunc.Binary = e2e.GetFuncBinaryPath() + if knFunc.Binary == "" { + t.Log("'func' binary not defined. Please set E2E_FUNC_BIN_PATH environment variable prior to running tests") + t.FailNow() + } + } + cmd := knFunc.Exec() + if cmd.Error != nil { + t.FailNow() + } + knFunc.ShouldDumpCmdLine = true + knFunc.ShouldFailOnError = true + return &knFunc +} diff --git a/test/_common/shell.go b/test/_common/shell.go new file mode 100644 index 00000000..2d6dc22f --- /dev/null +++ b/test/_common/shell.go @@ -0,0 +1,18 @@ +package common + +import ( + "testing" +) + +func NewShellCmd(t *testing.T, fromDirectory string) *TestExecCmd { + + shellCmd := TestExecCmd{ + Binary: "sh", + BinaryArgs: []string{"-c"}, + SourceDir: fromDirectory, + ShouldDumpCmdLine: true, + ShouldDumpOnSuccess: true, + T: t, + } + return &shellCmd +} diff --git a/test/_e2e/cleaner.go b/test/_e2e/cleaner.go new file mode 100644 index 00000000..5aa9353a --- /dev/null +++ b/test/_e2e/cleaner.go @@ -0,0 +1,29 @@ +package e2e + +import "strings" + +// CleanOutput Some commands, such as deploy command, spans spinner chars and cursor shifts at output which are captured and merged +// regular output messages. This functions is meant to remove these chars in order to facilitate tests assertions and data extraction from output +func CleanOutput(stdOutput string) string { + toRemove := []string{ + "🕛 ", + "🕐 ", + "🕑 ", + "🕒 ", + "🕓 ", + "🕔 ", + "🕕 ", + "🕖 ", + "🕗 ", + "🕘 ", + "🕙 ", + "🕚 ", + "\033[1A", + "\033[1B", + "\033[K", + } + for _, c := range toRemove { + stdOutput = strings.ReplaceAll(stdOutput, c, "") + } + return stdOutput +} diff --git a/test/_e2e/cmd_deploy_test.go b/test/_e2e/cmd_deploy_test.go index da5473f4..0773a318 100644 --- a/test/_e2e/cmd_deploy_test.go +++ b/test/_e2e/cmd_deploy_test.go @@ -41,29 +41,3 @@ func Deploy(t *testing.T, knFunc *TestShellCmdRunner, project *FunctionTestProje project.IsDeployed = true } - -// CleanOutput Some commands, such as deploy command, spans spinner chars and cursor shifts at output which are captured and merged -// regular output messages. This functions is meant to remove these chars in order to facilitate tests assertions and data extraction from output -func CleanOutput(deployOutput string) string { - toRemove := []string{ - "🕛 ", - "🕐 ", - "🕑 ", - "🕒 ", - "🕓 ", - "🕔 ", - "🕕 ", - "🕖 ", - "🕗 ", - "🕘 ", - "🕙 ", - "🕚 ", - "\033[1A", - "\033[1B", - "\033[K", - } - for _, c := range toRemove { - deployOutput = strings.ReplaceAll(deployOutput, c, "") - } - return deployOutput -} diff --git a/test/_e2e/knative.go b/test/_e2e/knative.go index 58c90470..cfb7ed4f 100644 --- a/test/_e2e/knative.go +++ b/test/_e2e/knative.go @@ -39,10 +39,18 @@ func RetrieveKnativeServiceResource(t *testing.T, serviceName string) *unstructu } // GetCurrentServiceRevision retrieves current revision name for the deployed function -func GetCurrentServiceRevision(t *testing.T, project *FunctionTestProject) string { - resource := RetrieveKnativeServiceResource(t, project.FunctionName) +func GetCurrentServiceRevision(t *testing.T, serviceName string) string { + resource := RetrieveKnativeServiceResource(t, serviceName) rootMap := resource.UnstructuredContent() statusMap := rootMap["status"].(map[string]interface{}) latestReadyRevision := statusMap["latestReadyRevisionName"].(string) return latestReadyRevision } + +func GetKnativeServiceUrl(t *testing.T, functionName string) string { + resource := RetrieveKnativeServiceResource(t, functionName) + rootMap := resource.UnstructuredContent() + statusMap := rootMap["status"].(map[string]interface{}) + url := statusMap["url"].(string) + return url +} diff --git a/test/_e2e/ready_check_test.go b/test/_e2e/ready_check.go similarity index 85% rename from test/_e2e/ready_check_test.go rename to test/_e2e/ready_check.go index dc864745..b289817c 100644 --- a/test/_e2e/ready_check_test.go +++ b/test/_e2e/ready_check.go @@ -22,9 +22,9 @@ func ReadyCheck(t *testing.T, knFunc *TestShellCmdRunner, project FunctionTestPr } // NewRevisionCheck waits for a new revision to report as ready -func NewRevisionCheck(t *testing.T, previousRevision string, project *FunctionTestProject) (newRevision string) { +func NewRevisionCheck(t *testing.T, previousRevision string, serviceName string) (newRevision string) { err := wait.PollImmediate(5*time.Second, 1*time.Minute, func() (done bool, err error) { - newRevision = GetCurrentServiceRevision(t, project) + newRevision = GetCurrentServiceRevision(t, serviceName) t.Logf("Waiting for new revision deployment (previous revision [%v], current revision [%v])", previousRevision, newRevision) return newRevision != "" && newRevision != previousRevision, nil }) diff --git a/test/_e2e/update_test.go b/test/_e2e/update_test.go index fcbc36a3..c952ecbd 100644 --- a/test/_e2e/update_test.go +++ b/test/_e2e/update_test.go @@ -33,13 +33,13 @@ func Update(t *testing.T, knFunc *TestShellCmdRunner, project *FunctionTestProje t.Fatal("an error has occurred while updating project folder with new sources.", err.Error()) } - previousRevision := GetCurrentServiceRevision(t, project) + previousRevision := GetCurrentServiceRevision(t, project.FunctionName) // Redeploy function Deploy(t, knFunc, project) // Waits New Revision to become ready - NewRevisionCheck(t, previousRevision, project) + NewRevisionCheck(t, previousRevision, project.FunctionName) // Indicates new project (from update templates) is in use project.IsNewRevision = true diff --git a/test/_oncluster/asserts.go b/test/_oncluster/asserts.go new file mode 100644 index 00000000..8c1930f8 --- /dev/null +++ b/test/_oncluster/asserts.go @@ -0,0 +1,35 @@ +package oncluster + +import ( + "testing" + + "gotest.tools/v3/assert" +) + +// AssertNoError ensure err is nil otherwise fails testing +func AssertNoError(t *testing.T, err error) { + if err != nil { + t.Error(err.Error()) + t.FailNow() + } +} + +// AssertThatTektonPipelineRunSucceed verifies the pipeline and pipelinerun were actually created +// on the cluster and ensure all the Tasks of the pipelinerun executed successfully +// Also it logs a brief summary of execution of the pipeline for potential debug purposes +func AssertThatTektonPipelineRunSucceed(t *testing.T, functionName string) { + assert.Assert(t, TektonPipelineExists(t, functionName), "tekton pipeline not found on cluster") + RunSummary := TektonPipelineLastRunSummary(t, functionName) + t.Logf("Tekton Run Summary:\n %v", RunSummary.ToString()) + assert.Assert(t, RunSummary.IsSucceed(), "expected pipeline run was not succeeded") +} + +// AssertThatTektonPipelineResourcesNotExists is intended to check the pipeline and pipelinerun resources +// do not exists. This is meant to be called after a `func delete` to ensure everything is cleaned +func AssertThatTektonPipelineResourcesNotExists(t *testing.T, functionName string) { + if !t.Failed() { + t.Log("Checking resources got cleaned") + assert.Assert(t, !TektonPipelineExists(t, functionName), "tekton pipeline was found but it should not exist") + assert.Assert(t, !TektonPipelineRunExists(t, functionName), "tekton pipelinerun was found but it should not exist") + } +} diff --git a/test/_oncluster/git_helper.go b/test/_oncluster/git_helper.go new file mode 100644 index 00000000..ee899fa6 --- /dev/null +++ b/test/_oncluster/git_helper.go @@ -0,0 +1,67 @@ +package oncluster + +import ( + "fmt" + "os" + "testing" + + yaml "gopkg.in/yaml.v2" + common "knative.dev/kn-plugin-func/test/_common" +) + +type Git struct { + URL string + Revision string + ContextDir string +} + +// UpdateFuncYamlGit update func.yaml file by setting build to git as well as git fields. +func UpdateFuncYamlGit(t *testing.T, projectDir string, git Git) { + + funcYamlPath := projectDir + "/func.yaml" + data, err := os.ReadFile(funcYamlPath) + AssertNoError(t, err) + + m := make(map[interface{}]interface{}) + err = yaml.Unmarshal([]byte(data), &m) + AssertNoError(t, err) + + gitMap := make(map[interface{}]interface{}) + m["build"] = "git" + m["git"] = gitMap + + changeLog := fmt.Sprintln("build:", "git") + updateGitField := func(targetField string, targetValue string) { + if targetValue != "" { + gitMap[targetField] = targetValue + changeLog += fmt.Sprintln("git.", targetField, ":", targetValue) + } + } + updateGitField("url", git.URL) + updateGitField("revision", git.Revision) + updateGitField("contextDir", git.ContextDir) + + outData, _ := yaml.Marshal(m) + err = os.WriteFile(funcYamlPath, outData, 0644) + AssertNoError(t, err) + t.Logf("func.yaml changed:\n%v", changeLog) +} + +// GitInitialCommitAndPush Runs repeatable git commands used on every initial repository setup +// such as `git init`, `git config user`, `git add .`, `git remote add ...` and `git push` +func GitInitialCommitAndPush(t *testing.T, gitProjectPath string, originCloneURL string) (sh *common.TestExecCmd) { + + sh = common.NewShellCmd(t, gitProjectPath) + sh.ShouldFailOnError = true + sh.ShouldDumpOnSuccess = true + sh.Exec(`git init`) + sh.Exec(`git branch -M main`) + sh.Exec(`git add .`) + sh.Exec(`git config user.name "John Smith"`) + sh.Exec(`git config user.email "john.smith@example.com"`) + sh.Exec(`git commit -m "initial commit"`) + sh.Exec(`git remote add origin ` + originCloneURL) + sh.Exec(`git push -u origin main`) + return sh + +} diff --git a/test/_oncluster/nodejs_helper.go b/test/_oncluster/nodejs_helper.go new file mode 100644 index 00000000..85d53b06 --- /dev/null +++ b/test/_oncluster/nodejs_helper.go @@ -0,0 +1,23 @@ +package oncluster + +import ( + "fmt" + "os" + "path/filepath" + "testing" +) + +// WriteNewSimpleIndexJS is used to replace the content of "index.js" of a Node JS function created by a test case. +// File content will cause the deployed function to, when invoked, return the value specified on `withBodyReturning` +// params, which is handy for test assertions. +func WriteNewSimpleIndexJS(t *testing.T, nodeJsFuncProjectDir string, withBodyReturning string) { + indexJsContent := fmt.Sprintf(` +function invoke(context) { + return { body: '%v' } +} +module.exports = invoke; +`, withBodyReturning) + + err := os.WriteFile(filepath.Join(nodeJsFuncProjectDir, "index.js"), []byte(indexJsContent), 0644) + AssertNoError(t, err) +} diff --git a/test/_oncluster/scenario_basic_test.go b/test/_oncluster/scenario_basic_test.go new file mode 100644 index 00000000..458f8384 --- /dev/null +++ b/test/_oncluster/scenario_basic_test.go @@ -0,0 +1,71 @@ +//go:build oncluster +// +build oncluster + +package oncluster + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "gotest.tools/v3/assert" + common "knative.dev/kn-plugin-func/test/_common" + e2e "knative.dev/kn-plugin-func/test/_e2e" +) + +// TestDefault covers basic test scenario that ensure on cluster build from a "default branch" and +// code changes (new commits) will be properly built and deployed on new revision +func TestBasicDefault(t *testing.T) { + + var funcName = "test-func-basic" + var funcPath = filepath.Join(os.TempDir(), funcName) + + func() { + gitServer := common.GitTestServerProvider{} + gitServer.Init(t) + remoteRepo := gitServer.CreateRepository(funcName) + defer gitServer.DeleteRepository(funcName) + + knFunc := common.NewKnFuncShellCli(t) + knFunc.Exec("create", "-l", "node", funcPath) + defer os.RemoveAll(funcPath) + + // Write an `index.js` that make node func to return 'first revision' + WriteNewSimpleIndexJS(t, funcPath, "first revision") + + sh := GitInitialCommitAndPush(t, funcPath, remoteRepo.ExternalCloneURL) + + // Update func.yaml build as git + url + context-dir + UpdateFuncYamlGit(t, funcPath, Git{URL: remoteRepo.ClusterCloneURL}) + + // Deploy it + knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath) + defer knFunc.Exec("delete", "-p", funcPath) + + // Assert "first revision" is returned + result := knFunc.Exec("invoke", "-p", funcPath) + assert.Assert(t, strings.Contains(result.Stdout, "first revision"), "Func body does not contain 'first revision'") + + previousServiceRevision := e2e.GetCurrentServiceRevision(t, funcName) + + // Update index.js to force node func to return 'new revision' + WriteNewSimpleIndexJS(t, funcPath, "new revision") + sh.Exec(`git add index.js`) + sh.Exec(`git commit -m "revision 2"`) + sh.Exec(`git push`) + + // Re-Deploy Func + knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath) + e2e.NewRevisionCheck(t, previousServiceRevision, funcName) // Wait New Service Revision + + // -- Assertions -- + result = knFunc.Exec("invoke", "-p", funcPath) + assert.Assert(t, strings.Contains(result.Stdout, "new revision"), "Func body does not contain 'new revision'") + AssertThatTektonPipelineRunSucceed(t, funcName) + + }() + + AssertThatTektonPipelineResourcesNotExists(t, funcName) + +} diff --git a/test/_oncluster/scenario_context-dir_test.go b/test/_oncluster/scenario_context-dir_test.go new file mode 100644 index 00000000..9c3a2981 --- /dev/null +++ b/test/_oncluster/scenario_context-dir_test.go @@ -0,0 +1,59 @@ +//go:build oncluster +// +build oncluster + +package oncluster + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "gotest.tools/v3/assert" + common "knative.dev/kn-plugin-func/test/_common" + e2e "knative.dev/kn-plugin-func/test/_e2e" +) + +// TestContextDirFunc tests the following use case: +// - As a Developer I want my function located in a specific directory on my project, hosted on my +// public git repository from the main branch, to get deployed on my cluster +func TestContextDirFunc(t *testing.T) { + + var gitProjectName = "test-project" + var gitProjectPath = filepath.Join(os.TempDir(), gitProjectName) + var funcName = "test-func-context-dir" + var funcContextDir = filepath.Join("functions", funcName) + var funcPath = filepath.Join(gitProjectPath, funcContextDir) + + func() { + + gitServer := common.GitTestServerProvider{} + gitServer.Init(t) + remoteRepo := gitServer.CreateRepository(gitProjectName) + defer gitServer.DeleteRepository(gitProjectName) + + knFunc := common.NewKnFuncShellCli(t) + knFunc.Exec("create", "-l", "node", funcPath) + + WriteNewSimpleIndexJS(t, funcPath, "hello dir") + + defer os.RemoveAll(gitProjectPath) + + // Initial commit to repository: git init + commit + push + GitInitialCommitAndPush(t, gitProjectPath, remoteRepo.ExternalCloneURL) + + // Update func.yaml build as git + url + context-dir + UpdateFuncYamlGit(t, funcPath, Git{URL: remoteRepo.ClusterCloneURL, ContextDir: funcContextDir}) + + knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath) + defer knFunc.Exec("delete", "-p", funcPath) + + // -- Assertions -- + result := knFunc.Exec("invoke", "-p", funcPath) + assert.Assert(t, strings.Contains(result.Stdout, "hello dir"), "Func body does not contain 'hello dir'") + AssertThatTektonPipelineRunSucceed(t, funcName) + + }() + + AssertThatTektonPipelineResourcesNotExists(t, funcName) +} diff --git a/test/_oncluster/scenario_from-cli-local_test.go b/test/_oncluster/scenario_from-cli-local_test.go new file mode 100644 index 00000000..42784610 --- /dev/null +++ b/test/_oncluster/scenario_from-cli-local_test.go @@ -0,0 +1,36 @@ +//go:build oncluster +// +build oncluster + +package oncluster + +import ( + "os" + "path/filepath" + "testing" + + common "knative.dev/kn-plugin-func/test/_common" + e2e "knative.dev/kn-plugin-func/test/_e2e" +) + +// TestFromCliBuildLocal tests the scenario which func.yaml indicates that builds should be on cluster +// but users wants to run a local build on its machine +func TestFromCliBuildLocal(t *testing.T) { + + var funcName = "test-func-cli-local" + var funcPath = filepath.Join(os.TempDir(), funcName) + + knFunc := common.NewKnFuncShellCli(t) + knFunc.Exec("create", "-l", "node", funcPath) + defer os.RemoveAll(funcPath) + + // Update func.yaml build as local + some fake url (it should not call it anyway) + UpdateFuncYamlGit(t, funcPath, Git{URL: "http://fake-repo/repo.git"}) + + knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath, "--build", "local") + defer knFunc.Exec("delete", "-p", funcPath) + + // -- Assertions -- + knFunc.Exec("invoke", "-p", funcPath) + AssertThatTektonPipelineResourcesNotExists(t, funcName) + +} diff --git a/test/_oncluster/scenario_from-cli_test.go b/test/_oncluster/scenario_from-cli_test.go new file mode 100644 index 00000000..326b01c8 --- /dev/null +++ b/test/_oncluster/scenario_from-cli_test.go @@ -0,0 +1,143 @@ +//go:build oncluster +// +build oncluster + +package oncluster + +/* +Tests on this file covers the scenarios when func.yaml is not modified (build: local) +and git build strategy is specified thru CLI. + +A) Default Branch Test +func deploy --build=git --git-url=http://gitserver/myfunc.git + +b) Feature Branch Test +func deploy --build=git --git-url=http://gitserver/myfunc.git --git-branch=feature/my-branch + +c) Context Dir test +func deploy --build=git --git-url=http://gitserver/myfunc.git --git-dir=functions/myfunc +*/ + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "gotest.tools/v3/assert" + common "knative.dev/kn-plugin-func/test/_common" + e2e "knative.dev/kn-plugin-func/test/_e2e" +) + +// TestFromCliDefaultBranch triggers a default branch test by using CLI flags +func TestFromCliDefaultBranch(t *testing.T) { + + var gitProjectName = "test-func-yaml-build-local" + var gitProjectPath = filepath.Join(os.TempDir(), gitProjectName) + var funcName = gitProjectName + var funcPath = gitProjectPath + + gitServer := common.GitTestServerProvider{} + gitServer.Init(t) + remoteRepo := gitServer.CreateRepository(gitProjectName) + defer gitServer.DeleteRepository(gitProjectName) + + knFunc := common.NewKnFuncShellCli(t) + knFunc.Exec("create", "-l", "node", funcPath) + defer os.RemoveAll(gitProjectPath) + + GitInitialCommitAndPush(t, gitProjectPath, remoteRepo.ExternalCloneURL) + + knFunc.Exec("deploy", + "-r", e2e.GetRegistry(), + "-p", funcPath, + "--build", "git", + "--git-url", remoteRepo.ClusterCloneURL) + + defer knFunc.Exec("delete", "-p", funcPath) + + // ## ASSERTIONS + result := knFunc.Exec("invoke", "-p", funcPath) + assert.Assert(t, strings.Contains(result.Stdout, "Hello"), "Func body does not contain 'Hello'") + AssertThatTektonPipelineRunSucceed(t, funcName) + +} + +// TestFromCliFeatureBranch trigger a feature branch test by using CLI flags +func TestFromCliFeatureBranch(t *testing.T) { + + var funcName = "test-func-cli-feature-branch" + var funcPath = filepath.Join(os.TempDir(), funcName) + + gitServer := common.GitTestServerProvider{} + gitServer.Init(t) + remoteRepo := gitServer.CreateRepository(funcName) + defer gitServer.DeleteRepository(funcName) + + knFunc := common.NewKnFuncShellCli(t) + knFunc.Exec("create", "-l", "node", funcPath) + defer os.RemoveAll(funcPath) + + GitInitialCommitAndPush(t, funcPath, remoteRepo.ExternalCloneURL) + + WriteNewSimpleIndexJS(t, funcPath, "hello branch") + + sh := common.NewShellCmd(t, funcPath) + sh.ShouldFailOnError = true + sh.Exec("git checkout -b feature/branch") + sh.Exec("git add index.js") + sh.Exec(`git commit -m "feature branch change"`) + sh.Exec("git push -u origin feature/branch") + + knFunc.Exec("deploy", + "-r", e2e.GetRegistry(), + "-p", funcPath, + "--build", "git", + "--git-url", remoteRepo.ClusterCloneURL, + "--git-branch", "feature/branch") + + defer knFunc.Exec("delete", "-p", funcPath) + + // ## ASSERTIONS + result := knFunc.Exec("invoke", "-p", funcPath) + assert.Assert(t, strings.Contains(result.Stdout, "hello branch"), "Func body does not contain 'hello branch'") + AssertThatTektonPipelineRunSucceed(t, funcName) + +} + +// TestFromCliContextDirFunc triggers a context dir test by using CLI flags +func TestFromCliContextDirFunc(t *testing.T) { + + var gitProjectName = "test-project" + var gitProjectPath = filepath.Join(os.TempDir(), gitProjectName) + var funcName = "test-func-context-dir" + var funcContextDir = filepath.Join("functions", funcName) + var funcPath = filepath.Join(gitProjectPath, funcContextDir) + + gitServer := common.GitTestServerProvider{} + gitServer.Init(t) + remoteRepo := gitServer.CreateRepository(gitProjectName) + defer gitServer.DeleteRepository(gitProjectName) + + knFunc := common.NewKnFuncShellCli(t) + knFunc.Exec("create", "-l", "node", funcPath) + defer os.RemoveAll(gitProjectPath) + + WriteNewSimpleIndexJS(t, funcPath, "hello dir") + + GitInitialCommitAndPush(t, gitProjectPath, remoteRepo.ExternalCloneURL) + + knFunc.Exec("deploy", + "-r", e2e.GetRegistry(), + "-p", funcPath, + "--build", "git", + "--git-url", remoteRepo.ClusterCloneURL, + "--git-dir", funcContextDir) + + defer knFunc.Exec("delete", "-p", funcPath) + + // -- Assertions -- + result := knFunc.Exec("invoke", "-p", funcPath) + assert.Assert(t, strings.Contains(result.Stdout, "hello dir"), "Func body does not contain 'hello dir'") + AssertThatTektonPipelineRunSucceed(t, funcName) + +} diff --git a/test/_oncluster/scenario_revision_test.go b/test/_oncluster/scenario_revision_test.go new file mode 100644 index 00000000..a24934f3 --- /dev/null +++ b/test/_oncluster/scenario_revision_test.go @@ -0,0 +1,121 @@ +//go:build oncluster +// +build oncluster + +package oncluster + +/* +Tests on this file covers "on cluster build" use cases: + +A) I want my function hosted on my public git repository from a FEATURE BRANCH to get built deployed +b) I want my function hosted on my public git repository from a specific GIT TAG to get built and deployed +c) I want my function hosted on my public git repository from a specific COMMIT HASH to get built and deployed + +*/ + +import ( + "os" + "path/filepath" + "strings" + "testing" + + common "knative.dev/kn-plugin-func/test/_common" + e2e "knative.dev/kn-plugin-func/test/_e2e" +) + +func TestFromFeatureBranch(t *testing.T) { + + setupCodeFn := func(sh *common.TestExecCmd, funcProjectPath string, clusterCloneUrl string) { + + WriteNewSimpleIndexJS(t, funcProjectPath, "hello branch") + sh.Exec("git checkout -b feature/branch") + sh.Exec("git add index.js") + sh.Exec(`git commit -m "feature branch change"`) + sh.Exec("git push -u origin feature/branch") + UpdateFuncYamlGit(t, funcProjectPath, Git{URL: clusterCloneUrl, Revision: "feature/branch"}) + + } + assertBodyFn := func(response string) bool { + return strings.Contains(response, "hello branch") + } + GitRevisionCheck(t, "test-func-feature-branch", setupCodeFn, assertBodyFn) +} + +func TestFromRevisionTag(t *testing.T) { + + setupCodeFn := func(sh *common.TestExecCmd, funcProjectPath string, clusterCloneUrl string) { + + WriteNewSimpleIndexJS(t, funcProjectPath, "hello v1") + sh.Exec("git add index.js") + sh.Exec(`git commit -m "version 1"`) + sh.Exec("git push origin main") + sh.Exec("git tag tag-v1") + sh.Exec("git push origin tag-v1") + WriteNewSimpleIndexJS(t, funcProjectPath, "hello v2") + sh.Exec("git add index.js") + sh.Exec(`git commit -m "version 2"`) + sh.Exec("git push origin main") + UpdateFuncYamlGit(t, funcProjectPath, Git{URL: clusterCloneUrl, Revision: "tag-v1"}) + + } + assertBodyFn := func(response string) bool { + return strings.Contains(response, "hello v1") + } + GitRevisionCheck(t, "test-func-tag", setupCodeFn, assertBodyFn) +} + +func TestFromCommitHash(t *testing.T) { + + setupCodeFn := func(sh *common.TestExecCmd, funcProjectPath string, clusterCloneUrl string) { + + WriteNewSimpleIndexJS(t, funcProjectPath, "hello v1") + sh.Exec("git add index.js") + sh.Exec(`git commit -m "version 1"`) + sh.Exec("git push origin main") + gitRevParse := sh.Exec("git rev-parse HEAD") + WriteNewSimpleIndexJS(t, funcProjectPath, "hello v2") + sh.Exec("git add index.js") + sh.Exec(`git commit -m "version 2"`) + sh.Exec("git push origin main") + commitHash := strings.TrimSpace(gitRevParse.Stdout) + UpdateFuncYamlGit(t, funcProjectPath, Git{URL: clusterCloneUrl, Revision: commitHash}) + + t.Logf("Revision Check: commit hash resolved to [%v]", commitHash) + } + assertBodyFn := func(response string) bool { + return strings.Contains(response, "hello v1") + } + GitRevisionCheck(t, "test-func-commit", setupCodeFn, assertBodyFn) +} + +func GitRevisionCheck( + t *testing.T, + funcName string, + setupCodeFn func(shell *common.TestExecCmd, funcProjectPath string, clusterCloneUrl string), + assertBodyFn func(response string) bool) { + + var funcPath = filepath.Join(os.TempDir(), funcName) + + gitServer := common.GitTestServerProvider{} + gitServer.Init(t) + remoteRepo := gitServer.CreateRepository(funcName) + defer gitServer.DeleteRepository(funcName) + + knFunc := common.NewKnFuncShellCli(t) + knFunc.Exec("create", "-l", "node", funcPath) + defer os.RemoveAll(funcPath) + + sh := GitInitialCommitAndPush(t, funcPath, remoteRepo.ExternalCloneURL) + + // Setup specific code + setupCodeFn(sh, funcPath, remoteRepo.ClusterCloneURL) + + knFunc.Exec("deploy", "-r", e2e.GetRegistry(), "-p", funcPath) + defer knFunc.Exec("delete", "-p", funcPath) + + // -- Assertions -- + result := knFunc.Exec("invoke", "-p", funcPath) + if !assertBodyFn(result.Stdout) { + t.Error("Func Body does not contains expected expression") + } + AssertThatTektonPipelineRunSucceed(t, funcName) +} diff --git a/test/_oncluster/scenario_runtime_test.go b/test/_oncluster/scenario_runtime_test.go new file mode 100644 index 00000000..50840484 --- /dev/null +++ b/test/_oncluster/scenario_runtime_test.go @@ -0,0 +1,69 @@ +//go:build oncluster || runtime +// +build oncluster runtime + +package oncluster + +import ( + "os" + "path/filepath" + "strings" + "testing" + + common "knative.dev/kn-plugin-func/test/_common" + e2e "knative.dev/kn-plugin-func/test/_e2e" +) + +// TestRuntime will invoke a language runtime test against (by default) to all runtimes. +// The Environment Variable E2E_RUNTIMES can be used to select the languages/runtimes to be tested +func TestRuntime(t *testing.T) { + + var runtimeList = []string{} + runtimes, present := os.LookupEnv("E2E_RUNTIMES") + + if present { + if runtimes != "" { + runtimeList = strings.Split(runtimes, " ") + } + } else { + runtimeList = []string{"node", "python", "quarkus", "springboot", "typescript"} // "go" and "rust" pending support + } + for _, lang := range runtimeList { + t.Run(lang+"_test", func(t *testing.T) { + runtimeImpl(t, lang) + }) + } + +} + +func runtimeImpl(t *testing.T, lang string) { + + var gitProjectName = "test-func-lang-" + lang + var gitProjectPath = filepath.Join(os.TempDir(), gitProjectName) + var funcName = gitProjectName + var funcPath = gitProjectPath + + gitServer := common.GitTestServerProvider{} + gitServer.Init(t) + remoteRepo := gitServer.CreateRepository(gitProjectName) + defer gitServer.DeleteRepository(gitProjectName) + + knFunc := common.NewKnFuncShellCli(t) + knFunc.Exec("create", "-l", lang, funcPath) + defer os.RemoveAll(gitProjectPath) + + GitInitialCommitAndPush(t, gitProjectPath, remoteRepo.ExternalCloneURL) + + knFunc.Exec("deploy", + "-r", e2e.GetRegistry(), + "-p", funcPath, + "--build", "git", + "--git-url", remoteRepo.ClusterCloneURL) + + defer knFunc.Exec("delete", "-p", funcPath) + + // -- Assertions -- + result := knFunc.Exec("invoke", "-p", funcPath) + t.Log(result) + AssertThatTektonPipelineRunSucceed(t, funcName) + +} diff --git a/test/_oncluster/tekton.go b/test/_oncluster/tekton.go new file mode 100644 index 00000000..9b1ae1d9 --- /dev/null +++ b/test/_oncluster/tekton.go @@ -0,0 +1,96 @@ +package oncluster + +import ( + "context" + "fmt" + "strings" + "testing" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/kn-plugin-func/k8s" + "knative.dev/kn-plugin-func/pipelines/tekton" +) + +// TektonPipelineExists verifies pipeline with a given prefix exists on cluster +func TektonPipelineExists(t *testing.T, pipelinePrefix string) bool { + namespace, _, _ := k8s.GetClientConfig().Namespace() + client, ns, _ := tekton.NewTektonClientAndResolvedNamespace(namespace) + pipelines, err := client.Pipelines(ns).List(context.Background(), v1.ListOptions{}) + if err != nil { + t.Error(err.Error()) + } + for _, pipeline := range pipelines.Items { + if strings.HasPrefix(pipeline.Name, pipelinePrefix) && strings.HasSuffix(pipeline.Name, "-pipeline") { + return true + } + } + return false +} + +// TektonPipelineRunExists verifies pipelinerun with a given prefix exists on cluster +func TektonPipelineRunExists(t *testing.T, pipelineRunPrefix string) bool { + namespace, _, _ := k8s.GetClientConfig().Namespace() + client, ns, _ := tekton.NewTektonClientAndResolvedNamespace(namespace) + pipelineRuns, err := client.PipelineRuns(ns).List(context.Background(), v1.ListOptions{}) + if err != nil { + t.Error(err.Error()) + } + for _, run := range pipelineRuns.Items { + if strings.HasPrefix(run.Name, pipelineRunPrefix) { + return true + } + } + return false +} + +type PipelineRunSummary struct { + PipelineRunName string + PipelineRunStatus string + TasksRunSummary []PipelineTaskRunSummary +} +type PipelineTaskRunSummary struct { + TaskName string + TaskStatus string +} + +func (p *PipelineRunSummary) ToString() string { + r := fmt.Sprintf("run: %-42v, status: %v\n", p.PipelineRunName, p.PipelineRunStatus) + for _, t := range p.TasksRunSummary { + r = r + fmt.Sprintf(" task: %-15v, status: %v\n", t.TaskName, t.TaskStatus) + } + return r +} + +func (p *PipelineRunSummary) IsSucceed() bool { + return p.PipelineRunStatus == "Succeeded" +} + +// TektonPipelTektonPipelineLastRunSummary gather information about a pipeline run such as +// list of tasks executed and status of each task execution. It is meant to be used on assertions +func TektonPipelineLastRunSummary(t *testing.T, pipelinePrefix string) *PipelineRunSummary { + namespace, _, _ := k8s.GetClientConfig().Namespace() + client, ns, _ := tekton.NewTektonClientAndResolvedNamespace(namespace) + pipelineRuns, err := client.PipelineRuns(ns).List(context.Background(), v1.ListOptions{}) + if err != nil { + t.Error(err.Error()) + } + lr := PipelineRunSummary{} + for _, run := range pipelineRuns.Items { + if strings.HasPrefix(run.Name, pipelinePrefix) { + lr.PipelineRunName = run.Name + if len(run.Status.Conditions) > 0 { + lr.PipelineRunStatus = run.Status.Conditions[0].Reason + } + lr.TasksRunSummary = []PipelineTaskRunSummary{} + for _, taskRun := range run.Status.TaskRuns { + trun := PipelineTaskRunSummary{} + trun.TaskName = taskRun.PipelineTaskName + if len(taskRun.Status.Conditions) > 0 { + trun.TaskStatus = taskRun.Status.Conditions[0].Reason + } + lr.TasksRunSummary = append(lr.TasksRunSummary, trun) + } + } + } + return &lr +} diff --git a/test/e2e_oncluster_tests.sh b/test/e2e_oncluster_tests.sh new file mode 100755 index 00000000..cdaf1a69 --- /dev/null +++ b/test/e2e_oncluster_tests.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# 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 +# +# https://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. + +# +# Runs basic lifecycle E2E tests against kn func cli for a given language/runtime. +# By default it will run e2e tests against 'func' binary, but you can change it to use 'kn func' instead +# +# The following environment variable can be set in order to customize e2e execution: +# +# E2E_USE_KN_FUNC When set to "true" indicates e2e to issue func command using kn cli. +# +# E2E_REGISTRY_URL Indicates a specific registry (i.e: "quay.io/user") should be used. Make sure +# to authenticate to the registry (i.e: docker login ...) prior to execute the script +# By default it uses "ttl.sh" registry +# +# E2E_FUNC_BIN_PATH Path to func binary. Derived by this script when not set +# +# E2E_RUNTIMES List of runtimes (space separated) to execute TestRuntime. +# + +set -o errexit +set -o nounset +set -o pipefail + +runtime=${1:-} +use_kn_func=${E2E_USE_KN_FUNC:-} + +curdir=$(pwd) +cd $(dirname $0) +cd ../ + +REGISTRY_PROJ=knfunc$(head -c 128