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  }