blob: 39bd35b721a6261930294556770f9401b5024a38 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestSrcConfig(t *testing.T) {
t.Parallel()
var testSrcConfig = make(map[string]SrcConfig)
testSrcConfig["project"] = SrcConfig{
DefaultSpecs: []ProblemSpec{
{
Name: "Unhealthy",
PeriodDays: 1,
Thresholds: Thresholds{
TestPendingTime: PercentileThresholds{P50Mins: 60, P95Mins: 120},
PendingTime: PercentileThresholds{P50Mins: 60, P95Mins: 120},
BuildTime: PercentileThresholds{P50Mins: 60, P95Mins: 120},
FailRate: AverageThresholds{Average: 0.2},
InfraFailRate: AverageThresholds{Average: 0.1},
},
},
{
Name: "Low Value",
PeriodDays: 1,
Thresholds: Thresholds{
FailRate: AverageThresholds{Average: 0.99},
InfraFailRate: AverageThresholds{Average: 0.99},
},
},
},
BucketSpecs: map[string]BuilderSpecs{
"bucket": {
"builder": BuilderSpec{
ProblemSpecs: []ProblemSpec{
{
Name: "Unhealthy",
PeriodDays: 1,
Score: UNHEALTHY_SCORE,
Thresholds: Thresholds{
Default: "_default",
},
},
{
Name: "Low Value",
PeriodDays: 1,
Score: LOW_VALUE_SCORE,
Thresholds: Thresholds{
Default: "_default",
},
},
},
},
},
"slow-bucket": {
"slow-builder": BuilderSpec{
ProblemSpecs: []ProblemSpec{
{
Name: "Low Value",
PeriodDays: 1,
Score: LOW_VALUE_SCORE,
Thresholds: Thresholds{
Default: "_default",
},
},
{
Name: "Unhealthy",
PeriodDays: 1,
Score: UNHEALTHY_SCORE,
Thresholds: Thresholds{
TestPendingTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
PendingTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
BuildTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
FailRate: AverageThresholds{Average: 0.4},
InfraFailRate: AverageThresholds{Average: 0.3},
},
},
},
},
},
"custom-bucket": {
"custom-builder": BuilderSpec{
ProblemSpecs: []ProblemSpec{
{
Name: "Unhealthy",
PeriodDays: 1,
Score: UNHEALTHY_SCORE,
Thresholds: Thresholds{
TestPendingTime: PercentileThresholds{P50Mins: 60, P95Mins: 120},
PendingTime: PercentileThresholds{P50Mins: 60, P95Mins: 120},
BuildTime: PercentileThresholds{P50Mins: 60, P95Mins: 120},
FailRate: AverageThresholds{Average: 0.4},
InfraFailRate: AverageThresholds{Average: 0.3},
},
},
{
Name: "Low Value",
PeriodDays: 1,
Score: LOW_VALUE_SCORE,
Thresholds: Thresholds{
TestPendingTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
PendingTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
BuildTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
FailRate: AverageThresholds{Average: 0.99},
InfraFailRate: AverageThresholds{Average: 0.99},
},
},
},
},
},
"improper-bucket": {
"improper-builder": BuilderSpec{
ProblemSpecs: []ProblemSpec{
{
Name: "Unhealthy",
PeriodDays: 1,
Score: UNHEALTHY_SCORE,
Thresholds: Thresholds{
Default: "_default",
TestPendingTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
PendingTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
BuildTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
FailRate: AverageThresholds{Average: 0.4},
InfraFailRate: AverageThresholds{Average: 0.3},
},
},
},
},
"improper-builder2": BuilderSpec{
ProblemSpecs: []ProblemSpec{
{
Name: "Unhealthy",
PeriodDays: 1,
Score: UNHEALTHY_SCORE,
Thresholds: Thresholds{
Default: "not_default",
TestPendingTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
PendingTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
BuildTime: PercentileThresholds{P50Mins: 600, P95Mins: 1200},
FailRate: AverageThresholds{Average: 0.4},
InfraFailRate: AverageThresholds{Average: 0.3},
},
},
},
},
"improper-builder3": BuilderSpec{
ProblemSpecs: []ProblemSpec{},
},
},
},
}
Convey("Healthy builder is healthy, default thresholds", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "bucket",
Builder: "builder",
Metrics: []*Metric{
{Type: "build_mins_p50", Value: 59},
{Type: "build_mins_p95", Value: 119},
{Type: "pending_mins_p50", Value: 59},
{Type: "pending_mins_p95", Value: 119},
{Type: "fail_rate", Value: 0.05},
{Type: "infra_fail_rate", Value: 0},
},
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 1)
So(savedThresholds, ShouldResemble, testSrcConfig)
So(outputRows[0].HealthScore, ShouldEqual, HEALTHY_SCORE)
explanation := scoreExplanation(outputRows[0], savedThresholds)
So(explanation, ShouldBeEmpty)
})
Convey("P50 percentile above threshold, default thresholds", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "bucket",
Builder: "builder",
Metrics: []*Metric{
{Type: "build_mins_p50", Value: 61},
},
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 1)
So(outputRows[0].HealthScore, ShouldEqual, UNHEALTHY_SCORE)
So(savedThresholds, ShouldResemble, testSrcConfig)
explanation := scoreExplanation(outputRows[0], savedThresholds)
So(explanation, ShouldContainSubstring, "build_mins_p50")
})
Convey("P95 percentile above thresholds, default thresholds", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "bucket",
Builder: "builder",
Metrics: []*Metric{
{Type: "pending_mins_p95", Value: 121},
},
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 1)
So(outputRows[0].HealthScore, ShouldEqual, UNHEALTHY_SCORE)
explanation := scoreExplanation(outputRows[0], savedThresholds)
So(explanation, ShouldContainSubstring, "pending_mins_p95")
So(savedThresholds, ShouldResemble, testSrcConfig)
})
Convey("Fail rate above thresholds, default thresholds", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "bucket",
Builder: "builder",
Metrics: []*Metric{
{Type: "fail_rate", Value: 0.3},
},
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 1)
So(outputRows[0].HealthScore, ShouldEqual, UNHEALTHY_SCORE)
explanation := scoreExplanation(outputRows[0], savedThresholds)
So(explanation, ShouldContainSubstring, "fail_rate")
So(savedThresholds, ShouldResemble, testSrcConfig)
})
Convey("P50 build time below thresholds, slow builder", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "slow-bucket",
Builder: "slow-builder",
Metrics: []*Metric{
{Type: "build_mins_p50", Value: 200},
},
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 1)
So(outputRows[0].HealthScore, ShouldEqual, HEALTHY_SCORE)
explanation := scoreExplanation(outputRows[0], savedThresholds)
So(explanation, ShouldBeEmpty)
So(savedThresholds, ShouldResemble, testSrcConfig)
})
Convey("Infra fail rate above thresholds, slow builder", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "slow-bucket",
Builder: "slow-builder",
Metrics: []*Metric{
{Type: "infra_fail_rate", Value: 0.5},
},
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 1)
So(outputRows[0].HealthScore, ShouldEqual, UNHEALTHY_SCORE)
explanation := scoreExplanation(outputRows[0], savedThresholds)
So(explanation, ShouldContainSubstring, "infra_fail_rate")
So(savedThresholds, ShouldResemble, testSrcConfig)
})
Convey("Default thresholds with custom thresholds error", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "slow-bucket",
Builder: "slow-builder",
Metrics: []*Metric{
{Type: "infra_fail_rate", Value: 0.5},
},
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 1)
So(outputRows[0].HealthScore, ShouldEqual, UNHEALTHY_SCORE)
explanation := scoreExplanation(outputRows[0], savedThresholds)
So(explanation, ShouldContainSubstring, "infra_fail_rate")
So(savedThresholds, ShouldResemble, testSrcConfig)
})
Convey("Multiple healthy builders", t, func() {
ctx := context.Background()
rows := []Row{
{
Project: "project",
Bucket: "bucket",
Builder: "builder",
Metrics: []*Metric{
{Type: "build_mins_p50", Value: 59},
{Type: "build_mins_p95", Value: 119},
{Type: "pending_mins_p50", Value: 59},
{Type: "pending_mins_p95", Value: 119},
{Type: "fail_rate", Value: 0.05},
{Type: "infra_fail_rate", Value: 0},
},
},
{
Project: "project",
Bucket: "slow-bucket",
Builder: "slow-builder",
Metrics: []*Metric{
{Type: "build_mins_p50", Value: 59},
{Type: "build_mins_p95", Value: 119},
{Type: "pending_mins_p50", Value: 59},
{Type: "pending_mins_p95", Value: 119},
{Type: "fail_rate", Value: 0.05},
{Type: "infra_fail_rate", Value: 0},
},
},
}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 2)
So(savedThresholds, ShouldResemble, testSrcConfig)
So(outputRows[0].HealthScore, ShouldEqual, HEALTHY_SCORE)
explanation := scoreExplanation(outputRows[0], savedThresholds)
So(explanation, ShouldBeEmpty)
So(outputRows[1].HealthScore, ShouldEqual, HEALTHY_SCORE)
explanation = scoreExplanation(outputRows[1], savedThresholds)
So(explanation, ShouldBeEmpty)
})
Convey("One healthy, one unhealthy builder", t, func() {
ctx := context.Background()
rows := []Row{
{
Project: "project",
Bucket: "bucket",
Builder: "builder",
Metrics: []*Metric{
{Type: "build_mins_p50", Value: 61},
{Type: "build_mins_p95", Value: 121},
{Type: "pending_mins_p50", Value: 61},
{Type: "pending_mins_p95", Value: 121},
{Type: "fail_rate", Value: 0.3},
{Type: "infra_fail_rate", Value: 0.2},
},
},
{
Project: "project",
Bucket: "slow-bucket",
Builder: "slow-builder",
Metrics: []*Metric{
{Type: "build_mins_p50", Value: 59},
{Type: "build_mins_p95", Value: 119},
{Type: "pending_mins_p50", Value: 59},
{Type: "pending_mins_p95", Value: 119},
{Type: "fail_rate", Value: 0.05},
{Type: "infra_fail_rate", Value: 0},
},
},
}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 2)
So(savedThresholds, ShouldResemble, testSrcConfig)
So(outputRows[0].HealthScore, ShouldEqual, UNHEALTHY_SCORE)
explanation := scoreExplanation(outputRows[0], savedThresholds)
So(explanation, ShouldContainSubstring, "build_mins")
So(explanation, ShouldContainSubstring, "infra_fail_rate")
So(outputRows[1].HealthScore, ShouldEqual, HEALTHY_SCORE)
explanation = scoreExplanation(outputRows[1], savedThresholds)
So(explanation, ShouldBeEmpty)
})
Convey("One low value, one unhealthy", t, func() {
ctx := context.Background()
rows := []Row{
{
Project: "project",
Bucket: "bucket",
Builder: "builder",
Metrics: []*Metric{
{Type: "build_mins_p50", Value: 61},
{Type: "build_mins_p95", Value: 121},
{Type: "pending_mins_p50", Value: 61},
{Type: "pending_mins_p95", Value: 121},
{Type: "fail_rate", Value: 1.0},
{Type: "infra_fail_rate", Value: 1.0},
},
},
{
Project: "project",
Bucket: "slow-bucket",
Builder: "slow-builder",
Metrics: []*Metric{
{Type: "build_mins_p50", Value: 61},
{Type: "build_mins_p95", Value: 121},
{Type: "pending_mins_p50", Value: 61},
{Type: "pending_mins_p95", Value: 121},
{Type: "fail_rate", Value: 0.90},
{Type: "infra_fail_rate", Value: 0.90},
},
},
}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 2)
So(savedThresholds, ShouldResemble, testSrcConfig)
So(outputRows[0].HealthScore, ShouldEqual, LOW_VALUE_SCORE)
explanation := scoreExplanation(outputRows[0], savedThresholds)
So(explanation, ShouldContainSubstring, "fail_rate")
So(outputRows[1].HealthScore, ShouldEqual, UNHEALTHY_SCORE)
explanation = scoreExplanation(outputRows[1], savedThresholds)
So(explanation, ShouldContainSubstring, "fail_rate")
})
Convey("Improper threshold config, both default and custom thresholds", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "improper-bucket",
Builder: "improper-builder",
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldNotBeNil)
So(len(outputRows), ShouldEqual, 1)
So(outputRows[0].ScoreExplanation, ShouldContainSubstring, "default")
So(outputRows[0].ScoreExplanation, ShouldContainSubstring, "custom")
So(outputRows[0].HealthScore, ShouldEqual, UNSET_SCORE)
So(savedThresholds, ShouldResemble, testSrcConfig)
})
Convey("Improper threshold config, Default set to unknown sentinel value", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "improper-bucket",
Builder: "improper-builder2",
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldNotBeNil)
So(len(outputRows), ShouldEqual, 1)
So(outputRows[0].ScoreExplanation, ShouldContainSubstring, "unknown sentinel")
So(outputRows[0].HealthScore, ShouldEqual, UNSET_SCORE)
So(savedThresholds, ShouldResemble, testSrcConfig)
})
Convey("Improper ProblemSpecs, no ProblemSpecs", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "improper-bucket",
Builder: "improper-builder3",
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldNotBeNil)
So(len(outputRows), ShouldEqual, 1)
So(outputRows[0].ScoreExplanation, ShouldContainSubstring, "no ProblemSpecs")
So(outputRows[0].HealthScore, ShouldEqual, UNSET_SCORE)
So(savedThresholds, ShouldResemble, testSrcConfig)
})
Convey("Unconfigured builder", t, func() {
ctx := context.Background()
rows := []Row{{
Project: "project",
Bucket: "unconfigured-bucket",
Builder: "unconfigured-builder",
}}
savedThresholds := testSrcConfig
outputRows, err := calculateIntermediateHealthScores(ctx, rows, testSrcConfig)
So(err, ShouldBeNil)
So(len(outputRows), ShouldEqual, 1)
So(outputRows[0].ScoreExplanation, ShouldBeBlank)
So(outputRows[0].HealthScore, ShouldEqual, UNSET_SCORE)
So(savedThresholds, ShouldResemble, testSrcConfig)
})
Convey("Sort ProblemSpecs", t, func() {
ps := []ProblemSpec{
{
Name: "Low Value",
Score: 1,
Thresholds: Thresholds{
Default: "Low Value",
},
},
{
Name: "Unhealthy",
Score: UNHEALTHY_SCORE,
Thresholds: Thresholds{
Default: "Unhealthy",
},
},
}
sortProblemSpecs(ps)
So(ps[0].Score, ShouldEqual, UNHEALTHY_SCORE)
})
Convey("Compare Thresholds Helper", t, func() {
const unhealthyIndex = 0
const lowValueIndex = 1
row := Row{
Project: "project",
Bucket: "custom-bucket",
Builder: "custom-builder",
Metrics: []*Metric{
{Type: "fail_rate", Value: 1.0},
},
}
ps := testSrcConfig["project"].BucketSpecs[row.Bucket][row.Builder].ProblemSpecs
compareThresholdsHelper(&row, &ps[unhealthyIndex], row.Metrics[0], ps[unhealthyIndex].Thresholds.FailRate.Average)
So(row.HealthScore, ShouldEqual, UNHEALTHY_SCORE)
compareThresholdsHelper(&row, &ps[lowValueIndex], row.Metrics[0], ps[lowValueIndex].Thresholds.FailRate.Average)
So(row.HealthScore, ShouldEqual, LOW_VALUE_SCORE)
})
}