@@ -196,11 +196,14 @@ func (o *FeatureGateTestAnalyzerOptions) Run(ctx context.Context) error {
196196 // Separate warnings and blocking errors
197197 blockingErrors := []error {}
198198 warnings := []error {}
199+ var blockingResults , warningResults []ValidationResult
199200 for _ , vr := range validationResults {
200201 if vr .IsWarning {
201202 warnings = append (warnings , vr .Error )
203+ warningResults = append (warningResults , vr )
202204 } else {
203205 blockingErrors = append (blockingErrors , vr .Error )
206+ blockingResults = append (blockingResults , vr )
204207 }
205208 }
206209
@@ -269,24 +272,20 @@ func (o *FeatureGateTestAnalyzerOptions) Run(ctx context.Context) error {
269272
270273 if len (warnings ) > 0 {
271274 md .Textf ("**Non-blocking warnings (optional variants):**\n " )
272- for _ , warn := range warnings {
273- md .Textf (" - %s\n " , warn .Error ())
274- }
275+ writeGroupedValidationResults (warningResults , md )
275276 md .Text ("" )
276277 }
277278
278279 if len (blockingErrors ) > 0 {
279280 md .Textf ("**Blocking errors:**\n " )
280- for _ , err := range blockingErrors {
281- md .Textf (" - %s\n " , err .Error ())
282- }
281+ writeGroupedValidationResults (blockingResults , md )
283282 md .Text ("" )
284283 }
285284 md .Text ("" )
286285 }
287286
288287 // Only add blocking errors to the error list (warnings don't fail the job)
289- errs = append (errs , blockingErrors ... )
288+ errs = append (errs , groupErrorsByCategory ( blockingResults ) ... )
290289 featureGateHTMLData = append (featureGateHTMLData , buildHTMLFeatureGateData (enabledFeatureGate , testingResults , blockingErrors , release ))
291290
292291 }
@@ -429,14 +428,20 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian
429428 Error : fmt .Errorf ("warning: variant %v includes test data from candidate-tier jobs which are not covered by Component Readiness and lack standard regression protection" ,
430429 jobVariant ),
431430 IsWarning : true ,
431+ Category : CategoryCandidateTier ,
432432 })
433433 }
434434
435+ if len (testedVariant .TestResults ) == 0 {
436+ continue
437+ }
438+
435439 if len (testedVariant .TestResults ) < requiredNumberOfTests {
436440 results = append (results , ValidationResult {
437441 Error : fmt .Errorf ("error: only %d tests found, need at least %d for %q on %v" ,
438442 len (testedVariant .TestResults ), requiredNumberOfTests , featureGate , jobVariant ),
439443 IsWarning : isOptional ,
444+ Category : CategoryInsufficientTests ,
440445 })
441446 }
442447
@@ -450,7 +455,8 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian
450455 results = append (results , ValidationResult {
451456 Error : fmt .Errorf ("error: %q only has %d runs, need at least %d runs for %q on %v" ,
452457 testResults .TestName , testResults .TotalRuns , requiredNumberOfTestRunsPerVariant , featureGate , jobVariant ),
453- IsWarning : isOptional ,
458+ IsWarning : isOptional ,
459+ Category : CategoryInsufficientRuns ,
454460 })
455461 }
456462 if testResults .TotalRuns == 0 {
@@ -464,6 +470,7 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian
464470 Error : fmt .Errorf ("error: %q only passed %d%%, need at least %d%% for %q on %v" ,
465471 testResults .TestName , displayActual , displayExpected , featureGate , jobVariant ),
466472 IsWarning : isOptional ,
473+ Category : CategoryPassRate ,
467474 })
468475 }
469476 }
@@ -476,13 +483,15 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian
476483 Error : fmt .Errorf ("warning: \" install should succeed: overall\" test not found for Install feature gate %q on %v" ,
477484 featureGate , jobVariant ),
478485 IsWarning : true ,
486+ Category : CategoryInstallTest ,
479487 })
480488 } else {
481489 if installTest .TotalRuns < requiredNumberOfTestRunsPerVariant {
482490 results = append (results , ValidationResult {
483491 Error : fmt .Errorf ("error: \" install should succeed: overall\" only has %d runs, need at least %d runs for %q on %v" ,
484492 installTest .TotalRuns , requiredNumberOfTestRunsPerVariant , featureGate , jobVariant ),
485493 IsWarning : isOptional ,
494+ Category : CategoryInstallTest ,
486495 })
487496 }
488497 if installTest .TotalRuns > 0 {
@@ -494,6 +503,7 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian
494503 Error : fmt .Errorf ("error: \" install should succeed: overall\" only passed %d%%, need at least %d%% for %q on %v" ,
495504 displayActual , displayExpected , featureGate , jobVariant ),
496505 IsWarning : isOptional ,
506+ Category : CategoryInstallTest ,
497507 })
498508 }
499509 }
@@ -504,6 +514,46 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian
504514 return results
505515}
506516
517+ func groupErrorsByCategory (results []ValidationResult ) []error {
518+ categoryOrder := []ValidationCategory {}
519+ grouped := map [ValidationCategory ][]ValidationResult {}
520+ for _ , vr := range results {
521+ if _ , seen := grouped [vr .Category ]; ! seen {
522+ categoryOrder = append (categoryOrder , vr .Category )
523+ }
524+ grouped [vr .Category ] = append (grouped [vr .Category ], vr )
525+ }
526+ var errs []error
527+ for i , cat := range categoryOrder {
528+ if i > 0 {
529+ errs = append (errs , fmt .Errorf ("" ))
530+ }
531+ for _ , vr := range grouped [cat ] {
532+ errs = append (errs , vr .Error )
533+ }
534+ }
535+ return errs
536+ }
537+
538+ func writeGroupedValidationResults (results []ValidationResult , md * utils.Markdown ) {
539+ categoryOrder := []ValidationCategory {}
540+ grouped := map [ValidationCategory ][]ValidationResult {}
541+ for _ , vr := range results {
542+ if _ , seen := grouped [vr .Category ]; ! seen {
543+ categoryOrder = append (categoryOrder , vr .Category )
544+ }
545+ grouped [vr .Category ] = append (grouped [vr .Category ], vr )
546+ }
547+ for i , cat := range categoryOrder {
548+ if i > 0 {
549+ md .Text ("" )
550+ }
551+ for _ , vr := range grouped [cat ] {
552+ md .Textf (" - %s\n " , vr .Error .Error ())
553+ }
554+ }
555+ }
556+
507557func writeTestingMarkDown (testingResults map [JobVariant ]* TestingResults , md * utils.Markdown ) {
508558 jobVariantsSet := sets .KeySet (testingResults )
509559 jobVariants := jobVariantsSet .UnsortedList ()
@@ -798,11 +848,22 @@ type TestResults struct {
798848 FlakedRuns int
799849}
800850
851+ type ValidationCategory string
852+
853+ const (
854+ CategoryCandidateTier ValidationCategory = "candidate-tier"
855+ CategoryInsufficientTests ValidationCategory = "insufficient-tests"
856+ CategoryInsufficientRuns ValidationCategory = "insufficient-runs"
857+ CategoryPassRate ValidationCategory = "pass-rate"
858+ CategoryInstallTest ValidationCategory = "install-test"
859+ )
860+
801861// ValidationResult represents a validation error or warning
802862type ValidationResult struct {
803863 Error error
804- IsWarning bool // if true, this is a non-blocking warning (for optional variants)
805- IsInfo bool // if true, this is informational telemetry (e.g., install test pass percentage)
864+ IsWarning bool // if true, this is a non-blocking warning (for optional variants)
865+ IsInfo bool // if true, this is informational telemetry (e.g., install test pass percentage)
866+ Category ValidationCategory // groups related results together in output
806867}
807868
808869func testResultByName (results []TestResults , testName string ) * TestResults {
@@ -1311,17 +1372,25 @@ func verifyJobPassRate(client *http.Client, release string, job sippy.SippyJob,
13111372 return nil , fmt .Errorf ("getting job %q results from sippy: %w" , job .Name , err )
13121373 }
13131374
1314- testResults := & TestResults {
1315- TestName : job .Name ,
1316- TotalRuns : len (jobRuns ),
1317- }
1318-
13191375 triagedTestFailures , err := getTriagedTestFailuresFromSippy (client , release , variant )
13201376 if err != nil {
13211377 return nil , fmt .Errorf ("getting triaged test failures from sippy: %w" , err )
13221378 }
13231379
1380+ fmt .Printf ("\n Ignoring job runs that have internal or external infrastructure failures from our analysis.\n \n " )
1381+
1382+ infraFailures := 0
1383+ testResults := & TestResults {
1384+ TestName : job .Name ,
1385+ TotalRuns : len (jobRuns ),
1386+ }
1387+
13241388 for _ , jobRun := range jobRuns {
1389+ if jobRun .OverallResult == "N" || jobRun .OverallResult == "n" {
1390+ infraFailures ++
1391+ continue
1392+ }
1393+
13251394 if jobRun .OverallResult == "F" && ! jobRun .KnownFailure {
13261395
13271396 untriagedTestFailures := []string {}
@@ -1348,6 +1417,8 @@ func verifyJobPassRate(client *http.Client, release string, job sippy.SippyJob,
13481417 testResults .SuccessfulRuns ++
13491418 }
13501419
1420+ testResults .TotalRuns -= infraFailures
1421+
13511422 return testResults , nil
13521423}
13531424
0 commit comments