go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/run/load_test.go (about) 1 // Copyright 2021 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 run 16 17 import ( 18 "context" 19 "fmt" 20 "testing" 21 "time" 22 23 "google.golang.org/grpc/codes" 24 "google.golang.org/protobuf/types/known/timestamppb" 25 26 "go.chromium.org/luci/common/errors" 27 "go.chromium.org/luci/gae/service/datastore" 28 "go.chromium.org/luci/grpc/appstatus" 29 30 "go.chromium.org/luci/cv/internal/common" 31 "go.chromium.org/luci/cv/internal/cvtesting" 32 33 . "github.com/smartystreets/goconvey/convey" 34 . "go.chromium.org/luci/common/testing/assertions" 35 ) 36 37 func TestLoadChildRuns(t *testing.T) { 38 t.Parallel() 39 40 Convey("LoadChildRuns works", t, func() { 41 ct := cvtesting.Test{} 42 ctx, cancel := ct.SetUp(t) 43 defer cancel() 44 45 put := func(runID common.RunID, depRuns common.RunIDs) { 46 So(datastore.Put(ctx, &Run{ 47 ID: runID, 48 DepRuns: depRuns, 49 }), ShouldBeNil) 50 } 51 52 const parentRun1 = common.RunID("parent/1-cow") 53 54 const orphanRun = common.RunID("orphan/1-chicken") 55 put(orphanRun, common.RunIDs{}) 56 out1, err := LoadChildRuns(ctx, parentRun1) 57 So(err, ShouldBeNil) 58 So(out1, ShouldHaveLength, 0) 59 60 const pendingRun = common.RunID("child/1-pending") 61 put(pendingRun, common.RunIDs{parentRun1}) 62 const runningRun = common.RunID("child/1-running") 63 put(runningRun, common.RunIDs{parentRun1}) 64 65 out2, err := LoadChildRuns(ctx, parentRun1) 66 So(err, ShouldBeNil) 67 So(out2, ShouldHaveLength, 2) 68 }) 69 } 70 71 func TestLoadRunLogEntries(t *testing.T) { 72 t.Parallel() 73 74 Convey("LoadRunLogEntries works", t, func() { 75 ct := cvtesting.Test{} 76 ctx, cancel := ct.SetUp(t) 77 defer cancel() 78 79 ev := int64(1) 80 put := func(runID common.RunID, entries ...*LogEntry) { 81 So(datastore.Put(ctx, &RunLog{ 82 Run: datastore.MakeKey(ctx, common.RunKind, string(runID)), 83 ID: ev, 84 Entries: &LogEntries{Entries: entries}, 85 }), ShouldBeNil) 86 ev += 1 87 } 88 89 const run1 = common.RunID("rust/123-1-beef") 90 const run2 = common.RunID("dart/789-2-cafe") 91 92 put( 93 run1, 94 &LogEntry{ 95 Time: timestamppb.New(ct.Clock.Now()), 96 Kind: &LogEntry_Created_{Created: &LogEntry_Created{ 97 ConfigGroupId: "fi/rst", 98 }}, 99 }, 100 ) 101 ct.Clock.Add(time.Minute) 102 put( 103 run1, 104 &LogEntry{ 105 Time: timestamppb.New(ct.Clock.Now()), 106 Kind: &LogEntry_ConfigChanged_{ConfigChanged: &LogEntry_ConfigChanged{ 107 ConfigGroupId: "se/cond", 108 }}, 109 }, 110 &LogEntry{ 111 Time: timestamppb.New(ct.Clock.Now()), 112 Kind: &LogEntry_TryjobsRequirementUpdated_{TryjobsRequirementUpdated: &LogEntry_TryjobsRequirementUpdated{}}, 113 }, 114 ) 115 116 ct.Clock.Add(time.Minute) 117 put( 118 run2, 119 &LogEntry{ 120 Time: timestamppb.New(ct.Clock.Now()), 121 Kind: &LogEntry_Created_{Created: &LogEntry_Created{ 122 ConfigGroupId: "fi/rst-but-run2", 123 }}, 124 }, 125 ) 126 127 out1, err := LoadRunLogEntries(ctx, run1) 128 So(err, ShouldBeNil) 129 So(out1, ShouldHaveLength, 3) 130 So(out1[0].GetCreated().GetConfigGroupId(), ShouldResemble, "fi/rst") 131 So(out1[1].GetConfigChanged().GetConfigGroupId(), ShouldResemble, "se/cond") 132 So(out1[2].GetTryjobsRequirementUpdated(), ShouldNotBeNil) 133 134 out2, err := LoadRunLogEntries(ctx, run2) 135 So(err, ShouldBeNil) 136 So(out2, ShouldHaveLength, 1) 137 So(out2[0].GetCreated().GetConfigGroupId(), ShouldResemble, "fi/rst-but-run2") 138 }) 139 } 140 141 func TestLoadRunsBuilder(t *testing.T) { 142 t.Parallel() 143 144 Convey("LoadRunsBuilder works", t, func() { 145 ct := cvtesting.Test{} 146 ctx, cancel := ct.SetUp(t) 147 defer cancel() 148 149 const lProject = "proj" 150 // Run statuses are used in this test to ensure Runs were actually loaded. 151 makeRun := func(id int, s Status) *Run { 152 r := &Run{ID: common.RunID(fmt.Sprintf("%s/%03d", lProject, id)), Status: s} 153 So(datastore.Put(ctx, r), ShouldBeNil) 154 return r 155 } 156 157 r1 := makeRun(1, Status_RUNNING) 158 r2 := makeRun(2, Status_CANCELLED) 159 r3 := makeRun(3, Status_PENDING) 160 r4 := makeRun(4, Status_SUCCEEDED) 161 r201 := makeRun(201, Status_FAILED) 162 r202 := makeRun(202, Status_FAILED) 163 r404 := makeRun(404, Status_PENDING) 164 r405 := makeRun(405, Status_PENDING) 165 So(datastore.Delete(ctx, r404, r405), ShouldBeNil) 166 167 Convey("Without checker", func() { 168 Convey("Every Run exists", func() { 169 verify := func(b LoadRunsBuilder) { 170 runsA, errs := b.Do(ctx) 171 So(errs, ShouldResemble, make(errors.MultiError, 2)) 172 So(runsA, ShouldResemble, []*Run{r201, r202}) 173 174 runsB, err := b.DoIgnoreNotFound(ctx) 175 So(err, ShouldBeNil) 176 So(runsB, ShouldResemble, runsA) 177 } 178 Convey("IDs", func() { 179 verify(LoadRunsFromIDs(r201.ID, r202.ID)) 180 }) 181 Convey("keys", func() { 182 verify(LoadRunsFromKeys( 183 datastore.MakeKey(ctx, common.RunKind, string(r201.ID)), 184 datastore.MakeKey(ctx, common.RunKind, string(r202.ID)), 185 )) 186 }) 187 }) 188 189 Convey("A missing Run", func() { 190 b := LoadRunsFromIDs(r404.ID) 191 192 runsA, errs := b.Do(ctx) 193 So(errs, ShouldResemble, errors.MultiError{datastore.ErrNoSuchEntity}) 194 So(runsA, ShouldResemble, []*Run{{ID: r404.ID}}) 195 196 runsB, err := b.DoIgnoreNotFound(ctx) 197 So(err, ShouldBeNil) 198 So(runsB, ShouldBeNil) 199 }) 200 Convey("Mix of existing and missing", func() { 201 b := LoadRunsFromIDs(r201.ID, r404.ID, r202.ID, r405.ID, r4.ID) 202 203 runsA, errs := b.Do(ctx) 204 So(errs, ShouldResemble, errors.MultiError{nil, datastore.ErrNoSuchEntity, nil, datastore.ErrNoSuchEntity, nil}) 205 So(runsA, ShouldResemble, []*Run{ 206 r201, 207 {ID: r404.ID}, 208 r202, 209 {ID: r405.ID}, 210 r4, 211 }) 212 213 runsB, err := b.DoIgnoreNotFound(ctx) 214 So(err, ShouldBeNil) 215 So(runsB, ShouldResemble, []*Run{r201, r202, r4}) 216 }) 217 }) 218 219 Convey("With checker", func() { 220 checker := fakeRunChecker{ 221 afterOnNotFound: appstatus.Error(codes.NotFound, "not-found-ds"), 222 } 223 224 Convey("No errors of any kind", func() { 225 b := LoadRunsFromIDs(r201.ID, r202.ID, r4.ID).Checker(checker) 226 227 runsA, errs := b.Do(ctx) 228 So(errs, ShouldResemble, make(errors.MultiError, 3)) 229 So(runsA, ShouldResemble, []*Run{r201, r202, r4}) 230 231 runsB, err := b.DoIgnoreNotFound(ctx) 232 So(err, ShouldBeNil) 233 So(runsB, ShouldResemble, runsA) 234 }) 235 236 Convey("Missing in datastore", func() { 237 b := LoadRunsFromIDs(r404.ID).Checker(checker) 238 239 runsA, errs := b.Do(ctx) 240 So(errs[0], ShouldHaveAppStatus, codes.NotFound) 241 So(runsA, ShouldResemble, []*Run{{ID: r404.ID}}) 242 243 runsB, err := b.DoIgnoreNotFound(ctx) 244 So(err, ShouldBeNil) 245 So(runsB, ShouldBeNil) 246 }) 247 248 Convey("Mix", func() { 249 checker.before = map[common.RunID]error{ 250 r1.ID: appstatus.Error(codes.NotFound, "not-found-before"), 251 r2.ID: errors.New("before-oops"), 252 } 253 checker.after = map[common.RunID]error{ 254 r3.ID: appstatus.Error(codes.NotFound, "not-found-after"), 255 r4.ID: errors.New("after-oops"), 256 } 257 Convey("only found and not found", func() { 258 b := LoadRunsFromIDs(r201.ID, r1.ID, r202.ID, r3.ID, r404.ID).Checker(checker) 259 260 runsA, errs := b.Do(ctx) 261 So(errs[0], ShouldBeNil) // r201 262 So(errs[1], ShouldErrLike, "not-found-before") 263 So(errs[2], ShouldBeNil) // r202 264 So(errs[3], ShouldErrLike, "not-found-after") 265 So(errs[4], ShouldErrLike, "not-found-ds") 266 So(runsA, ShouldResemble, []*Run{ 267 r201, 268 {ID: r1.ID}, 269 r202, 270 r3, // loaded & returned, despite errors 271 {ID: r404.ID}, 272 }) 273 274 runsB, err := b.DoIgnoreNotFound(ctx) 275 So(err, ShouldBeNil) 276 So(runsB, ShouldResemble, []*Run{r201, r202}) 277 }) 278 Convey("of everything", func() { 279 b := LoadRunsFromIDs(r201.ID, r1.ID, r2.ID, r3.ID, r4.ID, r404.ID).Checker(checker) 280 281 runsA, errs := b.Do(ctx) 282 So(errs[0], ShouldBeNil) // r201 283 So(errs[1], ShouldErrLike, "not-found-before") 284 So(errs[2], ShouldErrLike, "before-oops") 285 So(errs[3], ShouldErrLike, "not-found-after") 286 So(errs[4], ShouldErrLike, "after-oops") 287 So(errs[5], ShouldErrLike, "not-found-ds") 288 So(runsA, ShouldResemble, []*Run{ 289 r201, 290 {ID: r1.ID}, 291 {ID: r2.ID}, 292 r3, // loaded & returned, despite errors 293 r4, // loaded & returned, despite errors 294 {ID: r404.ID}, 295 }) 296 297 runsB, err := b.DoIgnoreNotFound(ctx) 298 So(err, ShouldErrLike, "before-oops") 299 So(runsB, ShouldBeNil) 300 }) 301 }) 302 }) 303 }) 304 } 305 306 type fakeRunChecker struct { 307 before map[common.RunID]error 308 beforeFunc func(common.RunID) error // applied only if Run is not in `before` 309 after map[common.RunID]error 310 afterOnNotFound error 311 } 312 313 func (f fakeRunChecker) Before(ctx context.Context, id common.RunID) error { 314 err := f.before[id] 315 if err == nil && f.beforeFunc != nil { 316 err = f.beforeFunc(id) 317 } 318 return err 319 } 320 321 func (f fakeRunChecker) After(ctx context.Context, runIfFound *Run) error { 322 if runIfFound == nil { 323 return f.afterOnNotFound 324 } 325 return f.after[runIfFound.ID] 326 }