go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/tryjob/util_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 tryjob 16 17 import ( 18 "context" 19 "slices" 20 "testing" 21 "time" 22 23 "go.chromium.org/luci/common/clock" 24 "go.chromium.org/luci/gae/service/datastore" 25 26 "go.chromium.org/luci/cv/internal/common" 27 "go.chromium.org/luci/cv/internal/cvtesting" 28 29 . "github.com/smartystreets/goconvey/convey" 30 . "go.chromium.org/luci/common/testing/assertions" 31 ) 32 33 func TestSaveTryjobs(t *testing.T) { 34 t.Parallel() 35 36 Convey("SaveTryjobs works", t, func() { 37 ct := cvtesting.Test{} 38 ctx, cancel := ct.SetUp(t) 39 defer cancel() 40 const bbHost = "buildbucket.example.com" 41 now := ct.Clock.Now().UTC() 42 var runID = common.MakeRunID("infra", now.Add(-1*time.Hour), 1, []byte("foo")) 43 var notified map[common.RunID]common.TryjobIDs 44 notifyFn := func(ctx context.Context, runID common.RunID, events *TryjobUpdatedEvents) error { 45 if notified == nil { 46 notified = make(map[common.RunID]common.TryjobIDs) 47 } 48 for _, evt := range events.Events { 49 notified[runID] = append(notified[runID], common.TryjobID(evt.TryjobId)) 50 } 51 return nil 52 } 53 54 Convey("No internal ID", func() { 55 Convey("No external ID", func() { 56 tj := &Tryjob{ 57 EVersion: 1, 58 EntityCreateTime: now, 59 EntityUpdateTime: now, 60 LaunchedBy: runID, 61 } 62 So(datastore.RunInTransaction(ctx, func(ctx context.Context) error { 63 return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn) 64 }, nil), ShouldBeNil) 65 So(tj.ID, ShouldNotBeEmpty) 66 So(notified, ShouldResemble, map[common.RunID]common.TryjobIDs{ 67 runID: {tj.ID}, 68 }) 69 }) 70 Convey("External ID provided", func() { 71 Convey("Does not map to any existing tryjob", func() { 72 eid := MustBuildbucketID(bbHost, 10) 73 tj := &Tryjob{ 74 ExternalID: eid, 75 EVersion: 1, 76 EntityCreateTime: now, 77 EntityUpdateTime: now, 78 LaunchedBy: runID, 79 } 80 So(datastore.RunInTransaction(ctx, func(ctx context.Context) error { 81 return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn) 82 }, nil), ShouldBeNil) 83 tj, err := eid.Load(ctx) 84 So(err, ShouldBeNil) 85 So(tj, ShouldNotBeNil) 86 So(notified, ShouldResemble, map[common.RunID]common.TryjobIDs{ 87 runID: {tj.ID}, 88 }) 89 }) 90 Convey("Map to an existing tryjob", func() { 91 eid := MustBuildbucketID(bbHost, 10) 92 eid.MustCreateIfNotExists(ctx) 93 tj := &Tryjob{ 94 ExternalID: eid, 95 EVersion: 1, 96 EntityCreateTime: now, 97 EntityUpdateTime: now, 98 LaunchedBy: runID, 99 } 100 So(datastore.RunInTransaction(ctx, func(ctx context.Context) error { 101 return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn) 102 }, nil), ShouldErrLike, "has already mapped to internal id") 103 So(notified, ShouldBeEmpty) 104 }) 105 }) 106 }) 107 108 Convey("Internal ID provided", func() { 109 const tjID = common.TryjobID(45366) 110 Convey("No external ID", func() { 111 tj := &Tryjob{ 112 ID: tjID, 113 EVersion: 1, 114 EntityCreateTime: now, 115 EntityUpdateTime: now, 116 ReusedBy: common.RunIDs{runID}, 117 } 118 So(datastore.RunInTransaction(ctx, func(ctx context.Context) error { 119 return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn) 120 }, nil), ShouldBeNil) 121 So(notified, ShouldResemble, map[common.RunID]common.TryjobIDs{ 122 runID: {tjID}, 123 }) 124 }) 125 Convey("External ID provided", func() { 126 Convey("Does not map to any existing tryjob", func() { 127 eid := MustBuildbucketID(bbHost, 10) 128 tj := &Tryjob{ 129 ID: tjID, 130 ExternalID: eid, 131 EVersion: 1, 132 EntityCreateTime: now, 133 EntityUpdateTime: now, 134 ReusedBy: common.RunIDs{runID}, 135 } 136 So(datastore.RunInTransaction(ctx, func(ctx context.Context) error { 137 return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn) 138 }, nil), ShouldBeNil) 139 So(eid.MustLoad(ctx).ID, ShouldEqual, tjID) 140 So(notified, ShouldResemble, map[common.RunID]common.TryjobIDs{ 141 runID: {tjID}, 142 }) 143 }) 144 145 Convey("Map to an existing tryjob", func() { 146 Convey("existing tryjob has the same ID", func() { 147 eid := MustBuildbucketID(bbHost, 10) 148 existing := eid.MustCreateIfNotExists(ctx) 149 ct.Clock.Add(10 * time.Minute) 150 tj := &Tryjob{ 151 ID: existing.ID, 152 ExternalID: eid, 153 EVersion: existing.EVersion + 1, 154 EntityUpdateTime: datastore.RoundTime(clock.Now(ctx).UTC()), 155 ReusedBy: common.RunIDs{runID}, 156 } 157 So(datastore.RunInTransaction(ctx, func(ctx context.Context) error { 158 return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn) 159 }, nil), ShouldBeNil) 160 So(eid.MustLoad(ctx).EVersion, ShouldEqual, existing.EVersion+1) 161 So(notified, ShouldResemble, map[common.RunID]common.TryjobIDs{ 162 runID: {tj.ID}, 163 }) 164 }) 165 Convey("existing tryjob has a different ID", func() { 166 eid := MustBuildbucketID(bbHost, 10) 167 existing := eid.MustCreateIfNotExists(ctx) 168 ct.Clock.Add(10 * time.Minute) 169 tj := &Tryjob{ 170 ID: existing.ID + 10, 171 ExternalID: eid, 172 EVersion: existing.EVersion + 1, 173 EntityUpdateTime: datastore.RoundTime(clock.Now(ctx).UTC()), 174 ReusedBy: common.RunIDs{runID}, 175 } 176 So(datastore.RunInTransaction(ctx, func(ctx context.Context) error { 177 return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn) 178 }, nil), ShouldErrLike, "has already mapped to") 179 So(notified, ShouldBeEmpty) 180 }) 181 }) 182 }) 183 }) 184 }) 185 } 186 187 func TestQueryTryjobIDsUpdatedBefore(t *testing.T) { 188 t.Parallel() 189 190 Convey("QueryTryjobIDsUpdatedBefore", t, func() { 191 ct := cvtesting.Test{} 192 ctx, cancel := ct.SetUp(t) 193 defer cancel() 194 195 nextBuildID := int64(1) 196 createNTryjobs := func(n int) []*Tryjob { 197 tryjobs := make([]*Tryjob, n) 198 for i := range tryjobs { 199 eid := MustBuildbucketID("example.com", nextBuildID) 200 nextBuildID++ 201 tryjobs[i] = eid.MustCreateIfNotExists(ctx) 202 } 203 return tryjobs 204 } 205 206 var allTryjobs []*Tryjob 207 allTryjobs = append(allTryjobs, createNTryjobs(1000)...) 208 ct.Clock.Add(1 * time.Minute) 209 allTryjobs = append(allTryjobs, createNTryjobs(1000)...) 210 ct.Clock.Add(1 * time.Minute) 211 allTryjobs = append(allTryjobs, createNTryjobs(1000)...) 212 213 before := ct.Clock.Now().Add(-30 * time.Second) 214 var expected common.TryjobIDs 215 for _, tj := range allTryjobs { 216 if tj.EntityUpdateTime.Before(before) { 217 expected = append(expected, tj.ID) 218 } 219 } 220 slices.Sort(expected) 221 222 actual, err := QueryTryjobIDsUpdatedBefore(ctx, before) 223 So(err, ShouldBeNil) 224 So(actual, ShouldResemble, expected) 225 }) 226 }