go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/changepoints/bayesian/confidence_interval_test.go (about) 1 // Copyright 2023 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package bayesian 16 17 import ( 18 "testing" 19 20 "go.chromium.org/luci/analysis/internal/changepoints/inputbuffer" 21 22 . "github.com/smartystreets/goconvey/convey" 23 ) 24 25 func TestChangePointPositionConfidenceInterval(t *testing.T) { 26 a := ChangepointPredictor{ 27 HasUnexpectedPrior: BetaDistribution{ 28 Alpha: 0.3, 29 Beta: 0.5, 30 }, 31 UnexpectedAfterRetryPrior: BetaDistribution{ 32 Alpha: 0.5, 33 Beta: 0.5, 34 }, 35 } 36 Convey("6 commit positions, each with 1 verdict", t, func() { 37 var ( 38 positions = []int{1, 2, 3, 4, 5, 6} 39 total = []int{2, 2, 1, 1, 2, 2} 40 hasUnexpected = []int{0, 0, 0, 1, 2, 2} 41 ) 42 vs := inputbuffer.Verdicts(positions, total, hasUnexpected) 43 min, max := a.changePointPositionConfidenceInterval(vs, 0.005) 44 So(min, ShouldEqual, 1) 45 So(max, ShouldEqual, 4) 46 }) 47 48 Convey("4 commit positions, 2 verdict each", t, func() { 49 var ( 50 positions = []int{1, 1, 2, 2, 3, 3, 4, 4} 51 total = []int{2, 2, 2, 2, 2, 2, 2, 2} 52 hasUnexpected = []int{0, 0, 0, 0, 1, 1, 1, 1} 53 ) 54 vs := inputbuffer.Verdicts(positions, total, hasUnexpected) 55 min, max := a.changePointPositionConfidenceInterval(vs, 0.005) 56 So(min, ShouldEqual, 2) 57 So(max, ShouldEqual, 6) 58 }) 59 60 Convey("2 commit position with multiple verdicts each", t, func() { 61 var ( 62 positions = []int{1, 1, 2, 2, 2, 2} 63 total = []int{3, 3, 1, 2, 3, 3} 64 hasUnexpected = []int{0, 0, 0, 2, 3, 3} 65 ) 66 vs := inputbuffer.Verdicts(positions, total, hasUnexpected) 67 min, max := a.changePointPositionConfidenceInterval(vs, 0.005) 68 // There is only 1 possible position for change point 69 So(min, ShouldEqual, 2) 70 So(max, ShouldEqual, 2) 71 }) 72 73 Convey("Pass to flake transition", t, func() { 74 var ( 75 positions = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 76 total = []int{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2} 77 hasUnexpected = []int{0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} 78 ) 79 vs := inputbuffer.Verdicts(positions, total, hasUnexpected) 80 min, max := a.changePointPositionConfidenceInterval(vs, 0.005) 81 So(min, ShouldEqual, 1) 82 So(max, ShouldEqual, 13) 83 }) 84 85 Convey("Flake to fail transition", t, func() { 86 var ( 87 positions = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 88 total = []int{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2} 89 hasUnexpected = []int{1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2} 90 ) 91 vs := inputbuffer.Verdicts(positions, total, hasUnexpected) 92 min, max := a.changePointPositionConfidenceInterval(vs, 0.005) 93 So(min, ShouldEqual, 1) 94 So(max, ShouldEqual, 13) 95 }) 96 97 Convey("(Fail, Pass after retry) to (Fail, Fail after retry)", t, func() { 98 var ( 99 positions = []int{1, 2, 3, 4, 5, 6, 7, 8} 100 total = []int{2, 2, 2, 2, 2, 2, 2, 2} 101 hasUnexpected = []int{2, 2, 2, 2, 2, 2, 2, 2} 102 retries = []int{2, 2, 2, 2, 2, 2, 2, 2} 103 unexpectedAfterRetry = []int{0, 0, 0, 0, 2, 2, 2, 2} 104 ) 105 vs := inputbuffer.VerdictsWithRetries(positions, total, hasUnexpected, retries, unexpectedAfterRetry) 106 min, max := a.changePointPositionConfidenceInterval(vs, 0.005) 107 So(min, ShouldEqual, 2) 108 So(max, ShouldEqual, 5) 109 }) 110 111 Convey("(Fail, Fail after retry) to (Fail, Flaky on retry)", t, func() { 112 var ( 113 positions = []int{1, 2, 3, 5, 5, 5, 7, 7} 114 total = []int{3, 3, 3, 1, 3, 3, 3, 3} 115 hasUnexpected = []int{3, 3, 3, 1, 3, 3, 3, 3} 116 retries = []int{3, 3, 3, 1, 3, 3, 3, 3} 117 unexpectedAfterRetry = []int{3, 3, 3, 1, 0, 0, 1, 1} 118 ) 119 vs := inputbuffer.VerdictsWithRetries(positions, total, hasUnexpected, retries, unexpectedAfterRetry) 120 min, max := a.changePointPositionConfidenceInterval(vs, 0.005) 121 So(min, ShouldEqual, 1) 122 So(max, ShouldEqual, 3) 123 }) 124 } 125 126 // Output as of March 2023 on Cloudtop CPU AMD EPYC 7B12 127 // BenchmarkChangePointPositionConfidenceInterval-24 1947 613173 ns/op 128 func BenchmarkChangePointPositionConfidenceInterval(b *testing.B) { 129 a := ChangepointPredictor{ 130 ChangepointLikelihood: 0.01, 131 HasUnexpectedPrior: BetaDistribution{ 132 Alpha: 0.3, 133 Beta: 0.5, 134 }, 135 UnexpectedAfterRetryPrior: BetaDistribution{ 136 Alpha: 0.5, 137 Beta: 0.5, 138 }, 139 } 140 141 var vs []inputbuffer.PositionVerdict 142 143 for i := 0; i <= 1000; i++ { 144 vs = append(vs, inputbuffer.PositionVerdict{ 145 CommitPosition: i, 146 IsSimpleExpectedPass: true, 147 }) 148 } 149 for i := 1001; i < 2000; i++ { 150 vs = append(vs, inputbuffer.PositionVerdict{ 151 CommitPosition: i, 152 IsSimpleExpectedPass: false, 153 Details: inputbuffer.VerdictDetails{ 154 Runs: []inputbuffer.Run{ 155 { 156 Unexpected: inputbuffer.ResultCounts{ 157 FailCount: 1, 158 }, 159 }, 160 }, 161 }, 162 }) 163 } 164 165 for i := 0; i < b.N; i++ { 166 min, max := a.changePointPositionConfidenceInterval(vs, 0.005) 167 if min > 1001 || max < 1001 { 168 panic("Invalid result") 169 } 170 } 171 } 172 173 func TestChangePoints(t *testing.T) { 174 Convey("Confidence Interval For ChangePoints", t, func() { 175 a := ChangepointPredictor{ 176 ChangepointLikelihood: 0.0001, 177 HasUnexpectedPrior: BetaDistribution{ 178 Alpha: 0.3, 179 Beta: 0.5, 180 }, 181 UnexpectedAfterRetryPrior: BetaDistribution{ 182 Alpha: 0.5, 183 Beta: 0.5, 184 }, 185 } 186 positions := make([]int, 300) 187 total := make([]int, 300) 188 hasUnexpected := make([]int, 300) 189 for i := 0; i < 300; i++ { 190 positions[i] = i + 1 191 total[i] = 1 192 if i >= 100 && i <= 199 { 193 hasUnexpected[i] = 1 194 } 195 } 196 197 vs := inputbuffer.Verdicts(positions, total, hasUnexpected) 198 cps := a.ChangePoints(vs, ConfidenceIntervalTail) 199 So(cps, ShouldResemble, []inputbuffer.ChangePoint{ 200 { 201 NominalIndex: 100, 202 LowerBound99ThIndex: 98, 203 UpperBound99ThIndex: 100, 204 }, 205 { 206 NominalIndex: 200, 207 LowerBound99ThIndex: 199, 208 UpperBound99ThIndex: 201, 209 }, 210 }) 211 }) 212 }