github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/pipeline/controller_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"reflect"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"github.com/sirupsen/logrus"
    30  	pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
    31  
    32  	corev1 "k8s.io/api/core/v1"
    33  	"k8s.io/apimachinery/pkg/api/equality"
    34  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/util/diff"
    37  	"k8s.io/client-go/util/workqueue"
    38  	"knative.dev/pkg/apis"
    39  	duckv1 "knative.dev/pkg/apis/duck/v1"
    40  	prowjobv1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    41  	"sigs.k8s.io/prow/pkg/config"
    42  	"sigs.k8s.io/prow/pkg/kube"
    43  	"sigs.k8s.io/prow/pkg/pod-utils/decorate"
    44  	"sigs.k8s.io/prow/pkg/pod-utils/downwardapi"
    45  )
    46  
    47  const (
    48  	errorGetProwJob        = "error-get-prowjob"
    49  	errorGetPipelineRun    = "error-get-pipeline"
    50  	errorDeletePipelineRun = "error-delete-pipeline"
    51  	errorCreatePipelineRun = "error-create-pipeline"
    52  	errorUpdateProwJob     = "error-update-prowjob"
    53  	pipelineID             = "123"
    54  )
    55  
    56  type fakeReconciler struct {
    57  	jobs      map[string]prowjobv1.ProwJob
    58  	pipelines map[string]pipelinev1beta1.PipelineRun
    59  	nows      metav1.Time
    60  }
    61  
    62  func (r *fakeReconciler) now() metav1.Time {
    63  	fmt.Println(r.nows)
    64  	return r.nows
    65  }
    66  
    67  const fakePJCtx = "prow-context"
    68  const fakePJNS = "prow-job"
    69  
    70  func (r *fakeReconciler) getProwJob(name string) (*prowjobv1.ProwJob, error) {
    71  	logrus.Debugf("getProwJob: name=%s", name)
    72  	if name == errorGetProwJob {
    73  		return nil, errors.New("injected get prowjob error")
    74  	}
    75  	k := toKey(fakePJCtx, fakePJNS, name)
    76  	pj, present := r.jobs[k]
    77  	if !present {
    78  		return nil, apierrors.NewNotFound(prowjobv1.Resource("ProwJob"), name)
    79  	}
    80  	return &pj, nil
    81  }
    82  
    83  func (r *fakeReconciler) patchProwJob(pj *prowjobv1.ProwJob, newpj *prowjobv1.ProwJob) (*prowjobv1.ProwJob, error) {
    84  	logrus.Debugf("patchProwJob: name=%s", pj.GetName())
    85  	if pj.Name == errorUpdateProwJob {
    86  		return nil, errors.New("injected update prowjob error")
    87  	}
    88  	if pj == nil {
    89  		return nil, errors.New("nil prowjob")
    90  	}
    91  	k := toKey(fakePJCtx, fakePJNS, pj.Name)
    92  	if _, present := r.jobs[k]; !present {
    93  		return nil, apierrors.NewNotFound(prowjobv1.Resource("ProwJob"), pj.Name)
    94  	}
    95  	r.jobs[k] = *newpj
    96  	return newpj, nil
    97  }
    98  
    99  func (r *fakeReconciler) getPipelineRun(context, namespace, name string) (*pipelinev1beta1.PipelineRun, error) {
   100  	logrus.Debugf("getPipelineRun: ctx=%s, ns=%s, name=%s", context, namespace, name)
   101  	if namespace == errorGetPipelineRun {
   102  		return nil, errors.New("injected create pipeline error")
   103  	}
   104  	k := toKey(context, namespace, name)
   105  	p, present := r.pipelines[k]
   106  	if !present {
   107  		return nil, apierrors.NewNotFound(pipelinev1beta1.Resource("PipelineRun"), name)
   108  	}
   109  	return &p, nil
   110  }
   111  
   112  func (r *fakeReconciler) listProwJobs(namespace string) ([]*prowjobv1.ProwJob, error) {
   113  	logrus.Debugf("listProwJobs: namespace=%s", namespace)
   114  	pjs := []*prowjobv1.ProwJob{}
   115  	for _, v := range r.jobs {
   116  		if v.Namespace == namespace {
   117  			pjs = append(pjs, v.DeepCopy())
   118  		}
   119  	}
   120  	return pjs, nil
   121  }
   122  
   123  func (r *fakeReconciler) deletePipelineRun(context, namespace, name string) error {
   124  	logrus.Debugf("deletePipelineRun: ctx=%s, ns=%s, name=%s", context, namespace, name)
   125  	if namespace == errorDeletePipelineRun {
   126  		return errors.New("injected create pipeline error")
   127  	}
   128  	k := toKey(context, namespace, name)
   129  	if _, present := r.pipelines[k]; !present {
   130  		return apierrors.NewNotFound(pipelinev1beta1.Resource("PipelineRun"), name)
   131  	}
   132  	delete(r.pipelines, k)
   133  	return nil
   134  }
   135  
   136  func (r *fakeReconciler) createPipelineRun(context, namespace string, p *pipelinev1beta1.PipelineRun) (*pipelinev1beta1.PipelineRun, error) {
   137  	logrus.Debugf("createPipelineRun: ctx=%s, ns=%s", context, namespace)
   138  	if p == nil {
   139  		return nil, errors.New("nil pipeline")
   140  	}
   141  	if namespace == errorCreatePipelineRun {
   142  		return nil, errors.New("injected create pipeline error")
   143  	}
   144  	k := toKey(context, namespace, p.Name)
   145  	if _, alreadyExists := r.pipelines[k]; alreadyExists {
   146  		return nil, apierrors.NewAlreadyExists(prowjobv1.Resource("ProwJob"), p.Name)
   147  	}
   148  	r.pipelines[k] = *p
   149  	return p, nil
   150  }
   151  
   152  func (r *fakeReconciler) pipelineID(pj prowjobv1.ProwJob) (string, string, error) {
   153  	return pipelineID, "", nil
   154  }
   155  
   156  func (r *fakeReconciler) cancelPipelineRun(context string, pr *pipelinev1beta1.PipelineRun) error {
   157  	pr.Spec.Status = pipelinev1beta1.PipelineRunSpecStatusCancelledRunFinally
   158  	return nil
   159  }
   160  
   161  type fakeLimiter struct {
   162  	workqueue.RateLimitingInterface
   163  	added string
   164  }
   165  
   166  func (fl *fakeLimiter) ShutDown() {}
   167  func (fl *fakeLimiter) ShuttingDown() bool {
   168  	return false
   169  }
   170  func (fl *fakeLimiter) Get() (interface{}, bool) {
   171  	return "not implemented", true
   172  }
   173  func (fl *fakeLimiter) Done(interface{})   {}
   174  func (fl *fakeLimiter) Forget(interface{}) {}
   175  func (fl *fakeLimiter) AddRateLimited(a interface{}) {
   176  	fl.added = a.(string)
   177  }
   178  func (fl *fakeLimiter) Add(a interface{}) {
   179  	fl.added = a.(string)
   180  }
   181  func (fl *fakeLimiter) AddAfter(a interface{}, d time.Duration) {
   182  	fl.added = a.(string)
   183  }
   184  func (fl *fakeLimiter) Len() int {
   185  	return 0
   186  }
   187  func (fl *fakeLimiter) NumRequeues(item interface{}) int {
   188  	return 0
   189  }
   190  
   191  func TestEnqueueKey(t *testing.T) {
   192  	cases := []struct {
   193  		name     string
   194  		context  string
   195  		obj      interface{}
   196  		expected string
   197  	}{
   198  		{
   199  			name:    "enqueue pipeline directly",
   200  			context: "hey",
   201  			obj: &pipelinev1beta1.PipelineRun{
   202  				ObjectMeta: metav1.ObjectMeta{
   203  					Namespace: "foo",
   204  					Name:      "bar",
   205  				},
   206  			},
   207  			expected: toKey("hey", "foo", "bar"),
   208  		},
   209  		{
   210  			name:    "enqueue prowjob's spec namespace",
   211  			context: "rolo",
   212  			obj: &prowjobv1.ProwJob{
   213  				ObjectMeta: metav1.ObjectMeta{
   214  					Namespace: "default",
   215  					Name:      "dude",
   216  				},
   217  				Spec: prowjobv1.ProwJobSpec{
   218  					Namespace: "tomassi",
   219  				},
   220  			},
   221  			expected: toKey("rolo", "tomassi", "dude"),
   222  		},
   223  		{
   224  			name:    "ignore random object",
   225  			context: "foo",
   226  			obj:     "bar",
   227  		},
   228  	}
   229  
   230  	for _, tc := range cases {
   231  		t.Run(tc.name, func(t *testing.T) {
   232  			var fl fakeLimiter
   233  			c := controller{
   234  				workqueue: &fl,
   235  			}
   236  			c.enqueueKey(tc.context, tc.obj)
   237  			if !reflect.DeepEqual(fl.added, tc.expected) {
   238  				t.Errorf("%q != expected %q", fl.added, tc.expected)
   239  			}
   240  		})
   241  	}
   242  }
   243  
   244  func TestReconcile(t *testing.T) {
   245  	duplicateAppendix := "-duplicate"
   246  	logrus.SetLevel(logrus.DebugLevel)
   247  	now := metav1.Now()
   248  	future := metav1.Time{Time: time.Now().Add(time.Minute)}
   249  	pipelineSpec := pipelinev1beta1.PipelineRunSpec{}
   250  	noJobChange := func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) prowjobv1.ProwJob {
   251  		return pj
   252  	}
   253  	noPipelineRunChange := func(_ prowjobv1.ProwJob, p pipelinev1beta1.PipelineRun) pipelinev1beta1.PipelineRun {
   254  		return p
   255  	}
   256  	cases := []struct {
   257  		name                string
   258  		namespace           string
   259  		context             string
   260  		observedJob         *prowjobv1.ProwJob
   261  		observedPipelineRun *pipelinev1beta1.PipelineRun
   262  		expectedJob         func(prowjobv1.ProwJob, pipelinev1beta1.PipelineRun) prowjobv1.ProwJob
   263  		expectedPipelineRun func(prowjobv1.ProwJob, pipelinev1beta1.PipelineRun) pipelinev1beta1.PipelineRun
   264  		duplicateStartTime  *metav1.Time
   265  		err                 bool
   266  	}{
   267  		{
   268  			name: "new prow job creates pipeline",
   269  			observedJob: &prowjobv1.ProwJob{
   270  				Spec: prowjobv1.ProwJobSpec{
   271  					Agent:           prowjobv1.TektonAgent,
   272  					PipelineRunSpec: &pipelineSpec,
   273  				},
   274  				Status: prowjobv1.ProwJobStatus{
   275  					BuildID: pipelineID,
   276  				},
   277  			},
   278  			expectedJob: func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) prowjobv1.ProwJob {
   279  				pj.Status = prowjobv1.ProwJobStatus{
   280  					StartTime:   now,
   281  					PendingTime: &now,
   282  					State:       prowjobv1.PendingState,
   283  					Description: descScheduling,
   284  					BuildID:     pipelineID,
   285  				}
   286  				return pj
   287  			},
   288  			expectedPipelineRun: func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) pipelinev1beta1.PipelineRun {
   289  				pj.Spec.Type = prowjobv1.PeriodicJob
   290  				p, err := makePipelineRun(pj)
   291  				if err != nil {
   292  					panic(err)
   293  				}
   294  				return *p
   295  			},
   296  		},
   297  		{
   298  			name:               "new prow job followed by a newer duplicate does not create a pipeline",
   299  			duplicateStartTime: &future,
   300  			observedJob: &prowjobv1.ProwJob{
   301  				Status: prowjobv1.ProwJobStatus{
   302  					State:     prowjobv1.PendingState,
   303  					BuildID:   pipelineID,
   304  					StartTime: now,
   305  				},
   306  				Spec: prowjobv1.ProwJobSpec{
   307  					Agent:           prowjobv1.TektonAgent,
   308  					PipelineRunSpec: &pipelineSpec,
   309  					Job:             "foobar",
   310  				},
   311  			},
   312  			expectedJob: func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) prowjobv1.ProwJob {
   313  				if !strings.Contains(pj.Name, duplicateAppendix) {
   314  					pj.Status = prowjobv1.ProwJobStatus{
   315  						StartTime:      now,
   316  						State:          prowjobv1.AbortedState,
   317  						Description:    descAborted,
   318  						BuildID:        pipelineID,
   319  						CompletionTime: &now,
   320  					}
   321  					return pj
   322  				}
   323  				pj.Status = prowjobv1.ProwJobStatus{
   324  					StartTime: future,
   325  					State:     prowjobv1.PendingState,
   326  					BuildID:   pipelineID + duplicateAppendix,
   327  				}
   328  				return pj
   329  			},
   330  		},
   331  		{
   332  			name:               "new prow job preceded by an older duplicate creates a pipeline",
   333  			duplicateStartTime: &now,
   334  			observedJob: &prowjobv1.ProwJob{
   335  				Status: prowjobv1.ProwJobStatus{
   336  					State:     prowjobv1.PendingState,
   337  					BuildID:   pipelineID,
   338  					StartTime: future,
   339  				},
   340  				Spec: prowjobv1.ProwJobSpec{
   341  					Agent:           prowjobv1.TektonAgent,
   342  					PipelineRunSpec: &pipelineSpec,
   343  					Job:             "foobar",
   344  				},
   345  			},
   346  			expectedJob: func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) prowjobv1.ProwJob {
   347  				if !strings.Contains(pj.Name, duplicateAppendix) {
   348  					pj.Status = prowjobv1.ProwJobStatus{
   349  						StartTime:   future,
   350  						State:       prowjobv1.PendingState,
   351  						Description: descScheduling,
   352  						BuildID:     pipelineID,
   353  					}
   354  					return pj
   355  				}
   356  				pj.Status = prowjobv1.ProwJobStatus{
   357  					StartTime:      now,
   358  					State:          prowjobv1.AbortedState,
   359  					BuildID:        pipelineID + duplicateAppendix,
   360  					CompletionTime: &now,
   361  					Description:    descAborted,
   362  				}
   363  				return pj
   364  			},
   365  			expectedPipelineRun: func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) pipelinev1beta1.PipelineRun {
   366  				pj.Spec.Type = prowjobv1.PeriodicJob
   367  				p, err := makePipelineRun(pj)
   368  				if err != nil {
   369  					panic(err)
   370  				}
   371  				return *p
   372  			},
   373  		},
   374  		{
   375  			name: "do not create pipeline run for failed prowjob",
   376  			observedJob: &prowjobv1.ProwJob{
   377  				Spec: prowjobv1.ProwJobSpec{
   378  					Agent:           prowjobv1.TektonAgent,
   379  					PipelineRunSpec: &pipelineSpec,
   380  				},
   381  				Status: prowjobv1.ProwJobStatus{
   382  					State:   prowjobv1.FailureState,
   383  					BuildID: pipelineID,
   384  				},
   385  			},
   386  			expectedJob: noJobChange,
   387  		},
   388  		{
   389  			name: "do not create pipeline run for successful prowjob",
   390  			observedJob: &prowjobv1.ProwJob{
   391  				Spec: prowjobv1.ProwJobSpec{
   392  					Agent:           prowjobv1.TektonAgent,
   393  					PipelineRunSpec: &pipelineSpec,
   394  				},
   395  				Status: prowjobv1.ProwJobStatus{
   396  					State: prowjobv1.SuccessState,
   397  				},
   398  			},
   399  			expectedJob: noJobChange,
   400  		},
   401  		{
   402  			name: "do not create pipeline run for aborted prowjob",
   403  			observedJob: &prowjobv1.ProwJob{
   404  				Spec: prowjobv1.ProwJobSpec{
   405  					Agent:           prowjobv1.TektonAgent,
   406  					PipelineRunSpec: &pipelineSpec,
   407  				},
   408  				Status: prowjobv1.ProwJobStatus{
   409  					State:   prowjobv1.AbortedState,
   410  					BuildID: pipelineID,
   411  				},
   412  			},
   413  			expectedJob: noJobChange,
   414  		},
   415  		{
   416  			name: "delete pipeline run after deleting prowjob",
   417  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   418  				pj := prowjobv1.ProwJob{}
   419  				pj.Spec.Type = prowjobv1.PeriodicJob
   420  				pj.Spec.PipelineRunSpec = &pipelinev1beta1.PipelineRunSpec{}
   421  				pj.Status.BuildID = pipelineID
   422  				p, err := makePipelineRun(pj)
   423  				if err != nil {
   424  					panic(err)
   425  				}
   426  				return p
   427  			}(),
   428  		},
   429  		{
   430  			name: "do not delete deleted pipeline runs",
   431  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   432  				pj := prowjobv1.ProwJob{}
   433  				pj.Spec.Type = prowjobv1.PeriodicJob
   434  				pj.Spec.PipelineRunSpec = &pipelinev1beta1.PipelineRunSpec{}
   435  				pj.Status.BuildID = pipelineID
   436  				p, err := makePipelineRun(pj)
   437  				p.DeletionTimestamp = &now
   438  				if err != nil {
   439  					panic(err)
   440  				}
   441  				return p
   442  			}(),
   443  			expectedPipelineRun: noPipelineRunChange,
   444  		},
   445  		{
   446  			name: "only delete pipeline runs created by controller",
   447  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   448  				pj := prowjobv1.ProwJob{}
   449  				pj.Spec.Type = prowjobv1.PeriodicJob
   450  				pj.Spec.PipelineRunSpec = &pipelinev1beta1.PipelineRunSpec{}
   451  				pj.Status.BuildID = pipelineID
   452  				p, err := makePipelineRun(pj)
   453  				if err != nil {
   454  					panic(err)
   455  				}
   456  				delete(p.Labels, kube.CreatedByProw)
   457  				return p
   458  			}(),
   459  			expectedPipelineRun: noPipelineRunChange,
   460  		},
   461  		{
   462  			name:    "delete prow pipeline runs in the wrong cluster",
   463  			context: "wrong-cluster",
   464  			observedJob: &prowjobv1.ProwJob{
   465  				Spec: prowjobv1.ProwJobSpec{
   466  					Agent:   prowjobv1.TektonAgent,
   467  					Cluster: "target-cluster",
   468  					PipelineRunSpec: &pipelinev1beta1.PipelineRunSpec{
   469  						ServiceAccountName: "robot",
   470  					},
   471  				},
   472  				Status: prowjobv1.ProwJobStatus{
   473  					State:       prowjobv1.PendingState,
   474  					StartTime:   metav1.Now(),
   475  					Description: "fancy",
   476  				},
   477  			},
   478  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   479  				pj := prowjobv1.ProwJob{}
   480  				pj.Spec.Type = prowjobv1.PeriodicJob
   481  				pj.Spec.Agent = prowjobv1.TektonAgent
   482  				pj.Spec.PipelineRunSpec = &pipelineSpec
   483  				pj.Status.BuildID = pipelineID
   484  				p, err := makePipelineRun(pj)
   485  				if err != nil {
   486  					panic(err)
   487  				}
   488  				return p
   489  			}(),
   490  			expectedJob: noJobChange,
   491  		},
   492  		{
   493  			name:    "ignore random pipeline run in the wrong cluster",
   494  			context: "wrong-cluster",
   495  			observedJob: &prowjobv1.ProwJob{
   496  				Spec: prowjobv1.ProwJobSpec{
   497  					Agent:   prowjobv1.TektonAgent,
   498  					Cluster: "target-cluster",
   499  					PipelineRunSpec: &pipelinev1beta1.PipelineRunSpec{
   500  						ServiceAccountName: "robot",
   501  					},
   502  				},
   503  				Status: prowjobv1.ProwJobStatus{
   504  					State:       prowjobv1.PendingState,
   505  					StartTime:   metav1.Now(),
   506  					Description: "fancy",
   507  				},
   508  			},
   509  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   510  				pj := prowjobv1.ProwJob{}
   511  				pj.Spec.Type = prowjobv1.PeriodicJob
   512  				pj.Spec.Agent = prowjobv1.TektonAgent
   513  				pj.Spec.PipelineRunSpec = &pipelineSpec
   514  				pj.Status.BuildID = pipelineID
   515  				p, err := makePipelineRun(pj)
   516  				if err != nil {
   517  					panic(err)
   518  				}
   519  				delete(p.Labels, kube.CreatedByProw)
   520  				return p
   521  			}(),
   522  			expectedJob:         noJobChange,
   523  			expectedPipelineRun: noPipelineRunChange,
   524  		},
   525  		{
   526  			name: "update job status if pipeline run resets",
   527  			observedJob: &prowjobv1.ProwJob{
   528  				Spec: prowjobv1.ProwJobSpec{
   529  					Agent: prowjobv1.TektonAgent,
   530  					PipelineRunSpec: &pipelinev1beta1.PipelineRunSpec{
   531  						ServiceAccountName: "robot",
   532  					},
   533  				},
   534  				Status: prowjobv1.ProwJobStatus{
   535  					State:       prowjobv1.PendingState,
   536  					StartTime:   metav1.Now(),
   537  					Description: "fancy",
   538  				},
   539  			},
   540  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   541  				pj := prowjobv1.ProwJob{}
   542  				pj.Spec.Type = prowjobv1.PeriodicJob
   543  				pj.Spec.Agent = prowjobv1.TektonAgent
   544  				pj.Spec.PipelineRunSpec = &pipelinev1beta1.PipelineRunSpec{
   545  					ServiceAccountName: "robot",
   546  				}
   547  				pj.Status.BuildID = pipelineID
   548  				p, err := makePipelineRun(pj)
   549  				if err != nil {
   550  					panic(err)
   551  				}
   552  				return p
   553  			}(),
   554  			expectedJob: func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) prowjobv1.ProwJob {
   555  				pj.Status.State = prowjobv1.PendingState
   556  				pj.Status.Description = descScheduling
   557  				return pj
   558  			},
   559  			expectedPipelineRun: noPipelineRunChange,
   560  		},
   561  		{
   562  			name: "prowjob goes pending when pipeline run starts",
   563  			observedJob: &prowjobv1.ProwJob{
   564  				Spec: prowjobv1.ProwJobSpec{
   565  					Agent:           prowjobv1.TektonAgent,
   566  					PipelineRunSpec: &pipelineSpec,
   567  				},
   568  				Status: prowjobv1.ProwJobStatus{
   569  					State:       prowjobv1.PendingState,
   570  					Description: "fancy",
   571  				},
   572  			},
   573  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   574  				pj := prowjobv1.ProwJob{}
   575  				pj.Spec.Type = prowjobv1.PeriodicJob
   576  				pj.Spec.Agent = prowjobv1.TektonAgent
   577  				pj.Spec.PipelineRunSpec = &pipelineSpec
   578  				pj.Status.BuildID = pipelineID
   579  				p, err := makePipelineRun(pj)
   580  				if err != nil {
   581  					panic(err)
   582  				}
   583  				p.Status.SetCondition(&apis.Condition{
   584  					Type:    apis.ConditionReady,
   585  					Message: "hello",
   586  				})
   587  				return p
   588  			}(),
   589  			expectedJob: func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) prowjobv1.ProwJob {
   590  				pj.Status = prowjobv1.ProwJobStatus{
   591  					StartTime:   now,
   592  					State:       prowjobv1.PendingState,
   593  					Description: "scheduling",
   594  				}
   595  				return pj
   596  			},
   597  			expectedPipelineRun: noPipelineRunChange,
   598  		},
   599  		{
   600  			name: "prowjob succeeds when run pipeline succeeds",
   601  			observedJob: &prowjobv1.ProwJob{
   602  				Spec: prowjobv1.ProwJobSpec{
   603  					Agent:           prowjobv1.TektonAgent,
   604  					PipelineRunSpec: &pipelineSpec,
   605  				},
   606  				Status: prowjobv1.ProwJobStatus{
   607  					State:       prowjobv1.PendingState,
   608  					Description: "fancy",
   609  				},
   610  			},
   611  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   612  				pj := prowjobv1.ProwJob{}
   613  				pj.Spec.Type = prowjobv1.PeriodicJob
   614  				pj.Spec.Agent = prowjobv1.TektonAgent
   615  				pj.Spec.PipelineRunSpec = &pipelineSpec
   616  				pj.Status.BuildID = pipelineID
   617  				p, err := makePipelineRun(pj)
   618  				if err != nil {
   619  					panic(err)
   620  				}
   621  				p.Status.SetCondition(&apis.Condition{
   622  					Type:    apis.ConditionSucceeded,
   623  					Status:  corev1.ConditionTrue,
   624  					Message: "hello",
   625  				})
   626  				return p
   627  			}(),
   628  			expectedJob: func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) prowjobv1.ProwJob {
   629  				pj.Status = prowjobv1.ProwJobStatus{
   630  					StartTime:      now,
   631  					CompletionTime: &now,
   632  					State:          prowjobv1.SuccessState,
   633  					Description:    "hello",
   634  				}
   635  				return pj
   636  			},
   637  			expectedPipelineRun: noPipelineRunChange,
   638  		},
   639  		{
   640  			name: "prowjob fails when pipeline run fails",
   641  			observedJob: &prowjobv1.ProwJob{
   642  				Spec: prowjobv1.ProwJobSpec{
   643  					Agent:           prowjobv1.TektonAgent,
   644  					PipelineRunSpec: &pipelineSpec,
   645  				},
   646  				Status: prowjobv1.ProwJobStatus{
   647  					State:       prowjobv1.PendingState,
   648  					Description: "fancy",
   649  				},
   650  			},
   651  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   652  				pj := prowjobv1.ProwJob{}
   653  				pj.Spec.Type = prowjobv1.PeriodicJob
   654  				pj.Spec.Agent = prowjobv1.TektonAgent
   655  				pj.Spec.PipelineRunSpec = &pipelineSpec
   656  				pj.Status.BuildID = pipelineID
   657  				p, err := makePipelineRun(pj)
   658  				if err != nil {
   659  					panic(err)
   660  				}
   661  				p.Status.SetCondition(&apis.Condition{
   662  					Type:    apis.ConditionSucceeded,
   663  					Status:  corev1.ConditionFalse,
   664  					Message: "hello",
   665  				})
   666  				return p
   667  			}(),
   668  			expectedJob: func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) prowjobv1.ProwJob {
   669  				pj.Status = prowjobv1.ProwJobStatus{
   670  					StartTime:      now,
   671  					CompletionTime: &now,
   672  					State:          prowjobv1.FailureState,
   673  					Description:    "hello",
   674  				}
   675  				return pj
   676  			},
   677  			expectedPipelineRun: noPipelineRunChange,
   678  		},
   679  		{
   680  			name:      "error when we cannot get prowjob",
   681  			namespace: errorGetProwJob,
   682  			err:       true,
   683  			observedJob: &prowjobv1.ProwJob{
   684  				Spec: prowjobv1.ProwJobSpec{
   685  					Agent:           prowjobv1.TektonAgent,
   686  					PipelineRunSpec: &pipelineSpec,
   687  				},
   688  				Status: prowjobv1.ProwJobStatus{
   689  					State:       prowjobv1.PendingState,
   690  					Description: "fancy",
   691  				},
   692  			},
   693  		},
   694  		{
   695  			name:      "error when we cannot get pipeline run",
   696  			namespace: errorGetPipelineRun,
   697  			err:       true,
   698  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   699  				pj := prowjobv1.ProwJob{}
   700  				pj.Spec.Type = prowjobv1.PeriodicJob
   701  				pj.Spec.Agent = prowjobv1.TektonAgent
   702  				pj.Spec.PipelineRunSpec = &pipelineSpec
   703  				pj.Status.BuildID = pipelineID
   704  				p, err := makePipelineRun(pj)
   705  				if err != nil {
   706  					panic(err)
   707  				}
   708  				p.Status.SetCondition(&apis.Condition{
   709  					Type:    apis.ConditionSucceeded,
   710  					Status:  corev1.ConditionTrue,
   711  					Message: "hello",
   712  				})
   713  				return p
   714  			}(),
   715  		},
   716  		{
   717  			name:      "error when we cannot delete pipeline run",
   718  			namespace: errorDeletePipelineRun,
   719  			err:       true,
   720  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   721  				pj := prowjobv1.ProwJob{}
   722  				pj.Spec.Type = prowjobv1.PeriodicJob
   723  				pj.Spec.PipelineRunSpec = &pipelinev1beta1.PipelineRunSpec{}
   724  				pj.Status.BuildID = pipelineID
   725  				p, err := makePipelineRun(pj)
   726  				if err != nil {
   727  					panic(err)
   728  				}
   729  				return p
   730  			}(),
   731  		},
   732  		{
   733  			name:      "set prow job in error state when we cannot create pipeline run",
   734  			namespace: errorCreatePipelineRun,
   735  			err:       false,
   736  			observedJob: &prowjobv1.ProwJob{
   737  				Spec: prowjobv1.ProwJobSpec{
   738  					Agent:           prowjobv1.TektonAgent,
   739  					PipelineRunSpec: &pipelineSpec,
   740  				},
   741  			},
   742  			expectedJob: func(pj prowjobv1.ProwJob, _ pipelinev1beta1.PipelineRun) prowjobv1.ProwJob {
   743  				pj.Status = prowjobv1.ProwJobStatus{
   744  					BuildID:        pipelineID,
   745  					StartTime:      now,
   746  					CompletionTime: &now,
   747  					State:          prowjobv1.ErrorState,
   748  					Description:    "start pipeline: injected create pipeline error",
   749  				}
   750  				return pj
   751  			},
   752  		},
   753  		{
   754  			name: "error when pipelinerunspec is nil",
   755  			err:  true,
   756  			observedJob: &prowjobv1.ProwJob{
   757  				Spec: prowjobv1.ProwJobSpec{
   758  					Agent:           prowjobv1.TektonAgent,
   759  					PipelineRunSpec: nil,
   760  				},
   761  				Status: prowjobv1.ProwJobStatus{
   762  					State: prowjobv1.TriggeredState,
   763  				},
   764  			},
   765  		},
   766  		{
   767  			name:      "error when we cannot update prowjob",
   768  			namespace: errorUpdateProwJob,
   769  			err:       true,
   770  			observedJob: &prowjobv1.ProwJob{
   771  				Spec: prowjobv1.ProwJobSpec{
   772  					Agent:           prowjobv1.TektonAgent,
   773  					PipelineRunSpec: &pipelineSpec,
   774  				},
   775  				Status: prowjobv1.ProwJobStatus{
   776  					State:       prowjobv1.PendingState,
   777  					Description: "fancy",
   778  				},
   779  			},
   780  			observedPipelineRun: func() *pipelinev1beta1.PipelineRun {
   781  				pj := prowjobv1.ProwJob{}
   782  				pj.Spec.Type = prowjobv1.PeriodicJob
   783  				pj.Spec.Agent = prowjobv1.TektonAgent
   784  				pj.Spec.PipelineRunSpec = &pipelineSpec
   785  				pj.Status.BuildID = pipelineID
   786  				p, err := makePipelineRun(pj)
   787  				if err != nil {
   788  					panic(err)
   789  				}
   790  				p.Status.SetCondition(&apis.Condition{
   791  					Type:    apis.ConditionSucceeded,
   792  					Status:  corev1.ConditionTrue,
   793  					Message: "hello",
   794  				})
   795  				return p
   796  			}(),
   797  		}}
   798  
   799  	for _, tc := range cases {
   800  		t.Run(tc.name, func(t *testing.T) {
   801  			name := "the-object-name"
   802  			// prowjobs all live in the same ns, so use name for injecting errors
   803  			if tc.namespace == errorGetProwJob {
   804  				name = errorGetProwJob
   805  			} else if tc.namespace == errorUpdateProwJob {
   806  				name = errorUpdateProwJob
   807  			}
   808  			if tc.context == "" {
   809  				tc.context = kube.DefaultClusterAlias
   810  			}
   811  			r := &fakeReconciler{
   812  				jobs:      map[string]prowjobv1.ProwJob{},
   813  				pipelines: map[string]pipelinev1beta1.PipelineRun{},
   814  				nows:      now,
   815  			}
   816  
   817  			jk := toKey(fakePJCtx, fakePJNS, name)
   818  			jkDuplicate := toKey(fakePJCtx, fakePJNS, name+duplicateAppendix)
   819  			if j := tc.observedJob; j != nil {
   820  				j.Name = name
   821  				j.Spec.Type = prowjobv1.PeriodicJob
   822  				if tc.duplicateStartTime != nil {
   823  					duplicate := j.DeepCopy()
   824  					duplicate.Name = name + duplicateAppendix
   825  					duplicate.Status.StartTime = *tc.duplicateStartTime
   826  					duplicate.Status.BuildID = j.Status.BuildID + duplicateAppendix
   827  					r.jobs[jkDuplicate] = *duplicate
   828  				}
   829  				r.jobs[jk] = *j
   830  			}
   831  			pk := toKey(tc.context, tc.namespace, name)
   832  			if p := tc.observedPipelineRun; p != nil {
   833  				p.Name = name
   834  				p.Labels[kube.ProwJobIDLabel] = name
   835  				r.pipelines[pk] = *p
   836  			}
   837  
   838  			expectedJobs := map[string]prowjobv1.ProwJob{}
   839  			if j := tc.expectedJob; j != nil {
   840  				expectedJobs[jk] = j(r.jobs[jk], r.pipelines[pk])
   841  				if tc.duplicateStartTime != nil {
   842  					// use empty pipeline run as we don't care about the duplicate
   843  					expectedJobs[jkDuplicate] = j(r.jobs[jkDuplicate], pipelinev1beta1.PipelineRun{})
   844  				}
   845  			}
   846  			expectedPipelineRuns := map[string]pipelinev1beta1.PipelineRun{}
   847  			if p := tc.expectedPipelineRun; p != nil {
   848  				expectedPipelineRuns[pk] = p(r.jobs[jk], r.pipelines[pk])
   849  			}
   850  
   851  			tk := toKey(tc.context, tc.namespace, name)
   852  			err := reconcile(r, tk)
   853  			switch {
   854  			case err != nil:
   855  				if !tc.err {
   856  					t.Errorf("unexpected error: %v", err)
   857  				}
   858  			case tc.err:
   859  				t.Error("failed to receive expected error")
   860  			case !equality.Semantic.DeepEqual(r.jobs, expectedJobs):
   861  				t.Errorf("prowjobs do not match:\n%s", diff.ObjectReflectDiff(expectedJobs, r.jobs))
   862  			case !equality.Semantic.DeepEqual(r.pipelines, expectedPipelineRuns):
   863  				t.Errorf("pipelineruns do not match:\n%s", diff.ObjectReflectDiff(expectedPipelineRuns, r.pipelines))
   864  			}
   865  		})
   866  	}
   867  }
   868  
   869  func TestPipelineMeta(t *testing.T) {
   870  	cases := []struct {
   871  		name     string
   872  		pj       prowjobv1.ProwJob
   873  		expected func(prowjobv1.ProwJob, *metav1.ObjectMeta)
   874  	}{
   875  		{
   876  			name: "Use pj.Spec.Namespace for pipeline namespace",
   877  			pj: prowjobv1.ProwJob{
   878  				ObjectMeta: metav1.ObjectMeta{
   879  					Name:      "whatever",
   880  					Namespace: "wrong",
   881  				},
   882  				Spec: prowjobv1.ProwJobSpec{
   883  					Namespace: "correct",
   884  				},
   885  			},
   886  			expected: func(pj prowjobv1.ProwJob, meta *metav1.ObjectMeta) {
   887  				meta.Name = pj.Name
   888  				meta.Namespace = pj.Spec.Namespace
   889  				meta.Labels, meta.Annotations = decorate.LabelsAndAnnotationsForJob(pj)
   890  			},
   891  		},
   892  	}
   893  
   894  	for _, tc := range cases {
   895  		t.Run(tc.name, func(t *testing.T) {
   896  			var expected metav1.ObjectMeta
   897  			tc.expected(tc.pj, &expected)
   898  			actual := pipelineMeta(tc.pj.Name, tc.pj)
   899  			if !equality.Semantic.DeepEqual(actual, expected) {
   900  				t.Errorf("pipeline meta does not match:\n%s", diff.ObjectReflectDiff(expected, actual))
   901  			}
   902  		})
   903  	}
   904  }
   905  
   906  func TestMakeResourcesBeta1(t *testing.T) {
   907  	cases := []struct {
   908  		name        string
   909  		job         func(prowjobv1.ProwJob) prowjobv1.ProwJob
   910  		pipelineRun func(pipelinev1beta1.PipelineRun) pipelinev1beta1.PipelineRun
   911  		err         bool
   912  	}{
   913  		{
   914  			name: "reject empty prow job",
   915  			job:  func(_ prowjobv1.ProwJob) prowjobv1.ProwJob { return prowjobv1.ProwJob{} },
   916  			err:  true,
   917  		},
   918  		{
   919  			name: "return valid pipeline with valid prowjob",
   920  		},
   921  		{
   922  			name: "configure implicit git repository",
   923  			job: func(pj prowjobv1.ProwJob) prowjobv1.ProwJob {
   924  				pj.Spec.Type = prowjobv1.PresubmitJob
   925  				pj.Spec.Refs = &prowjobv1.Refs{
   926  					CloneURI: "https://source.host/test/test.git",
   927  					BaseRef:  "feature-branch",
   928  					Pulls:    []prowjobv1.Pull{{Number: 1}},
   929  				}
   930  				pj.Spec.TektonPipelineRunSpec.V1Beta1.PipelineSpec = &pipelinev1beta1.PipelineSpec{
   931  					Tasks: []pipelinev1beta1.PipelineTask{{
   932  						Name:    "implicit git resource",
   933  						TaskRef: &pipelinev1beta1.TaskRef{Name: config.ProwImplicitGitResource},
   934  					}},
   935  				}
   936  
   937  				return pj
   938  			},
   939  			pipelineRun: func(pr pipelinev1beta1.PipelineRun) pipelinev1beta1.PipelineRun {
   940  				pr.Spec.Params[4].Value = pipelinev1beta1.ParamValue{
   941  					Type:      pipelinev1beta1.ParamTypeString,
   942  					StringVal: string(prowjobv1.PresubmitJob),
   943  				}
   944  				pr.Spec.Params = append(pr.Spec.Params,
   945  					pipelinev1beta1.Param{Name: "PULL_BASE_REF", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString, StringVal: "feature-branch"}},
   946  					pipelinev1beta1.Param{Name: "PULL_BASE_SHA", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString}},
   947  					pipelinev1beta1.Param{Name: "PULL_HEAD_REF", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString}},
   948  					pipelinev1beta1.Param{Name: "PULL_NUMBER", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString, StringVal: "1"}},
   949  					pipelinev1beta1.Param{Name: "PULL_PULL_SHA", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString}},
   950  					pipelinev1beta1.Param{Name: "PULL_REFS", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString, StringVal: "feature-branch,1:"}},
   951  					pipelinev1beta1.Param{Name: "PULL_TITLE", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString}},
   952  					pipelinev1beta1.Param{Name: "REPO_NAME", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString}},
   953  					pipelinev1beta1.Param{Name: "REPO_OWNER", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString}},
   954  				)
   955  				pr.Spec.PipelineSpec.Tasks = []pipelinev1beta1.PipelineTask{
   956  					{
   957  						TaskRef: &pipelinev1beta1.TaskRef{Name: "git-clone"},
   958  						Params: []pipelinev1beta1.Param{
   959  							{Name: "url", Value: pipelinev1beta1.ParamValue{StringVal: "https://source.host/test/test.git"}},
   960  							{Name: "revision", Value: pipelinev1beta1.ParamValue{StringVal: "pull/1/head"}},
   961  						},
   962  					},
   963  				}
   964  				return pr
   965  			},
   966  		},
   967  		{
   968  			name: "configure sources when extra refs are configured",
   969  			job: func(pj prowjobv1.ProwJob) prowjobv1.ProwJob {
   970  				pj.Spec.ExtraRefs = []prowjobv1.Refs{{Org: "org0"}, {Org: "org1"}}
   971  				pj.Spec.TektonPipelineRunSpec.V1Beta1.PipelineSpec = &pipelinev1beta1.PipelineSpec{
   972  					Tasks: []pipelinev1beta1.PipelineTask{
   973  						{Name: "git resource A", TaskRef: &pipelinev1beta1.TaskRef{Name: "PROW_EXTRA_GIT_REF_0"}},
   974  						{Name: "git resource B", TaskRef: &pipelinev1beta1.TaskRef{Name: "PROW_EXTRA_GIT_REF_1"}},
   975  					},
   976  				}
   977  				return pj
   978  			},
   979  			pipelineRun: func(pr pipelinev1beta1.PipelineRun) pipelinev1beta1.PipelineRun {
   980  				pr.Spec.PipelineSpec.Tasks = []pipelinev1beta1.PipelineTask{
   981  					{
   982  						TaskRef: &pipelinev1beta1.TaskRef{Name: "git-clone"},
   983  						Params: []pipelinev1beta1.Param{
   984  							{Name: "url", Value: pipelinev1beta1.ParamValue{StringVal: "https://github.com/org0/.git"}},
   985  							{Name: "revision"},
   986  						},
   987  					},
   988  					{
   989  						TaskRef: &pipelinev1beta1.TaskRef{Name: "git-clone"},
   990  						Params: []pipelinev1beta1.Param{
   991  							{Name: "url", Value: pipelinev1beta1.ParamValue{StringVal: "https://github.com/org1/.git"}},
   992  							{Name: "revision"},
   993  						},
   994  					},
   995  				}
   996  				return pr
   997  			},
   998  		},
   999  		{
  1000  			name: "do not override unrelated git resources",
  1001  			job: func(pj prowjobv1.ProwJob) prowjobv1.ProwJob {
  1002  				pj.Spec.TektonPipelineRunSpec.V1Beta1.PipelineSpec = &pipelinev1beta1.PipelineSpec{
  1003  					Tasks: []pipelinev1beta1.PipelineTask{
  1004  						{Name: "git resource A", TaskRef: &pipelinev1beta1.TaskRef{Name: "PROW_EXTRA_GIT_REF_LOL_JK"}},
  1005  						{Name: "git resource B", TaskRef: &pipelinev1beta1.TaskRef{Name: "some-other-ref"}},
  1006  					},
  1007  				}
  1008  				return pj
  1009  			},
  1010  		},
  1011  	}
  1012  
  1013  	for _, tc := range cases {
  1014  		t.Run(tc.name, func(t *testing.T) {
  1015  			const randomPipelineRunID = "so-many-pipelines"
  1016  			pj := prowjobv1.ProwJob{}
  1017  			pj.Name = "world"
  1018  			pj.Namespace = "hello"
  1019  			pj.Spec.Type = prowjobv1.PeriodicJob
  1020  			pj.Spec.Job = "ci-job"
  1021  			pj.Spec.TektonPipelineRunSpec = &prowjobv1.TektonPipelineRunSpec{
  1022  				V1Beta1: &pipelinev1beta1.PipelineRunSpec{},
  1023  			}
  1024  			pj.Status.BuildID = randomPipelineRunID
  1025  
  1026  			if tc.job != nil {
  1027  				pj = tc.job(pj)
  1028  			}
  1029  
  1030  			actualRun, err := makePipelineRun(pj)
  1031  			if err != nil {
  1032  				if !tc.err {
  1033  					t.Errorf("unexpected error: %v", err)
  1034  				}
  1035  				return
  1036  			} else if tc.err {
  1037  				t.Error("failed to receive expected error")
  1038  			}
  1039  
  1040  			jobSpecRaw, err := json.Marshal(downwardapi.NewJobSpec(pj.Spec, randomPipelineRunID, pj.Name))
  1041  			if err != nil {
  1042  				t.Errorf("failed to marshal job spec: %v", err)
  1043  			}
  1044  			pipelineRunSpec, err := pj.Spec.GetPipelineRunSpec()
  1045  			if err != nil {
  1046  				t.Errorf("failed to get pipeline run spec: %v", err)
  1047  			}
  1048  			expectedRun := pipelinev1beta1.PipelineRun{
  1049  				ObjectMeta: pipelineMeta(pj.Name, pj),
  1050  				Spec:       *pipelineRunSpec,
  1051  			}
  1052  			expectedRun.Spec.Params = []pipelinev1beta1.Param{
  1053  				{Name: "BUILD_ID", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString, StringVal: randomPipelineRunID}},
  1054  				{Name: "CI", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString, StringVal: "true"}},
  1055  				{Name: "JOB_NAME", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString, StringVal: pj.Spec.Job}},
  1056  				{Name: "JOB_SPEC", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString, StringVal: string(jobSpecRaw)}},
  1057  				{Name: "JOB_TYPE", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString, StringVal: string(prowjobv1.PeriodicJob)}},
  1058  				{Name: "PROW_JOB_ID", Value: pipelinev1beta1.ParamValue{Type: pipelinev1beta1.ParamTypeString, StringVal: pj.Name}},
  1059  			}
  1060  			if tc.pipelineRun != nil {
  1061  				expectedRun = tc.pipelineRun(expectedRun)
  1062  			}
  1063  
  1064  			if diff := cmp.Diff(actualRun, &expectedRun); diff != "" {
  1065  				t.Error(diff)
  1066  			}
  1067  		})
  1068  	}
  1069  }
  1070  
  1071  func TestDescription(t *testing.T) {
  1072  	cases := []struct {
  1073  		name     string
  1074  		message  string
  1075  		reason   string
  1076  		fallback string
  1077  		expected string
  1078  	}{
  1079  		{
  1080  			name:     "prefer message over reason or fallback",
  1081  			message:  "hello",
  1082  			reason:   "world",
  1083  			fallback: "doh",
  1084  			expected: "hello",
  1085  		},
  1086  		{
  1087  			name:     "prefer reason over fallback",
  1088  			reason:   "world",
  1089  			fallback: "other",
  1090  			expected: "world",
  1091  		},
  1092  		{
  1093  			name:     "use fallback if nothing else set",
  1094  			fallback: "fancy",
  1095  			expected: "fancy",
  1096  		},
  1097  	}
  1098  
  1099  	for _, tc := range cases {
  1100  		bc := apis.Condition{
  1101  			Message: tc.message,
  1102  			Reason:  tc.reason,
  1103  		}
  1104  		if actual := description(bc, tc.fallback); actual != tc.expected {
  1105  			t.Errorf("%s: actual %q != expected %q", tc.name, actual, tc.expected)
  1106  		}
  1107  	}
  1108  }
  1109  
  1110  func TestProwJobStatus(t *testing.T) {
  1111  	now := metav1.Now()
  1112  	later := metav1.NewTime(now.Time.Add(1 * time.Hour))
  1113  	cases := []struct {
  1114  		name     string
  1115  		input    pipelinev1beta1.PipelineRunStatus
  1116  		state    prowjobv1.ProwJobState
  1117  		desc     string
  1118  		fallback string
  1119  	}{
  1120  		{
  1121  			name:  "empty conditions returns pending/scheduling",
  1122  			state: prowjobv1.PendingState,
  1123  			desc:  descScheduling,
  1124  		},
  1125  		{
  1126  			name: "truly succeeded state returns success",
  1127  			input: pipelinev1beta1.PipelineRunStatus{
  1128  				Status: duckv1.Status{
  1129  					Conditions: []apis.Condition{
  1130  						{
  1131  							Type:    apis.ConditionSucceeded,
  1132  							Status:  corev1.ConditionTrue,
  1133  							Message: "fancy",
  1134  						},
  1135  					},
  1136  				},
  1137  			},
  1138  			state:    prowjobv1.SuccessState,
  1139  			desc:     "fancy",
  1140  			fallback: descSucceeded,
  1141  		},
  1142  		{
  1143  			name: "falsely succeeded state returns failure",
  1144  			input: pipelinev1beta1.PipelineRunStatus{
  1145  				Status: duckv1.Status{
  1146  					Conditions: []apis.Condition{
  1147  						{
  1148  							Type:    apis.ConditionSucceeded,
  1149  							Status:  corev1.ConditionFalse,
  1150  							Message: "weird",
  1151  						},
  1152  					},
  1153  				},
  1154  			},
  1155  			state:    prowjobv1.FailureState,
  1156  			desc:     "weird",
  1157  			fallback: descFailed,
  1158  		},
  1159  		{
  1160  			name: "unstarted job returns pending/initializing",
  1161  			input: pipelinev1beta1.PipelineRunStatus{
  1162  				Status: duckv1.Status{
  1163  					Conditions: []apis.Condition{
  1164  						{
  1165  							Type:    apis.ConditionSucceeded,
  1166  							Status:  corev1.ConditionUnknown,
  1167  							Message: "hola",
  1168  						},
  1169  					},
  1170  				},
  1171  			},
  1172  			state:    prowjobv1.PendingState,
  1173  			desc:     "hola",
  1174  			fallback: descInitializing,
  1175  		},
  1176  		{
  1177  			name: "unfinished job returns running",
  1178  			input: pipelinev1beta1.PipelineRunStatus{
  1179  				PipelineRunStatusFields: pipelinev1beta1.PipelineRunStatusFields{
  1180  					StartTime: now.DeepCopy(),
  1181  				},
  1182  				Status: duckv1.Status{
  1183  					Conditions: []apis.Condition{
  1184  						{
  1185  							Type:    apis.ConditionSucceeded,
  1186  							Status:  corev1.ConditionUnknown,
  1187  							Message: "hola",
  1188  						},
  1189  					},
  1190  				},
  1191  			},
  1192  			state:    prowjobv1.PendingState,
  1193  			desc:     "hola",
  1194  			fallback: descRunning,
  1195  		},
  1196  		{
  1197  			name: "pipelines with unknown success status are still running",
  1198  			input: pipelinev1beta1.PipelineRunStatus{
  1199  				PipelineRunStatusFields: pipelinev1beta1.PipelineRunStatusFields{
  1200  					StartTime:      now.DeepCopy(),
  1201  					CompletionTime: later.DeepCopy(),
  1202  				},
  1203  				Status: duckv1.Status{
  1204  					Conditions: []apis.Condition{
  1205  						{
  1206  							Type:    apis.ConditionSucceeded,
  1207  							Status:  corev1.ConditionUnknown,
  1208  							Message: "hola",
  1209  						},
  1210  					},
  1211  				},
  1212  			},
  1213  			state:    prowjobv1.PendingState,
  1214  			desc:     "hola",
  1215  			fallback: descRunning,
  1216  		},
  1217  		{
  1218  			name: "completed pipelines without a succeeded condition end in error",
  1219  			input: pipelinev1beta1.PipelineRunStatus{
  1220  				PipelineRunStatusFields: pipelinev1beta1.PipelineRunStatusFields{
  1221  					StartTime:      now.DeepCopy(),
  1222  					CompletionTime: later.DeepCopy(),
  1223  				},
  1224  			},
  1225  			state: prowjobv1.ErrorState,
  1226  			desc:  descMissingCondition,
  1227  		},
  1228  	}
  1229  
  1230  	for _, tc := range cases {
  1231  		if len(tc.fallback) > 0 {
  1232  			tc.desc = tc.fallback
  1233  			tc.fallback = ""
  1234  			tc.name += " [fallback]"
  1235  			cond := tc.input.Conditions[0]
  1236  			cond.Message = ""
  1237  			tc.input.Conditions = []apis.Condition{cond}
  1238  			cases = append(cases, tc)
  1239  		}
  1240  	}
  1241  
  1242  	for _, tc := range cases {
  1243  		t.Run(tc.name, func(t *testing.T) {
  1244  			state, desc := prowJobStatus(tc.input)
  1245  			if state != tc.state {
  1246  				t.Errorf("state %q != expected %q", state, tc.state)
  1247  			}
  1248  			if desc != tc.desc {
  1249  				t.Errorf("description %q != expected %q", desc, tc.desc)
  1250  			}
  1251  		})
  1252  	}
  1253  }