| Index: go/src/infra/monitoring/analyzer/test_failures.go
|
| diff --git a/go/src/infra/monitoring/analyzer/test_failures.go b/go/src/infra/monitoring/analyzer/test_failures.go
|
| index 5941420ce6c01de40163439b3239def933dce23a..205262c1a690e7782b196488b22e2ffa738eea5d 100644
|
| --- a/go/src/infra/monitoring/analyzer/test_failures.go
|
| +++ b/go/src/infra/monitoring/analyzer/test_failures.go
|
| @@ -21,31 +21,86 @@ type TestFailureAnalyzer struct {
|
| // running.
|
| func (a *TestFailureAnalyzer) Analyze(f stepFailure) (*StepAnalyzerResult, error) {
|
| ret := &StepAnalyzerResult{}
|
| - if !strings.HasSuffix(f.step.Name, "tests") {
|
| + // Some test steps have names like "webkit_tests iOS(dbug)" so we look at the first
|
| + // term before the space, if there is one.
|
| + s := strings.Split(f.step.Name, " ")
|
| + if !strings.HasSuffix(s[0], "tests") {
|
| return ret, nil
|
| }
|
| ret.Recognized = true
|
|
|
| testResults, err := a.Client.TestResults(f.masterName, f.builderName, f.step.Name, f.build.Number)
|
| if err != nil {
|
| + ret.Reasons = append(ret.Reasons, f.step.Name)
|
| return ret, fmt.Errorf("Error fetching test results: %v", err)
|
| }
|
|
|
| if len(testResults.Tests) == 0 {
|
| - log.Errorf("No test results for %v", f)
|
| + return ret, fmt.Errorf("No test results for %v", f)
|
| }
|
| - log.Infof("%d test results", len(testResults.Tests))
|
|
|
| for testName, testResults := range testResults.Tests {
|
| - // This string splitting logic was copied from builder_alerts.
|
| - // I'm not sure it's really necessary since the strings appear to always
|
| - // be either "PASS" or "FAIL" in practice.
|
| - expected := strings.Split(testResults.Expected, " ")
|
| - actual := strings.Split(testResults.Actual, " ")
|
| - ue := unexpected(expected, actual)
|
| + res, ok := testResults.(map[string]interface{})
|
| + if !ok {
|
| + log.Errorf("Couldn't convert test results to map: %s", testName)
|
| + continue
|
| + }
|
| +
|
| + // If res is a simple top-level test result, just check it here.
|
| + if res["expected"] != nil || res["actual"] != nil {
|
| + expected := strings.Split(res["expected"].(string), " ")
|
| + actual := strings.Split(res["actual"].(string), " ")
|
| + ue := unexpected(expected, actual)
|
| + if len(ue) > 0 && res["bugs"] == nil {
|
| + ret.Reasons = append(ret.Reasons, testName)
|
| + }
|
| + continue
|
| + }
|
| +
|
| + // res is not a simple top-level test result, so recurse to find
|
| + // the actual results.
|
| + ue, err := traverseResults(testName, res)
|
| + if err != nil {
|
| + return nil, err
|
| + }
|
| if len(ue) > 0 {
|
| + // Don't give too many reasons, just the top level test/suite names.
|
| + // TODO: figure out a way to coalesce the unexpected results list into
|
| + // something compact but still meaningful in an alerting context.
|
| ret.Reasons = append(ret.Reasons, testName)
|
| }
|
| }
|
| return ret, nil
|
| }
|
| +
|
| +// testResults json is an arbitrarily deep tree, whose nodes are the actual
|
| +// test results, so we recurse to find them.
|
| +func traverseResults(parent string, testResults map[string]interface{}) ([]string, error) {
|
| + ret := []string{}
|
| + for testName, testResults := range testResults {
|
| + res, ok := testResults.(map[string]interface{})
|
| + if !ok {
|
| + return nil, fmt.Errorf("Couldn't convert test results to map: %s/%s", parent, testName)
|
| + }
|
| + // First check if results is actually results, or just another branch
|
| + // in the tree of test results
|
| + // Uuuuugly.
|
| + if res["expected"] == nil || res["actual"] == nil {
|
| + // Assume it's a branch.
|
| + res, err := traverseResults(fmt.Sprintf("%s/%s", parent, testName), res)
|
| + if err != nil {
|
| + return ret, err
|
| + }
|
| + ret = append(ret, res...)
|
| + continue
|
| + }
|
| +
|
| + expected := strings.Split(res["expected"].(string), " ")
|
| + actual := strings.Split(res["actual"].(string), " ")
|
| + ue := unexpected(expected, actual)
|
| + if len(ue) > 0 && res["bugs"] == nil {
|
| + ret = append(ret, fmt.Sprintf("%s/%s: %+v vs %+v", parent, testName, expected, actual))
|
| + }
|
| + }
|
| + return ret, nil
|
| +}
|
|
|