go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/clustering/runs/span_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 runs
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"go.chromium.org/luci/server/span"
    23  
    24  	"go.chromium.org/luci/analysis/internal/clustering/algorithms"
    25  	"go.chromium.org/luci/analysis/internal/clustering/rules"
    26  	"go.chromium.org/luci/analysis/internal/clustering/shards"
    27  	"go.chromium.org/luci/analysis/internal/config"
    28  	"go.chromium.org/luci/analysis/internal/testutil"
    29  
    30  	. "github.com/smartystreets/goconvey/convey"
    31  	. "go.chromium.org/luci/common/testing/assertions"
    32  )
    33  
    34  func TestSpan(t *testing.T) {
    35  	Convey(`With Spanner Test Database`, t, func() {
    36  		ctx := testutil.SpannerTestContext(t)
    37  
    38  		Convey(`Reads`, func() {
    39  			reference := time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)
    40  			runs := []*ReclusteringRun{
    41  				NewRun(0).WithProject("otherproject").WithAttemptTimestamp(reference).WithCompletedProgress().Build(),
    42  				NewRun(1).WithAttemptTimestamp(reference.Add(-5 * time.Minute)).Build(),
    43  				NewRun(2).WithAttemptTimestamp(reference.Add(-10 * time.Minute)).Build(),
    44  				NewRun(3).WithAttemptTimestamp(reference.Add(-20 * time.Minute)).WithReportedProgress(500).Build(),
    45  				NewRun(4).WithAttemptTimestamp(reference.Add(-30 * time.Minute)).WithReportedProgress(500).Build(),
    46  				NewRun(5).WithAttemptTimestamp(reference.Add(-40 * time.Minute)).WithCompletedProgress().Build(),
    47  				NewRun(6).WithAttemptTimestamp(reference.Add(-50 * time.Minute)).WithCompletedProgress().Build(),
    48  			}
    49  			err := SetRunsForTesting(ctx, runs)
    50  			So(err, ShouldBeNil)
    51  
    52  			// For ReadLast... methods, this is the fake row that is expected
    53  			// to be returned if no row exists.
    54  			expectedFake := &ReclusteringRun{
    55  				Project:           "emptyproject",
    56  				AttemptTimestamp:  time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC),
    57  				AlgorithmsVersion: 1,
    58  				ConfigVersion:     config.StartingEpoch,
    59  				RulesVersion:      rules.StartingEpoch,
    60  				ShardCount:        1,
    61  				ShardsReported:    1,
    62  				Progress:          1000,
    63  			}
    64  
    65  			Convey(`Read`, func() {
    66  				Convey(`Not Exists`, func() {
    67  					run, err := Read(span.Single(ctx), testProject, reference)
    68  					So(err, ShouldEqual, NotFound)
    69  					So(run, ShouldBeNil)
    70  				})
    71  				Convey(`Exists`, func() {
    72  					run, err := Read(span.Single(ctx), testProject, runs[2].AttemptTimestamp)
    73  					So(err, ShouldBeNil)
    74  					So(run, ShouldResemble, runs[2])
    75  				})
    76  			})
    77  			Convey(`ReadLastUpTo`, func() {
    78  				Convey(`Not Exists`, func() {
    79  					run, err := ReadLastUpTo(span.Single(ctx), "emptyproject", MaxAttemptTimestamp)
    80  					So(err, ShouldBeNil)
    81  					So(run, ShouldResemble, expectedFake)
    82  				})
    83  				Convey(`Latest`, func() {
    84  					run, err := ReadLastUpTo(span.Single(ctx), testProject, MaxAttemptTimestamp)
    85  					So(err, ShouldBeNil)
    86  					So(run, ShouldResemble, runs[1])
    87  				})
    88  				Convey(`Stale`, func() {
    89  					run, err := ReadLastUpTo(span.Single(ctx), testProject, reference.Add(-10*time.Minute))
    90  					So(err, ShouldBeNil)
    91  					So(run, ShouldResemble, runs[2])
    92  				})
    93  			})
    94  			Convey(`ReadLastWithProgressUpTo`, func() {
    95  				Convey(`Not Exists`, func() {
    96  					run, err := ReadLastWithProgressUpTo(span.Single(ctx), "emptyproject", MaxAttemptTimestamp)
    97  					So(err, ShouldBeNil)
    98  					So(run, ShouldResemble, expectedFake)
    99  				})
   100  				Convey(`Exists`, func() {
   101  					run, err := ReadLastWithProgressUpTo(span.Single(ctx), testProject, MaxAttemptTimestamp)
   102  					So(err, ShouldBeNil)
   103  					So(run, ShouldResemble, runs[3])
   104  				})
   105  				Convey(`Stale`, func() {
   106  					run, err := ReadLastWithProgressUpTo(span.Single(ctx), testProject, reference.Add(-30*time.Minute))
   107  					So(err, ShouldBeNil)
   108  					So(run, ShouldResemble, runs[4])
   109  				})
   110  			})
   111  			Convey(`ReadLastCompleteUpTo`, func() {
   112  				Convey(`Not Exists`, func() {
   113  					run, err := ReadLastCompleteUpTo(span.Single(ctx), "emptyproject", MaxAttemptTimestamp)
   114  					So(err, ShouldBeNil)
   115  					So(run, ShouldResemble, expectedFake)
   116  				})
   117  				Convey(`Exists`, func() {
   118  					run, err := ReadLastCompleteUpTo(span.Single(ctx), testProject, MaxAttemptTimestamp)
   119  					So(err, ShouldBeNil)
   120  					So(run, ShouldResemble, runs[5])
   121  				})
   122  				Convey(`Stale`, func() {
   123  					run, err := ReadLastCompleteUpTo(span.Single(ctx), testProject, reference.Add(-50*time.Minute))
   124  					So(err, ShouldBeNil)
   125  					So(run, ShouldResemble, runs[6])
   126  				})
   127  			})
   128  		})
   129  		Convey(`Query Progress`, func() {
   130  			Convey(`Rule Progress`, func() {
   131  				rulesVersion := time.Date(2021, time.January, 1, 1, 0, 0, 0, time.UTC)
   132  
   133  				reference := time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)
   134  				runs := []*ReclusteringRun{
   135  					NewRun(0).WithAttemptTimestamp(reference.Add(-5 * time.Minute)).WithRulesVersion(rulesVersion).WithNoReportedProgress().Build(),
   136  					NewRun(1).WithAttemptTimestamp(reference.Add(-10 * time.Minute)).WithRulesVersion(rulesVersion).WithReportedProgress(500).Build(),
   137  					NewRun(2).WithAttemptTimestamp(reference.Add(-20 * time.Minute)).WithRulesVersion(rulesVersion.Add(-1 * time.Hour)).WithCompletedProgress().Build(),
   138  				}
   139  				err := SetRunsForTesting(ctx, runs)
   140  				So(err, ShouldBeNil)
   141  
   142  				progress, err := ReadReclusteringProgress(ctx, testProject)
   143  				So(err, ShouldBeNil)
   144  
   145  				So(progress.IncorporatesRulesVersion(rulesVersion.Add(1*time.Hour)), ShouldBeFalse)
   146  				So(progress.IncorporatesRulesVersion(rulesVersion), ShouldBeFalse)
   147  				So(progress.IncorporatesRulesVersion(rulesVersion.Add(-1*time.Minute)), ShouldBeFalse)
   148  				So(progress.IncorporatesRulesVersion(rulesVersion.Add(-1*time.Hour)), ShouldBeTrue)
   149  				So(progress.IncorporatesRulesVersion(rulesVersion.Add(-2*time.Hour)), ShouldBeTrue)
   150  			})
   151  			Convey(`Algorithms Upgrading`, func() {
   152  				reference := time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)
   153  				runs := []*ReclusteringRun{
   154  					NewRun(0).WithAttemptTimestamp(reference.Add(-5 * time.Minute)).WithAlgorithmsVersion(algorithms.AlgorithmsVersion + 1).WithNoReportedProgress().Build(),
   155  					NewRun(1).WithAttemptTimestamp(reference.Add(-10 * time.Minute)).WithAlgorithmsVersion(algorithms.AlgorithmsVersion + 1).WithReportedProgress(500).Build(),
   156  					NewRun(2).WithAttemptTimestamp(reference.Add(-20 * time.Minute)).WithAlgorithmsVersion(algorithms.AlgorithmsVersion).WithCompletedProgress().Build(),
   157  				}
   158  				err := SetRunsForTesting(ctx, runs)
   159  				So(err, ShouldBeNil)
   160  
   161  				progress, err := ReadReclusteringProgress(ctx, testProject)
   162  				So(err, ShouldBeNil)
   163  
   164  				So(progress.Next.AlgorithmsVersion, ShouldEqual, algorithms.AlgorithmsVersion+1)
   165  				So(progress.IsReclusteringToNewAlgorithms(), ShouldBeTrue)
   166  			})
   167  			Convey(`Config Upgrading`, func() {
   168  				configVersion := time.Date(2025, time.January, 1, 1, 0, 0, 0, time.UTC)
   169  
   170  				reference := time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)
   171  				runs := []*ReclusteringRun{
   172  					NewRun(0).WithAttemptTimestamp(reference.Add(-5 * time.Minute)).WithConfigVersion(configVersion).WithNoReportedProgress().Build(),
   173  					NewRun(1).WithAttemptTimestamp(reference.Add(-10 * time.Minute)).WithConfigVersion(configVersion).WithReportedProgress(500).Build(),
   174  					NewRun(2).WithAttemptTimestamp(reference.Add(-20 * time.Minute)).WithConfigVersion(configVersion.Add(-1 * time.Hour)).WithCompletedProgress().Build(),
   175  				}
   176  				err := SetRunsForTesting(ctx, runs)
   177  				So(err, ShouldBeNil)
   178  
   179  				progress, err := ReadReclusteringProgress(ctx, testProject)
   180  				So(err, ShouldBeNil)
   181  
   182  				So(progress.Next.ConfigVersion, ShouldEqual, configVersion)
   183  				So(progress.IsReclusteringToNewConfig(), ShouldBeTrue)
   184  			})
   185  			Convey(`Algorithms and Config Stable`, func() {
   186  				reference := time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)
   187  				configVersion := time.Date(2025, time.January, 1, 1, 0, 0, 0, time.UTC)
   188  				algVersion := int64(2)
   189  				runs := []*ReclusteringRun{
   190  					NewRun(0).WithAttemptTimestamp(reference.Add(-5 * time.Minute)).WithAlgorithmsVersion(algVersion).WithConfigVersion(configVersion).WithNoReportedProgress().Build(),
   191  					NewRun(1).WithAttemptTimestamp(reference.Add(-10 * time.Minute)).WithAlgorithmsVersion(algVersion).WithConfigVersion(configVersion).WithReportedProgress(500).Build(),
   192  					NewRun(2).WithAttemptTimestamp(reference.Add(-20 * time.Minute)).WithAlgorithmsVersion(algVersion).WithConfigVersion(configVersion).WithCompletedProgress().Build(),
   193  				}
   194  				err := SetRunsForTesting(ctx, runs)
   195  				So(err, ShouldBeNil)
   196  
   197  				progress, err := ReadReclusteringProgress(ctx, testProject)
   198  				So(err, ShouldBeNil)
   199  
   200  				So(progress.Next.AlgorithmsVersion, ShouldEqual, algVersion)
   201  				So(progress.Next.ConfigVersion, ShouldEqual, configVersion)
   202  				So(progress.IsReclusteringToNewAlgorithms(), ShouldBeFalse)
   203  				So(progress.IsReclusteringToNewConfig(), ShouldBeFalse)
   204  			})
   205  			Convey(`Stale Progress Read`, func() {
   206  				rulesVersion := time.Date(2021, time.January, 1, 1, 0, 0, 0, time.UTC)
   207  
   208  				reference := time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)
   209  				runs := []*ReclusteringRun{
   210  					NewRun(0).WithAttemptTimestamp(reference.Add(-1 * time.Minute)).WithRulesVersion(rulesVersion.Add(1 * time.Hour)).WithCompletedProgress().Build(),
   211  					NewRun(1).WithAttemptTimestamp(reference.Add(-5 * time.Minute)).WithRulesVersion(rulesVersion).WithNoReportedProgress().Build(),
   212  					NewRun(2).WithAttemptTimestamp(reference.Add(-10 * time.Minute)).WithRulesVersion(rulesVersion).WithReportedProgress(500).Build(),
   213  					NewRun(3).WithAttemptTimestamp(reference.Add(-20 * time.Minute)).WithRulesVersion(rulesVersion.Add(-1 * time.Hour)).WithCompletedProgress().Build(),
   214  				}
   215  				err := SetRunsForTesting(ctx, runs)
   216  				So(err, ShouldBeNil)
   217  
   218  				progress, err := ReadReclusteringProgressUpTo(ctx, testProject, reference.Add(-2*time.Minute))
   219  				So(err, ShouldBeNil)
   220  
   221  				So(progress.IncorporatesRulesVersion(rulesVersion), ShouldBeFalse)
   222  				So(progress.IncorporatesRulesVersion(rulesVersion.Add(-1*time.Hour)), ShouldBeTrue)
   223  			})
   224  			Convey(`Uses live progress`, func() {
   225  				rulesVersion := time.Date(2021, time.January, 1, 1, 0, 0, 0, time.UTC)
   226  				configVersion := time.Date(2025, time.January, 1, 1, 0, 0, 0, time.UTC)
   227  
   228  				reference := time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)
   229  				runs := []*ReclusteringRun{
   230  					NewRun(0).WithAttemptTimestamp(reference.Add(-1 * time.Minute)).
   231  						WithRulesVersion(rulesVersion).
   232  						WithConfigVersion(configVersion).
   233  						WithAlgorithmsVersion(algorithms.AlgorithmsVersion + 1).
   234  						WithShardCount(2).
   235  						WithNoReportedProgress().Build(),
   236  					NewRun(1).WithAttemptTimestamp(reference.Add(-2 * time.Minute)).
   237  						WithRulesVersion(rulesVersion).
   238  						WithConfigVersion(configVersion).
   239  						WithAlgorithmsVersion(algorithms.AlgorithmsVersion + 1).
   240  						WithReportedProgress(500).Build(),
   241  					NewRun(2).WithAttemptTimestamp(reference.Add(-3 * time.Minute)).
   242  						WithRulesVersion(rulesVersion.Add(-1 * time.Hour)).
   243  						WithConfigVersion(configVersion.Add(-2 * time.Hour)).
   244  						WithAlgorithmsVersion(algorithms.AlgorithmsVersion).
   245  						WithCompletedProgress().Build(),
   246  				}
   247  				err := SetRunsForTesting(ctx, runs)
   248  				So(err, ShouldBeNil)
   249  
   250  				expectedProgress := &ReclusteringProgress{
   251  					ProgressPerMille: 500,
   252  					Next: ReclusteringTarget{
   253  						RulesVersion:      rulesVersion,
   254  						ConfigVersion:     configVersion,
   255  						AlgorithmsVersion: algorithms.AlgorithmsVersion + 1,
   256  					},
   257  					Last: ReclusteringTarget{
   258  						RulesVersion:      rulesVersion.Add(-1 * time.Hour),
   259  						ConfigVersion:     configVersion.Add(-2 * time.Hour),
   260  						AlgorithmsVersion: algorithms.AlgorithmsVersion,
   261  					},
   262  				}
   263  
   264  				Convey(`No live progress available`, func() {
   265  					// No reclustering shard entries to use to calculate progress.
   266  
   267  					progress, err := ReadReclusteringProgress(ctx, testProject)
   268  					So(err, ShouldBeNil)
   269  
   270  					So(progress, ShouldResemble, expectedProgress)
   271  				})
   272  				Convey(`No shard progress`, func() {
   273  					// Not all shards have reported progress.
   274  					shds := []shards.ReclusteringShard{
   275  						shards.NewShard(0).WithProject(testProject).WithAttemptTimestamp(reference.Add(-1 * time.Minute)).WithNoProgress().Build(),
   276  						shards.NewShard(1).WithProject(testProject).WithAttemptTimestamp(reference.Add(-1 * time.Minute)).WithProgress(100).Build(),
   277  					}
   278  					err := shards.SetShardsForTesting(ctx, shds)
   279  					So(err, ShouldBeNil)
   280  
   281  					progress, err := ReadReclusteringProgress(ctx, testProject)
   282  					So(err, ShouldBeNil)
   283  
   284  					So(progress, ShouldResemble, expectedProgress)
   285  				})
   286  				Convey(`Partial progress`, func() {
   287  					// All shards have reported partial progress.
   288  					shds := []shards.ReclusteringShard{
   289  						shards.NewShard(0).WithProject(testProject).WithAttemptTimestamp(reference.Add(-1 * time.Minute)).WithProgress(200).Build(),
   290  						shards.NewShard(1).WithProject(testProject).WithAttemptTimestamp(reference.Add(-1 * time.Minute)).WithProgress(100).Build(),
   291  					}
   292  					err := shards.SetShardsForTesting(ctx, shds)
   293  					So(err, ShouldBeNil)
   294  
   295  					expectedProgress.ProgressPerMille = 150
   296  					progress, err := ReadReclusteringProgress(ctx, testProject)
   297  					So(err, ShouldBeNil)
   298  					So(progress, ShouldResemble, expectedProgress)
   299  				})
   300  				Convey(`Complete progress`, func() {
   301  					// All shards have reported progress as being complete.
   302  					shds := []shards.ReclusteringShard{
   303  						shards.NewShard(0).WithProject(testProject).WithAttemptTimestamp(reference.Add(-1 * time.Minute)).WithProgress(shards.MaxProgress).Build(),
   304  						shards.NewShard(1).WithProject(testProject).WithAttemptTimestamp(reference.Add(-1 * time.Minute)).WithProgress(shards.MaxProgress).Build(),
   305  					}
   306  					err := shards.SetShardsForTesting(ctx, shds)
   307  					So(err, ShouldBeNil)
   308  
   309  					expectedProgress.ProgressPerMille = 1000
   310  					expectedProgress.Last = ReclusteringTarget{
   311  						RulesVersion:      rulesVersion,
   312  						ConfigVersion:     configVersion,
   313  						AlgorithmsVersion: algorithms.AlgorithmsVersion + 1,
   314  					}
   315  					progress, err := ReadReclusteringProgress(ctx, testProject)
   316  					So(err, ShouldBeNil)
   317  					So(progress, ShouldResemble, expectedProgress)
   318  				})
   319  			})
   320  		})
   321  		Convey(`UpdateProgress`, func() {
   322  			reference := time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)
   323  			assertProgress := func(shardsReported, progress int64) {
   324  				run, err := Read(span.Single(ctx), testProject, reference)
   325  				So(err, ShouldBeNil)
   326  				So(run.ShardsReported, ShouldEqual, shardsReported)
   327  				So(run.Progress, ShouldEqual, progress)
   328  			}
   329  			updateProgress := func(shardsReported, progress int64) error {
   330  				_, err := span.ReadWriteTransaction(ctx, func(ctx context.Context) error {
   331  					return UpdateProgress(ctx, testProject, reference, shardsReported, progress)
   332  				})
   333  				return err
   334  			}
   335  
   336  			runs := []*ReclusteringRun{
   337  				NewRun(0).WithAttemptTimestamp(reference).WithShardCount(2).WithNoReportedProgress().Build(),
   338  			}
   339  			err := SetRunsForTesting(ctx, runs)
   340  			So(err, ShouldBeNil)
   341  
   342  			err = updateProgress(0, 0)
   343  			So(err, ShouldBeNil)
   344  			assertProgress(0, 0)
   345  
   346  			err = updateProgress(2, 2000)
   347  			So(err, ShouldBeNil)
   348  			assertProgress(2, 2000)
   349  		})
   350  		Convey(`Create`, func() {
   351  			testCreate := func(bc *ReclusteringRun) error {
   352  				_, err := span.ReadWriteTransaction(ctx, func(ctx context.Context) error {
   353  					return Create(ctx, bc)
   354  				})
   355  				return err
   356  			}
   357  			r := NewRun(100).Build()
   358  			Convey(`Valid`, func() {
   359  				testExists := func(expectedRun *ReclusteringRun) {
   360  					txn, cancel := span.ReadOnlyTransaction(ctx)
   361  					defer cancel()
   362  					run, err := Read(txn, expectedRun.Project, expectedRun.AttemptTimestamp)
   363  
   364  					So(err, ShouldBeNil)
   365  					So(run, ShouldResemble, expectedRun)
   366  				}
   367  
   368  				err := testCreate(r)
   369  				So(err, ShouldBeNil)
   370  				testExists(r)
   371  			})
   372  			Convey(`With invalid Project`, func() {
   373  				Convey(`Unspecified`, func() {
   374  					r.Project = ""
   375  					err := testCreate(r)
   376  					So(err, ShouldErrLike, "project: unspecified")
   377  				})
   378  				Convey(`Invalid`, func() {
   379  					r.Project = "!"
   380  					err := testCreate(r)
   381  					So(err, ShouldErrLike, `project: must match ^[a-z0-9\-]{1,40}$`)
   382  				})
   383  			})
   384  			Convey(`With invalid Attempt Timestamp`, func() {
   385  				r.AttemptTimestamp = time.Time{}
   386  				err := testCreate(r)
   387  				So(err, ShouldErrLike, "attempt timestamp must be valid")
   388  			})
   389  			Convey(`With invalid Algorithms Version`, func() {
   390  				r.AlgorithmsVersion = 0
   391  				err := testCreate(r)
   392  				So(err, ShouldErrLike, "algorithms version must be valid")
   393  			})
   394  			Convey(`With invalid Rules Version`, func() {
   395  				r.RulesVersion = time.Time{}
   396  				err := testCreate(r)
   397  				So(err, ShouldErrLike, "rules version must be valid")
   398  			})
   399  			Convey(`With invalid Shard Count`, func() {
   400  				r.ShardCount = 0
   401  				err := testCreate(r)
   402  				So(err, ShouldErrLike, "shard count must be valid")
   403  			})
   404  			Convey(`With invalid Shards Reported`, func() {
   405  				r.ShardsReported = r.ShardCount + 1
   406  				err := testCreate(r)
   407  				So(err, ShouldErrLike, "shards reported must be valid")
   408  			})
   409  			Convey(`With invalid Progress`, func() {
   410  				r.Progress = r.ShardCount*1000 + 1
   411  				err := testCreate(r)
   412  				So(err, ShouldErrLike, "progress must be valid")
   413  			})
   414  		})
   415  	})
   416  }