github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/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  	buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1"
    27  	duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/equality"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/util/diff"
    33  
    34  	prowjobv1 "k8s.io/test-infra/prow/apis/prowjobs/v1"
    35  	"k8s.io/test-infra/prow/kube"
    36  	"k8s.io/test-infra/prow/pod-utils/decorate"
    37  )
    38  
    39  const (
    40  	errorGetProwJob    = "error-get-prowjob"
    41  	errorGetBuild      = "error-get-build"
    42  	errorDeleteBuild   = "error-delete-build"
    43  	errorCreateBuild   = "error-create-build"
    44  	errorUpdateProwJob = "error-update-prowjob"
    45  )
    46  
    47  type fakeReconciler struct {
    48  	jobs   map[string]prowjobv1.ProwJob
    49  	builds map[string]buildv1alpha1.Build
    50  	nows   metav1.Time
    51  }
    52  
    53  func (r *fakeReconciler) now() metav1.Time {
    54  	fmt.Println(r.nows)
    55  	return r.nows
    56  }
    57  
    58  const fakePJCtx = "prow-context"
    59  const fakePJNS = "prow-job"
    60  
    61  func (r *fakeReconciler) getProwJob(name string) (*prowjobv1.ProwJob, error) {
    62  	if name == errorGetProwJob {
    63  		return nil, errors.New("injected get prowjob error")
    64  	}
    65  	k := toKey(fakePJCtx, fakePJNS, 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  
    73  func (r *fakeReconciler) updateProwJob(pj *prowjobv1.ProwJob) (*prowjobv1.ProwJob, error) {
    74  	if pj.Name == errorUpdateProwJob {
    75  		return nil, errors.New("injected update prowjob error")
    76  	}
    77  	if pj == nil {
    78  		return nil, errors.New("nil prowjob")
    79  	}
    80  	k := toKey(fakePJCtx, fakePJNS, pj.Name)
    81  	if _, present := r.jobs[k]; !present {
    82  		return nil, apierrors.NewNotFound(prowjobv1.Resource("ProwJob"), pj.Name)
    83  	}
    84  	r.jobs[k] = *pj
    85  	return pj, nil
    86  }
    87  
    88  func (r *fakeReconciler) getBuild(context, namespace, name string) (*buildv1alpha1.Build, error) {
    89  	if namespace == errorGetBuild {
    90  		return nil, errors.New("injected create build error")
    91  	}
    92  	k := toKey(context, namespace, name)
    93  	b, present := r.builds[k]
    94  	if !present {
    95  		return nil, apierrors.NewNotFound(buildv1alpha1.Resource("Build"), name)
    96  	}
    97  	return &b, nil
    98  }
    99  func (r *fakeReconciler) deleteBuild(context, namespace, name string) error {
   100  	if namespace == errorDeleteBuild {
   101  		return errors.New("injected create build error")
   102  	}
   103  	k := toKey(context, namespace, name)
   104  	if _, present := r.builds[k]; !present {
   105  		return apierrors.NewNotFound(buildv1alpha1.Resource("Build"), name)
   106  	}
   107  	delete(r.builds, k)
   108  	return nil
   109  }
   110  
   111  func (r *fakeReconciler) createBuild(context, namespace string, b *buildv1alpha1.Build) (*buildv1alpha1.Build, error) {
   112  	if b == nil {
   113  		return nil, errors.New("nil build")
   114  	}
   115  	if namespace == errorCreateBuild {
   116  		return nil, errors.New("injected create build error")
   117  	}
   118  	k := toKey(context, namespace, b.Name)
   119  	if _, alreadyExists := r.builds[k]; alreadyExists {
   120  		return nil, apierrors.NewAlreadyExists(prowjobv1.Resource("ProwJob"), b.Name)
   121  	}
   122  	r.builds[k] = *b
   123  	return b, nil
   124  }
   125  
   126  func (r *fakeReconciler) buildID(pj prowjobv1.ProwJob) (string, error) {
   127  	return "7777777777", nil
   128  }
   129  
   130  type fakeLimiter struct {
   131  	added string
   132  }
   133  
   134  func (fl *fakeLimiter) ShutDown() {}
   135  func (fl *fakeLimiter) Get() (interface{}, bool) {
   136  	return "not implemented", true
   137  }
   138  func (fl *fakeLimiter) Done(interface{})   {}
   139  func (fl *fakeLimiter) Forget(interface{}) {}
   140  func (fl *fakeLimiter) AddRateLimited(a interface{}) {
   141  	fl.added = a.(string)
   142  }
   143  
   144  func TestEnqueueKey(t *testing.T) {
   145  	cases := []struct {
   146  		name     string
   147  		context  string
   148  		obj      interface{}
   149  		expected string
   150  	}{
   151  		{
   152  			name:    "enqueue build directly",
   153  			context: "hey",
   154  			obj: &buildv1alpha1.Build{
   155  				ObjectMeta: metav1.ObjectMeta{
   156  					Namespace: "foo",
   157  					Name:      "bar",
   158  				},
   159  			},
   160  			expected: toKey("hey", "foo", "bar"),
   161  		},
   162  		{
   163  			name:    "enqueue prowjob's spec namespace",
   164  			context: "rolo",
   165  			obj: &prowjobv1.ProwJob{
   166  				ObjectMeta: metav1.ObjectMeta{
   167  					Namespace: "default",
   168  					Name:      "dude",
   169  				},
   170  				Spec: prowjobv1.ProwJobSpec{
   171  					Namespace: "tomassi",
   172  				},
   173  			},
   174  			expected: toKey("rolo", "tomassi", "dude"),
   175  		},
   176  		{
   177  			name:    "ignore random object",
   178  			context: "foo",
   179  			obj:     "bar",
   180  		},
   181  	}
   182  
   183  	for _, tc := range cases {
   184  		t.Run(tc.name, func(t *testing.T) {
   185  			var fl fakeLimiter
   186  			c := controller{
   187  				workqueue: &fl,
   188  			}
   189  			c.enqueueKey(tc.context, tc.obj)
   190  			if !reflect.DeepEqual(fl.added, tc.expected) {
   191  				t.Errorf("%q != expected %q", fl.added, tc.expected)
   192  			}
   193  		})
   194  	}
   195  }
   196  
   197  func TestReconcile(t *testing.T) {
   198  	now := metav1.Now()
   199  	buildSpec := buildv1alpha1.BuildSpec{}
   200  	noJobChange := func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   201  		return pj
   202  	}
   203  	noBuildChange := func(_ prowjobv1.ProwJob, b buildv1alpha1.Build) buildv1alpha1.Build {
   204  		return b
   205  	}
   206  	cases := []struct {
   207  		name          string
   208  		namespace     string
   209  		context       string
   210  		observedJob   *prowjobv1.ProwJob
   211  		observedBuild *buildv1alpha1.Build
   212  		expectedJob   func(prowjobv1.ProwJob, buildv1alpha1.Build) prowjobv1.ProwJob
   213  		expectedBuild func(prowjobv1.ProwJob, buildv1alpha1.Build) buildv1alpha1.Build
   214  		err           bool
   215  	}{{
   216  		name: "new prow job creates build",
   217  		observedJob: &prowjobv1.ProwJob{
   218  			Spec: prowjobv1.ProwJobSpec{
   219  				Agent:     prowjobv1.KnativeBuildAgent,
   220  				BuildSpec: &buildSpec,
   221  			},
   222  		},
   223  		expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   224  			pj.Status = prowjobv1.ProwJobStatus{
   225  				StartTime:   now,
   226  				State:       prowjobv1.TriggeredState,
   227  				Description: descScheduling,
   228  			}
   229  			return pj
   230  		},
   231  		expectedBuild: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) buildv1alpha1.Build {
   232  			pj.Spec.Type = prowjobv1.PeriodicJob
   233  			b, err := makeBuild(pj, "50")
   234  			if err != nil {
   235  				panic(err)
   236  			}
   237  			return *b
   238  		},
   239  	},
   240  		{
   241  			name: "do not create build for failed prowjob",
   242  			observedJob: &prowjobv1.ProwJob{
   243  				Spec: prowjobv1.ProwJobSpec{
   244  					Agent:     prowjobv1.KnativeBuildAgent,
   245  					BuildSpec: &buildSpec,
   246  				},
   247  				Status: prowjobv1.ProwJobStatus{
   248  					State: prowjobv1.FailureState,
   249  				},
   250  			},
   251  			expectedJob: noJobChange,
   252  		},
   253  		{
   254  			name: "do not create build for successful prowjob",
   255  			observedJob: &prowjobv1.ProwJob{
   256  				Spec: prowjobv1.ProwJobSpec{
   257  					Agent:     prowjobv1.KnativeBuildAgent,
   258  					BuildSpec: &buildSpec,
   259  				},
   260  				Status: prowjobv1.ProwJobStatus{
   261  					State: prowjobv1.SuccessState,
   262  				},
   263  			},
   264  			expectedJob: noJobChange,
   265  		},
   266  		{
   267  			name: "do not create build for aborted prowjob",
   268  			observedJob: &prowjobv1.ProwJob{
   269  				Spec: prowjobv1.ProwJobSpec{
   270  					Agent:     prowjobv1.KnativeBuildAgent,
   271  					BuildSpec: &buildSpec,
   272  				},
   273  				Status: prowjobv1.ProwJobStatus{
   274  					State: prowjobv1.AbortedState,
   275  				},
   276  			},
   277  			expectedJob: noJobChange,
   278  		},
   279  		{
   280  			name: "delete build after deleting prowjob",
   281  			observedBuild: func() *buildv1alpha1.Build {
   282  				pj := prowjobv1.ProwJob{}
   283  				pj.Spec.Type = prowjobv1.PeriodicJob
   284  				pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{}
   285  				b, err := makeBuild(pj, "7")
   286  				if err != nil {
   287  					panic(err)
   288  				}
   289  				return b
   290  			}(),
   291  		},
   292  		{
   293  			name: "do not delete deleted builds",
   294  			observedBuild: func() *buildv1alpha1.Build {
   295  				pj := prowjobv1.ProwJob{}
   296  				pj.Spec.Type = prowjobv1.PeriodicJob
   297  				pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{}
   298  				b, err := makeBuild(pj, "6")
   299  				b.DeletionTimestamp = &now
   300  				if err != nil {
   301  					panic(err)
   302  				}
   303  				return b
   304  			}(),
   305  			expectedBuild: noBuildChange,
   306  		},
   307  		{
   308  			name: "only delete builds created by controller",
   309  			observedBuild: func() *buildv1alpha1.Build {
   310  				pj := prowjobv1.ProwJob{}
   311  				pj.Spec.Type = prowjobv1.PeriodicJob
   312  				pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{}
   313  				b, err := makeBuild(pj, "9999")
   314  				if err != nil {
   315  					panic(err)
   316  				}
   317  				delete(b.Labels, kube.CreatedByProw)
   318  				return b
   319  			}(),
   320  			expectedBuild: noBuildChange,
   321  		},
   322  		{
   323  			name:    "delete prow builds in the wrong cluster",
   324  			context: "wrong-cluster",
   325  			observedJob: &prowjobv1.ProwJob{
   326  				Spec: prowjobv1.ProwJobSpec{
   327  					Agent:   prowjobv1.KnativeBuildAgent,
   328  					Cluster: "target-cluster",
   329  					BuildSpec: &buildv1alpha1.BuildSpec{
   330  						ServiceAccountName: "robot",
   331  					},
   332  				},
   333  				Status: prowjobv1.ProwJobStatus{
   334  					State:       prowjobv1.PendingState,
   335  					StartTime:   metav1.Now(),
   336  					Description: "fancy",
   337  				},
   338  			},
   339  			observedBuild: func() *buildv1alpha1.Build {
   340  				pj := prowjobv1.ProwJob{}
   341  				pj.Spec.Type = prowjobv1.PeriodicJob
   342  				pj.Spec.Agent = prowjobv1.KnativeBuildAgent
   343  				pj.Spec.BuildSpec = &buildSpec
   344  				b, err := makeBuild(pj, "5")
   345  				if err != nil {
   346  					panic(err)
   347  				}
   348  				return b
   349  			}(),
   350  			expectedJob: noJobChange,
   351  		},
   352  		{
   353  			name:    "ignore random builds in the wrong cluster",
   354  			context: "wrong-cluster",
   355  			observedJob: &prowjobv1.ProwJob{
   356  				Spec: prowjobv1.ProwJobSpec{
   357  					Agent:   prowjobv1.KnativeBuildAgent,
   358  					Cluster: "target-cluster",
   359  					BuildSpec: &buildv1alpha1.BuildSpec{
   360  						ServiceAccountName: "robot",
   361  					},
   362  				},
   363  				Status: prowjobv1.ProwJobStatus{
   364  					State:       prowjobv1.PendingState,
   365  					StartTime:   metav1.Now(),
   366  					Description: "fancy",
   367  				},
   368  			},
   369  			observedBuild: func() *buildv1alpha1.Build {
   370  				pj := prowjobv1.ProwJob{}
   371  				pj.Spec.Type = prowjobv1.PeriodicJob
   372  				pj.Spec.Agent = prowjobv1.KnativeBuildAgent
   373  				pj.Spec.BuildSpec = &buildSpec
   374  				b, err := makeBuild(pj, "5")
   375  				if err != nil {
   376  					panic(err)
   377  				}
   378  				delete(b.Labels, kube.CreatedByProw)
   379  				return b
   380  			}(),
   381  			expectedJob:   noJobChange,
   382  			expectedBuild: noBuildChange,
   383  		},
   384  		{
   385  			name: "update job status if build resets",
   386  			observedJob: &prowjobv1.ProwJob{
   387  				Spec: prowjobv1.ProwJobSpec{
   388  					Agent: prowjobv1.KnativeBuildAgent,
   389  					BuildSpec: &buildv1alpha1.BuildSpec{
   390  						ServiceAccountName: "robot",
   391  					},
   392  				},
   393  				Status: prowjobv1.ProwJobStatus{
   394  					State:       prowjobv1.PendingState,
   395  					StartTime:   metav1.Now(),
   396  					Description: "fancy",
   397  				},
   398  			},
   399  			observedBuild: func() *buildv1alpha1.Build {
   400  				pj := prowjobv1.ProwJob{}
   401  				pj.Spec.Type = prowjobv1.PeriodicJob
   402  				pj.Spec.Agent = prowjobv1.KnativeBuildAgent
   403  				pj.Spec.BuildSpec = &buildSpec
   404  				b, err := makeBuild(pj, "5")
   405  				if err != nil {
   406  					panic(err)
   407  				}
   408  				return b
   409  			}(),
   410  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   411  				pj.Status.State = prowjobv1.TriggeredState
   412  				pj.Status.Description = descScheduling
   413  				return pj
   414  			},
   415  			expectedBuild: noBuildChange,
   416  		},
   417  		{
   418  			name: "prowjob goes pending when build starts",
   419  			observedJob: &prowjobv1.ProwJob{
   420  				Spec: prowjobv1.ProwJobSpec{
   421  					Agent:     prowjobv1.KnativeBuildAgent,
   422  					BuildSpec: &buildSpec,
   423  				},
   424  				Status: prowjobv1.ProwJobStatus{
   425  					State:       prowjobv1.TriggeredState,
   426  					Description: "fancy",
   427  				},
   428  			},
   429  			observedBuild: func() *buildv1alpha1.Build {
   430  				pj := prowjobv1.ProwJob{}
   431  				pj.Spec.Type = prowjobv1.PeriodicJob
   432  				pj.Spec.Agent = prowjobv1.KnativeBuildAgent
   433  				pj.Spec.BuildSpec = &buildSpec
   434  				b, err := makeBuild(pj, "1")
   435  				if err != nil {
   436  					panic(err)
   437  				}
   438  				b.Status.SetCondition(&duckv1alpha1.Condition{
   439  					Type:    buildv1alpha1.BuildSucceeded,
   440  					Message: "hello",
   441  				})
   442  				b.Status.StartTime = now
   443  				return b
   444  			}(),
   445  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   446  				pj.Status = prowjobv1.ProwJobStatus{
   447  					StartTime:   now,
   448  					State:       prowjobv1.PendingState,
   449  					Description: "hello",
   450  				}
   451  				return pj
   452  			},
   453  			expectedBuild: noBuildChange,
   454  		},
   455  		{
   456  			name: "prowjob succeeds when build succeeds",
   457  			observedJob: &prowjobv1.ProwJob{
   458  				Spec: prowjobv1.ProwJobSpec{
   459  					Agent:     prowjobv1.KnativeBuildAgent,
   460  					BuildSpec: &buildSpec,
   461  				},
   462  				Status: prowjobv1.ProwJobStatus{
   463  					State:       prowjobv1.PendingState,
   464  					Description: "fancy",
   465  				},
   466  			},
   467  			observedBuild: func() *buildv1alpha1.Build {
   468  				pj := prowjobv1.ProwJob{}
   469  				pj.Spec.Type = prowjobv1.PeriodicJob
   470  				pj.Spec.Agent = prowjobv1.KnativeBuildAgent
   471  				pj.Spec.BuildSpec = &buildSpec
   472  				b, err := makeBuild(pj, "22")
   473  				if err != nil {
   474  					panic(err)
   475  				}
   476  				b.Status.SetCondition(&duckv1alpha1.Condition{
   477  					Type:    buildv1alpha1.BuildSucceeded,
   478  					Status:  corev1.ConditionTrue,
   479  					Message: "hello",
   480  				})
   481  				b.Status.CompletionTime = now
   482  				b.Status.StartTime = now
   483  				return b
   484  			}(),
   485  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   486  				pj.Status = prowjobv1.ProwJobStatus{
   487  					StartTime:      now,
   488  					CompletionTime: &now,
   489  					State:          prowjobv1.SuccessState,
   490  					Description:    "hello",
   491  				}
   492  				return pj
   493  			},
   494  			expectedBuild: noBuildChange,
   495  		},
   496  		{
   497  			name: "prowjob fails when build fails",
   498  			observedJob: &prowjobv1.ProwJob{
   499  				Spec: prowjobv1.ProwJobSpec{
   500  					Agent:     prowjobv1.KnativeBuildAgent,
   501  					BuildSpec: &buildSpec,
   502  				},
   503  				Status: prowjobv1.ProwJobStatus{
   504  					State:       prowjobv1.PendingState,
   505  					Description: "fancy",
   506  				},
   507  			},
   508  			observedBuild: func() *buildv1alpha1.Build {
   509  				pj := prowjobv1.ProwJob{}
   510  				pj.Spec.Type = prowjobv1.PeriodicJob
   511  				pj.Spec.Agent = prowjobv1.KnativeBuildAgent
   512  				pj.Spec.BuildSpec = &buildSpec
   513  				b, err := makeBuild(pj, "21")
   514  				if err != nil {
   515  					panic(err)
   516  				}
   517  				b.Status.SetCondition(&duckv1alpha1.Condition{
   518  					Type:    buildv1alpha1.BuildSucceeded,
   519  					Status:  corev1.ConditionFalse,
   520  					Message: "hello",
   521  				})
   522  				b.Status.StartTime = now
   523  				b.Status.CompletionTime = now
   524  				return b
   525  			}(),
   526  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   527  				pj.Status = prowjobv1.ProwJobStatus{
   528  					StartTime:      now,
   529  					CompletionTime: &now,
   530  					State:          prowjobv1.FailureState,
   531  					Description:    "hello",
   532  				}
   533  				return pj
   534  			},
   535  			expectedBuild: noBuildChange,
   536  		},
   537  		{
   538  			name:      "error when we cannot get prowjob",
   539  			namespace: errorGetProwJob,
   540  			err:       true,
   541  			observedJob: &prowjobv1.ProwJob{
   542  				Spec: prowjobv1.ProwJobSpec{
   543  					Agent:     prowjobv1.KnativeBuildAgent,
   544  					BuildSpec: &buildSpec,
   545  				},
   546  				Status: prowjobv1.ProwJobStatus{
   547  					State:       prowjobv1.PendingState,
   548  					Description: "fancy",
   549  				},
   550  			},
   551  		},
   552  		{
   553  			name:      "error when we cannot get build",
   554  			namespace: errorGetBuild,
   555  			err:       true,
   556  			observedBuild: func() *buildv1alpha1.Build {
   557  				pj := prowjobv1.ProwJob{}
   558  				pj.Spec.Type = prowjobv1.PeriodicJob
   559  				pj.Spec.Agent = prowjobv1.KnativeBuildAgent
   560  				pj.Spec.BuildSpec = &buildSpec
   561  				b, err := makeBuild(pj, "-72")
   562  				if err != nil {
   563  					panic(err)
   564  				}
   565  				b.Status.SetCondition(&duckv1alpha1.Condition{
   566  					Type:    buildv1alpha1.BuildSucceeded,
   567  					Status:  corev1.ConditionTrue,
   568  					Message: "hello",
   569  				})
   570  				b.Status.CompletionTime = now
   571  				b.Status.StartTime = now
   572  				return b
   573  			}(),
   574  		},
   575  		{
   576  			name:      "error when we cannot delete build",
   577  			namespace: errorDeleteBuild,
   578  			err:       true,
   579  			observedBuild: func() *buildv1alpha1.Build {
   580  				pj := prowjobv1.ProwJob{}
   581  				pj.Spec.Type = prowjobv1.PeriodicJob
   582  				pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{}
   583  				b, err := makeBuild(pj, "44")
   584  				if err != nil {
   585  					panic(err)
   586  				}
   587  				return b
   588  			}(),
   589  		},
   590  		{
   591  			name:      "error when we cannot create build",
   592  			namespace: errorCreateBuild,
   593  			err:       true,
   594  			observedJob: &prowjobv1.ProwJob{
   595  				Spec: prowjobv1.ProwJobSpec{
   596  					Agent:     prowjobv1.KnativeBuildAgent,
   597  					BuildSpec: &buildSpec,
   598  				},
   599  			},
   600  			expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob {
   601  				pj.Status = prowjobv1.ProwJobStatus{
   602  					StartTime:   now,
   603  					State:       prowjobv1.TriggeredState,
   604  					Description: descScheduling,
   605  				}
   606  				return pj
   607  			},
   608  		},
   609  		{
   610  			name: "error when buildspec is nil",
   611  			err:  true,
   612  			observedJob: &prowjobv1.ProwJob{
   613  				Spec: prowjobv1.ProwJobSpec{
   614  					Agent:     prowjobv1.KnativeBuildAgent,
   615  					BuildSpec: nil,
   616  				},
   617  				Status: prowjobv1.ProwJobStatus{
   618  					State: prowjobv1.TriggeredState,
   619  				},
   620  			},
   621  		},
   622  		{
   623  			name:      "error when we cannot update prowjob",
   624  			namespace: errorUpdateProwJob,
   625  			err:       true,
   626  			observedJob: &prowjobv1.ProwJob{
   627  				Spec: prowjobv1.ProwJobSpec{
   628  					Agent:     prowjobv1.KnativeBuildAgent,
   629  					BuildSpec: &buildSpec,
   630  				},
   631  				Status: prowjobv1.ProwJobStatus{
   632  					State:       prowjobv1.PendingState,
   633  					Description: "fancy",
   634  				},
   635  			},
   636  			observedBuild: func() *buildv1alpha1.Build {
   637  				pj := prowjobv1.ProwJob{}
   638  				pj.Spec.Type = prowjobv1.PeriodicJob
   639  				pj.Spec.Agent = prowjobv1.KnativeBuildAgent
   640  				pj.Spec.BuildSpec = &buildSpec
   641  				b, err := makeBuild(pj, "42")
   642  				if err != nil {
   643  					panic(err)
   644  				}
   645  				b.Status.SetCondition(&duckv1alpha1.Condition{
   646  					Type:    buildv1alpha1.BuildSucceeded,
   647  					Status:  corev1.ConditionTrue,
   648  					Message: "hello",
   649  				})
   650  				b.Status.CompletionTime = now
   651  				b.Status.StartTime = now
   652  				return b
   653  			}(),
   654  		}}
   655  
   656  	for _, tc := range cases {
   657  		t.Run(tc.name, func(t *testing.T) {
   658  			name := "the-object-name"
   659  			// prowjobs all live in the same ns, so use name for injecting errors
   660  			if tc.namespace == errorGetProwJob {
   661  				name = errorGetProwJob
   662  			} else if tc.namespace == errorUpdateProwJob {
   663  				name = errorUpdateProwJob
   664  			}
   665  			bk := toKey(tc.context, tc.namespace, name)
   666  			jk := toKey(fakePJCtx, fakePJNS, name)
   667  			r := &fakeReconciler{
   668  				jobs:   map[string]prowjobv1.ProwJob{},
   669  				builds: map[string]buildv1alpha1.Build{},
   670  				nows:   now,
   671  			}
   672  			if j := tc.observedJob; j != nil {
   673  				j.Name = name
   674  				j.Spec.Type = prowjobv1.PeriodicJob
   675  				r.jobs[jk] = *j
   676  			}
   677  			if b := tc.observedBuild; b != nil {
   678  				b.Name = name
   679  				r.builds[bk] = *b
   680  			}
   681  			expectedJobs := map[string]prowjobv1.ProwJob{}
   682  			if j := tc.expectedJob; j != nil {
   683  				expectedJobs[jk] = j(r.jobs[jk], r.builds[bk])
   684  			}
   685  			expectedBuilds := map[string]buildv1alpha1.Build{}
   686  			if b := tc.expectedBuild; b != nil {
   687  				expectedBuilds[bk] = b(r.jobs[jk], r.builds[bk])
   688  			}
   689  			err := reconcile(r, bk)
   690  			switch {
   691  			case err != nil:
   692  				if !tc.err {
   693  					t.Errorf("unexpected error: %v", err)
   694  				}
   695  			case tc.err:
   696  				t.Error("failed to receive expected error")
   697  			case !equality.Semantic.DeepEqual(r.jobs, expectedJobs):
   698  				t.Errorf("prowjobs do not match:\n%s", diff.ObjectReflectDiff(expectedJobs, r.jobs))
   699  			case !equality.Semantic.DeepEqual(r.builds, expectedBuilds):
   700  				t.Errorf("builds do not match:\n%s", diff.ObjectReflectDiff(expectedBuilds, r.builds))
   701  			}
   702  		})
   703  	}
   704  
   705  }
   706  
   707  func TestDefaultArguments(t *testing.T) {
   708  	cases := []struct {
   709  		name     string
   710  		t        buildv1alpha1.TemplateInstantiationSpec
   711  		env      map[string]string
   712  		expected buildv1alpha1.TemplateInstantiationSpec
   713  	}{
   714  		{
   715  			name: "nothing set works",
   716  		},
   717  		{
   718  			name: "add env",
   719  			env: map[string]string{
   720  				"hello": "world",
   721  			},
   722  			expected: buildv1alpha1.TemplateInstantiationSpec{
   723  				Arguments: []buildv1alpha1.ArgumentSpec{{Name: "hello", Value: "world"}},
   724  			},
   725  		},
   726  		{
   727  			name: "do not override env",
   728  			t: buildv1alpha1.TemplateInstantiationSpec{
   729  				Arguments: []buildv1alpha1.ArgumentSpec{
   730  					{Name: "ignore", Value: "this"},
   731  					{Name: "keep", Value: "original value"},
   732  				},
   733  			},
   734  			env: map[string]string{
   735  				"hello": "world",
   736  				"keep":  "should not see this",
   737  			},
   738  			expected: buildv1alpha1.TemplateInstantiationSpec{
   739  				Arguments: []buildv1alpha1.ArgumentSpec{
   740  					{Name: "ignore", Value: "this"},
   741  					{Name: "keep", Value: "original value"},
   742  					{Name: "hello", Value: "world"},
   743  				},
   744  			},
   745  		},
   746  	}
   747  
   748  	for _, tc := range cases {
   749  		t.Run(tc.name, func(t *testing.T) {
   750  			templ := tc.t
   751  			defaultArguments(&templ, tc.env)
   752  			if !equality.Semantic.DeepEqual(templ, tc.expected) {
   753  				t.Errorf("builds do not match:\n%s", diff.ObjectReflectDiff(&tc.expected, templ))
   754  			}
   755  
   756  		})
   757  	}
   758  }
   759  
   760  func TestDefaultEnv(t *testing.T) {
   761  	cases := []struct {
   762  		name     string
   763  		c        corev1.Container
   764  		env      map[string]string
   765  		expected corev1.Container
   766  	}{
   767  		{
   768  			name: "nothing set works",
   769  		},
   770  		{
   771  			name: "add env",
   772  			env: map[string]string{
   773  				"hello": "world",
   774  			},
   775  			expected: corev1.Container{
   776  				Env: []corev1.EnvVar{{Name: "hello", Value: "world"}},
   777  			},
   778  		},
   779  		{
   780  			name: "do not override env",
   781  			c: corev1.Container{
   782  				Env: []corev1.EnvVar{
   783  					{Name: "ignore", Value: "this"},
   784  					{Name: "keep", Value: "original value"},
   785  				},
   786  			},
   787  			env: map[string]string{
   788  				"hello": "world",
   789  				"keep":  "should not see this",
   790  			},
   791  			expected: corev1.Container{
   792  				Env: []corev1.EnvVar{
   793  					{Name: "ignore", Value: "this"},
   794  					{Name: "keep", Value: "original value"},
   795  					{Name: "hello", Value: "world"},
   796  				},
   797  			},
   798  		},
   799  	}
   800  
   801  	for _, tc := range cases {
   802  		t.Run(tc.name, func(t *testing.T) {
   803  			c := tc.c
   804  			defaultEnv(&c, tc.env)
   805  			if !equality.Semantic.DeepEqual(c, tc.expected) {
   806  				t.Errorf("builds do not match:\n%s", diff.ObjectReflectDiff(&tc.expected, c))
   807  			}
   808  		})
   809  	}
   810  }
   811  
   812  func TestInjectSource(t *testing.T) {
   813  	cases := []struct {
   814  		name     string
   815  		build    buildv1alpha1.Build
   816  		pj       prowjobv1.ProwJob
   817  		expected func(*buildv1alpha1.Build, prowjobv1.ProwJob)
   818  		err      bool
   819  	}{
   820  		{
   821  			name: "do nothing when source is set",
   822  			build: buildv1alpha1.Build{
   823  				Spec: buildv1alpha1.BuildSpec{
   824  					Source: &buildv1alpha1.SourceSpec{},
   825  				},
   826  			},
   827  		},
   828  		{
   829  			name: "do nothing when no refs are set",
   830  			pj: prowjobv1.ProwJob{
   831  				Spec: prowjobv1.ProwJobSpec{
   832  					Type: prowjobv1.PeriodicJob,
   833  				},
   834  			},
   835  		},
   836  		{
   837  			name: "inject source, volumes, workdir when refs are set",
   838  			build: buildv1alpha1.Build{
   839  				Spec: buildv1alpha1.BuildSpec{
   840  					Steps: []corev1.Container{
   841  						{}, // Override
   842  						{WorkingDir: "do not override"},
   843  					},
   844  					Template: &buildv1alpha1.TemplateInstantiationSpec{},
   845  				},
   846  			},
   847  			pj: prowjobv1.ProwJob{
   848  				Spec: prowjobv1.ProwJobSpec{
   849  					ExtraRefs: []prowjobv1.Refs{{Org: "hi", Repo: "there"}},
   850  					DecorationConfig: &prowjobv1.DecorationConfig{
   851  						UtilityImages: &prowjobv1.UtilityImages{},
   852  					},
   853  				},
   854  			},
   855  			expected: func(b *buildv1alpha1.Build, pj prowjobv1.ProwJob) {
   856  				src, _, cloneVolumes, err := decorate.CloneRefs(pj, codeMount, logMount)
   857  				if err != nil {
   858  					t.Fatalf("failed to make clonerefs container: %v", err)
   859  				}
   860  				src.Name = ""
   861  				b.Spec.Volumes = append(b.Spec.Volumes, cloneVolumes...)
   862  				b.Spec.Source = &buildv1alpha1.SourceSpec{
   863  					Custom: src,
   864  				}
   865  				wd := workDir(pj.Spec.ExtraRefs[0])
   866  				b.Spec.Template.Arguments = append(b.Spec.Template.Arguments, wd)
   867  				b.Spec.Steps[0].WorkingDir = wd.Value
   868  
   869  			},
   870  		},
   871  	}
   872  
   873  	for _, tc := range cases {
   874  		t.Run(tc.name, func(t *testing.T) {
   875  			expected := tc.build
   876  			if tc.expected != nil {
   877  				tc.expected(&expected, tc.pj)
   878  			}
   879  
   880  			actual := &tc.build
   881  			err := injectSource(actual, tc.pj)
   882  			switch {
   883  			case err != nil:
   884  				if !tc.err {
   885  					t.Errorf("unexpected error: %v", err)
   886  				}
   887  			case tc.err:
   888  				t.Error("failed to return expected error")
   889  			case !equality.Semantic.DeepEqual(actual, &expected):
   890  				t.Errorf("builds do not match:\n%s", diff.ObjectReflectDiff(&expected, actual))
   891  			}
   892  		})
   893  	}
   894  }
   895  
   896  func TestBuildMeta(t *testing.T) {
   897  	cases := []struct {
   898  		name     string
   899  		pj       prowjobv1.ProwJob
   900  		expected func(prowjobv1.ProwJob, *metav1.ObjectMeta)
   901  	}{
   902  		{
   903  			name: "Use pj.Spec.Namespace for build namespace",
   904  			pj: prowjobv1.ProwJob{
   905  				ObjectMeta: metav1.ObjectMeta{
   906  					Name:      "whatever",
   907  					Namespace: "wrong",
   908  				},
   909  				Spec: prowjobv1.ProwJobSpec{
   910  					Namespace: "correct",
   911  				},
   912  			},
   913  			expected: func(pj prowjobv1.ProwJob, meta *metav1.ObjectMeta) {
   914  				meta.Name = pj.Name
   915  				meta.Namespace = pj.Spec.Namespace
   916  				meta.Labels, meta.Annotations = decorate.LabelsAndAnnotationsForJob(pj)
   917  			},
   918  		},
   919  	}
   920  
   921  	for _, tc := range cases {
   922  		t.Run(tc.name, func(t *testing.T) {
   923  			var expected metav1.ObjectMeta
   924  			tc.expected(tc.pj, &expected)
   925  			actual := buildMeta(tc.pj)
   926  			if !equality.Semantic.DeepEqual(actual, expected) {
   927  				t.Errorf("build meta does not match:\n%s", diff.ObjectReflectDiff(expected, actual))
   928  			}
   929  		})
   930  	}
   931  }
   932  
   933  func TestMakeBuild(t *testing.T) {
   934  	cases := []struct {
   935  		name string
   936  		job  func(prowjobv1.ProwJob) prowjobv1.ProwJob
   937  		err  bool
   938  	}{
   939  		{
   940  			name: "reject empty prow job",
   941  			job:  func(_ prowjobv1.ProwJob) prowjobv1.ProwJob { return prowjobv1.ProwJob{} },
   942  			err:  true,
   943  		},
   944  		{
   945  			name: "return valid build with valid prowjob",
   946  		},
   947  		{
   948  			name: "configure source when refs are set",
   949  			job: func(pj prowjobv1.ProwJob) prowjobv1.ProwJob {
   950  				pj.Spec.ExtraRefs = []prowjobv1.Refs{{Org: "bonus"}}
   951  				pj.Spec.DecorationConfig = &prowjobv1.DecorationConfig{
   952  					UtilityImages: &prowjobv1.UtilityImages{},
   953  				}
   954  				return pj
   955  			},
   956  		},
   957  		{
   958  			name: "do not override source when set",
   959  			job: func(pj prowjobv1.ProwJob) prowjobv1.ProwJob {
   960  				pj.Spec.ExtraRefs = []prowjobv1.Refs{{Org: "bonus"}}
   961  				pj.Spec.DecorationConfig = &prowjobv1.DecorationConfig{
   962  					UtilityImages: &prowjobv1.UtilityImages{},
   963  				}
   964  				pj.Spec.BuildSpec.Source = &buildv1alpha1.SourceSpec{}
   965  				return pj
   966  			},
   967  		},
   968  	}
   969  
   970  	for _, tc := range cases {
   971  		t.Run(tc.name, func(t *testing.T) {
   972  			pj := prowjobv1.ProwJob{}
   973  			pj.Name = "world"
   974  			pj.Namespace = "hello"
   975  			pj.Spec.Type = prowjobv1.PeriodicJob
   976  			pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{}
   977  			pj.Spec.BuildSpec.Steps = append(pj.Spec.BuildSpec.Steps, corev1.Container{})
   978  			pj.Spec.BuildSpec.Template = &buildv1alpha1.TemplateInstantiationSpec{}
   979  			if tc.job != nil {
   980  				pj = tc.job(pj)
   981  			}
   982  			const randomBuildID = "so-many-builds"
   983  			actual, err := makeBuild(pj, randomBuildID)
   984  			if err != nil {
   985  				if !tc.err {
   986  					t.Errorf("unexpected error: %v", err)
   987  				}
   988  				return
   989  			} else if tc.err {
   990  				t.Error("failed to receive expected error")
   991  			}
   992  			expected := buildv1alpha1.Build{
   993  				ObjectMeta: buildMeta(pj),
   994  				Spec:       *pj.Spec.BuildSpec,
   995  			}
   996  			env, err := buildEnv(pj, randomBuildID)
   997  			if err != nil {
   998  				t.Fatalf("failed to create expected build env: %v", err)
   999  			}
  1000  			injectEnvironment(&expected, env)
  1001  			err = injectSource(&expected, pj)
  1002  			if err != nil {
  1003  				t.Fatalf("failed to inject expected source: %v", err)
  1004  			}
  1005  			if !equality.Semantic.DeepEqual(actual, &expected) {
  1006  				t.Errorf("builds do not match:\n%s", diff.ObjectReflectDiff(&expected, actual))
  1007  			}
  1008  		})
  1009  	}
  1010  }
  1011  
  1012  func TestDescription(t *testing.T) {
  1013  	cases := []struct {
  1014  		name     string
  1015  		message  string
  1016  		reason   string
  1017  		fallback string
  1018  		expected string
  1019  	}{
  1020  		{
  1021  			name:     "prefer message over reason or fallback",
  1022  			message:  "hello",
  1023  			reason:   "world",
  1024  			fallback: "doh",
  1025  			expected: "hello",
  1026  		},
  1027  		{
  1028  			name:     "prefer reason over fallback",
  1029  			reason:   "world",
  1030  			fallback: "other",
  1031  			expected: "world",
  1032  		},
  1033  		{
  1034  			name:     "use fallback if nothing else set",
  1035  			fallback: "fancy",
  1036  			expected: "fancy",
  1037  		},
  1038  	}
  1039  
  1040  	for _, tc := range cases {
  1041  		bc := duckv1alpha1.Condition{
  1042  			Message: tc.message,
  1043  			Reason:  tc.reason,
  1044  		}
  1045  		if actual := description(bc, tc.fallback); actual != tc.expected {
  1046  			t.Errorf("%s: actual %q != expected %q", tc.name, actual, tc.expected)
  1047  		}
  1048  	}
  1049  }
  1050  
  1051  func TestProwJobStatus(t *testing.T) {
  1052  	now := metav1.Now()
  1053  	later := metav1.NewTime(now.Time.Add(1 * time.Hour))
  1054  	cases := []struct {
  1055  		name     string
  1056  		input    buildv1alpha1.BuildStatus
  1057  		state    prowjobv1.ProwJobState
  1058  		desc     string
  1059  		fallback string
  1060  	}{
  1061  		{
  1062  			name:  "empty conditions returns triggered/scheduling",
  1063  			state: prowjobv1.TriggeredState,
  1064  			desc:  descScheduling,
  1065  		},
  1066  		{
  1067  			name: "truly succeeded state returns success",
  1068  			input: buildv1alpha1.BuildStatus{
  1069  				Conditions: []duckv1alpha1.Condition{
  1070  					{
  1071  						Type:    buildv1alpha1.BuildSucceeded,
  1072  						Status:  corev1.ConditionTrue,
  1073  						Message: "fancy",
  1074  					},
  1075  				},
  1076  			},
  1077  			state:    prowjobv1.SuccessState,
  1078  			desc:     "fancy",
  1079  			fallback: descSucceeded,
  1080  		},
  1081  		{
  1082  			name: "falsely succeeded state returns failure",
  1083  			input: buildv1alpha1.BuildStatus{
  1084  				Conditions: []duckv1alpha1.Condition{
  1085  					{
  1086  						Type:    buildv1alpha1.BuildSucceeded,
  1087  						Status:  corev1.ConditionFalse,
  1088  						Message: "weird",
  1089  					},
  1090  				},
  1091  			},
  1092  			state:    prowjobv1.FailureState,
  1093  			desc:     "weird",
  1094  			fallback: descFailed,
  1095  		},
  1096  		{
  1097  			name: "unstarted job returns triggered/initializing",
  1098  			input: buildv1alpha1.BuildStatus{
  1099  				Conditions: []duckv1alpha1.Condition{
  1100  					{
  1101  						Type:    buildv1alpha1.BuildSucceeded,
  1102  						Status:  corev1.ConditionUnknown,
  1103  						Message: "hola",
  1104  					},
  1105  				},
  1106  			},
  1107  			state:    prowjobv1.TriggeredState,
  1108  			desc:     "hola",
  1109  			fallback: descInitializing,
  1110  		},
  1111  		{
  1112  			name: "unfinished job returns running",
  1113  			input: buildv1alpha1.BuildStatus{
  1114  				StartTime: now,
  1115  				Conditions: []duckv1alpha1.Condition{
  1116  					{
  1117  						Type:    buildv1alpha1.BuildSucceeded,
  1118  						Status:  corev1.ConditionUnknown,
  1119  						Message: "hola",
  1120  					},
  1121  				},
  1122  			},
  1123  			state:    prowjobv1.PendingState,
  1124  			desc:     "hola",
  1125  			fallback: descRunning,
  1126  		},
  1127  		{
  1128  			name: "builds with unknown success status are still running",
  1129  			input: buildv1alpha1.BuildStatus{
  1130  				StartTime:      now,
  1131  				CompletionTime: later,
  1132  				Conditions: []duckv1alpha1.Condition{
  1133  					{
  1134  						Type:    buildv1alpha1.BuildSucceeded,
  1135  						Status:  corev1.ConditionUnknown,
  1136  						Message: "hola",
  1137  					},
  1138  				},
  1139  			},
  1140  			state:    prowjobv1.PendingState,
  1141  			desc:     "hola",
  1142  			fallback: descRunning,
  1143  		},
  1144  		{
  1145  			name: "completed builds without a succeeded condition end in error",
  1146  			input: buildv1alpha1.BuildStatus{
  1147  				StartTime:      now,
  1148  				CompletionTime: later,
  1149  			},
  1150  			state: prowjobv1.ErrorState,
  1151  			desc:  descMissingCondition,
  1152  		},
  1153  	}
  1154  
  1155  	for _, tc := range cases {
  1156  		if len(tc.fallback) > 0 {
  1157  			tc.desc = tc.fallback
  1158  			tc.fallback = ""
  1159  			tc.name += " [fallback]"
  1160  			cond := tc.input.Conditions[0]
  1161  			cond.Message = ""
  1162  			tc.input.Conditions = []duckv1alpha1.Condition{cond}
  1163  			cases = append(cases, tc)
  1164  		}
  1165  	}
  1166  
  1167  	for _, tc := range cases {
  1168  		t.Run(tc.name, func(t *testing.T) {
  1169  			state, desc := prowJobStatus(tc.input)
  1170  			if state != tc.state {
  1171  				t.Errorf("state %q != expected %q", state, tc.state)
  1172  			}
  1173  			if desc != tc.desc {
  1174  				t.Errorf("description %q != expected %q", desc, tc.desc)
  1175  			}
  1176  		})
  1177  	}
  1178  }