go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/led/job/helpers_for_test.go (about) 1 // Copyright 2020 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 job 16 17 import ( 18 "sort" 19 "testing" 20 "time" 21 22 . "github.com/smartystreets/goconvey/convey" 23 24 "go.chromium.org/luci/buildbucket" 25 bbpb "go.chromium.org/luci/buildbucket/proto" 26 api "go.chromium.org/luci/swarming/proto/api_v2" 27 ) 28 29 type testCase struct { 30 name string 31 fn func(*Definition) 32 33 // These control if the test is disabled for one of the test types. 34 skipBB bool 35 // If true, the job should be for a v2 Buildbucket build. 36 v2Build bool 37 38 skipSW bool // skips all swarming tests 39 skipSWEmpty bool // skips just swarming tests with an empty Task 40 skipSWSlices bool // skips just swarming tests with a 3-slice Task 41 } 42 43 // Time from the beginning of the task request until the given slice expires. 44 // 45 // Note that swarming slice expirations in the Definition are stored relative to 46 // the previous slice. 47 const ( 48 swSlice1ExpSecs = 60 49 swSlice2ExpSecs = 240 50 swSlice3ExpSecs = 600 51 52 swSlice1Exp = swSlice1ExpSecs * time.Second // relative: 60-0 = 60 53 swSlice2Exp = swSlice2ExpSecs * time.Second // relative: 240-60 = 180 54 swSlice3Exp = swSlice3ExpSecs * time.Second // relative: 600-240 = 360 55 ) 56 57 func testBBJob(v2Build bool) *Definition { 58 jd := &Definition{JobType: &Definition_Buildbucket{ 59 Buildbucket: &Buildbucket{ 60 Name: "default-task-name", 61 }, 62 }} 63 if v2Build { 64 jd.GetBuildbucket().BbagentArgs = &bbpb.BBAgentArgs{ 65 Build: &bbpb.Build{ 66 Input: &bbpb.Build_Input{ 67 Experiments: []string{ 68 buildbucket.ExperimentBBAgentDownloadCipd, 69 }, 70 }, 71 }, 72 } 73 jd.GetBuildbucket().LegacyKitchen = false 74 } 75 return jd 76 } 77 78 func testSWJob(sliceExps ...time.Duration) *Definition { 79 ret := &Definition{JobType: &Definition_Swarming{ 80 Swarming: &Swarming{}, 81 }} 82 if len(sliceExps) > 0 { 83 sw := ret.GetSwarming() 84 sw.Task = &api.NewTaskRequest{ 85 Name: "default-task-name", 86 } 87 88 for _, exp := range sliceExps { 89 sw.Task.TaskSlices = append(sw.Task.TaskSlices, &api.TaskSlice{ 90 ExpirationSecs: int32(exp.Seconds()), 91 }) 92 } 93 } 94 return ret 95 } 96 97 func must(value any, err error) any { 98 So(err, ShouldBeNil) 99 return value 100 } 101 102 // runCases runs some 'edit' tests for the given operation name (e.g. a method 103 // of the Editor interface). 104 // 105 // This will invoke every function in `tests` with an empty Buildbucket 106 // or Swarming Definition (so, every function in `tests` is called exactly 107 // twice). 108 // 109 // If a given test function would like to add type-specific verification, it 110 // should switch on the type of the Definition. 111 func runCases(t *testing.T, opName string, tests []testCase) { 112 Convey(opName, t, func() { 113 Convey(`bb`, func() { 114 for _, tc := range tests { 115 ConveyIf(!tc.skipBB, tc.name, func() { 116 tc.fn(testBBJob(tc.v2Build)) 117 }) 118 } 119 }) 120 Convey(`sw (empty)`, func() { 121 for _, tc := range tests { 122 ConveyIf(!(tc.skipSW || tc.skipSWEmpty), tc.name, func() { 123 tc.fn(testSWJob()) 124 }) 125 } 126 }) 127 Convey(`sw (slices)`, func() { 128 for _, tc := range tests { 129 ConveyIf(!(tc.skipSW || tc.skipSWSlices), tc.name, func() { 130 // Make a swarming job which expires in 600 seconds. 131 tc.fn(testSWJob( 132 swSlice1Exp, 133 swSlice2Exp-swSlice1Exp, 134 swSlice3Exp-swSlice2Exp, 135 )) 136 }) 137 } 138 }) 139 }) 140 } 141 142 func ConveyIf(cond bool, items ...any) { 143 if cond { 144 Convey(items...) 145 } else { 146 SkipConvey(items...) 147 } 148 } 149 150 func SoEdit(jd *Definition, cb func(Editor)) { 151 So(jd.Edit(cb), ShouldBeNil) 152 } 153 154 func SoHLEdit(jd *Definition, cb func(HighLevelEditor)) { 155 So(jd.HighLevelEdit(cb), ShouldBeNil) 156 } 157 158 func mustGetDimensions(jd *Definition) ExpiringDimensions { 159 ret, err := jd.Info().Dimensions() 160 So(err, ShouldBeNil) 161 return ret 162 } 163 164 // baselineDims sets some baseline dimensions on jd and returns the 165 // ExpiringDimensions which Info().GetDimensions() should now return. 166 func baselineDims(jd *Definition) ExpiringDimensions { 167 toSet := ExpiringDimensions{ 168 "key": []ExpiringValue{ 169 {Value: "B", Expiration: swSlice2Exp}, 170 {Value: "Z"}, 171 {Value: "A", Expiration: swSlice1Exp}, 172 {Value: "AA", Expiration: swSlice1Exp}, 173 {Value: "C", Expiration: swSlice3Exp}, 174 }, 175 } 176 177 SoEdit(jd, func(je Editor) { 178 // set in a non-sorted order 179 je.SetDimensions(toSet) 180 }) 181 182 // Info().GetDimensions() always returns filled expirations. 183 toSet["key"][1].Expiration = swSlice3Exp 184 185 sort.Slice(toSet["key"], func(i, j int) bool { 186 return toSet["key"][i].Value < toSet["key"][j].Value 187 }) 188 189 return toSet 190 }