package regression import ( "context" "fmt" "github.com/jackc/pgx/v5/pgxpool" ) // 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, pool *pgxpool.Pool, repoID, baseSHA, headSHA string) (*Result, error) { baseFingerprints, err := fingerprintsForCommit(ctx, pool, repoID, baseSHA) if err != nil { return nil, fmt.Errorf("base commit fingerprints: %w", err) } headFingerprints, err := fingerprintsForCommit(ctx, pool, 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, pool *pgxpool.Pool, repoID, sha string) ([]string, error) { rows, err := pool.Query(ctx, ` SELECT DISTINCT a.fingerprint FROM artifacts a JOIN commits c ON c.id = a.commit_id WHERE a.repository_id = $1 AND c.sha = $2 AND a.fingerprint IS NOT NULL `, repoID, sha) if err != nil { return nil, err } defer rows.Close() var fps []string for rows.Next() { var fp string if err := rows.Scan(&fp); err != nil { return nil, err } fps = append(fps, fp) } 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 }