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 }