sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/crier/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 crier 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "reflect" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/sirupsen/logrus" 29 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/types" 31 ctrlruntime "sigs.k8s.io/controller-runtime" 32 ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" 33 fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" 34 "sigs.k8s.io/controller-runtime/pkg/reconcile" 35 36 prowv1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 37 ) 38 39 const reporterName = "fakeReporter" 40 41 // Fake Reporter 42 // Sets: Which jobs should be reported 43 // Asserts: Which jobs are actually reported 44 type fakeReporter struct { 45 reported []string 46 shouldReportFunc func(pj *prowv1.ProwJob) bool 47 res *reconcile.Result 48 err error 49 } 50 51 func (f *fakeReporter) Report(_ context.Context, _ *logrus.Entry, pj *prowv1.ProwJob) ([]*prowv1.ProwJob, *reconcile.Result, error) { 52 f.reported = append(f.reported, pj.Spec.Job) 53 return []*prowv1.ProwJob{pj}, f.res, f.err 54 } 55 56 func (f *fakeReporter) GetName() string { 57 return reporterName 58 } 59 60 func (f *fakeReporter) ShouldReport(_ context.Context, _ *logrus.Entry, pj *prowv1.ProwJob) bool { 61 return f.shouldReportFunc(pj) 62 } 63 64 func TestReconcile(t *testing.T) { 65 66 const toReconcile = "foo" 67 tests := []struct { 68 name string 69 job *prowv1.ProwJob 70 enablementChecker func(org, repo string) bool 71 shouldReport bool 72 result *reconcile.Result 73 reportErr error 74 75 expectResult reconcile.Result 76 expectReport bool 77 expectPatch bool 78 expectedError error 79 }{ 80 { 81 name: "reports/patches known job", 82 job: &prowv1.ProwJob{ 83 Spec: prowv1.ProwJobSpec{ 84 Job: "foo", 85 Report: true, 86 }, 87 Status: prowv1.ProwJobStatus{ 88 State: prowv1.TriggeredState, 89 }, 90 }, 91 shouldReport: true, 92 expectReport: true, 93 expectPatch: true, 94 }, 95 { 96 name: "reports/patches job whose org/repo in refs enabled", 97 job: &prowv1.ProwJob{ 98 Spec: prowv1.ProwJobSpec{ 99 Job: "foo", 100 Report: true, 101 Refs: &prowv1.Refs{Org: "org", Repo: "repo"}, 102 }, 103 Status: prowv1.ProwJobStatus{ 104 State: prowv1.TriggeredState, 105 }, 106 }, 107 enablementChecker: func(org, repo string) bool { return org == "org" && repo == "repo" }, 108 shouldReport: true, 109 expectReport: true, 110 expectPatch: true, 111 }, 112 { 113 name: "reports/patches job whose org/repo in extra refs enabled", 114 job: &prowv1.ProwJob{ 115 Spec: prowv1.ProwJobSpec{ 116 Job: "foo", 117 Report: true, 118 ExtraRefs: []prowv1.Refs{{Org: "org", Repo: "repo"}}, 119 }, 120 Status: prowv1.ProwJobStatus{ 121 State: prowv1.TriggeredState, 122 }, 123 }, 124 enablementChecker: func(org, repo string) bool { return org == "org" && repo == "repo" }, 125 shouldReport: true, 126 expectReport: true, 127 expectPatch: true, 128 }, 129 { 130 name: "reports/patches job whose org/repo in extra refs enabled, completed", 131 job: &prowv1.ProwJob{ 132 Spec: prowv1.ProwJobSpec{ 133 Job: "foo", 134 Report: true, 135 ExtraRefs: []prowv1.Refs{{Org: "org", Repo: "repo"}}, 136 }, 137 Status: prowv1.ProwJobStatus{ 138 State: prowv1.SuccessState, 139 CompletionTime: &v1.Time{Time: time.Now()}, 140 }, 141 }, 142 enablementChecker: func(org, repo string) bool { return org == "org" && repo == "repo" }, 143 shouldReport: true, 144 expectReport: true, 145 expectPatch: true, 146 }, 147 { 148 name: "reports/patches job whose org/repo in extra refs and refs have conflicting settings", 149 job: &prowv1.ProwJob{ 150 Spec: prowv1.ProwJobSpec{ 151 Job: "foo", 152 Report: true, 153 Refs: &prowv1.Refs{Org: "org", Repo: "repo"}, 154 ExtraRefs: []prowv1.Refs{{Org: "other-org", Repo: "other-repo"}}, 155 }, 156 Status: prowv1.ProwJobStatus{ 157 State: prowv1.TriggeredState, 158 }, 159 }, 160 enablementChecker: func(org, repo string) bool { return org == "org" && repo == "repo" }, 161 shouldReport: true, 162 expectReport: true, 163 expectPatch: true, 164 }, 165 { 166 name: "doesn't reports/patches job whose org/repo is not enabled", 167 job: &prowv1.ProwJob{ 168 Spec: prowv1.ProwJobSpec{ 169 Job: "foo", 170 Report: true, 171 Refs: &prowv1.Refs{Org: "org", Repo: "repo"}, 172 }, 173 Status: prowv1.ProwJobStatus{ 174 State: prowv1.TriggeredState, 175 }, 176 }, 177 enablementChecker: func(_, _ string) bool { return false }, 178 shouldReport: false, 179 expectReport: false, 180 expectPatch: false, 181 }, 182 { 183 name: "doesn't report when it shouldn't", 184 job: &prowv1.ProwJob{ 185 Spec: prowv1.ProwJobSpec{ 186 Job: "foo", 187 Report: true, 188 }, 189 Status: prowv1.ProwJobStatus{ 190 State: prowv1.TriggeredState, 191 }, 192 }, 193 shouldReport: false, 194 expectReport: false, 195 }, 196 { 197 name: "doesn't report nonexistant job", 198 shouldReport: true, 199 expectReport: false, 200 }, 201 { 202 name: "doesn't report when SkipReport=true (i.e. Spec.Report=false)", 203 job: &prowv1.ProwJob{ 204 Spec: prowv1.ProwJobSpec{ 205 Job: "foo", 206 Report: false, 207 }, 208 }, 209 shouldReport: true, 210 expectReport: false, 211 }, 212 { 213 name: "doesn't report empty job", 214 job: &prowv1.ProwJob{}, 215 shouldReport: true, 216 expectReport: false, 217 }, 218 { 219 name: "previously-reported job isn't reported", 220 job: &prowv1.ProwJob{ 221 Spec: prowv1.ProwJobSpec{ 222 Job: "foo", 223 Report: true, 224 }, 225 Status: prowv1.ProwJobStatus{ 226 State: prowv1.TriggeredState, 227 PrevReportStates: map[string]prowv1.ProwJobState{ 228 reporterName: prowv1.TriggeredState, 229 }, 230 }, 231 }, 232 shouldReport: true, 233 expectReport: false, 234 }, 235 { 236 name: "error is returned", 237 job: &prowv1.ProwJob{ 238 Spec: prowv1.ProwJobSpec{ 239 Job: "foo", 240 Report: true, 241 }, 242 Status: prowv1.ProwJobStatus{ 243 State: prowv1.TriggeredState, 244 }, 245 }, 246 shouldReport: true, 247 reportErr: errors.New("some-err"), 248 expectedError: fmt.Errorf("failed to report job: %w", errors.New("some-err")), 249 }, 250 { 251 name: "*reconcile.Result is returned, prowjob is not updated", 252 job: &prowv1.ProwJob{ 253 Spec: prowv1.ProwJobSpec{ 254 Job: "foo", 255 Report: true, 256 }, 257 Status: prowv1.ProwJobStatus{ 258 State: prowv1.TriggeredState, 259 }, 260 }, 261 shouldReport: true, 262 result: &reconcile.Result{RequeueAfter: time.Minute}, 263 expectResult: reconcile.Result{RequeueAfter: time.Minute}, 264 expectReport: true, 265 }, 266 } 267 268 for _, test := range tests { 269 test := test 270 t.Run(test.name, func(t *testing.T) { 271 t.Parallel() 272 rp := fakeReporter{ 273 shouldReportFunc: func(*prowv1.ProwJob) bool { 274 return test.shouldReport 275 }, 276 res: test.result, 277 err: test.reportErr, 278 } 279 280 builder := fakectrlruntimeclient.NewClientBuilder() 281 if test.job != nil { 282 test.job.Name = toReconcile 283 builder.WithRuntimeObjects(test.job) 284 } 285 cs := &patchTrackingClient{Client: builder.Build()} 286 r := &reconciler{ 287 pjclientset: cs, 288 reporter: &rp, 289 enablementChecker: test.enablementChecker, 290 } 291 292 result, err := r.Reconcile(context.Background(), ctrlruntime.Request{NamespacedName: types.NamespacedName{Name: toReconcile}}) 293 if !reflect.DeepEqual(err, test.expectedError) { 294 t.Fatalf("actual err %v differs from expected err %v", err, test.expectedError) 295 } 296 if err != nil { 297 return 298 } 299 if diff := cmp.Diff(result, test.expectResult); diff != "" { 300 t.Errorf("result differs from expected result: %s", diff) 301 } 302 303 var expectReports []string 304 if test.expectReport { 305 expectReports = []string{toReconcile} 306 } 307 if !reflect.DeepEqual(expectReports, rp.reported) { 308 t.Errorf("mismatch report: wants %v, got %v", expectReports, rp.reported) 309 } 310 311 if (cs.patches != 0) != test.expectPatch { 312 if test.expectPatch { 313 t.Error("expected patch, but didn't get it") 314 } else { 315 t.Error("got unexpected patch") 316 } 317 } 318 }) 319 } 320 } 321 322 type patchTrackingClient struct { 323 ctrlruntimeclient.Client 324 patches int 325 } 326 327 func (c *patchTrackingClient) Patch(ctx context.Context, obj ctrlruntimeclient.Object, patch ctrlruntimeclient.Patch, opts ...ctrlruntimeclient.PatchOption) error { 328 c.patches++ 329 return c.Client.Patch(ctx, obj, patch, opts...) 330 }