Repository: kubeflow/kfp-tekton (language: python)

Issue:
Pipelineloop should support to iterate with an string with separator char
/kind feature

**Description:**
Current pipeline loop only supports to iterate from `iterateParam` with string value like:
```
value: '["I am the first one", "I am the second one", "I am the third one"]'
```

I would like to introduce a new field `iterateParamSeparator` to allow user to pass the separator char and allow us to split the iterateParam as a string array for the loop.

This field is optional, it only works with string type param.

eg: 
```
iterateParam= "aaa|bbb|ccc|ddd"
iterateParamSeparator="|"
```




Discussion / hints:
@pugangxa @Tomcli  if you agree this this enhancement, I'm going to create the PR Thanks.
@wzhanw is the `'["I am the first one", "I am the second one", "I am the third one"]'` format still supported? We want to ensure it won't break any backward compatibility. 
I'm okay with the new proposed iterateParam. FYI @Udiknedormin are you aware of this change?
@wzhanw As discussed, there's multiple format for loop parameters, like `from/to`, list and string with separators. It's OK to have this feature for the format of `string with separators`, thanks.
> @wzhanw is the `'["I am the first one", "I am the second one", "I am the third one"]'` format still supported? We want to ensure it won't break any backward compatibility.

@Tomcli  sure,  we will not break the old feature.

Fix patch:
diff --git a/tekton-catalog/pipeline-loops/go.mod b/tekton-catalog/pipeline-loops/go.mod
index 060840aa8b..8c51caee1a 100644
--- a/tekton-catalog/pipeline-loops/go.mod
+++ b/tekton-catalog/pipeline-loops/go.mod
@@ -3,16 +3,11 @@ module github.com/kubeflow/kfp-tekton/tekton-catalog/pipeline-loops
 go 1.13
 
 require (
-	contrib.go.opencensus.io/exporter/stackdriver v0.13.5 // indirect
-	github.com/go-kit/kit v0.10.0 // indirect
-	github.com/go-openapi/validate v0.19.5 // indirect
 	github.com/google/go-cmp v0.5.6
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/tektoncd/pipeline v0.30.0
 	go.uber.org/zap v1.19.1
 	gomodules.xyz/jsonpatch/v2 v2.2.0
-	gopkg.in/evanphx/json-patch.v4 v4.9.0 // indirect
-	honnef.co/go/tools v0.0.1-2020.1.5 // indirect
 	k8s.io/api v0.21.4
 	k8s.io/apimachinery v0.21.4
 	k8s.io/client-go v0.21.4
diff --git a/tekton-catalog/pipeline-loops/pkg/apis/pipelineloop/v1alpha1/pipelineloop_types.go b/tekton-catalog/pipeline-loops/pkg/apis/pipelineloop/v1alpha1/pipelineloop_types.go
index c8a3bd57f1..db47d29dbe 100644
--- a/tekton-catalog/pipeline-loops/pkg/apis/pipelineloop/v1alpha1/pipelineloop_types.go
+++ b/tekton-catalog/pipeline-loops/pkg/apis/pipelineloop/v1alpha1/pipelineloop_types.go
@@ -51,6 +51,10 @@ type PipelineLoopSpec struct {
 	// IterateParam is the name of the task parameter that is iterated upon.
 	IterateParam string `json:"iterateParam"`
 
+	// The separator for IterateParam if the IterateParam is a strings with separator char, this field is optional.
+	// +optional
+	IterateParamSeparator string `json:"iterateParamStringSeparator,omitempty"`
+
 	IterateNumeric string `json:"iterateNumeric"`
 
 	// Time after which the TaskRun times out.
diff --git a/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun.go b/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun.go
index abd7bf1150..0d8a9925cf 100644
--- a/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun.go
+++ b/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun.go
@@ -20,13 +20,15 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"k8s.io/apimachinery/pkg/runtime"
-	duckv1 "knative.dev/pkg/apis/duck/v1"
 	"log"
 	"reflect"
 	"strconv"
+	"strings"
 	"time"
 
+	"k8s.io/apimachinery/pkg/runtime"
+	duckv1 "knative.dev/pkg/apis/duck/v1"
+
 	"github.com/hashicorp/go-multierror"
 
 	"github.com/kubeflow/kfp-tekton/tekton-catalog/pipeline-loops/pkg/apis/pipelineloop"
@@ -586,6 +588,8 @@ func computeIterations(run *v1alpha1.Run, tls *pipelineloopv1alpha1.PipelineLoop
 	step := -1
 	to := -1
 	iterationElements := []interface{}{}
+	iterationParamStr := ""
+	iterationParamStrSeparator := ""
 	for _, p := range run.Spec.Params {
 		if tls.IterateNumeric != "" {
 			if p.Name == "from" {
@@ -597,53 +601,64 @@ func computeIterations(run *v1alpha1.Run, tls *pipelineloopv1alpha1.PipelineLoop
 			if p.Name == "to" {
 				to, _ = strconv.Atoi(p.Value.StringVal)
 			}
-		} else if p.Name == tls.IterateParam {
-			if p.Value.Type == v1beta1.ParamTypeString {
-				// Transfer p.Value to Array.
-				var strings []string
-				var ints []int
-				var dictsString []map[string]string
-				var dictsInt []map[string]int
-				errString := json.Unmarshal([]byte(p.Value.StringVal), &strings)
-				errInt := json.Unmarshal([]byte(p.Value.StringVal), &ints)
-				errDictString := json.Unmarshal([]byte(p.Value.StringVal), &dictsString)
-				errDictInt := json.Unmarshal([]byte(p.Value.StringVal), &dictsInt)
-				if errString != nil && errInt != nil && errDictString != nil && errDictInt != nil {
-					return 0, iterationElements, fmt.Errorf("The value of the iterate parameter %q can not transfer to array", tls.IterateParam)
+		} else {
+			if p.Name == tls.IterateParam {
+				if p.Value.Type == v1beta1.ParamTypeString {
+					iterationParamStr = strings.Trim(p.Value.StringVal, " ")
 				}
-
-				if errString == nil {
-					numberOfIterations = len(strings)
-					for _, v := range strings {
-						iterationElements = append(iterationElements, v)
-					}
-					break
-				} else if errInt == nil {
-					numberOfIterations = len(ints)
-					for _, v := range ints {
-						iterationElements = append(iterationElements, v)
-					}
-					break
-				} else if errDictString == nil {
-					numberOfIterations = len(dictsString)
-					for _, v := range dictsString {
-						iterationElements = append(iterationElements, v)
-					}
-					break
-				} else if errDictInt == nil {
-					numberOfIterations = len(dictsInt)
-					for _, v := range dictsInt {
+				if p.Value.Type == v1beta1.ParamTypeArray {
+					numberOfIterations = len(p.Value.ArrayVal)
+					for _, v := range p.Value.ArrayVal {
 						iterationElements = append(iterationElements, v)
 					}
 					break
 				}
 			}
-			if p.Value.Type == v1beta1.ParamTypeArray {
-				numberOfIterations = len(p.Value.ArrayVal)
-				for _, v := range p.Value.ArrayVal {
+			if p.Name == tls.IterateParamSeparator {
+				iterationParamStrSeparator = p.Value.StringVal
+			}
+		}
+	}
+	if iterationParamStr != "" {
+		// Transfer p.Value to Array.
+		if iterationParamStrSeparator != "" {
+			stringArr := strings.Split(iterationParamStr, iterationParamStrSeparator)
+			numberOfIterations = len(stringArr)
+			for _, v := range stringArr {
+				iterationElements = append(iterationElements, v)
+			}
+		} else {
+			var strings []string
+			var ints []int
+			var dictsString []map[string]string
+			var dictsInt []map[string]int
+			errString := json.Unmarshal([]byte(iterationParamStr), &strings)
+			errInt := json.Unmarshal([]byte(iterationParamStr), &ints)
+			errDictString := json.Unmarshal([]byte(iterationParamStr), &dictsString)
+			errDictInt := json.Unmarshal([]byte(iterationParamStr), &dictsInt)
+			if errString != nil && errInt != nil && errDictString != nil && errDictInt != nil {
+				return 0, iterationElements, fmt.Errorf("The value of the iterate parameter %q can not transfer to array", tls.IterateParam)
+			}
+			if errString == nil {
+				numberOfIterations = len(strings)
+				for _, v := range strings {
+					iterationElements = append(iterationElements, v)
+				}
+			} else if errInt == nil {
+				numberOfIterations = len(ints)
+				for _, v := range ints {
+					iterationElements = append(iterationElements, v)
+				}
+			} else if errDictString == nil {
+				numberOfIterations = len(dictsString)
+				for _, v := range dictsString {
+					iterationElements = append(iterationElements, v)
+				}
+			} else if errDictInt == nil {
+				numberOfIterations = len(dictsInt)
+				for _, v := range dictsInt {
 					iterationElements = append(iterationElements, v)
 				}
-				break
 			}
 		}
 	}
@@ -669,6 +684,8 @@ func getParameters(run *v1alpha1.Run, tls *pipelineloopv1alpha1.PipelineLoopSpec
 	var out []v1beta1.Param
 	if tls.IterateParam != "" {
 		// IterateParam defined
+		var iterationParam, iterationParamStrSeparator *v1beta1.Param
+		var item, separator v1beta1.Param
 		for i, p := range run.Spec.Params {
 			if p.Name == tls.IterateParam {
 				if p.Value.Type == v1beta1.ParamTypeArray {
@@ -678,42 +695,59 @@ func getParameters(run *v1alpha1.Run, tls *pipelineloopv1alpha1.PipelineLoopSpec
 					})
 				}
 				if p.Value.Type == v1beta1.ParamTypeString {
-					var strings []string
-					var ints []int
-					var dictsString []map[string]string
-					var dictsInt []map[string]int
-					errString := json.Unmarshal([]byte(p.Value.StringVal), &strings)
-					errInt := json.Unmarshal([]byte(p.Value.StringVal), &ints)
-					errDictString := json.Unmarshal([]byte(p.Value.StringVal), &dictsString)
-					errDictInt := json.Unmarshal([]byte(p.Value.StringVal), &dictsInt)
-					if errString == nil {
+					item = p
+					iterationParam = &item
+				}
+			} else if p.Name == tls.IterateParamSeparator {
+				separator = p
+				iterationParamStrSeparator = &separator
+			} else {
+				out = append(out, run.Spec.Params[i])
+			}
+		}
+		if iterationParam != nil {
+			if iterationParamStrSeparator != nil {
+				iterationParamStr := strings.Trim(iterationParam.Value.StringVal, " ")
+				stringArr := strings.Split(iterationParamStr, iterationParamStrSeparator.Value.StringVal)
+				out = append(out, v1beta1.Param{
+					Name:  iterationParam.Name,
+					Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: stringArr[iteration-1]},
+				})
+
+			} else {
+				var strings []string
+				var ints []int
+				var dictsString []map[string]string
+				var dictsInt []map[string]int
+				errString := json.Unmarshal([]byte(iterationParam.Value.StringVal), &strings)
+				errInt := json.Unmarshal([]byte(iterationParam.Value.StringVal), &ints)
+				errDictString := json.Unmarshal([]byte(iterationParam.Value.StringVal), &dictsString)
+				errDictInt := json.Unmarshal([]byte(iterationParam.Value.StringVal), &dictsInt)
+				if errString == nil {
+					out = append(out, v1beta1.Param{
+						Name:  iterationParam.Name,
+						Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: strings[iteration-1]},
+					})
+				} else if errInt == nil {
+					out = append(out, v1beta1.Param{
+						Name:  iterationParam.Name,
+						Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: strconv.Itoa(ints[iteration-1])},
+					})
+				} else if errDictString == nil {
+					for dictParam := range dictsString[iteration-1] {
 						out = append(out, v1beta1.Param{
-							Name:  p.Name,
-							Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: strings[iteration-1]},
+							Name:  iterationParam.Name + "-subvar-" + dictParam,
+							Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: dictsString[iteration-1][dictParam]},
 						})
-					} else if errInt == nil {
+					}
+				} else if errDictInt == nil {
+					for dictParam := range dictsInt[iteration-1] {
 						out = append(out, v1beta1.Param{
-							Name:  p.Name,
-							Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: strconv.Itoa(ints[iteration-1])},
+							Name:  iterationParam.Name + "-subvar-" + dictParam,
+							Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: strconv.Itoa(dictsInt[iteration-1][dictParam])},
 						})
-					} else if errDictString == nil {
-						for dictParam := range dictsString[iteration-1] {
-							out = append(out, v1beta1.Param{
-								Name:  p.Name + "-subvar-" + dictParam,
-								Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: dictsString[iteration-1][dictParam]},
-							})
-						}
-					} else if errDictInt == nil {
-						for dictParam := range dictsInt[iteration-1] {
-							out = append(out, v1beta1.Param{
-								Name:  p.Name + "-subvar-" + dictParam,
-								Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: strconv.Itoa(dictsInt[iteration-1][dictParam])},
-							})
-						}
 					}
 				}
-			} else {
-				out = append(out, run.Spec.Params[i])
 			}
 		}
 	} else {


Test patch:
diff --git a/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun_test.go b/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun_test.go
index 6cae8d08a2..b4701daadf 100644
--- a/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun_test.go
+++ b/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun_test.go
@@ -274,8 +274,9 @@ var aPipeline = &v1beta1.Pipeline{
 var aPipelineLoop = &pipelineloopv1alpha1.PipelineLoop{
 	ObjectMeta: metav1.ObjectMeta{Name: "a-pipelineloop", Namespace: "foo"},
 	Spec: pipelineloopv1alpha1.PipelineLoopSpec{
-		PipelineRef:  &v1beta1.PipelineRef{Name: "a-pipeline"},
-		IterateParam: "current-item",
+		PipelineRef:           &v1beta1.PipelineRef{Name: "a-pipeline"},
+		IterateParam:          "current-item",
+		IterateParamSeparator: "separator",
 	},
 }
 
@@ -618,6 +619,74 @@ var runPipelineLoopWithInStringParams = &v1alpha1.Run{
 	},
 }
 
+var runPipelineLoopWithInStringSeparatorParams = &v1alpha1.Run{
+	ObjectMeta: metav1.ObjectMeta{
+		Name:      "run-pipelineloop",
+		Namespace: "foo",
+		Labels: map[string]string{
+			"myTestLabel":                    "myTestLabelValue",
+			"custom.tekton.dev/pipelineLoop": "a-pipelineloop",
+			"tekton.dev/pipeline":            "pr-loop-example",
+			"tekton.dev/pipelineRun":         "pr-loop-example",
+			"tekton.dev/pipelineTask":        "loop-task",
+		},
+		Annotations: map[string]string{
+			"myTestAnnotation": "myTestAnnotationValue",
+		},
+	},
+	Spec: v1alpha1.RunSpec{
+		Params: []v1beta1.Param{{
+			Name:  "current-item",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "item1|item2"},
+		}, {
+			Name:  "separator",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "|"},
+		}, {
+			Name:  "additional-parameter",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "stuff"},
+		}},
+		Ref: &v1alpha1.TaskRef{
+			APIVersion: pipelineloopv1alpha1.SchemeGroupVersion.String(),
+			Kind:       pipelineloop.PipelineLoopControllerName,
+			Name:       "a-pipelineloop",
+		},
+	},
+}
+
+var runPipelineLoopWithSpaceSeparatorParams = &v1alpha1.Run{
+	ObjectMeta: metav1.ObjectMeta{
+		Name:      "run-pipelineloop",
+		Namespace: "foo",
+		Labels: map[string]string{
+			"myTestLabel":                    "myTestLabelValue",
+			"custom.tekton.dev/pipelineLoop": "a-pipelineloop",
+			"tekton.dev/pipeline":            "pr-loop-example",
+			"tekton.dev/pipelineRun":         "pr-loop-example",
+			"tekton.dev/pipelineTask":        "loop-task",
+		},
+		Annotations: map[string]string{
+			"myTestAnnotation": "myTestAnnotationValue",
+		},
+	},
+	Spec: v1alpha1.RunSpec{
+		Params: []v1beta1.Param{{
+			Name:  "current-item",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "item1 item2"},
+		}, {
+			Name:  "separator",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: " "},
+		}, {
+			Name:  "additional-parameter",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "stuff"},
+		}},
+		Ref: &v1alpha1.TaskRef{
+			APIVersion: pipelineloopv1alpha1.SchemeGroupVersion.String(),
+			Kind:       pipelineloop.PipelineLoopControllerName,
+			Name:       "a-pipelineloop",
+		},
+	},
+}
+
 var runPipelineLoopWithIterateNumeric = &v1alpha1.Run{
 	ObjectMeta: metav1.ObjectMeta{
 		Name:      "run-pipelineloop",
@@ -1286,94 +1355,117 @@ func TestReconcilePipelineLoopRun(t *testing.T) {
 		expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunIteration1},
 		expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
 	}, {
-		name:                 "Reconcile a new run with iterateNumeric defined",
-		pipeline:             nPipeline,
-		pipelineloop:         nPipelineLoop,
-		run:                  runPipelineLoopWithIterateNumeric,
-		pipelineruns:         []*v1beta1.PipelineRun{},
-		expectedStatus:       corev1.ConditionUnknown,
-		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
-		expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunIterateNumeric1},
-		expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
-	}, {
-		name:                 "Reconcile a new run with a pipelineloop that contains an inline task",
-		pipelineloop:         aPipelineLoopWithInlineTask,
-		run:                  runPipelineLoopWithInlineTask,
-		pipelineruns:         []*v1beta1.PipelineRun{},
-		expectedStatus:       corev1.ConditionUnknown,
-		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
-		expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunWithInlineTaskIteration1},
-		expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
-	}, {
-		name:                 "Reconcile a new run with a pipelineloop that contains a workspace",
-		pipeline:             aPipeline,
-		pipelineloop:         wsPipelineLoop,
-		run:                  runWsPipelineLoop,
-		pipelineruns:         []*v1beta1.PipelineRun{},
-		expectedStatus:       corev1.ConditionUnknown,
-		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
-		expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunWithWorkSpace},
-		expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
-	}, {
-		name:                 "Reconcile a run after all PipelineRuns have succeeded",
-		pipeline:             aPipeline,
-		pipelineloop:         aPipelineLoop,
-		run:                  loopRunning(runPipelineLoop),
-		pipelineruns:         []*v1beta1.PipelineRun{successful(expectedPipelineRunIteration1), successful(expectedPipelineRunIteration2)},
-		expectedStatus:       corev1.ConditionTrue,
-		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonSucceeded,
-		expectedPipelineruns: []*v1beta1.PipelineRun{successful(expectedPipelineRunIteration1), successful(expectedPipelineRunIteration2)},
-		expectedEvents:       []string{"Normal Succeeded All PipelineRuns completed successfully"},
-	}, {
-		name:                 "Reconcile a run after the first PipelineRun has failed",
+		name:                 "Reconcile a new run with a pipelineloop and a string params with separator",
 		pipeline:             aPipeline,
 		pipelineloop:         aPipelineLoop,
-		run:                  loopRunning(runPipelineLoop),
-		pipelineruns:         []*v1beta1.PipelineRun{failed(expectedPipelineRunIteration1)},
-		expectedStatus:       corev1.ConditionFalse,
-		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonFailed,
-		expectedPipelineruns: []*v1beta1.PipelineRun{failed(expectedPipelineRunIteration1)},
-		expectedEvents:       []string{"Warning Failed PipelineRun " + expectedPipelineRunIteration1.Name + " has failed"},
-	}, {
-		name:                 "Reconcile a run with retries after the first PipelineRun has failed",
-		pipeline:             aPipeline,
-		pipelineloop:         aPipelineLoop,
-		run:                  loopRunning(setRetries(runPipelineLoop, 1)),
-		pipelineruns:         []*v1beta1.PipelineRun{failed(expectedPipelineRunFailed)},
-		expectedStatus:       corev1.ConditionUnknown,
-		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
-		expectedPipelineruns: []*v1beta1.PipelineRun{setDeleted(failed(expectedPipelineRunFailed)), expectedPipelineRunRetry},
-	}, {
-		name:                 "Reconcile a new run with a pipelineloop with Parallelism specified",
-		pipeline:             paraPipeline,
-		pipelineloop:         paraPipelineLoop,
-		run:                  paraRunPipelineLoop,
+		run:                  runPipelineLoopWithInStringSeparatorParams,
 		pipelineruns:         []*v1beta1.PipelineRun{},
 		expectedStatus:       corev1.ConditionUnknown,
 		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
-		expectedPipelineruns: []*v1beta1.PipelineRun{expectedParaPipelineRun, expectedParaPipelineRun1},
-		expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
-	}, {
-		name:                 "Reconcile a new run with a nested pipelineloop",
-		pipeline:             nestedPipeline,
-		pipelineloop:         nestedPipelineLoop,
-		run:                  runNestedPipelineLoop,
-		pipelineruns:         []*v1beta1.PipelineRun{},
-		expectedStatus:       corev1.ConditionUnknown,
-		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
-		expectedPipelineruns: []*v1beta1.PipelineRun{expectedNestedPipelineRun},
+		expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunIteration1},
 		expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
-	}, {
-		name:                 "Reconcile a run with condition pipelinerun, and the first PipelineRun condition check failed",
-		pipeline:             aPipeline,
-		pipelineloop:         aPipelineLoop,
-		run:                  loopRunning(conditionRunPipelineLoop),
-		pipelineruns:         []*v1beta1.PipelineRun{successfulWithSkipedTasks(expectedConditionPipelineRunIteration1)},
-		expectedStatus:       corev1.ConditionTrue,
-		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonSucceeded,
-		expectedPipelineruns: []*v1beta1.PipelineRun{successfulWithSkipedTasks(expectedConditionPipelineRunIteration1)},
-		expectedEvents:       []string{"Normal Succeeded PipelineRuns completed successfully with the conditions are met"},
-	}}
+	},
+		{
+			name:                 "Reconcile a new run with a pipelineloop and a string params with whitespace separator",
+			pipeline:             aPipeline,
+			pipelineloop:         aPipelineLoop,
+			run:                  runPipelineLoopWithSpaceSeparatorParams,
+			pipelineruns:         []*v1beta1.PipelineRun{},
+			expectedStatus:       corev1.ConditionUnknown,
+			expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+			expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunIteration1},
+			expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
+		}, {
+			name:                 "Reconcile a new run with iterateNumeric defined",
+			pipeline:             nPipeline,
+			pipelineloop:         nPipelineLoop,
+			run:                  runPipelineLoopWithIterateNumeric,
+			pipelineruns:         []*v1beta1.PipelineRun{},
+			expectedStatus:       corev1.ConditionUnknown,
+			expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+			expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunIterateNumeric1},
+			expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
+		}, {
+			name:                 "Reconcile a new run with a pipelineloop that contains an inline task",
+			pipelineloop:         aPipelineLoopWithInlineTask,
+			run:                  runPipelineLoopWithInlineTask,
+			pipelineruns:         []*v1beta1.PipelineRun{},
+			expectedStatus:       corev1.ConditionUnknown,
+			expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+			expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunWithInlineTaskIteration1},
+			expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
+		}, {
+			name:                 "Reconcile a new run with a pipelineloop that contains a workspace",
+			pipeline:             aPipeline,
+			pipelineloop:         wsPipelineLoop,
+			run:                  runWsPipelineLoop,
+			pipelineruns:         []*v1beta1.PipelineRun{},
+			expectedStatus:       corev1.ConditionUnknown,
+			expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+			expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunWithWorkSpace},
+			expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
+		}, {
+			name:                 "Reconcile a run after all PipelineRuns have succeeded",
+			pipeline:             aPipeline,
+			pipelineloop:         aPipelineLoop,
+			run:                  loopRunning(runPipelineLoop),
+			pipelineruns:         []*v1beta1.PipelineRun{successful(expectedPipelineRunIteration1), successful(expectedPipelineRunIteration2)},
+			expectedStatus:       corev1.ConditionTrue,
+			expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonSucceeded,
+			expectedPipelineruns: []*v1beta1.PipelineRun{successful(expectedPipelineRunIteration1), successful(expectedPipelineRunIteration2)},
+			expectedEvents:       []string{"Normal Succeeded All PipelineRuns completed successfully"},
+		}, {
+			name:                 "Reconcile a run after the first PipelineRun has failed",
+			pipeline:             aPipeline,
+			pipelineloop:         aPipelineLoop,
+			run:                  loopRunning(runPipelineLoop),
+			pipelineruns:         []*v1beta1.PipelineRun{failed(expectedPipelineRunIteration1)},
+			expectedStatus:       corev1.ConditionFalse,
+			expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonFailed,
+			expectedPipelineruns: []*v1beta1.PipelineRun{failed(expectedPipelineRunIteration1)},
+			expectedEvents:       []string{"Warning Failed PipelineRun " + expectedPipelineRunIteration1.Name + " has failed"},
+		}, {
+			name:                 "Reconcile a run with retries after the first PipelineRun has failed",
+			pipeline:             aPipeline,
+			pipelineloop:         aPipelineLoop,
+			run:                  loopRunning(setRetries(runPipelineLoop, 1)),
+			pipelineruns:         []*v1beta1.PipelineRun{failed(expectedPipelineRunFailed)},
+			expectedStatus:       corev1.ConditionUnknown,
+			expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+			expectedPipelineruns: []*v1beta1.PipelineRun{setDeleted(failed(expectedPipelineRunFailed)), expectedPipelineRunRetry},
+		}, {
+			name:                 "Reconcile a new run with a pipelineloop with Parallelism specified",
+			pipeline:             paraPipeline,
+			pipelineloop:         paraPipelineLoop,
+			run:                  paraRunPipelineLoop,
+			pipelineruns:         []*v1beta1.PipelineRun{},
+			expectedStatus:       corev1.ConditionUnknown,
+			expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+			expectedPipelineruns: []*v1beta1.PipelineRun{expectedParaPipelineRun, expectedParaPipelineRun1},
+			expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
+		}, {
+			name:                 "Reconcile a new run with a nested pipelineloop",
+			pipeline:             nestedPipeline,
+			pipelineloop:         nestedPipelineLoop,
+			run:                  runNestedPipelineLoop,
+			pipelineruns:         []*v1beta1.PipelineRun{},
+			expectedStatus:       corev1.ConditionUnknown,
+			expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+			expectedPipelineruns: []*v1beta1.PipelineRun{expectedNestedPipelineRun},
+			expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
+		}, {
+			name:                 "Reconcile a run with condition pipelinerun, and the first PipelineRun condition check failed",
+			pipeline:             aPipeline,
+			pipelineloop:         aPipelineLoop,
+			run:                  loopRunning(conditionRunPipelineLoop),
+			pipelineruns:         []*v1beta1.PipelineRun{successfulWithSkipedTasks(expectedConditionPipelineRunIteration1)},
+			expectedStatus:       corev1.ConditionTrue,
+			expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonSucceeded,
+			expectedPipelineruns: []*v1beta1.PipelineRun{successfulWithSkipedTasks(expectedConditionPipelineRunIteration1)},
+			expectedEvents:       []string{"Normal Succeeded PipelineRuns completed successfully with the conditions are met"},
+		}}
+
+	//testcases = testcases[1:3]
 
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {