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  }