blob: 7f5b409104b61c636bf26fa34d92582a56a1f7e5 [file] [log] [blame]
// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package job
import (
"sort"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
"go.chromium.org/luci/buildbucket"
bbpb "go.chromium.org/luci/buildbucket/proto"
api "go.chromium.org/luci/swarming/proto/api_v2"
)
type testCase struct {
name string
fn func(*Definition)
// These control if the test is disabled for one of the test types.
skipBB bool
// If true, the job should be for a v2 Buildbucket build.
v2Build bool
skipSW bool // skips all swarming tests
skipSWEmpty bool // skips just swarming tests with an empty Task
skipSWSlices bool // skips just swarming tests with a 3-slice Task
}
// Time from the beginning of the task request until the given slice expires.
//
// Note that swarming slice expirations in the Definition are stored relative to
// the previous slice.
const (
swSlice1ExpSecs = 60
swSlice2ExpSecs = 240
swSlice3ExpSecs = 600
swSlice1Exp = swSlice1ExpSecs * time.Second // relative: 60-0 = 60
swSlice2Exp = swSlice2ExpSecs * time.Second // relative: 240-60 = 180
swSlice3Exp = swSlice3ExpSecs * time.Second // relative: 600-240 = 360
)
func testBBJob(v2Build bool) *Definition {
jd := &Definition{JobType: &Definition_Buildbucket{
Buildbucket: &Buildbucket{
Name: "default-task-name",
},
}}
if v2Build {
jd.GetBuildbucket().BbagentArgs = &bbpb.BBAgentArgs{
Build: &bbpb.Build{
Input: &bbpb.Build_Input{
Experiments: []string{
buildbucket.ExperimentBBAgentDownloadCipd,
},
},
},
}
jd.GetBuildbucket().LegacyKitchen = false
}
return jd
}
func testSWJob(sliceExps ...time.Duration) *Definition {
ret := &Definition{JobType: &Definition_Swarming{
Swarming: &Swarming{},
}}
if len(sliceExps) > 0 {
sw := ret.GetSwarming()
sw.Task = &api.NewTaskRequest{
Name: "default-task-name",
}
for _, exp := range sliceExps {
sw.Task.TaskSlices = append(sw.Task.TaskSlices, &api.TaskSlice{
ExpirationSecs: int32(exp.Seconds()),
})
}
}
return ret
}
func must(value any, err error) any {
So(err, ShouldBeNil)
return value
}
// runCases runs some 'edit' tests for the given operation name (e.g. a method
// of the Editor interface).
//
// This will invoke every function in `tests` with an empty Buildbucket
// or Swarming Definition (so, every function in `tests` is called exactly
// twice).
//
// If a given test function would like to add type-specific verification, it
// should switch on the type of the Definition.
func runCases(t *testing.T, opName string, tests []testCase) {
Convey(opName, t, func() {
Convey(`bb`, func() {
for _, tc := range tests {
ConveyIf(!tc.skipBB, tc.name, func() {
tc.fn(testBBJob(tc.v2Build))
})
}
})
Convey(`sw (empty)`, func() {
for _, tc := range tests {
ConveyIf(!(tc.skipSW || tc.skipSWEmpty), tc.name, func() {
tc.fn(testSWJob())
})
}
})
Convey(`sw (slices)`, func() {
for _, tc := range tests {
ConveyIf(!(tc.skipSW || tc.skipSWSlices), tc.name, func() {
// Make a swarming job which expires in 600 seconds.
tc.fn(testSWJob(
swSlice1Exp,
swSlice2Exp-swSlice1Exp,
swSlice3Exp-swSlice2Exp,
))
})
}
})
})
}
func ConveyIf(cond bool, items ...any) {
if cond {
Convey(items...)
} else {
SkipConvey(items...)
}
}
func SoEdit(jd *Definition, cb func(Editor)) {
So(jd.Edit(cb), ShouldBeNil)
}
func SoHLEdit(jd *Definition, cb func(HighLevelEditor)) {
So(jd.HighLevelEdit(cb), ShouldBeNil)
}
func mustGetDimensions(jd *Definition) ExpiringDimensions {
ret, err := jd.Info().Dimensions()
So(err, ShouldBeNil)
return ret
}
// baselineDims sets some baseline dimensions on jd and returns the
// ExpiringDimensions which Info().GetDimensions() should now return.
func baselineDims(jd *Definition) ExpiringDimensions {
toSet := ExpiringDimensions{
"key": []ExpiringValue{
{Value: "B", Expiration: swSlice2Exp},
{Value: "Z"},
{Value: "A", Expiration: swSlice1Exp},
{Value: "AA", Expiration: swSlice1Exp},
{Value: "C", Expiration: swSlice3Exp},
},
}
SoEdit(jd, func(je Editor) {
// set in a non-sorted order
je.SetDimensions(toSet)
})
// Info().GetDimensions() always returns filled expirations.
toSet["key"][1].Expiration = swSlice3Exp
sort.Slice(toSet["key"], func(i, j int) bool {
return toSet["key"][i].Value < toSet["key"][j].Value
})
return toSet
}