package regression import ( "context" "fmt" "gorm.io/gorm" ) // Result holds the regression comparison between two commits. type Result struct { BaseSHA string `json:"base_sha"` HeadSHA string `json:"head_sha"` RepoName string `json:"repo_name"` New []string `json:"new"` // Fingerprints in head but not base. Fixed []string `json:"fixed"` // Fingerprints in base but not head. Recurring []string `json:"recurring"` // Fingerprints in both. IsRegression bool `json:"is_regression"` } // Compare computes the set difference of crash fingerprints between a base and head commit. func Compare(ctx context.Context, db *gorm.DB, repoID uint, baseSHA, headSHA string) (*Result, error) { baseFingerprints, err := fingerprintsForCommit(ctx, db, repoID, baseSHA) if err != nil { return nil, fmt.Errorf("base commit fingerprints: %w", err) } headFingerprints, err := fingerprintsForCommit(ctx, db, repoID, headSHA) if err != nil { return nil, fmt.Errorf("head commit fingerprints: %w", err) } baseSet := toSet(baseFingerprints) headSet := toSet(headFingerprints) var newFPs, fixedFPs, recurringFPs []string for fp := range headSet { if baseSet[fp] { recurringFPs = append(recurringFPs, fp) } else { newFPs = append(newFPs, fp) } } for fp := range baseSet { if !headSet[fp] { fixedFPs = append(fixedFPs, fp) } } return &Result{ BaseSHA: baseSHA, HeadSHA: headSHA, New: newFPs, Fixed: fixedFPs, Recurring: recurringFPs, IsRegression: len(newFPs) > 0, }, nil } func fingerprintsForCommit(ctx context.Context, db *gorm.DB, repoID uint, sha string) ([]string, error) { var fps []string err := db.WithContext(ctx). Table("artifacts"). Distinct("crash_signatures.fingerprint"). Joins("JOIN commits ON commits.id = artifacts.commit_id"). Joins("JOIN crash_signatures ON crash_signatures.id = artifacts.crash_signature_id"). Where("artifacts.repository_id = ? AND commits.sha = ?", repoID, sha). Where("artifacts.crash_signature_id IS NOT NULL"). Pluck("crash_signatures.fingerprint", &fps).Error if err != nil { return nil, err } return fps, nil } func toSet(items []string) map[string]bool { s := make(map[string]bool, len(items)) for _, item := range items { s[item] = true } return s }