go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/testresults/query_failure_rate_test.go (about) 1 // Copyright 2022 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 testresults 16 17 import ( 18 "fmt" 19 "testing" 20 "time" 21 22 "go.chromium.org/luci/server/span" 23 24 "go.chromium.org/luci/analysis/internal/testutil" 25 "go.chromium.org/luci/analysis/pbutil" 26 pb "go.chromium.org/luci/analysis/proto/v1" 27 28 . "github.com/smartystreets/goconvey/convey" 29 . "go.chromium.org/luci/common/testing/assertions" 30 ) 31 32 func TestQueryFailureRate(t *testing.T) { 33 Convey("QueryFailureRate", t, func() { 34 ctx := testutil.IntegrationTestContext(t) 35 36 var1 := pbutil.Variant("key1", "val1", "key2", "val1") 37 var3 := pbutil.Variant("key1", "val2", "key2", "val2") 38 39 err := CreateQueryFailureRateTestData(ctx) 40 So(err, ShouldBeNil) 41 42 project, asAtTime, tvs := QueryFailureRateSampleRequest() 43 opts := QueryFailureRateOptions{ 44 Project: project, 45 SubRealms: []string{"realm"}, 46 TestVariants: tvs, 47 AsAtTime: asAtTime, 48 } 49 expectedResult := QueryFailureRateSampleResponse() 50 txn, cancel := span.ReadOnlyTransaction(ctx) 51 defer cancel() 52 53 Convey("Baseline", func() { 54 result, err := QueryFailureRate(txn, opts) 55 So(err, ShouldBeNil) 56 So(result, ShouldResembleProto, expectedResult) 57 }) 58 Convey("Project filter works correctly", func() { 59 opts.Project = "none" 60 expectedResult.TestVariants = []*pb.TestVariantFailureRateAnalysis{ 61 emptyAnalysis("test_id", var1), 62 emptyAnalysis("test_id", var3), 63 } 64 65 result, err := QueryFailureRate(txn, opts) 66 So(err, ShouldBeNil) 67 So(result, ShouldResembleProto, expectedResult) 68 }) 69 Convey("Realm filter works correctly", func() { 70 // No data exists in this realm. 71 opts.SubRealms = []string{"otherrealm"} 72 expectedResult.TestVariants = []*pb.TestVariantFailureRateAnalysis{ 73 emptyAnalysis("test_id", var1), 74 emptyAnalysis("test_id", var3), 75 } 76 77 result, err := QueryFailureRate(txn, opts) 78 So(err, ShouldBeNil) 79 So(result, ShouldResembleProto, expectedResult) 80 }) 81 Convey("Works for tests without data", func() { 82 notExistsVariant := pbutil.Variant("key1", "val1", "key2", "not_exists") 83 opts.TestVariants = append(opts.TestVariants, 84 &pb.TestVariantIdentifier{ 85 TestId: "not_exists_test_id", 86 Variant: var1, 87 }, 88 &pb.TestVariantIdentifier{ 89 TestId: "test_id", 90 Variant: notExistsVariant, 91 }) 92 93 expectedResult.TestVariants = append(expectedResult.TestVariants, 94 emptyAnalysis("not_exists_test_id", var1), 95 emptyAnalysis("test_id", notExistsVariant)) 96 97 result, err := QueryFailureRate(txn, opts) 98 So(err, ShouldBeNil) 99 So(result, ShouldResembleProto, expectedResult) 100 }) 101 Convey("Batching works correctly", func() { 102 // Ensure the order of test variants in the request and response 103 // remain correct even when there are multiple batches. 104 var expandedInput []*pb.TestVariantIdentifier 105 var expectedOutput []*pb.TestVariantFailureRateAnalysis 106 for i := 0; i < batchSize; i++ { 107 testID := fmt.Sprintf("test_id_%v", i) 108 expandedInput = append(expandedInput, &pb.TestVariantIdentifier{ 109 TestId: testID, 110 Variant: var1, 111 }) 112 expectedOutput = append(expectedOutput, emptyAnalysis(testID, var1)) 113 } 114 115 expandedInput = append(expandedInput, tvs...) 116 expectedOutput = append(expectedOutput, expectedResult.TestVariants...) 117 118 opts.TestVariants = expandedInput 119 expectedResult.TestVariants = expectedOutput 120 121 result, err := QueryFailureRate(txn, opts) 122 So(err, ShouldBeNil) 123 So(result, ShouldResembleProto, expectedResult) 124 }) 125 }) 126 } 127 128 // emptyAnalysis returns an empty analysis proto with intervals populated. 129 func emptyAnalysis(testId string, variant *pb.Variant) *pb.TestVariantFailureRateAnalysis { 130 return &pb.TestVariantFailureRateAnalysis{ 131 TestId: testId, 132 Variant: variant, 133 IntervalStats: []*pb.TestVariantFailureRateAnalysis_IntervalStats{ 134 {IntervalAge: 1}, 135 {IntervalAge: 2}, 136 {IntervalAge: 3}, 137 {IntervalAge: 4}, 138 {IntervalAge: 5}, 139 }, 140 RunFlakyVerdictExamples: []*pb.TestVariantFailureRateAnalysis_VerdictExample{}, 141 RecentVerdicts: []*pb.TestVariantFailureRateAnalysis_RecentVerdict{}, 142 } 143 } 144 145 func TestJumpBack24WeekdayHours(t *testing.T) { 146 Convey("jumpBack24WeekdayHours", t, func() { 147 // Expect jumpBack24WeekdayHours to go back in time just far enough 148 // that 24 workday hours are between the returned time and now. 149 Convey("Monday", func() { 150 // Given an input on a Monday (e.g. 14th of March 2022), expect 151 // failureRateQueryAfterTime to return the corresponding time 152 // on the previous Friday. 153 154 now := time.Date(2022, time.March, 14, 23, 59, 59, 999999999, time.UTC) 155 afterTime := jumpBack24WeekdayHours(now) 156 So(afterTime, ShouldEqual, time.Date(2022, time.March, 11, 23, 59, 59, 999999999, time.UTC)) 157 158 now = time.Date(2022, time.March, 14, 0, 0, 0, 0, time.UTC) 159 afterTime = jumpBack24WeekdayHours(now) 160 So(afterTime, ShouldEqual, time.Date(2022, time.March, 11, 0, 0, 0, 0, time.UTC)) 161 }) 162 Convey("Sunday", func() { 163 // Given a time on a Sunday (e.g. 13th of March 2022), expect 164 // failureRateQueryAfterTime to return the start of the previous 165 // Friday. 166 startOfFriday := time.Date(2022, time.March, 11, 0, 0, 0, 0, time.UTC) 167 168 now := time.Date(2022, time.March, 13, 23, 59, 59, 999999999, time.UTC) 169 afterTime := jumpBack24WeekdayHours(now) 170 So(afterTime, ShouldEqual, startOfFriday) 171 172 now = time.Date(2022, time.March, 13, 0, 0, 0, 0, time.UTC) 173 afterTime = jumpBack24WeekdayHours(now) 174 So(afterTime, ShouldEqual, startOfFriday) 175 }) 176 Convey("Saturday", func() { 177 // Given a time on a Saturday (e.g. 12th of March 2022), expect 178 // failureRateQueryAfterTime to return the start of the previous 179 // Friday. 180 startOfFriday := time.Date(2022, time.March, 11, 0, 0, 0, 0, time.UTC) 181 182 now := time.Date(2022, time.March, 12, 23, 59, 59, 999999999, time.UTC) 183 afterTime := jumpBack24WeekdayHours(now) 184 So(afterTime, ShouldEqual, startOfFriday) 185 186 now = time.Date(2022, time.March, 12, 0, 0, 0, 0, time.UTC) 187 afterTime = jumpBack24WeekdayHours(now) 188 So(afterTime, ShouldEqual, startOfFriday) 189 }) 190 Convey("Tuesday to Friday", func() { 191 // Given an input on a Tuesday (e.g. 15th of March 2022), expect 192 // failureRateQueryAfterTime to return the corresponding time 193 // the previous day. 194 now := time.Date(2022, time.March, 15, 1, 2, 3, 4, time.UTC) 195 afterTime := jumpBack24WeekdayHours(now) 196 So(afterTime, ShouldEqual, time.Date(2022, time.March, 14, 1, 2, 3, 4, time.UTC)) 197 198 // Given an input on a Friday (e.g. 18th of March 2022), expect 199 // failureRateQueryAfterTime to return the corresponding time 200 // the previous day. 201 now = time.Date(2022, time.March, 18, 1, 2, 3, 4, time.UTC) 202 afterTime = jumpBack24WeekdayHours(now) 203 So(afterTime, ShouldEqual, time.Date(2022, time.March, 17, 1, 2, 3, 4, time.UTC)) 204 }) 205 }) 206 }