blob: bdcf6c12eba667486d65752a7fb4869503c2dd68 [file] [log] [blame]
package find_breaks
import (
"fmt"
"go.skia.org/infra/task_scheduler/go/db"
)
// failure is a struct which represents a failure of a given task spec,
// potentially merged from several failed tasks.
type failure struct {
// id is provided by the creator as a handle for the failure.
id string
// brokeIn is the slice of commits which may have caused the failure.
// Should not extend outside the current commit window, even if the real
// cause of the failure might be outside.
brokeIn slice
// failing is the slice of commits affected by the failure. Must overlap
// all of brokeIn. Should not extend outside the current commit window.
failing slice
// fixedIn is the slice of commits which may have fixed the failure. May
// be empty if the failure has not yet been fixed.
fixedIn slice
}
// valid returns an error if the failure is not valid.
func (f *failure) valid() error {
// All of the component slices must be valid.
if !f.brokeIn.Valid() {
return fmt.Errorf("failure: brokeIn is invalid: %v", f.brokeIn)
}
if !f.failing.Valid() {
return fmt.Errorf("failure: failing is invalid: %v", f.failing)
}
if !f.fixedIn.Valid() {
return fmt.Errorf("failure: fixedIn is invalid: %v", f.fixedIn)
}
// brokeIn must be a subslice of failing.
if f.brokeIn.Overlap(f.failing).Len() != f.brokeIn.Len() {
return fmt.Errorf("failure: brokeIn is not a subslice of failing: %v and %v", f.brokeIn, f.failing)
}
// brokeIn and fixedIn can't overlap.
if f.brokeIn.Overlap(f.fixedIn).Len() != 0 {
return fmt.Errorf("failure: brokeIn overlaps fixedIn: %v and %v", f.brokeIn, f.fixedIn)
}
// failing and fixedIn can't overlap.
if f.failing.Overlap(f.fixedIn).Len() != 0 {
return fmt.Errorf("failure: failing overlaps fixedIn: %v and %v", f.failing, f.fixedIn)
}
return nil
}
// findFailuresForSpec creates a slice of failure instances from the given slice
// of Tasks within the given slice of commits. Assumes all tasks are of the same
// TaskSpec, and that the given slice of commits are in chronological order, ie.
// oldest first.
func findFailuresForSpec(tasks []*db.Task, commits []string) ([]*failure, error) {
rv := []*failure{}
byCommit := map[string]*db.Task{}
for _, t := range tasks {
for _, c := range t.Commits {
byCommit[c] = t
}
}
var f *failure
firstFailed := -1
lastFailed := -1
wildCardStart := -1
for idx, c := range commits {
t := byCommit[c]
if t != nil && t.Status == db.TASK_STATUS_FAILURE {
if f == nil {
brokeIn := makeSlice(t.Commits, commits)
if wildCardStart != -1 {
brokeIn.start = wildCardStart
}
f = &failure{
id: t.Id,
brokeIn: brokeIn,
fixedIn: newSlice(-1, -1),
}
}
if firstFailed == -1 {
firstFailed = idx
if wildCardStart != -1 {
firstFailed = wildCardStart
}
}
lastFailed = idx + 1
wildCardStart = -1
} else if t != nil && t.Status == db.TASK_STATUS_SUCCESS {
if f != nil {
f.fixedIn = makeSlice(t.Commits, commits)
if wildCardStart != -1 {
f.fixedIn.start = wildCardStart
}
if f.brokeIn.Empty() {
f.failing = slice{
start: 0,
end: f.fixedIn.start,
}
} else {
f.failing = slice{
start: f.brokeIn.start,
end: f.fixedIn.start,
}
}
if err := f.valid(); err != nil {
return nil, err
}
rv = append(rv, f)
f = nil
}
firstFailed = -1
lastFailed = -1
wildCardStart = -1
} else {
// The task encountered a mishap, or is not
// finished, or we somehow have a blamelist gap. We
// essentially have to treat it as a wild card; if the
// previous task succeeded and the subsequent task
// succeeded, then we assume it's a success. If the
// previous task failed and the subsequent task failed,
// then we assume it's a failure. If the previous
// task failed and the subsequent task
// succeeded, then the commits for both this
// task and the next must be part of fixedIn. If
// the previous task succeeded and the
// subsequent task failed, the commits for this
// task and the next must be part of brokeIn.
if wildCardStart == -1 {
wildCardStart = idx
}
}
}
if f != nil {
f.failing = newSlice(firstFailed, lastFailed)
if !f.brokeIn.Empty() && f.brokeIn.start < f.failing.start {
f.failing.start = f.brokeIn.start
}
f.fixedIn = newSlice(-1, -1)
if err := f.valid(); err != nil {
return nil, err
}
rv = append(rv, f)
}
return rv, nil
}
// findFailures creates a slice of failure instances from the given slice of
// Tasks within the given slice of commits.
func findFailures(tasks []*db.Task, commits []string) ([]*failure, error) {
rv := []*failure{}
bySpec := map[string][]*db.Task{}
for _, t := range tasks {
if !t.Done() {
continue
}
bySpec[t.Name] = append(bySpec[t.Name], t)
}
for _, t := range bySpec {
fails, err := findFailuresForSpec(t, commits)
if err != nil {
return nil, err
}
rv = append(rv, fails...)
}
return rv, nil
}