go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/led/job/job_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  	"context"
    19  	"os"
    20  	"testing"
    21  
    22  	"google.golang.org/protobuf/encoding/protojson"
    23  
    24  	"go.chromium.org/luci/buildbucket/cmd/bbagent/bbinput"
    25  	bbpb "go.chromium.org/luci/buildbucket/proto"
    26  	"go.chromium.org/luci/common/data/rand/cryptorand"
    27  	"go.chromium.org/luci/led/job/experiments"
    28  	"go.chromium.org/luci/luciexe/exe"
    29  	swarmingpb "go.chromium.org/luci/swarming/proto/api_v2"
    30  
    31  	. "github.com/smartystreets/goconvey/convey"
    32  	. "go.chromium.org/luci/common/testing/assertions"
    33  )
    34  
    35  const phonyTagExperiment = "luci.test.add_phony_tag_experiment"
    36  
    37  func init() {
    38  	experiments.Register(phonyTagExperiment, func(ctx context.Context, b *bbpb.Build, task *swarmingpb.NewTaskRequest) error {
    39  		task.Tags = append(task.Tags, "phony_tag:experiment")
    40  		return nil
    41  	})
    42  }
    43  
    44  func TestFlattenToSwarming(t *testing.T) {
    45  	t.Parallel()
    46  
    47  	Convey(`FlattenToSwarming`, t, func() {
    48  		fixture, err := os.ReadFile("jobcreate/testdata/bbagent_cas.job.json")
    49  		So(err, ShouldBeNil)
    50  
    51  		ctx := cryptorand.MockForTest(context.Background(), 4)
    52  
    53  		bbJob := &Definition{}
    54  		So(protojson.Unmarshal(fixture, bbJob), ShouldBeNil)
    55  		bb := bbJob.GetBuildbucket()
    56  		totalExpiration := bb.BbagentArgs.Build.SchedulingTimeout.Seconds
    57  
    58  		Convey(`bbagent`, func() {
    59  			So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil)
    60  
    61  			sw := bbJob.GetSwarming()
    62  			So(sw, ShouldNotBeNil)
    63  			So(sw.Task.Tags, ShouldResemble, []string{
    64  				"allow_milo:1",
    65  				"log_location:logdog://luci-logdog-dev.appspot.com/infra/led/username/1dd4751f899d743d0780c9644375aae211327818655f3d20f84abef6a9df0898/+/build.proto",
    66  			})
    67  
    68  			So(sw.Task.TaskSlices, ShouldHaveLength, 2)
    69  
    70  			slice0 := sw.Task.TaskSlices[0]
    71  			So(slice0.ExpirationSecs, ShouldEqual, 240)
    72  			So(slice0.Properties.Dimensions, ShouldResembleProto, []*swarmingpb.StringPair{
    73  				{
    74  					Key:   "caches",
    75  					Value: "builder_1d1f048016f3dc7294e1abddfd758182bc95619cec2a87d01a3f24517b4e2814_v2",
    76  				},
    77  				{Key: "cpu", Value: "x86-64"},
    78  				{Key: "os", Value: "Ubuntu"},
    79  				{Key: "pool", Value: "Chrome"},
    80  			})
    81  
    82  			So(slice0.Properties.Command[:3], ShouldResemble, []string{
    83  				"bbagent${EXECUTABLE_SUFFIX}", "--output",
    84  				"${ISOLATED_OUTDIR}/build.proto.json",
    85  			})
    86  			bbArgs, err := bbinput.Parse(slice0.Properties.Command[3])
    87  			So(err, ShouldBeNil)
    88  
    89  			So(bbArgs.PayloadPath, ShouldResemble, "kitchen-checkout")
    90  			So(bbArgs.Build.Exe.Cmd, ShouldResemble, []string{"luciexe"})
    91  
    92  			props := ledProperties{}
    93  			err = exe.ParseProperties(bbArgs.Build.Input.Properties, map[string]any{
    94  				"$recipe_engine/led": &props,
    95  			})
    96  			So(err, ShouldBeNil)
    97  
    98  			expectedProps := ledProperties{
    99  				LedRunID: "infra/led/username/1dd4751f899d743d0780c9644375aae211327818655f3d20f84abef6a9df0898",
   100  				CIPDInput: &cipdInput{
   101  					"infra/recipe_bundles/chromium.googlesource.com/infra/luci/recipes-py",
   102  					"HEAD",
   103  				},
   104  			}
   105  			So(props, ShouldResemble, expectedProps)
   106  
   107  			// Added `kitchen-checkout` as the last CipdInputs entry.
   108  			So(slice0.Properties.CipdInput.Packages, ShouldHaveLength, 17) // see bbagent.job.json
   109  			So(slice0.Properties.CipdInput.Packages[16], ShouldResembleProto, &swarmingpb.CipdPackage{
   110  				Path:        "kitchen-checkout",
   111  				PackageName: expectedProps.CIPDInput.Package,
   112  				Version:     expectedProps.CIPDInput.Version,
   113  			})
   114  
   115  			slice1 := sw.Task.TaskSlices[1]
   116  			So(slice1.ExpirationSecs, ShouldEqual, 21360)
   117  			So(slice1.Properties.Dimensions, ShouldResembleProto, []*swarmingpb.StringPair{
   118  				{Key: "cpu", Value: "x86-64"},
   119  				{Key: "os", Value: "Ubuntu"},
   120  				{Key: "pool", Value: "Chrome"},
   121  			})
   122  
   123  			So(slice0.ExpirationSecs+slice1.ExpirationSecs, ShouldEqual, totalExpiration)
   124  		})
   125  
   126  		Convey(`kitchen`, func() {
   127  			bb.LegacyKitchen = true
   128  			So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldErrLike,
   129  				"kitchen job Definitions not supported")
   130  		})
   131  
   132  		Convey(`explicit max expiration`, func() {
   133  			// set a dimension to expire after end of current task
   134  			editDims(bbJob, "final=value@40000")
   135  
   136  			So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil)
   137  
   138  			sw := bbJob.GetSwarming()
   139  			So(sw, ShouldNotBeNil)
   140  			So(sw.Task.TaskSlices, ShouldHaveLength, 2)
   141  
   142  			So(sw.Task.TaskSlices[0].ExpirationSecs, ShouldEqual, 240)
   143  			So(sw.Task.TaskSlices[1].ExpirationSecs, ShouldEqual, 40000-240)
   144  		})
   145  
   146  		Convey(`no expiring dims`, func() {
   147  			bb := bbJob.GetBuildbucket()
   148  			for _, dim := range bb.GetBbagentArgs().Build.Infra.Swarming.TaskDimensions {
   149  				dim.Expiration = nil
   150  			}
   151  			for _, cache := range bb.GetBbagentArgs().Build.Infra.Swarming.Caches {
   152  				cache.WaitForWarmCache = nil
   153  			}
   154  
   155  			So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil)
   156  
   157  			sw := bbJob.GetSwarming()
   158  			So(sw, ShouldNotBeNil)
   159  			So(sw.Task.TaskSlices, ShouldHaveLength, 1)
   160  
   161  			So(sw.Task.TaskSlices[0].ExpirationSecs, ShouldEqual, 21600)
   162  		})
   163  
   164  		Convey(`CasUserPayload recipe`, func() {
   165  			SoHLEdit(bbJob, func(je HighLevelEditor) {
   166  				je.TaskPayloadSource("", "")
   167  				je.CASTaskPayload("some/path", &swarmingpb.CASReference{
   168  					CasInstance: "projects/chromium-swarm-dev/instances/default_instance",
   169  					Digest: &swarmingpb.Digest{
   170  						Hash:      "b7c329e532e221e23809ba23f9af5b309aa17d490d845580207493d381998bd9",
   171  						SizeBytes: 24,
   172  					},
   173  				})
   174  			})
   175  
   176  			So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil)
   177  
   178  			sw := bbJob.GetSwarming()
   179  			So(sw, ShouldNotBeNil)
   180  			So(sw.Task.TaskSlices, ShouldHaveLength, 2)
   181  
   182  			slice0 := sw.Task.TaskSlices[0]
   183  			for _, pkg := range slice0.Properties.CipdInput.Packages {
   184  				// there shouldn't be any recipe package any more
   185  				So(pkg.Version, ShouldNotEqual, "HEAD")
   186  			}
   187  
   188  			So(slice0.Properties.Command[:3], ShouldResemble, []string{
   189  				"bbagent${EXECUTABLE_SUFFIX}", "--output",
   190  				"${ISOLATED_OUTDIR}/build.proto.json",
   191  			})
   192  			bbArgs, err := bbinput.Parse(slice0.Properties.Command[3])
   193  			So(err, ShouldBeNil)
   194  
   195  			So(bbArgs.PayloadPath, ShouldResemble, "some/path")
   196  			So(bbArgs.Build.Exe.Cmd, ShouldResemble, []string{"luciexe"})
   197  
   198  			props := ledProperties{}
   199  			err = exe.ParseProperties(bbArgs.Build.Input.Properties, map[string]any{
   200  				"$recipe_engine/led": &props,
   201  			})
   202  			So(err, ShouldBeNil)
   203  			expectedProps := ledProperties{
   204  				LedRunID: "infra/led/username/1dd4751f899d743d0780c9644375aae211327818655f3d20f84abef6a9df0898",
   205  				RbeCasInput: &swarmingpb.CASReference{
   206  					CasInstance: "projects/chromium-swarm-dev/instances/default_instance",
   207  					Digest: &swarmingpb.Digest{
   208  						Hash:      "b7c329e532e221e23809ba23f9af5b309aa17d490d845580207493d381998bd9",
   209  						SizeBytes: 24,
   210  					},
   211  				},
   212  			}
   213  			So(props, ShouldResemble, expectedProps)
   214  		})
   215  		Convey(`With experiments`, func() {
   216  			SoHLEdit(bbJob, func(je HighLevelEditor) {
   217  				je.Experiments(map[string]bool{
   218  					phonyTagExperiment:             true, // see init() above
   219  					"should_be_skipped_as_unknown": true,
   220  				})
   221  			})
   222  
   223  			So(bbJob.FlattenToSwarming(ctx, "username", "parent_task_id", NoKitchenSupport(), "off"), ShouldBeNil)
   224  
   225  			sw := bbJob.GetSwarming()
   226  			So(sw, ShouldNotBeNil)
   227  			So(sw.Task.Tags[len(sw.Task.Tags)-1], ShouldEqual, "phony_tag:experiment")
   228  		})
   229  	})
   230  
   231  }