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

Issue:
Loop.range fails with Cannot determine number of iterations: The from or to parameters was not found in runs
/kind bug

**What steps did you take and what happened:**
Using the following DSL

``` py
from kfp import dsl, components
from kfp_tekton.tekton import Loop

op1_yaml = '''\
name: 'my-in-coop1'
inputs:
- {name: item, type: Integer}
- {name: param, type: Integer}
implementation:
    container:
        image: library/bash:4.4.23
        command: ['sh', '-c']
        args:
        - |
          set -e
          echo op1 "$0" "$1"
        - {inputValue: item}
        - {inputValue: param}
'''


@dsl.pipeline(name='pipeline')
def pipeline():
    with Loop.range(start=-1, step=1, end=1) as item:
        op1_template = components.load_component_from_text(op1_yaml)
        op1 = op1_template(item, 10)

if __name__ == '__main__':
  from kfp_tekton.compiler import TektonCompiler as Compiler
  from kfp_tekton.compiler.pipeline_utils import TektonPipelineConf
  tekton_pipeline_conf = TektonPipelineConf()
  tekton_pipeline_conf.set_tekton_inline_spec(True)
  # tekton_pipeline_conf.set_resource_in_separate_yaml(True)
  Compiler().compile(pipeline, __file__.replace('.py', '.yaml'), tekton_pipeline_conf=tekton_pipeline_conf)

```

pipeline loop fails in runtime with the error message 

> "type":"Succeeded",
> "status":"False",
> "lastTransitionTime":"2022-03-31T22:22:55Z",
> "reason":"PipelineLoopValidationFailed",
> "message":"Cannot determine number of iterations: The from or to parameters was not found in runs"
> 

This also happens for the following cases
- (start=-1, step=1, end=0)
- (start=-1, step=0, end=0)
- (start=1, step=1, end=-1)
- (start=0, step=1, end=-1)
- (start=-1, step=1, end=-1)
- (start=1, step=0, end=-1)
- (start=0, step=0, end=-1)
- (start=1, step=-1, end=-1)
- (start=0, step=-1, end=-1)

**What did you expect to happen:**

I expect the loop to iterate successfully.

**Additional information:**
[Miscellaneous information that will assist in solving the issue.]


**Environment:**

* Python Version (use `python --version`):
* SDK Version: 
* Tekton Version (use `tkn version`):
* Kubernetes Version (use `kubectl version`):
* OS (e.g. from `/etc/os-release`):



Discussion / hints:
Currently we do not support start from/to as -ve integers. 

Is this a requirement? 
@ScrapCodes It is, yes.
/assign @ScrapCodes
Thanks Prashant

Fix patch:
diff --git a/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun.go b/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun.go
index a25b591e44..8d46d43f46 100644
--- a/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun.go
+++ b/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun.go
@@ -778,9 +778,9 @@ func (c *Reconciler) updatePipelineRunStatus(ctx context.Context, iterationEleme
 func computeIterations(run *v1alpha1.Run, tls *pipelineloopv1alpha1.PipelineLoopSpec) (int, []interface{}, error) {
 	// Find the iterate parameter.
 	numberOfIterations := -1
-	from := -1
-	step := -1
-	to := -1
+	from := 0
+	step := 1
+	to := 0
 	iterationElements := []interface{}{}
 	iterationParamStr := ""
 	iterationParamStrSeparator := ""
@@ -854,20 +854,25 @@ func computeIterations(run *v1alpha1.Run, tls *pipelineloopv1alpha1.PipelineLoop
 			}
 		}
 	}
-	if from != -1 || to != -1 {
-		if from == -1 || to == -1 {
-			return 0, iterationElements, fmt.Errorf("The from or to parameters was not found in runs")
+	if from != to {
+		if step == 0 {
+			return 0, iterationElements, fmt.Errorf("invalid values step: %d found in runs", step)
 		}
-		if step <= 0 {
-			step = 1
+		if (from > to && step > 0) || (from < to && step < 0) {
+			return 0, iterationElements, fmt.Errorf("invalid values for from:%d, to:%d & step: %d found in runs", from, to, step)
 		}
-		// numberOfIterations is the number of (to - from) / step + 1
-		numberOfIterations = (to-from)/step + 1
-		for i := 0; i < numberOfIterations; i++ {
-			iterationElements = append(iterationElements, step*i+from)
+		numberOfIterations = 0
+		if step < 0 && from > to {
+			for i := from; i >= to; i = i + step {
+				numberOfIterations = numberOfIterations + 1
+				iterationElements = append(iterationElements, i)
+			}
+		} else {
+			for i := from; i <= to; i = i + step {
+				numberOfIterations = numberOfIterations + 1
+				iterationElements = append(iterationElements, i)
+			}
 		}
-	} else if numberOfIterations == -1 {
-		return 0, iterationElements, fmt.Errorf("The iterate parameter %q was not found", tls.IterateParam)
 	}
 	return numberOfIterations, iterationElements, nil
 }


Test patch:
diff --git a/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelineloop_range_test.go b/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelineloop_range_test.go
new file mode 100644
index 0000000000..06f0305970
--- /dev/null
+++ b/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelineloop_range_test.go
@@ -0,0 +1,337 @@
+/*
+Copyright 2022 The Kubeflow 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 pipelinelooprun
+
+import (
+	"context"
+	"sort"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	pipelineloopv1alpha1 "github.com/kubeflow/kfp-tekton/tekton-catalog/pipeline-loops/pkg/apis/pipelineloop/v1alpha1"
+	"github.com/kubeflow/kfp-tekton/tekton-catalog/pipeline-loops/test"
+	_ "github.com/mattn/go-sqlite3"
+	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
+	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
+	"github.com/tektoncd/pipeline/test/diff"
+	"github.com/tektoncd/pipeline/test/names"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	_ "knative.dev/pkg/system/testing"
+)
+
+var expectedPipelineRunWithRange1 = &v1beta1.PipelineRun{
+	ObjectMeta: metav1.ObjectMeta{
+		Name:      "run-pipelineloop-00001-9l9zj",
+		Namespace: "foo",
+		OwnerReferences: []metav1.OwnerReference{{
+			APIVersion:         "tekton.dev/v1alpha1",
+			Kind:               "Run",
+			Name:               "run-pipelineloop",
+			Controller:         &trueB,
+			BlockOwnerDeletion: &trueB,
+		}},
+		Labels: map[string]string{
+			"custom.tekton.dev/originalPipelineRun":   "pr-loop-example",
+			"custom.tekton.dev/parentPipelineRun":     "pr-loop-example",
+			"custom.tekton.dev/pipelineLoop":          "n-pipelineloop",
+			"tekton.dev/run":                          "run-pipelineloop",
+			"custom.tekton.dev/pipelineLoopIteration": "1",
+			"myTestLabel":                             "myTestLabelValue",
+		},
+		Annotations: map[string]string{
+			"myTestAnnotation": "myTestAnnotationValue",
+			"custom.tekton.dev/pipelineLoopCurrentIterationItem": "-1",
+		},
+	},
+	Spec: v1beta1.PipelineRunSpec{
+		PipelineRef: &v1beta1.PipelineRef{Name: "n-pipeline"},
+		Params: []v1beta1.Param{{
+			Name:  "additional-parameter",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "stuff"},
+		}, {
+			Name:  "iteration",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "-1"},
+		}},
+	},
+}
+
+var expectedPipelineRunWithRange2 = &v1beta1.PipelineRun{
+	ObjectMeta: metav1.ObjectMeta{
+		Name:      "run-pipelineloop-00001-9l9zj",
+		Namespace: "foo",
+		OwnerReferences: []metav1.OwnerReference{{
+			APIVersion:         "tekton.dev/v1alpha1",
+			Kind:               "Run",
+			Name:               "run-pipelineloop",
+			Controller:         &trueB,
+			BlockOwnerDeletion: &trueB,
+		}},
+		Labels: map[string]string{
+			"custom.tekton.dev/originalPipelineRun":   "pr-loop-example",
+			"custom.tekton.dev/parentPipelineRun":     "pr-loop-example",
+			"custom.tekton.dev/pipelineLoop":          "n-pipelineloop",
+			"tekton.dev/run":                          "run-pipelineloop",
+			"custom.tekton.dev/pipelineLoopIteration": "1",
+			"myTestLabel":                             "myTestLabelValue",
+		},
+		Annotations: map[string]string{
+			"myTestAnnotation": "myTestAnnotationValue",
+			"custom.tekton.dev/pipelineLoopCurrentIterationItem": "1",
+		},
+	},
+	Spec: v1beta1.PipelineRunSpec{
+		PipelineRef: &v1beta1.PipelineRef{Name: "n-pipeline"},
+		Params: []v1beta1.Param{{
+			Name:  "additional-parameter",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "stuff"},
+		}, {
+			Name:  "iteration",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "1"},
+		}},
+	},
+}
+var expectedPipelineRunWithRange3 = &v1beta1.PipelineRun{
+	ObjectMeta: metav1.ObjectMeta{
+		Name:      "run-pipelineloop-00001-9l9zj",
+		Namespace: "foo",
+		OwnerReferences: []metav1.OwnerReference{{
+			APIVersion:         "tekton.dev/v1alpha1",
+			Kind:               "Run",
+			Name:               "run-pipelineloop",
+			Controller:         &trueB,
+			BlockOwnerDeletion: &trueB,
+		}},
+		Labels: map[string]string{
+			"custom.tekton.dev/originalPipelineRun":   "pr-loop-example",
+			"custom.tekton.dev/parentPipelineRun":     "pr-loop-example",
+			"custom.tekton.dev/pipelineLoop":          "n-pipelineloop",
+			"tekton.dev/run":                          "run-pipelineloop",
+			"custom.tekton.dev/pipelineLoopIteration": "1",
+			"myTestLabel":                             "myTestLabelValue",
+		},
+		Annotations: map[string]string{
+			"myTestAnnotation": "myTestAnnotationValue",
+			"custom.tekton.dev/pipelineLoopCurrentIterationItem": "0",
+		},
+	},
+	Spec: v1beta1.PipelineRunSpec{
+		PipelineRef: &v1beta1.PipelineRef{Name: "n-pipeline"},
+		Params: []v1beta1.Param{{
+			Name:  "additional-parameter",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "stuff"},
+		}, {
+			Name:  "iteration",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "0"},
+		}},
+	},
+}
+
+func TestReconcilePipelineLoopRunRange(t *testing.T) {
+
+	testcases := []struct {
+		name                 string
+		from                 string
+		to                   string
+		step                 string
+		expectedStatus       corev1.ConditionStatus
+		expectedReason       pipelineloopv1alpha1.PipelineLoopRunReason
+		expectedPipelineruns []*v1beta1.PipelineRun
+		expectedEvents       []string
+	}{{
+		name:                 "Case from = to",
+		from:                 "1",
+		to:                   "1",
+		step:                 "1",
+		expectedStatus:       corev1.ConditionTrue,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonSucceeded,
+		expectedPipelineruns: []*v1beta1.PipelineRun{},
+		expectedEvents:       []string{"Normal Started ", "Normal Succeeded All PipelineRuns completed successfully"},
+	}, {
+		name:                 "Case from < to and +ve step increment",
+		from:                 "-1",
+		step:                 "1",
+		to:                   "0",
+		expectedStatus:       corev1.ConditionUnknown,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+		expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunWithRange1},
+		expectedEvents:       []string{"Normal Started ", "Normal Running Iterations completed: 0"},
+	}, {
+		name:                 "Case from < to and step == 0",
+		from:                 "-1",
+		step:                 "0",
+		to:                   "0",
+		expectedStatus:       corev1.ConditionFalse,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonFailedValidation,
+		expectedPipelineruns: []*v1beta1.PipelineRun{},
+		expectedEvents:       []string{"Normal Started ", "Warning Failed Cannot determine number of iterations: invalid values step: 0 found in runs"},
+	}, {
+		name:                 "Case from > to and +ve increment",
+		from:                 "1",
+		step:                 "1",
+		to:                   "-1",
+		expectedStatus:       corev1.ConditionFalse,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonFailedValidation,
+		expectedPipelineruns: []*v1beta1.PipelineRun{},
+		expectedEvents:       []string{"Normal Started ", "Warning Failed Cannot determine number of iterations: invalid values for from:1, to:-1 & step: 1 found in runs"},
+	}, {
+		name:                 "Case from > to and +ve step increment",
+		from:                 "0",
+		step:                 "1",
+		to:                   "-1",
+		expectedStatus:       corev1.ConditionFalse,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonFailedValidation,
+		expectedPipelineruns: []*v1beta1.PipelineRun{},
+		expectedEvents:       []string{"Normal Started ", "Warning Failed Cannot determine number of iterations: invalid values for from:0, to:-1 & step: 1 found in runs"},
+	}, {
+		name:                 "Case from = to",
+		from:                 "-1",
+		step:                 "1",
+		to:                   "-1",
+		expectedStatus:       corev1.ConditionTrue,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonSucceeded,
+		expectedPipelineruns: []*v1beta1.PipelineRun{},
+		expectedEvents:       []string{"Normal Started ", "Normal Succeeded All PipelineRuns completed successfully"},
+	}, {
+		name:                 "Case from > to and non -ve step increment",
+		from:                 "1",
+		step:                 "0",
+		to:                   "-1",
+		expectedStatus:       corev1.ConditionFalse,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonFailedValidation,
+		expectedPipelineruns: []*v1beta1.PipelineRun{},
+		expectedEvents:       []string{"Normal Started ", "Warning Failed Cannot determine number of iterations: invalid values step: 0 found in runs"},
+	}, {
+		name:                 "Case step == 0",
+		from:                 "0",
+		step:                 "0",
+		to:                   "-1",
+		expectedStatus:       corev1.ConditionFalse,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonFailedValidation,
+		expectedPipelineruns: []*v1beta1.PipelineRun{},
+		expectedEvents:       []string{"Normal Started ", "Warning Failed Cannot determine number of iterations: invalid values step: 0 found in runs"},
+	}, {
+		name:                 "Case from > to and -ve step increment",
+		from:                 "1",
+		step:                 "-1",
+		to:                   "-1",
+		expectedStatus:       corev1.ConditionUnknown,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+		expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunWithRange2},
+		expectedEvents:       []string{"Normal Started ", "Normal Running Iterations completed: 0"},
+	}, {
+		name:                 "Case from > to and -ve step increment",
+		from:                 "0",
+		step:                 "-1",
+		to:                   "-1",
+		expectedStatus:       corev1.ConditionUnknown,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+		expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunWithRange3},
+		expectedEvents:       []string{"Normal Started ", "Normal Running Iterations completed: 0"},
+	}}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx := context.Background()
+			names.TestingSeed()
+			run := specifyLoopRange(tc.from, tc.to, tc.step, runPipelineLoopWithIterateNumeric)
+			d := test.Data{
+				Runs:         []*v1alpha1.Run{run},
+				Pipelines:    []*v1beta1.Pipeline{nPipeline},
+				PipelineRuns: []*v1beta1.PipelineRun{},
+			}
+
+			testAssets, _ := getPipelineLoopController(t, d, []*pipelineloopv1alpha1.PipelineLoop{nPipelineLoop})
+			c := testAssets.Controller
+			clients := testAssets.Clients
+
+			if err := c.Reconciler.Reconcile(ctx, getRunName(run)); err != nil {
+				t.Fatalf("Error reconciling: %s", err)
+			}
+
+			// Fetch the updated Run
+			reconciledRun, err := clients.Pipeline.TektonV1alpha1().Runs(run.Namespace).Get(ctx, run.Name, metav1.GetOptions{})
+			if err != nil {
+				t.Fatalf("Error getting reconciled run from fake client: %s", err)
+			}
+
+			// Verify that the Run has the expected status and reason.
+			checkRunCondition(t, reconciledRun, tc.expectedStatus, tc.expectedReason)
+
+			// Verify that a PipelineRun was or was not created depending on the test.
+			// If the number of expected PipelineRuns is greater than the original number of PipelineRuns
+			// then the test expects a new PipelineRun to be created.  The new PipelineRun must be the
+			// last one in the list of expected PipelineRuns.
+			createdPipelineruns := getCreatedPipelinerun(t, clients)
+			// All the arrays and sub arrays are sorted to ensure there are no sporadic failures
+			// resulting from mismatch due to different ordering of items.
+			sort.Slice(createdPipelineruns, func(i, j int) bool {
+				return createdPipelineruns[i].Name < createdPipelineruns[j].Name
+			})
+			for _, createdPipelinerun := range createdPipelineruns {
+				sort.Slice(createdPipelinerun.Spec.Params, func(i, j int) bool {
+					return createdPipelinerun.Spec.Params[i].Name < createdPipelinerun.Spec.Params[j].Name
+				})
+				if createdPipelinerun.Spec.PipelineSpec != nil {
+					sort.Slice(createdPipelinerun.Spec.PipelineSpec.Params, func(i, j int) bool {
+						return createdPipelinerun.Spec.PipelineSpec.Params[i].Name < createdPipelinerun.Spec.PipelineSpec.Params[j].Name
+					})
+					sort.Slice(createdPipelinerun.Spec.PipelineSpec.Tasks, func(i, j int) bool {
+						return createdPipelinerun.Spec.PipelineSpec.Tasks[i].Name < createdPipelinerun.Spec.PipelineSpec.Tasks[j].Name
+					})
+					for _, t := range createdPipelinerun.Spec.PipelineSpec.Tasks {
+						sort.Slice(t.Params, func(i, j int) bool {
+							return t.Params[i].Name < t.Params[j].Name
+						})
+						if t.TaskSpec != nil {
+							sort.Slice(t.TaskSpec.Params, func(i, j int) bool {
+								return t.TaskSpec.Params[i].Name < t.TaskSpec.Params[j].Name
+							})
+						}
+					}
+				}
+			}
+			if len(tc.expectedPipelineruns) > 0 {
+				if len(createdPipelineruns) == 0 {
+					t.Errorf("A PipelineRun should have been created but was not")
+				} else {
+					pipelineRunsExpectedToBeCreated := make([]*v1beta1.PipelineRun, len(createdPipelineruns))
+					i := 0
+					for _, pr := range tc.expectedPipelineruns {
+						if pr.Labels["deleted"] != "True" {
+							pipelineRunsExpectedToBeCreated[i] = pr
+							i = i + 1 // skip the pr that were retried.
+						}
+					}
+
+					if d := cmp.Diff(pipelineRunsExpectedToBeCreated, createdPipelineruns); d != "" {
+						t.Errorf("Expected PipelineRun was not created. Diff %s", diff.PrintWantGot(d))
+					}
+				}
+			} else {
+				if len(createdPipelineruns) > 0 {
+					t.Errorf("A PipelineRun was created which was not expected")
+				}
+			}
+
+			// Verify expected events were created.
+			if err := checkEvents(testAssets.Recorder, tc.name, tc.expectedEvents); err != nil {
+				t.Errorf(err.Error())
+			}
+		})
+	}
+}
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 ba09d35cc6..671d7a7363 100644
--- a/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun_test.go
+++ b/tekton-catalog/pipeline-loops/pkg/reconciler/pipelinelooprun/pipelinelooprun_test.go
@@ -819,6 +819,22 @@ var runPipelineLoopWithSpaceSeparatorParams = &v1alpha1.Run{
 	},
 }
 
+func specifyLoopRange(from, to, step string, r *v1alpha1.Run) *v1alpha1.Run {
+	t := r.DeepCopy()
+	for n, i := range r.Spec.Params {
+		if i.Name == "from" {
+			t.Spec.Params[n].Value = v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: from}
+		}
+		if i.Name == "to" {
+			t.Spec.Params[n].Value = v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: to}
+		}
+		if i.Name == "step" {
+			t.Spec.Params[n].Value = v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: step}
+		}
+	}
+	return t
+}
+
 var runPipelineLoopWithIterateNumeric = &v1alpha1.Run{
 	ObjectMeta: metav1.ObjectMeta{
 		Name:      "run-pipelineloop",
@@ -911,14 +927,23 @@ var runWithNonexistentPipelineLoop = &v1alpha1.Run{
 	},
 }
 
-var runWithMissingIterateParam = &v1alpha1.Run{
+var runWithInvalidRange = &v1alpha1.Run{
 	ObjectMeta: metav1.ObjectMeta{
-		Name:      "bad-run-missing-iterate-param",
+		Name:      "run-invalid-range",
 		Namespace: "foo",
 	},
 	Spec: v1alpha1.RunSpec{
 		// current-item, which is the iterate parameter, is missing from parameters
 		Params: []v1beta1.Param{{
+			Name:  "from",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: `-11`},
+		}, {
+			Name:  "step",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: `1`},
+		}, {
+			Name:  "to",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: `-13`},
+		}, {
 			Name:  "additional-parameter",
 			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "stuff"},
 		}},
@@ -1207,6 +1232,42 @@ var expectedPipelineRunIterateNumeric1 = &v1beta1.PipelineRun{
 	},
 }
 
+var expectedPipelineRunIterateNumeric2 = &v1beta1.PipelineRun{
+	ObjectMeta: metav1.ObjectMeta{
+		Name:      "run-pipelineloop-00001-9l9zj",
+		Namespace: "foo",
+		OwnerReferences: []metav1.OwnerReference{{
+			APIVersion:         "tekton.dev/v1alpha1",
+			Kind:               "Run",
+			Name:               "run-pipelineloop",
+			Controller:         &trueB,
+			BlockOwnerDeletion: &trueB,
+		}},
+		Labels: map[string]string{
+			"custom.tekton.dev/originalPipelineRun":   "pr-loop-example",
+			"custom.tekton.dev/parentPipelineRun":     "pr-loop-example",
+			"custom.tekton.dev/pipelineLoop":          "n-pipelineloop",
+			"tekton.dev/run":                          "run-pipelineloop",
+			"custom.tekton.dev/pipelineLoopIteration": "1",
+			"myTestLabel":                             "myTestLabelValue",
+		},
+		Annotations: map[string]string{
+			"myTestAnnotation": "myTestAnnotationValue",
+			"custom.tekton.dev/pipelineLoopCurrentIterationItem": "-10",
+		},
+	},
+	Spec: v1beta1.PipelineRunSpec{
+		PipelineRef: &v1beta1.PipelineRef{Name: "n-pipeline"},
+		Params: []v1beta1.Param{{
+			Name:  "additional-parameter",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "stuff"},
+		}, {
+			Name:  "iteration",
+			Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeString, StringVal: "-10"},
+		}},
+	},
+}
+
 var expectedPipelineRunIterateNumericParam = &v1beta1.PipelineRun{
 	ObjectMeta: metav1.ObjectMeta{
 		Name:      "run-pipelineloop-00001-9l9zj",
@@ -1606,6 +1667,16 @@ func TestReconcilePipelineLoopRun(t *testing.T) {
 		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
 		expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunIterateNumeric1},
 		expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
+	}, {
+		name:                 "Reconcile a new run with -ve numeric range defined",
+		pipeline:             nPipeline,
+		pipelineloop:         nPipelineLoop,
+		run:                  specifyLoopRange("-10", "-15", "-1", runPipelineLoopWithIterateNumeric),
+		pipelineruns:         []*v1beta1.PipelineRun{},
+		expectedStatus:       corev1.ConditionUnknown,
+		expectedReason:       pipelineloopv1alpha1.PipelineLoopRunReasonRunning,
+		expectedPipelineruns: []*v1beta1.PipelineRun{expectedPipelineRunIterateNumeric2},
+		expectedEvents:       []string{"Normal Started", "Normal Running Iterations completed: 0"},
 	}, {
 		name:                 "Reconcile a new run with iterationNumberParam defined",
 		pipeline:             aPipeline,
@@ -1851,13 +1922,22 @@ func TestReconcilePipelineLoopRunFailures(t *testing.T) {
 			"Warning Failed Error retrieving PipelineLoop",
 		},
 	}, {
-		name:         "missing iterate parameter",
+		name:         "invalid range",
+		pipelineloop: aPipelineLoop,
+		run:          runWithInvalidRange,
+		reason:       pipelineloopv1alpha1.PipelineLoopRunReasonFailedValidation,
+		wantEvents: []string{
+			"Normal Started ",
+			`Warning Failed Cannot determine number of iterations: invalid values for from:-11, to:-13 & step: 1 found in runs`,
+		},
+	}, {
+		name:         "invalid range 2",
 		pipelineloop: aPipelineLoop,
-		run:          runWithMissingIterateParam,
+		run:          specifyLoopRange("10", "12", "-1", runWithInvalidRange),
 		reason:       pipelineloopv1alpha1.PipelineLoopRunReasonFailedValidation,
 		wantEvents: []string{
 			"Normal Started ",
-			`Warning Failed Cannot determine number of iterations: The iterate parameter "current-item" was not found`,
+			`Warning Failed Cannot determine number of iterations: invalid values for from:10, to:12 & step: -1 found in runs`,
 		},
 	}, {
 		name:         "iterate parameter not an array",