418
// MatrixNames (DB configs). Keying by (RunID, MatrixName) ensures shards belonging to the
419
// same run+config are counted once, regardless of how many shard JobIDs they produce.
420
>
func suiteRunKey(runID int64, matrixName string) string {
parser.go
421
>
return fmt.Sprintf("%d:%s", runID, matrixName)
422
>
}
423
424
// generateSuiteReports creates per-suite flake breakdown from all failures and test runs.
425
// Suite flake rate = % of (CI run × DB config) pairs where the suite had at least one
426
// non-retry failure.
427
>
func generateSuiteReports(allFailures []TestFailure, allTestRuns []TestRun) []SuiteReport {
parser.go
428
>
// Track unique (CI run × DB config) pairs per suite (denominator).
429
>
// Using MatrixName avoids the inflation caused by per-shard JobIDs: suites whose
430
>
// test methods are spread across N shards would otherwise be counted N times per
431
>
// (run × DB config).
432
>
suiteRuns := make(map[string]map[string]bool)
433
>
for _, run := range allTestRuns {
434
>
if run.Skipped || !isGoTestSuite(run.SuiteName) {
435
>
continue
436
}
437
>
if suiteRuns[run.SuiteName] == nil {
parser.go
438
>
suiteRuns[run.SuiteName] = make(map[string]bool)
439
>
}
440
>
suiteRuns[run.SuiteName][suiteRunKey(run.RunID, run.MatrixName)] = true
441
}
442
443
// Track (CI run × DB config) pairs with non-retry failures per suite (numerator)
444
>
suiteFailedRuns := make(map[string]map[string]bool)
parser.go
445
>
suiteLastFailure := make(map[string]time.Time)
446
>
for _, failure := range allFailures {
447
>
if !isGoTestSuite(failure.SuiteName) {
448
continue
449
}
450
// Only report the original, complete run
451
>
if normalizeTestName(failure.Name) != failure.Name {
parser.go
452
>
continue
453
}
454
>
if suiteFailedRuns[failure.SuiteName] == nil {
parser.go
455
>
suiteFailedRuns[failure.SuiteName] = make(map[string]bool)
456
>
}
457
>
runKey := suiteRunKey(failure.RunID, failure.MatrixName)
458
>
suiteFailedRuns[failure.SuiteName][runKey] = true
459
>
if failure.Timestamp.After(suiteLastFailure[failure.SuiteName]) {
460
>
suiteLastFailure[failure.SuiteName] = failure.Timestamp
461
>
}
462
}
463
465
>
for suiteName, runIDs := range suiteRuns {
466
>
failedRuns := len(suiteFailedRuns[suiteName])
467
>
if failedRuns == 0 {
468
>
continue
469
}
471
>
flakeRate := float64(failedRuns) / float64(totalRuns) * 100.0
472
>
reports = append(reports, SuiteReport{
473
>
SuiteName: suiteName,
474
>
FlakeRate: flakeRate,
475
>
FailedRuns: failedRuns,
476
>
TotalRuns: totalRuns,
477
>
LastFailure: suiteLastFailure[suiteName],
478
>
})
479
}
480
481
>
sort.Slice(reports, func(i, j int) bool {
parser.go
482
>
return reports[i].SuiteName < reports[j].SuiteName
483
>
})
484
486
}