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  }