cairn/internal/regression/regression.go

85 lines
2.3 KiB
Go

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
}