github.com/abayer/test-infra@v0.0.5/prow/cmd/build/controller_test.go (about)

     1  /*
     2  Copyright 2018 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  	"errors"
    21  	"fmt"
    22  	"reflect"
    23  	"testing"
    24  	"time"
    25  
    26  	prowjobv1 "k8s.io/test-infra/prow/apis/prowjobs/v1"
    27  
    28  	buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1"
    29  	corev1 "k8s.io/api/core/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  )
    33  
    34  const (
    35  	errorGetProwJob    = "error-get-prowjob"
    36  	errorGetBuild      = "error-get-build"
    37  	errorDeleteBuild   = "error-delete-build"
    38  	errorCreateBuild   = "error-create-build"
    39  	errorUpdateBuild   = "error-update-build"
    40  	errorUpdateProwJob = "error-update-prowjob"
    41  )
    42  
    43  type fakeReconciler struct {
    44  	jobs   map[string]prowjobv1.ProwJob
    45  	builds map[string]buildv1alpha1.Build
    46  	nows   metav1.Time
    47  }
    48  
    49  func key(namespace, name string) string {
    50  	if namespace == "" {
    51  		return name
    52  	}
    53  	return namespace + "/" + name
    54  }
    55  
    56  func (r *fakeReconciler) now() metav1.Time {
    57  	fmt.Println(r.nows)
    58  	return r.nows
    59  }
    60  
    61  func (r *fakeReconciler) getProwJob(namespace, name string) (*prowjobv1.ProwJob, error) {
    62  	if namespace == errorGetProwJob {
    63  		return nil, errors.New("injected create build error")
    64  	}
    65  	k := key(namespace, name)
    66  	pj, present := r.jobs[k]
    67  	if !present {
    68  		return nil, apierrors.NewNotFound(prowjobv1.Resource("ProwJob"), name)
    69  	}
    70  	return &pj, nil
    71  }
    72  func (r *fakeReconciler) getBuild(namespace, name string) (*buildv1alpha1.Build, error) {
    73  	if namespace == errorGetBuild {
    74  		return nil, errors.New("injected create build error")
    75  	}
    76  	k := key(namespace, name)
    77  	b, present := r.builds[k]
    78  	if !present {
    79  		return nil, apierrors.NewNotFound(buildv1alpha1.Resource("Build"), name)
    80  	}
    81  	return &b, nil
    82  }
    83  func (r *fakeReconciler) deleteBuild(namespace, name string) error {
    84  	if namespace == errorDeleteBuild {
    85  		return errors.New("injected create build error")
    86  	}
    87  	k := key(namespace, name)
    88  	if _, present := r.builds[k]; !present {
    89  		return apierrors.NewNotFound(buildv1alpha1.Resource("Build"), name)
    90  	}
    91  	delete(r.builds, k)
    92  	return nil
    93  }
    94  
    95  func (r *fakeReconciler) createBuild(namespace string, b *buildv1alpha1.Build) (*buildv1alpha1.Build, error) {
    96  	if b == nil {
    97  		return nil, errors.New("nil build")
    98  	}
    99  	if namespace == errorCreateBuild {
   100  		return nil, errors.New("injected create build error")
   101  	}
   102  	k := key(namespace, b.Name)
   103  	if _, alreadyExists := r.builds[k]; alreadyExists {
   104  		return nil, apierrors.NewAlreadyExists(prowjobv1.Resource("ProwJob"), b.Name)
   105  	}
   106  	r.builds[k] = *b
   107  	return b, nil
   108  }
   109  
   110  func (r *fakeReconciler) updateBuild(namespace string, b *buildv1alpha1.Build) (*buildv1alpha1.Build, error) {
   111  	if b == nil {
   112  		return nil, errors.New("nil build")
   113  	}
   114  	if namespace == errorUpdateBuild {
   115  		return nil, errors.New("injected update prowjob error")
   116  	}
   117  	k := key(namespace, b.Name)
   118  	b.Generation++ // For some reason this is always changing.
   119  	if _, present := r.builds[k]; !present {
   120  		return nil, apierrors.NewNotFound(buildv1alpha1.Resource("Build"), b.Name)
   121  	}
   122  	r.builds[k] = *b
   123  	return b, nil
   124  }
   125  
   126  func (r *fakeReconciler) updateProwJob(namespace string, pj *prowjobv1.ProwJob) (*prowjobv1.ProwJob, error) {
   127  	if pj == nil {
   128  		return nil, errors.New("nil prowjob")
   129  	}
   130  	if namespace == errorUpdateProwJob {
   131  		return nil, errors.New("injected update prowjob error")
   132  	}
   133  	k := key(namespace, pj.Name)
   134  	if _, present := r.jobs[k]; !present {
   135  		return nil, apierrors.NewNotFound(prowjobv1.Resource("ProwJob"), pj.Name)
   136  	}
   137  	r.jobs[k] = *pj
   138  	return pj, nil
   139  }
   140  
   141  func TestReconcile(t *testing.T) {
   142  	now := metav1.Now()
   143  	buildSpec := buildv1alpha1.BuildSpec{}
   144  	noJobChange := func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   145  		return pj
   146  	}
   147  	noBuildChange := func(_ prowjobv1.ProwJob, b buildv1alpha1.Build) buildv1alpha1.Build {
   148  		return b
   149  	}
   150  	cases := []struct {
   151  		name          string
   152  		namespace     string
   153  		observedJob   *prowjobv1.ProwJob
   154  		observedBuild *buildv1alpha1.Build
   155  		expectedJob   func(prowjobv1.ProwJob, buildv1alpha1.Build) prowjobv1.ProwJob
   156  		expectedBuild func(prowjobv1.ProwJob, buildv1alpha1.Build) buildv1alpha1.Build
   157  		err           bool
   158  	}{
   159  		{
   160  			name: "new prow job creates build",
   161  			observedJob: &prowjobv1.ProwJob{
   162  				Spec: prowjobv1.ProwJobSpec{
   163  					Agent:     prowjobv1.BuildAgent,
   164  					BuildSpec: &buildSpec,
   165  				},
   166  			},
   167  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   168  				pj.Status = prowjobv1.ProwJobStatus{
   169  					StartTime:   now,
   170  					State:       prowjobv1.TriggeredState,
   171  					Description: descScheduling,
   172  				}
   173  				return pj
   174  			},
   175  			expectedBuild: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) buildv1alpha1.Build {
   176  				b, err := makeBuild(pj)
   177  				if err != nil {
   178  					panic(err)
   179  				}
   180  				return *b
   181  			},
   182  		},
   183  		{
   184  			name: "do not create build for failed prowjob",
   185  			observedJob: &prowjobv1.ProwJob{
   186  				Spec: prowjobv1.ProwJobSpec{
   187  					Agent:     prowjobv1.BuildAgent,
   188  					BuildSpec: &buildSpec,
   189  				},
   190  				Status: prowjobv1.ProwJobStatus{
   191  					State: prowjobv1.FailureState,
   192  				},
   193  			},
   194  			expectedJob: noJobChange,
   195  		},
   196  		{
   197  			name: "do not create build for successful prowjob",
   198  			observedJob: &prowjobv1.ProwJob{
   199  				Spec: prowjobv1.ProwJobSpec{
   200  					Agent:     prowjobv1.BuildAgent,
   201  					BuildSpec: &buildSpec,
   202  				},
   203  				Status: prowjobv1.ProwJobStatus{
   204  					State: prowjobv1.SuccessState,
   205  				},
   206  			},
   207  			expectedJob: noJobChange,
   208  		},
   209  		{
   210  			name: "do not create build for aborted prowjob",
   211  			observedJob: &prowjobv1.ProwJob{
   212  				Spec: prowjobv1.ProwJobSpec{
   213  					Agent:     prowjobv1.BuildAgent,
   214  					BuildSpec: &buildSpec,
   215  				},
   216  				Status: prowjobv1.ProwJobStatus{
   217  					State: prowjobv1.AbortedState,
   218  				},
   219  			},
   220  			expectedJob: noJobChange,
   221  		},
   222  		{
   223  			name: "delete build after deleting prowjob",
   224  			observedBuild: func() *buildv1alpha1.Build {
   225  				pj := prowjobv1.ProwJob{}
   226  				pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{}
   227  				b, err := makeBuild(pj)
   228  				if err != nil {
   229  					panic(err)
   230  				}
   231  				return b
   232  			}(),
   233  		},
   234  		{
   235  			name: "only delete builds created by controller",
   236  			observedBuild: func() *buildv1alpha1.Build {
   237  				pj := prowjobv1.ProwJob{}
   238  				pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{}
   239  				b, err := makeBuild(pj)
   240  				if err != nil {
   241  					panic(err)
   242  				}
   243  				b.OwnerReferences = nil
   244  				return b
   245  			}(),
   246  			expectedBuild: noBuildChange,
   247  		},
   248  		{
   249  			name: "update build if spec changes",
   250  			observedJob: &prowjobv1.ProwJob{
   251  				Spec: prowjobv1.ProwJobSpec{
   252  					Agent: prowjobv1.BuildAgent,
   253  					BuildSpec: &buildv1alpha1.BuildSpec{
   254  						ServiceAccountName: "robot",
   255  					},
   256  				},
   257  				Status: prowjobv1.ProwJobStatus{
   258  					State:       prowjobv1.PendingState,
   259  					Description: "fancy",
   260  				},
   261  			},
   262  			observedBuild: func() *buildv1alpha1.Build {
   263  				pj := prowjobv1.ProwJob{}
   264  				pj.Spec.Agent = prowjobv1.BuildAgent
   265  				pj.Spec.BuildSpec = &buildSpec
   266  				b, err := makeBuild(pj)
   267  				if err != nil {
   268  					panic(err)
   269  				}
   270  				return b
   271  			}(),
   272  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   273  				pj.Status = prowjobv1.ProwJobStatus{
   274  					StartTime:   now,
   275  					State:       prowjobv1.TriggeredState,
   276  					Description: descScheduling,
   277  				}
   278  				return pj
   279  			},
   280  			expectedBuild: func(pj prowjobv1.ProwJob, b buildv1alpha1.Build) buildv1alpha1.Build {
   281  				b.Spec = *pj.Spec.BuildSpec
   282  				b.Generation++
   283  				return b
   284  			},
   285  		},
   286  		{
   287  			name: "prowjob goes pending when build starts",
   288  			observedJob: &prowjobv1.ProwJob{
   289  				Spec: prowjobv1.ProwJobSpec{
   290  					Agent:     prowjobv1.BuildAgent,
   291  					BuildSpec: &buildSpec,
   292  				},
   293  				Status: prowjobv1.ProwJobStatus{
   294  					State:       prowjobv1.TriggeredState,
   295  					Description: "fancy",
   296  				},
   297  			},
   298  			observedBuild: func() *buildv1alpha1.Build {
   299  				pj := prowjobv1.ProwJob{}
   300  				pj.Spec.Agent = prowjobv1.BuildAgent
   301  				pj.Spec.BuildSpec = &buildSpec
   302  				b, err := makeBuild(pj)
   303  				if err != nil {
   304  					panic(err)
   305  				}
   306  				b.Status.SetCondition(&buildv1alpha1.BuildCondition{
   307  					Type:    buildv1alpha1.BuildSucceeded,
   308  					Message: "hello",
   309  				})
   310  				b.Status.StartTime = now
   311  				return b
   312  			}(),
   313  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   314  				pj.Status = prowjobv1.ProwJobStatus{
   315  					StartTime:   now,
   316  					State:       prowjobv1.PendingState,
   317  					Description: "hello",
   318  				}
   319  				return pj
   320  			},
   321  			expectedBuild: noBuildChange,
   322  		},
   323  		{
   324  			name: "prowjob succeeds when build succeeds",
   325  			observedJob: &prowjobv1.ProwJob{
   326  				Spec: prowjobv1.ProwJobSpec{
   327  					Agent:     prowjobv1.BuildAgent,
   328  					BuildSpec: &buildSpec,
   329  				},
   330  				Status: prowjobv1.ProwJobStatus{
   331  					State:       prowjobv1.PendingState,
   332  					Description: "fancy",
   333  				},
   334  			},
   335  			observedBuild: func() *buildv1alpha1.Build {
   336  				pj := prowjobv1.ProwJob{}
   337  				pj.Spec.Agent = prowjobv1.BuildAgent
   338  				pj.Spec.BuildSpec = &buildSpec
   339  				b, err := makeBuild(pj)
   340  				if err != nil {
   341  					panic(err)
   342  				}
   343  				b.Status.SetCondition(&buildv1alpha1.BuildCondition{
   344  					Type:    buildv1alpha1.BuildSucceeded,
   345  					Status:  corev1.ConditionTrue,
   346  					Message: "hello",
   347  				})
   348  				b.Status.CompletionTime = now
   349  				b.Status.StartTime = now
   350  				return b
   351  			}(),
   352  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   353  				pj.Status = prowjobv1.ProwJobStatus{
   354  					StartTime:      now,
   355  					CompletionTime: &now,
   356  					State:          prowjobv1.SuccessState,
   357  					Description:    "hello",
   358  				}
   359  				return pj
   360  			},
   361  			expectedBuild: noBuildChange,
   362  		},
   363  		{
   364  			name: "prowjob fails when build fails",
   365  			observedJob: &prowjobv1.ProwJob{
   366  				Spec: prowjobv1.ProwJobSpec{
   367  					Agent:     prowjobv1.BuildAgent,
   368  					BuildSpec: &buildSpec,
   369  				},
   370  				Status: prowjobv1.ProwJobStatus{
   371  					State:       prowjobv1.PendingState,
   372  					Description: "fancy",
   373  				},
   374  			},
   375  			observedBuild: func() *buildv1alpha1.Build {
   376  				pj := prowjobv1.ProwJob{}
   377  				pj.Spec.Agent = prowjobv1.BuildAgent
   378  				pj.Spec.BuildSpec = &buildSpec
   379  				b, err := makeBuild(pj)
   380  				if err != nil {
   381  					panic(err)
   382  				}
   383  				b.Status.SetCondition(&buildv1alpha1.BuildCondition{
   384  					Type:    buildv1alpha1.BuildSucceeded,
   385  					Status:  corev1.ConditionFalse,
   386  					Message: "hello",
   387  				})
   388  				b.Status.StartTime = now
   389  				b.Status.CompletionTime = now
   390  				return b
   391  			}(),
   392  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   393  				pj.Status = prowjobv1.ProwJobStatus{
   394  					StartTime:      now,
   395  					CompletionTime: &now,
   396  					State:          prowjobv1.FailureState,
   397  					Description:    "hello",
   398  				}
   399  				return pj
   400  			},
   401  			expectedBuild: noBuildChange,
   402  		},
   403  		{
   404  			name:      "error when we cannot get prowjob",
   405  			namespace: errorGetProwJob,
   406  			err:       true,
   407  			observedJob: &prowjobv1.ProwJob{
   408  				Spec: prowjobv1.ProwJobSpec{
   409  					Agent:     prowjobv1.BuildAgent,
   410  					BuildSpec: &buildSpec,
   411  				},
   412  				Status: prowjobv1.ProwJobStatus{
   413  					State:       prowjobv1.PendingState,
   414  					Description: "fancy",
   415  				},
   416  			},
   417  		},
   418  		{
   419  			name:      "error when we cannot get build",
   420  			namespace: errorGetBuild,
   421  			err:       true,
   422  			observedBuild: func() *buildv1alpha1.Build {
   423  				pj := prowjobv1.ProwJob{}
   424  				pj.Spec.Agent = prowjobv1.BuildAgent
   425  				pj.Spec.BuildSpec = &buildSpec
   426  				b, err := makeBuild(pj)
   427  				if err != nil {
   428  					panic(err)
   429  				}
   430  				b.Status.SetCondition(&buildv1alpha1.BuildCondition{
   431  					Type:    buildv1alpha1.BuildSucceeded,
   432  					Status:  corev1.ConditionTrue,
   433  					Message: "hello",
   434  				})
   435  				b.Status.CompletionTime = now
   436  				b.Status.StartTime = now
   437  				return b
   438  			}(),
   439  		},
   440  		{
   441  			name:      "error when we cannot delete build",
   442  			namespace: errorDeleteBuild,
   443  			err:       true,
   444  			observedBuild: func() *buildv1alpha1.Build {
   445  				pj := prowjobv1.ProwJob{}
   446  				pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{}
   447  				b, err := makeBuild(pj)
   448  				if err != nil {
   449  					panic(err)
   450  				}
   451  				return b
   452  			}(),
   453  		},
   454  		{
   455  			name:      "error when we cannot create build",
   456  			namespace: errorCreateBuild,
   457  			err:       true,
   458  			observedJob: &prowjobv1.ProwJob{
   459  				Spec: prowjobv1.ProwJobSpec{
   460  					Agent:     prowjobv1.BuildAgent,
   461  					BuildSpec: &buildSpec,
   462  				},
   463  			},
   464  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   465  				pj.Status = prowjobv1.ProwJobStatus{
   466  					StartTime:   now,
   467  					State:       prowjobv1.TriggeredState,
   468  					Description: descScheduling,
   469  				}
   470  				return pj
   471  			},
   472  		},
   473  		{
   474  			name: "error when buildspec is nil",
   475  			err:  true,
   476  			observedJob: &prowjobv1.ProwJob{
   477  				Spec: prowjobv1.ProwJobSpec{
   478  					Agent:     prowjobv1.BuildAgent,
   479  					BuildSpec: nil,
   480  				},
   481  				Status: prowjobv1.ProwJobStatus{
   482  					State: prowjobv1.TriggeredState,
   483  				},
   484  			},
   485  		},
   486  		{
   487  			name:      "error when we cannot update prowjob",
   488  			namespace: errorUpdateProwJob,
   489  			err:       true,
   490  			observedJob: &prowjobv1.ProwJob{
   491  				Spec: prowjobv1.ProwJobSpec{
   492  					Agent:     prowjobv1.BuildAgent,
   493  					BuildSpec: &buildSpec,
   494  				},
   495  				Status: prowjobv1.ProwJobStatus{
   496  					State:       prowjobv1.PendingState,
   497  					Description: "fancy",
   498  				},
   499  			},
   500  			observedBuild: func() *buildv1alpha1.Build {
   501  				pj := prowjobv1.ProwJob{}
   502  				pj.Spec.Agent = prowjobv1.BuildAgent
   503  				pj.Spec.BuildSpec = &buildSpec
   504  				b, err := makeBuild(pj)
   505  				if err != nil {
   506  					panic(err)
   507  				}
   508  				b.Status.SetCondition(&buildv1alpha1.BuildCondition{
   509  					Type:    buildv1alpha1.BuildSucceeded,
   510  					Status:  corev1.ConditionTrue,
   511  					Message: "hello",
   512  				})
   513  				b.Status.CompletionTime = now
   514  				b.Status.StartTime = now
   515  				return b
   516  			}(),
   517  		},
   518  		{
   519  			name:      "error when we cannot update build",
   520  			namespace: errorUpdateBuild,
   521  			err:       true,
   522  			observedJob: &prowjobv1.ProwJob{
   523  				Spec: prowjobv1.ProwJobSpec{
   524  					Agent: prowjobv1.BuildAgent,
   525  					BuildSpec: &buildv1alpha1.BuildSpec{
   526  						ServiceAccountName: "robot",
   527  					},
   528  				},
   529  				Status: prowjobv1.ProwJobStatus{
   530  					State:       prowjobv1.PendingState,
   531  					Description: "fancy",
   532  				},
   533  			},
   534  			observedBuild: func() *buildv1alpha1.Build {
   535  				pj := prowjobv1.ProwJob{}
   536  				pj.Spec.Agent = prowjobv1.BuildAgent
   537  				pj.Spec.BuildSpec = &buildSpec
   538  				b, err := makeBuild(pj)
   539  				if err != nil {
   540  					panic(err)
   541  				}
   542  				return b
   543  			}(),
   544  		},
   545  	}
   546  
   547  	for _, tc := range cases {
   548  		t.Run(tc.name, func(t *testing.T) {
   549  			const name = "the-object-name"
   550  			k := key(tc.namespace, name)
   551  			r := &fakeReconciler{
   552  				jobs:   map[string]prowjobv1.ProwJob{},
   553  				builds: map[string]buildv1alpha1.Build{},
   554  				nows:   now,
   555  			}
   556  			if j := tc.observedJob; j != nil {
   557  				j.Name = name
   558  				r.jobs[k] = *j
   559  			}
   560  			if b := tc.observedBuild; b != nil {
   561  				b.Name = name
   562  				r.builds[k] = *b
   563  			}
   564  			expectedJobs := map[string]prowjobv1.ProwJob{}
   565  			if j := tc.expectedJob; j != nil {
   566  				expectedJobs[k] = j(r.jobs[k], r.builds[k])
   567  			}
   568  			expectedBuilds := map[string]buildv1alpha1.Build{}
   569  			if b := tc.expectedBuild; b != nil {
   570  				expectedBuilds[k] = b(r.jobs[k], r.builds[k])
   571  			}
   572  			err := reconcile(r, k)
   573  			switch {
   574  			case err != nil:
   575  				if !tc.err {
   576  					t.Errorf("unexpected error: %v", err)
   577  				}
   578  			case tc.err:
   579  				t.Error("failed to receive expected error")
   580  			case !reflect.DeepEqual(r.jobs, expectedJobs):
   581  				t.Errorf("prowjobs do not match: %#v != %#v %t", r.jobs, expectedJobs, r.jobs[k].Status.StartTime == expectedJobs[k].Status.StartTime)
   582  			case !reflect.DeepEqual(r.builds, expectedBuilds):
   583  				t.Errorf("builds do not match: %#v != %#v", r.builds, expectedBuilds)
   584  			}
   585  		})
   586  	}
   587  
   588  }
   589  
   590  func TestMakeBuild(t *testing.T) {
   591  	if _, err := makeBuild(prowjobv1.ProwJob{}); err == nil {
   592  		t.Error("failed to receive expected error")
   593  	}
   594  	pj := prowjobv1.ProwJob{}
   595  	pj.Name = "world"
   596  	pj.Namespace = "hello"
   597  	pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{}
   598  	switch b, err := makeBuild(pj); {
   599  	case err != nil:
   600  		t.Errorf("unexpected error: %v", err)
   601  	case !reflect.DeepEqual(b.Spec, *pj.Spec.BuildSpec):
   602  		t.Errorf("buildspecs do not match %#v != expected %#v", b.Spec, pj.Spec.BuildSpec)
   603  	case len(b.OwnerReferences) == 0:
   604  		t.Error("failed to find owner references")
   605  	}
   606  }
   607  
   608  func TestDescription(t *testing.T) {
   609  	cases := []struct {
   610  		name     string
   611  		message  string
   612  		reason   string
   613  		fallback string
   614  		expected string
   615  	}{
   616  		{
   617  			name:     "prefer message over reason or fallback",
   618  			message:  "hello",
   619  			reason:   "world",
   620  			fallback: "doh",
   621  			expected: "hello",
   622  		},
   623  		{
   624  			name:     "prefer reason over fallback",
   625  			reason:   "world",
   626  			fallback: "other",
   627  			expected: "world",
   628  		},
   629  		{
   630  			name:     "use fallback if nothing else set",
   631  			fallback: "fancy",
   632  			expected: "fancy",
   633  		},
   634  	}
   635  
   636  	for _, tc := range cases {
   637  		bc := buildv1alpha1.BuildCondition{
   638  			Message: tc.message,
   639  			Reason:  tc.reason,
   640  		}
   641  		if actual := description(bc, tc.fallback); actual != tc.expected {
   642  			t.Errorf("%s: actual %q != expected %q", tc.name, actual, tc.expected)
   643  		}
   644  	}
   645  }
   646  
   647  func TestProwJobStatus(t *testing.T) {
   648  	now := metav1.Now()
   649  	later := metav1.NewTime(now.Time.Add(1 * time.Hour))
   650  	cases := []struct {
   651  		name     string
   652  		input    buildv1alpha1.BuildStatus
   653  		state    prowjobv1.ProwJobState
   654  		desc     string
   655  		fallback string
   656  	}{
   657  		{
   658  			name:  "empty conditions returns triggered/scheduling",
   659  			state: prowjobv1.TriggeredState,
   660  			desc:  descScheduling,
   661  		},
   662  		{
   663  			name: "truly succeeded state returns success",
   664  			input: buildv1alpha1.BuildStatus{
   665  				Conditions: []buildv1alpha1.BuildCondition{
   666  					{
   667  						Type:    buildv1alpha1.BuildSucceeded,
   668  						Status:  corev1.ConditionTrue,
   669  						Message: "fancy",
   670  					},
   671  				},
   672  			},
   673  			state:    prowjobv1.SuccessState,
   674  			desc:     "fancy",
   675  			fallback: descSucceeded,
   676  		},
   677  		{
   678  			name: "falsely succeeded state returns failure",
   679  			input: buildv1alpha1.BuildStatus{
   680  				Conditions: []buildv1alpha1.BuildCondition{
   681  					{
   682  						Type:    buildv1alpha1.BuildSucceeded,
   683  						Status:  corev1.ConditionFalse,
   684  						Message: "weird",
   685  					},
   686  				},
   687  			},
   688  			state:    prowjobv1.FailureState,
   689  			desc:     "weird",
   690  			fallback: descFailed,
   691  		},
   692  		{
   693  			name: "unstarted job returns triggered/initializing",
   694  			input: buildv1alpha1.BuildStatus{
   695  				Conditions: []buildv1alpha1.BuildCondition{
   696  					{
   697  						Type:    buildv1alpha1.BuildSucceeded,
   698  						Status:  corev1.ConditionUnknown,
   699  						Message: "hola",
   700  					},
   701  				},
   702  			},
   703  			state:    prowjobv1.TriggeredState,
   704  			desc:     "hola",
   705  			fallback: descInitializing,
   706  		},
   707  		{
   708  			name: "unfinished job returns running",
   709  			input: buildv1alpha1.BuildStatus{
   710  				StartTime: now,
   711  				Conditions: []buildv1alpha1.BuildCondition{
   712  					{
   713  						Type:    buildv1alpha1.BuildSucceeded,
   714  						Status:  corev1.ConditionUnknown,
   715  						Message: "hola",
   716  					},
   717  				},
   718  			},
   719  			state:    prowjobv1.PendingState,
   720  			desc:     "hola",
   721  			fallback: descRunning,
   722  		},
   723  		{
   724  			name: "expect a finished job to have a success status",
   725  			input: buildv1alpha1.BuildStatus{
   726  				StartTime:      now,
   727  				CompletionTime: later,
   728  				Conditions: []buildv1alpha1.BuildCondition{
   729  					{
   730  						Type:    buildv1alpha1.BuildSucceeded,
   731  						Status:  corev1.ConditionUnknown,
   732  						Message: "hola",
   733  					},
   734  				},
   735  			},
   736  			state:    prowjobv1.ErrorState,
   737  			desc:     "hola",
   738  			fallback: descUnknown,
   739  		},
   740  		{
   741  			name: "expect a finished job to have a condition",
   742  			input: buildv1alpha1.BuildStatus{
   743  				StartTime:      now,
   744  				CompletionTime: later,
   745  			},
   746  			state: prowjobv1.ErrorState,
   747  			desc:  descMissingCondition,
   748  		},
   749  	}
   750  
   751  	for _, tc := range cases {
   752  		if len(tc.fallback) > 0 {
   753  			tc.desc = tc.fallback
   754  			tc.fallback = ""
   755  			tc.name += " [fallback]"
   756  			cond := tc.input.Conditions[0]
   757  			cond.Message = ""
   758  			tc.input.Conditions = []buildv1alpha1.BuildCondition{cond}
   759  			cases = append(cases, tc)
   760  		}
   761  	}
   762  
   763  	for _, tc := range cases {
   764  		t.Run(tc.name, func(t *testing.T) {
   765  			state, desc := prowJobStatus(tc.input)
   766  			if state != tc.state {
   767  				t.Errorf("state %q != expected %q", state, tc.state)
   768  			}
   769  			if desc != tc.desc {
   770  				t.Errorf("description %q != expected %q", desc, tc.desc)
   771  			}
   772  		})
   773  	}
   774  }