sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/crier/reporters/gcs/reporter_test.go (about) 1 /* 2 Copyright 2020 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 gcs 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 stdio "io" 24 "path" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/GoogleCloudPlatform/testgrid/metadata" 30 "github.com/google/go-cmp/cmp" 31 "github.com/sirupsen/logrus" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 34 prowv1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 35 "sigs.k8s.io/prow/pkg/config" 36 "sigs.k8s.io/prow/pkg/io" 37 "sigs.k8s.io/prow/pkg/io/fakeopener" 38 "sigs.k8s.io/prow/pkg/io/providers" 39 "sigs.k8s.io/prow/pkg/pod-utils/clone" 40 ) 41 42 type fca struct { 43 c config.Config 44 } 45 46 func (ca fca) Config() *config.Config { 47 return &ca.c 48 } 49 50 func TestReportJobFinished(t *testing.T) { 51 completionTime := &metav1.Time{Time: time.Date(2010, 10, 10, 19, 00, 0, 0, time.UTC)} 52 tests := []struct { 53 jobState prowv1.ProwJobState 54 completionTime *metav1.Time 55 passed bool 56 expectErr bool 57 }{ 58 { 59 jobState: prowv1.TriggeredState, 60 expectErr: true, 61 }, 62 { 63 jobState: prowv1.PendingState, 64 expectErr: true, 65 }, 66 { 67 jobState: prowv1.SuccessState, 68 completionTime: completionTime, 69 passed: true, 70 }, 71 { 72 jobState: prowv1.AbortedState, 73 completionTime: completionTime, 74 }, 75 { 76 jobState: prowv1.ErrorState, 77 completionTime: completionTime, 78 }, 79 { 80 jobState: prowv1.FailureState, 81 completionTime: completionTime, 82 }, 83 } 84 for _, tc := range tests { 85 t.Run(fmt.Sprintf("report %s job", tc.jobState), func(t *testing.T) { 86 ctx := context.Background() 87 cfg := fca{c: config.Config{ 88 ProwConfig: config.ProwConfig{ 89 Plank: config.Plank{ 90 DefaultDecorationConfigs: config.DefaultDecorationMapToSliceTesting( 91 map[string]*prowv1.DecorationConfig{"*": { 92 GCSConfiguration: &prowv1.GCSConfiguration{ 93 Bucket: "kubernetes-jenkins", 94 PathPrefix: "some-prefix", 95 PathStrategy: prowv1.PathStrategyLegacy, 96 DefaultOrg: "kubernetes", 97 DefaultRepo: "kubernetes", 98 }, 99 }}), 100 }, 101 }, 102 }}.Config 103 fakeOpener := &fakeopener.FakeOpener{} 104 reporter := New(cfg, fakeOpener, false) 105 106 pj := &prowv1.ProwJob{ 107 Spec: prowv1.ProwJobSpec{ 108 Type: prowv1.PresubmitJob, 109 Refs: &prowv1.Refs{ 110 Org: "kubernetes", 111 Repo: "test-infra", 112 Pulls: []prowv1.Pull{{Number: 12345}}, 113 }, 114 Agent: prowv1.KubernetesAgent, 115 Job: "my-little-job", 116 }, 117 Status: prowv1.ProwJobStatus{ 118 State: tc.jobState, 119 StartTime: metav1.Time{Time: time.Date(2010, 10, 10, 18, 30, 0, 0, time.UTC)}, 120 CompletionTime: tc.completionTime, 121 PodName: "some-pod", 122 BuildID: "123", 123 }, 124 } 125 126 err := reporter.reportFinishedJob(ctx, logrus.NewEntry(logrus.StandardLogger()), pj) 127 if tc.expectErr { 128 if err == nil { 129 t.Fatalf("Expected an error, but didn't get one.") 130 } 131 return 132 } else if err != nil { 133 t.Fatalf("Unexpected error: %v", err) 134 } 135 136 var content []byte 137 for path, buf := range fakeOpener.Buffer { 138 if strings.HasSuffix(path, prowv1.FinishedStatusFile) { 139 content, err = stdio.ReadAll(buf) 140 if err != nil { 141 t.Fatalf("Failed reading content: %v", err) 142 } 143 break 144 } 145 } 146 var result metadata.Finished 147 if err := json.Unmarshal(content, &result); err != nil { 148 t.Errorf("Couldn't decode result as metadata.Finished: %v", err) 149 } 150 if result.Timestamp == nil { 151 t.Errorf("Expected finished.json timestamp to be %d, but it was nil", pj.Status.CompletionTime.Unix()) 152 } else if *result.Timestamp != pj.Status.CompletionTime.Unix() { 153 t.Errorf("Expected finished.json timestamp to be %d, but got %d", pj.Status.CompletionTime.Unix(), *result.Timestamp) 154 } 155 if result.Passed == nil { 156 t.Errorf("Expected finished.json passed to be %v, but it was nil", tc.passed) 157 } else if *result.Passed != tc.passed { 158 t.Errorf("Expected finished.json passed to be %v, but got %v", tc.passed, *result.Passed) 159 } 160 }) 161 } 162 } 163 164 func TestReportJobStarted(t *testing.T) { 165 tests := []struct { 166 name string 167 existingStarted *metadata.Started 168 state prowv1.ProwJobState 169 cloneRecord []clone.Record 170 expect metadata.Started 171 }{ 172 { 173 name: "TriggeredState", 174 state: prowv1.TriggeredState, 175 expect: metadata.Started{ 176 Timestamp: 1286735400, 177 Pull: "12345", 178 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 179 RepoCommit: "def456", 180 DeprecatedRepoVersion: "def456", 181 Metadata: metadata.Metadata{"uploader": string("crier")}, 182 }, 183 }, 184 { 185 name: "PendingState", 186 state: prowv1.PendingState, 187 expect: metadata.Started{ 188 Timestamp: 1286735400, 189 Pull: "12345", 190 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 191 RepoCommit: "def456", 192 DeprecatedRepoVersion: "def456", 193 Metadata: metadata.Metadata{"uploader": string("crier")}, 194 }, 195 }, 196 { 197 name: "SuccessState", 198 state: prowv1.SuccessState, 199 expect: metadata.Started{ 200 Timestamp: 1286735400, 201 Pull: "12345", 202 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 203 RepoCommit: "def456", 204 DeprecatedRepoVersion: "def456", 205 Metadata: metadata.Metadata{"uploader": string("crier")}, 206 }, 207 }, 208 { 209 name: "AbortedState", 210 state: prowv1.AbortedState, 211 expect: metadata.Started{ 212 Timestamp: 1286735400, 213 Pull: "12345", 214 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 215 RepoCommit: "def456", 216 DeprecatedRepoVersion: "def456", 217 Metadata: metadata.Metadata{"uploader": string("crier")}, 218 }, 219 }, 220 { 221 name: "ErrorState", 222 state: prowv1.ErrorState, 223 expect: metadata.Started{ 224 Timestamp: 1286735400, 225 Pull: "12345", 226 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 227 RepoCommit: "def456", 228 DeprecatedRepoVersion: "def456", 229 Metadata: metadata.Metadata{"uploader": string("crier")}, 230 }, 231 }, 232 { 233 name: "FailureState", 234 state: prowv1.ErrorState, 235 expect: metadata.Started{ 236 Timestamp: 1286735400, 237 Pull: "12345", 238 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 239 RepoCommit: "def456", 240 DeprecatedRepoVersion: "def456", 241 Metadata: metadata.Metadata{"uploader": string("crier")}, 242 }, 243 }, 244 { 245 name: "overwrite-crier-uploaded", 246 state: prowv1.SuccessState, 247 existingStarted: &metadata.Started{ 248 Timestamp: 1286735400, 249 Pull: "12345", 250 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 251 RepoCommit: "def456", 252 Metadata: metadata.Metadata{"uploader": string("crier")}, 253 }, 254 cloneRecord: []clone.Record{ 255 { 256 Refs: prowv1.Refs{ 257 Org: "kubernetes", 258 Repo: "test-infra", 259 BaseRef: "main", 260 Pulls: []prowv1.Pull{{Number: 12345, SHA: "def456"}}, 261 }, 262 FinalSHA: "abc123", 263 }, 264 }, 265 expect: metadata.Started{ 266 Timestamp: 1286735400, 267 Pull: "12345", 268 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 269 Metadata: metadata.Metadata{"uploader": string("crier")}, 270 RepoCommit: "abc123", 271 DeprecatedRepoVersion: "abc123", 272 }, 273 }, 274 { 275 name: "overwrite-crier-uploaded-without-SHA", 276 state: prowv1.SuccessState, 277 existingStarted: &metadata.Started{ 278 Timestamp: 1286735400, 279 Pull: "12345", 280 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 281 RepoCommit: "def456", 282 Metadata: metadata.Metadata{"uploader": string("crier")}, 283 }, 284 cloneRecord: []clone.Record{ 285 { 286 Refs: prowv1.Refs{ 287 Org: "kubernetes", 288 Repo: "test-infra", 289 BaseRef: "main", 290 Pulls: []prowv1.Pull{{Number: 12345, SHA: "def456"}}, 291 }, 292 FinalSHA: "abc123", 293 }, 294 }, 295 expect: metadata.Started{ 296 Timestamp: 1286735400, 297 Pull: "12345", 298 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 299 Metadata: metadata.Metadata{"uploader": string("crier")}, 300 RepoCommit: "abc123", 301 DeprecatedRepoVersion: "abc123", 302 }, 303 }, 304 { 305 name: "no-overwrite-others-uploaded", 306 state: prowv1.SuccessState, 307 existingStarted: &metadata.Started{ 308 Timestamp: 1286735400, 309 Pull: "12345", 310 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 311 }, 312 cloneRecord: []clone.Record{ 313 { 314 Refs: prowv1.Refs{ 315 Org: "kubernetes", 316 Repo: "test-infra", 317 BaseRef: "main", 318 Pulls: []prowv1.Pull{{Number: 12345}}, 319 }, 320 FinalSHA: "abc123", 321 }, 322 }, 323 expect: metadata.Started{ 324 Timestamp: 1286735400, 325 Pull: "12345", 326 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 327 }, 328 }, 329 { 330 name: "no-cloneref-self-update", 331 state: prowv1.SuccessState, 332 existingStarted: &metadata.Started{ 333 Timestamp: 100, // Intentionally wrong timestamp. Crier will change it if it overwrites. 334 Pull: "12345", 335 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 336 RepoCommit: "main", 337 Metadata: metadata.Metadata{"uploader": string("crier")}, 338 }, 339 expect: metadata.Started{ 340 Timestamp: 100, 341 Pull: "12345", 342 Repos: map[string]string{"kubernetes/test-infra": "main,12345:def456"}, 343 RepoCommit: "main", 344 Metadata: metadata.Metadata{"uploader": string("crier")}, 345 }, 346 }, 347 } 348 349 for _, tc := range tests { 350 tc := tc 351 t.Run(tc.name, func(t *testing.T) { 352 ctx := context.Background() 353 cfg := fca{c: config.Config{ 354 ProwConfig: config.ProwConfig{ 355 Plank: config.Plank{ 356 DefaultDecorationConfigs: config.DefaultDecorationMapToSliceTesting( 357 map[string]*prowv1.DecorationConfig{"*": { 358 GCSConfiguration: &prowv1.GCSConfiguration{ 359 Bucket: "kubernetes-jenkins", 360 PathPrefix: "some-prefix", 361 PathStrategy: prowv1.PathStrategyLegacy, 362 DefaultOrg: "kubernetes", 363 DefaultRepo: "kubernetes", 364 }, 365 }}), 366 }, 367 }, 368 }}.Config 369 // Storage path decided by Prow 370 const subDir = "some-prefix/pr-logs/pull/test-infra/12345/my-little-job/123" 371 372 opener := &fakeopener.FakeOpener{} 373 if tc.existingStarted != nil { 374 content, err := json.Marshal(*tc.existingStarted) 375 if err != nil { 376 t.Fatalf("Failed to marshal started.json: %v", err) 377 } 378 storagePath, _ := providers.StoragePath("kubernetes-jenkins", path.Join(subDir, "started.json")) 379 if err := io.WriteContent(ctx, logrus.NewEntry(logrus.StandardLogger()), opener, storagePath, content); err != nil { 380 t.Fatalf("Failed creating started.json: %v", err) 381 } 382 } 383 if len(tc.cloneRecord) > 0 { 384 content, err := json.Marshal(tc.cloneRecord) 385 if err != nil { 386 t.Fatalf("Failed to marshal clone record: %v", err) 387 } 388 storagePath, _ := providers.StoragePath("kubernetes-jenkins", path.Join(subDir, "clone-records.json")) 389 if err := io.WriteContent(ctx, logrus.NewEntry(logrus.StandardLogger()), opener, storagePath, content); err != nil { 390 t.Fatalf("Failed seeding clone-records.json: %v", err) 391 } 392 } 393 394 reporter := New(cfg, opener, false) 395 396 pj := &prowv1.ProwJob{ 397 Spec: prowv1.ProwJobSpec{ 398 Type: prowv1.PresubmitJob, 399 Refs: &prowv1.Refs{ 400 Org: "kubernetes", 401 Repo: "test-infra", 402 BaseRef: "main", 403 Pulls: []prowv1.Pull{{Number: 12345, SHA: "def456"}}, 404 }, 405 Agent: prowv1.KubernetesAgent, 406 Job: "my-little-job", 407 }, 408 Status: prowv1.ProwJobStatus{ 409 State: tc.state, 410 StartTime: metav1.Time{Time: time.Date(2010, 10, 10, 18, 30, 0, 0, time.UTC)}, 411 PodName: "some-pod", 412 BuildID: "123", 413 }, 414 } 415 416 err := reporter.reportStartedJob(ctx, logrus.NewEntry(logrus.StandardLogger()), pj) 417 if err != nil { 418 t.Fatalf("Unexpected error: %v", err) 419 } 420 421 storagePath, _ := providers.StoragePath("kubernetes-jenkins", path.Join(subDir, "started.json")) 422 content, err := io.ReadContent(ctx, logrus.WithContext(ctx), opener, storagePath) 423 if err != nil { 424 t.Fatalf("Failed reading started.json: %v", err) 425 } 426 427 var result metadata.Started 428 if err := json.Unmarshal(content, &result); err != nil { 429 t.Fatalf("Couldn't decode result as metadata.Started: %v", err) 430 } 431 if diff := cmp.Diff(tc.expect, result); diff != "" { 432 t.Fatalf("Started.json mismatch. Want(-), got(+):\n%s", diff) 433 } 434 }) 435 } 436 } 437 438 func TestReportProwJob(t *testing.T) { 439 ctx := context.Background() 440 cfg := fca{c: config.Config{ 441 ProwConfig: config.ProwConfig{ 442 Plank: config.Plank{ 443 DefaultDecorationConfigs: config.DefaultDecorationMapToSliceTesting( 444 map[string]*prowv1.DecorationConfig{"*": { 445 GCSConfiguration: &prowv1.GCSConfiguration{ 446 Bucket: "kubernetes-jenkins", 447 PathPrefix: "some-prefix", 448 PathStrategy: prowv1.PathStrategyLegacy, 449 DefaultOrg: "kubernetes", 450 DefaultRepo: "kubernetes", 451 }, 452 }}), 453 }, 454 }, 455 }}.Config 456 fakeOpener := &fakeopener.FakeOpener{} 457 reporter := New(cfg, fakeOpener, false) 458 459 pj := &prowv1.ProwJob{ 460 Spec: prowv1.ProwJobSpec{ 461 Type: prowv1.PresubmitJob, 462 Refs: &prowv1.Refs{ 463 Org: "kubernetes", 464 Repo: "test-infra", 465 Pulls: []prowv1.Pull{{Number: 12345}}, 466 }, 467 Agent: prowv1.KubernetesAgent, 468 Job: "my-little-job", 469 }, 470 Status: prowv1.ProwJobStatus{ 471 State: prowv1.SuccessState, 472 StartTime: metav1.Time{Time: time.Date(2010, 10, 10, 18, 30, 0, 0, time.UTC)}, 473 CompletionTime: &metav1.Time{Time: time.Date(2010, 10, 10, 19, 00, 0, 0, time.UTC)}, 474 PodName: "some-pod", 475 BuildID: "123", 476 }, 477 } 478 479 if err := reporter.reportProwjob(ctx, logrus.NewEntry(logrus.StandardLogger()), pj); err != nil { 480 t.Fatalf("Unexpected error calling reportProwjob: %v", err) 481 } 482 483 var content []byte 484 var err error 485 for p, b := range fakeOpener.Buffer { 486 if strings.HasSuffix(p, prowv1.ProwJobFile) { 487 content, err = stdio.ReadAll(b) 488 if err != nil { 489 t.Fatalf("Failed reading content: %v", err) 490 } 491 break 492 } 493 } 494 495 var result prowv1.ProwJob 496 if err := json.Unmarshal(content, &result); err != nil { 497 t.Fatalf("Couldn't unmarshal %s: %v", prowv1.ProwJobFile, err) 498 } 499 if !cmp.Equal(*pj, result) { 500 t.Fatalf("Input prowjob mismatches output prowjob:\n%s", cmp.Diff(*pj, result)) 501 } 502 } 503 504 func TestShouldReport(t *testing.T) { 505 tests := []struct { 506 name string 507 buildID string 508 shouldReport bool 509 }{ 510 { 511 name: "tests with a build ID should be reported", 512 buildID: "123", 513 shouldReport: true, 514 }, 515 { 516 name: "tests without a build ID should not be reported", 517 buildID: "", 518 shouldReport: false, 519 }, 520 } 521 for _, tc := range tests { 522 t.Run(tc.name, func(t *testing.T) { 523 pj := &prowv1.ProwJob{ 524 Spec: prowv1.ProwJobSpec{ 525 Type: prowv1.PostsubmitJob, 526 Agent: prowv1.KubernetesAgent, 527 Job: "my-little-job", 528 }, 529 Status: prowv1.ProwJobStatus{ 530 State: prowv1.TriggeredState, 531 StartTime: metav1.Time{Time: time.Date(2010, 10, 10, 18, 30, 0, 0, time.UTC)}, 532 BuildID: tc.buildID, 533 }, 534 } 535 gr := New(fca{}.Config, nil, false) 536 result := gr.ShouldReport(context.Background(), logrus.NewEntry(logrus.StandardLogger()), pj) 537 if result != tc.shouldReport { 538 t.Errorf("Got ShouldReport() returned %v, but expected %v", result, tc.shouldReport) 539 } 540 }) 541 } 542 }