go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/changepoints/inputbuffer/input_buffer_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 inputbuffer
    16  
    17  import (
    18  	"testing"
    19  	"time"
    20  
    21  	. "github.com/smartystreets/goconvey/convey"
    22  )
    23  
    24  func TestEncodeAndDecode(t *testing.T) {
    25  	Convey(`Encode and decode should return the same result`, t, func() {
    26  		history := History{
    27  			Verdicts: []PositionVerdict{
    28  				{
    29  					CommitPosition:       1345,
    30  					IsSimpleExpectedPass: true,
    31  					Hour:                 time.Unix(1000*3600, 0),
    32  				},
    33  				{
    34  					CommitPosition:       1355,
    35  					IsSimpleExpectedPass: false,
    36  					Hour:                 time.Unix(1005*3600, 0),
    37  					Details: VerdictDetails{
    38  						IsExonerated: false,
    39  						Runs: []Run{
    40  							{
    41  								Expected: ResultCounts{
    42  									PassCount:  1,
    43  									FailCount:  2,
    44  									CrashCount: 3,
    45  									AbortCount: 4,
    46  								},
    47  								Unexpected: ResultCounts{
    48  									PassCount:  5,
    49  									FailCount:  6,
    50  									CrashCount: 7,
    51  									AbortCount: 8,
    52  								},
    53  								IsDuplicate: false,
    54  							},
    55  							{
    56  								Expected: ResultCounts{
    57  									PassCount: 1,
    58  								},
    59  								Unexpected: ResultCounts{
    60  									FailCount: 2,
    61  								},
    62  								IsDuplicate: true,
    63  							},
    64  						},
    65  					},
    66  				},
    67  				{
    68  					CommitPosition:       1357,
    69  					IsSimpleExpectedPass: true,
    70  					Hour:                 time.Unix(1003*3600, 0),
    71  				},
    72  				{
    73  					CommitPosition:       1357,
    74  					IsSimpleExpectedPass: false,
    75  					Hour:                 time.Unix(1005*3600, 0),
    76  					Details: VerdictDetails{
    77  						IsExonerated: true,
    78  						Runs: []Run{
    79  							{
    80  								Expected: ResultCounts{
    81  									PassCount: 1,
    82  								},
    83  								Unexpected: ResultCounts{
    84  									FailCount: 2,
    85  								},
    86  								IsDuplicate: true,
    87  							},
    88  							{
    89  								Expected: ResultCounts{
    90  									PassCount:  9,
    91  									FailCount:  10,
    92  									CrashCount: 11,
    93  									AbortCount: 12,
    94  								},
    95  								Unexpected: ResultCounts{
    96  									PassCount:  13,
    97  									FailCount:  14,
    98  									CrashCount: 15,
    99  									AbortCount: 16,
   100  								},
   101  								IsDuplicate: false,
   102  							},
   103  						},
   104  					},
   105  				},
   106  			},
   107  		}
   108  
   109  		hs := &HistorySerializer{}
   110  		encoded := hs.Encode(history)
   111  		decodedHistory := History{
   112  			Verdicts: make([]PositionVerdict, 0, 100),
   113  		}
   114  		err := hs.DecodeInto(&decodedHistory, encoded)
   115  		So(err, ShouldBeNil)
   116  		So(len(decodedHistory.Verdicts), ShouldEqual, 4)
   117  		So(decodedHistory, ShouldResemble, history)
   118  	})
   119  
   120  	Convey(`Encode and decode long history should not have error`, t, func() {
   121  		history := History{}
   122  		history.Verdicts = make([]PositionVerdict, 2000)
   123  		for i := 0; i < 2000; i++ {
   124  			history.Verdicts[i] = PositionVerdict{
   125  				CommitPosition:       i,
   126  				IsSimpleExpectedPass: false,
   127  				Hour:                 time.Unix(int64(i*3600), 0),
   128  				Details: VerdictDetails{
   129  					IsExonerated: false,
   130  					Runs: []Run{
   131  						{
   132  							Expected: ResultCounts{
   133  								PassCount: 1,
   134  							},
   135  							Unexpected: ResultCounts{
   136  								FailCount: 2,
   137  							},
   138  							IsDuplicate: false,
   139  						},
   140  						{
   141  							Expected: ResultCounts{
   142  								PassCount: 1,
   143  							},
   144  							Unexpected: ResultCounts{
   145  								FailCount: 2,
   146  							},
   147  							IsDuplicate: false,
   148  						},
   149  						{
   150  							Expected: ResultCounts{
   151  								PassCount: 1,
   152  							},
   153  							Unexpected: ResultCounts{
   154  								FailCount: 2,
   155  							},
   156  							IsDuplicate: false,
   157  						},
   158  					},
   159  				},
   160  			}
   161  		}
   162  		hs := &HistorySerializer{}
   163  		encoded := hs.Encode(history)
   164  		decodedHistory := History{
   165  			Verdicts: make([]PositionVerdict, 0, 2000),
   166  		}
   167  		err := hs.DecodeInto(&decodedHistory, encoded)
   168  		So(err, ShouldBeNil)
   169  		So(len(decodedHistory.Verdicts), ShouldEqual, 2000)
   170  		So(decodedHistory, ShouldResemble, history)
   171  	})
   172  }
   173  
   174  func TestInputBuffer(t *testing.T) {
   175  	Convey(`Add item to input buffer`, t, func() {
   176  		ib := NewWithCapacity(10, 100)
   177  		originalHotBuffer := ib.HotBuffer.Verdicts
   178  		originalColdBuffer := ib.ColdBuffer.Verdicts
   179  
   180  		// Insert 9 verdicts into hot buffer.
   181  		ib.InsertVerdict(createTestVerdict(1, 4))
   182  		So(ib.IsColdBufferDirty, ShouldBeFalse)
   183  		ib.InsertVerdict(createTestVerdict(2, 2))
   184  		So(ib.IsColdBufferDirty, ShouldBeFalse)
   185  		ib.InsertVerdict(createTestVerdict(3, 3))
   186  		So(ib.IsColdBufferDirty, ShouldBeFalse)
   187  		ib.InsertVerdict(createTestVerdict(2, 3))
   188  		So(ib.IsColdBufferDirty, ShouldBeFalse)
   189  		ib.InsertVerdict(createTestVerdict(4, 5))
   190  		So(ib.IsColdBufferDirty, ShouldBeFalse)
   191  		ib.InsertVerdict(createTestVerdict(1, 1))
   192  		So(ib.IsColdBufferDirty, ShouldBeFalse)
   193  		ib.InsertVerdict(createTestVerdict(2, 3))
   194  		So(ib.IsColdBufferDirty, ShouldBeFalse)
   195  		ib.InsertVerdict(createTestVerdict(7, 8))
   196  		So(ib.IsColdBufferDirty, ShouldBeFalse)
   197  		ib.InsertVerdict(createTestVerdict(7, 7))
   198  		So(ib.IsColdBufferDirty, ShouldBeFalse)
   199  		So(len(ib.HotBuffer.Verdicts), ShouldEqual, 9)
   200  		So(ib.HotBuffer.Verdicts, ShouldResemble, []PositionVerdict{
   201  			createTestVerdict(1, 1),
   202  			createTestVerdict(1, 4),
   203  			createTestVerdict(2, 2),
   204  			createTestVerdict(2, 3),
   205  			createTestVerdict(2, 3),
   206  			createTestVerdict(3, 3),
   207  			createTestVerdict(4, 5),
   208  			createTestVerdict(7, 7),
   209  			createTestVerdict(7, 8),
   210  		})
   211  		// Insert the last verdict, expecting a compaction.
   212  		ib.InsertVerdict(createTestVerdict(6, 2))
   213  		So(ib.IsColdBufferDirty, ShouldBeTrue)
   214  		So(len(ib.HotBuffer.Verdicts), ShouldEqual, 0)
   215  		So(len(ib.ColdBuffer.Verdicts), ShouldEqual, 10)
   216  		So(ib.ColdBuffer.Verdicts, ShouldResemble, []PositionVerdict{
   217  			createTestVerdict(1, 1),
   218  			createTestVerdict(1, 4),
   219  			createTestVerdict(2, 2),
   220  			createTestVerdict(2, 3),
   221  			createTestVerdict(2, 3),
   222  			createTestVerdict(3, 3),
   223  			createTestVerdict(4, 5),
   224  			createTestVerdict(6, 2),
   225  			createTestVerdict(7, 7),
   226  			createTestVerdict(7, 8),
   227  		})
   228  
   229  		// The pre-allocated buffer should be retained, at the same capacity.
   230  		So(&ib.HotBuffer.Verdicts[0:1][0], ShouldEqual, &originalHotBuffer[0:1][0])
   231  		So(&ib.ColdBuffer.Verdicts[0], ShouldEqual, &originalColdBuffer[0:1][0])
   232  	})
   233  
   234  	Convey(`Compaction should maintain order`, t, func() {
   235  		ib := Buffer{
   236  			HotBufferCapacity: 5,
   237  			HotBuffer: History{
   238  				Verdicts: []PositionVerdict{
   239  					createTestVerdict(1, 1),
   240  					createTestVerdict(3, 1),
   241  					createTestVerdict(5, 1),
   242  					createTestVerdict(7, 1),
   243  					createTestVerdict(9, 1),
   244  				},
   245  			},
   246  			ColdBufferCapacity: 10,
   247  			ColdBuffer: History{
   248  				// Allocate with capacity 10 so there is enough
   249  				// space to do an in-place compaction.
   250  				Verdicts: append(make([]PositionVerdict, 0, 10), []PositionVerdict{
   251  					createTestVerdict(2, 1),
   252  					createTestVerdict(4, 1),
   253  					createTestVerdict(6, 1),
   254  					createTestVerdict(8, 1),
   255  					createTestVerdict(10, 1),
   256  				}...),
   257  			},
   258  		}
   259  		originalHotBuffer := ib.HotBuffer.Verdicts
   260  		originalColdBuffer := ib.ColdBuffer.Verdicts
   261  		So(cap(originalColdBuffer), ShouldEqual, 10)
   262  
   263  		ib.Compact()
   264  		So(len(ib.HotBuffer.Verdicts), ShouldEqual, 0)
   265  		So(len(ib.ColdBuffer.Verdicts), ShouldEqual, 10)
   266  		So(ib.ColdBuffer.Verdicts, ShouldResemble, []PositionVerdict{
   267  			createTestVerdict(1, 1),
   268  			createTestVerdict(2, 1),
   269  			createTestVerdict(3, 1),
   270  			createTestVerdict(4, 1),
   271  			createTestVerdict(5, 1),
   272  			createTestVerdict(6, 1),
   273  			createTestVerdict(7, 1),
   274  			createTestVerdict(8, 1),
   275  			createTestVerdict(9, 1),
   276  			createTestVerdict(10, 1),
   277  		})
   278  
   279  		// The pre-allocated buffer should be retained, at the same capacity.
   280  		So(&ib.HotBuffer.Verdicts[0:1][0], ShouldEqual, &originalHotBuffer[0:1][0])
   281  		So(&ib.ColdBuffer.Verdicts[0], ShouldEqual, &originalColdBuffer[0:1][0])
   282  	})
   283  
   284  	Convey(`Cold buffer should keep old verdicts after compaction`, t, func() {
   285  		ib := Buffer{
   286  			HotBufferCapacity: 2,
   287  			HotBuffer: History{
   288  				Verdicts: []PositionVerdict{
   289  					createTestVerdict(7, 1),
   290  					createTestVerdict(9, 1),
   291  				},
   292  			},
   293  			ColdBufferCapacity: 5,
   294  			ColdBuffer: History{
   295  				Verdicts: []PositionVerdict{
   296  					createTestVerdict(2, 1),
   297  					createTestVerdict(4, 1),
   298  					createTestVerdict(6, 1),
   299  					createTestVerdict(8, 1),
   300  					createTestVerdict(10, 1),
   301  				},
   302  			},
   303  		}
   304  
   305  		ib.Compact()
   306  		So(len(ib.HotBuffer.Verdicts), ShouldEqual, 0)
   307  		So(len(ib.ColdBuffer.Verdicts), ShouldEqual, 7)
   308  		So(ib.ColdBuffer.Verdicts, ShouldResemble, []PositionVerdict{
   309  			createTestVerdict(2, 1),
   310  			createTestVerdict(4, 1),
   311  			createTestVerdict(6, 1),
   312  			createTestVerdict(7, 1),
   313  			createTestVerdict(8, 1),
   314  			createTestVerdict(9, 1),
   315  			createTestVerdict(10, 1),
   316  		})
   317  	})
   318  
   319  	Convey(`EvictBefore`, t, func() {
   320  		buffer := History{
   321  			Verdicts: []PositionVerdict{
   322  				createTestVerdict(2, 1),
   323  				createTestVerdict(4, 1),
   324  				createTestVerdict(6, 1),
   325  				createTestVerdict(8, 1),
   326  				createTestVerdict(10, 1),
   327  			},
   328  		}
   329  		originalVerdictsBuffer := buffer.Verdicts
   330  		So(cap(buffer.Verdicts), ShouldEqual, 5)
   331  
   332  		Convey(`Start of slice`, func() {
   333  			buffer.EvictBefore(0)
   334  			So(buffer, ShouldResemble, History{
   335  				Verdicts: []PositionVerdict{
   336  					createTestVerdict(2, 1),
   337  					createTestVerdict(4, 1),
   338  					createTestVerdict(6, 1),
   339  					createTestVerdict(8, 1),
   340  					createTestVerdict(10, 1),
   341  				},
   342  			})
   343  
   344  			So(&buffer.Verdicts[0:1][0], ShouldEqual, &originalVerdictsBuffer[0])
   345  			So(cap(buffer.Verdicts), ShouldEqual, 5)
   346  		})
   347  		Convey(`Middle of slice`, func() {
   348  			buffer.EvictBefore(2)
   349  			So(buffer, ShouldResemble, History{
   350  				Verdicts: []PositionVerdict{
   351  					createTestVerdict(6, 1),
   352  					createTestVerdict(8, 1),
   353  					createTestVerdict(10, 1),
   354  				},
   355  			})
   356  
   357  			// The pre-allocated buffer should be retained, at the same capacity.
   358  			So(&buffer.Verdicts[0:1][0], ShouldEqual, &originalVerdictsBuffer[0])
   359  			So(cap(buffer.Verdicts), ShouldEqual, 5)
   360  		})
   361  		Convey(`End of slice`, func() {
   362  			buffer.EvictBefore(5)
   363  			So(buffer, ShouldResemble, History{
   364  				Verdicts: []PositionVerdict{},
   365  			})
   366  
   367  			// The pre-allocated buffer should be retained, at the same capacity.
   368  			So(&buffer.Verdicts[0:1][0], ShouldEqual, &originalVerdictsBuffer[0])
   369  			So(cap(buffer.Verdicts), ShouldEqual, 5)
   370  		})
   371  		Convey(`Empty slice`, func() {
   372  			buffer := History{
   373  				Verdicts: []PositionVerdict{},
   374  			}
   375  			buffer.EvictBefore(0)
   376  			So(buffer, ShouldResemble, History{
   377  				Verdicts: []PositionVerdict{},
   378  			})
   379  		})
   380  	})
   381  
   382  	Convey(`Clear`, t, func() {
   383  		ib := NewWithCapacity(5, 10)
   384  		ib.HotBuffer.Verdicts = append(ib.HotBuffer.Verdicts, []PositionVerdict{
   385  			createTestVerdict(1, 1),
   386  			createTestVerdict(3, 1),
   387  			createTestVerdict(5, 1),
   388  			createTestVerdict(7, 1),
   389  			createTestVerdict(9, 1),
   390  		}...)
   391  		ib.ColdBuffer.Verdicts = append(ib.ColdBuffer.Verdicts, []PositionVerdict{
   392  			createTestVerdict(2, 1),
   393  			createTestVerdict(4, 1),
   394  			createTestVerdict(6, 1),
   395  			createTestVerdict(8, 1),
   396  			createTestVerdict(10, 1),
   397  		}...)
   398  		originalHotBuffer := ib.HotBuffer.Verdicts
   399  		originalColdBuffer := ib.ColdBuffer.Verdicts
   400  
   401  		ib.Clear()
   402  
   403  		So(ib, ShouldResemble, &Buffer{
   404  			HotBufferCapacity: 5,
   405  			HotBuffer: History{
   406  				Verdicts: []PositionVerdict{},
   407  			},
   408  			ColdBufferCapacity: 10,
   409  			ColdBuffer: History{
   410  				Verdicts: []PositionVerdict{},
   411  			},
   412  		})
   413  		// The pre-allocated buffer should be retained, at the same capacity.
   414  		So(&ib.HotBuffer.Verdicts[0:1][0], ShouldEqual, &originalHotBuffer[0])
   415  		So(&ib.ColdBuffer.Verdicts[0:1][0], ShouldEqual, &originalColdBuffer[0])
   416  	})
   417  }
   418  
   419  func createTestVerdict(pos int, hour int) PositionVerdict {
   420  	return PositionVerdict{
   421  		CommitPosition:       pos,
   422  		IsSimpleExpectedPass: true,
   423  		Hour:                 time.Unix(int64(3600*hour), 0),
   424  	}
   425  }