go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/led/job/edit_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  	"testing"
    19  
    20  	. "github.com/smartystreets/goconvey/convey"
    21  	. "go.chromium.org/luci/common/testing/assertions"
    22  
    23  	"go.chromium.org/luci/buildbucket"
    24  	bbpb "go.chromium.org/luci/buildbucket/proto"
    25  	swarmingpb "go.chromium.org/luci/swarming/proto/api_v2"
    26  )
    27  
    28  func TestClearCurrentIsolated(t *testing.T) {
    29  	t.Parallel()
    30  
    31  	runCases(t, `ClearCurrentIsolated`, []testCase{
    32  		{
    33  			name: "basic with rbe-cas input",
    34  			fn: func(jd *Definition) {
    35  				if jd.GetSwarming() != nil {
    36  					jd.GetSwarming().CasUserPayload = &swarmingpb.CASReference{
    37  						CasInstance: "instance",
    38  						Digest: &swarmingpb.Digest{
    39  							Hash:      "hash",
    40  							SizeBytes: 1,
    41  						},
    42  					}
    43  				}
    44  				for _, slc := range jd.GetSwarming().GetTask().GetTaskSlices() {
    45  					if slc.Properties == nil {
    46  						slc.Properties = &swarmingpb.TaskProperties{}
    47  					}
    48  					slc.Properties.CasInputRoot = &swarmingpb.CASReference{
    49  						CasInstance: "instance",
    50  						Digest: &swarmingpb.Digest{
    51  							Hash:      "hash",
    52  							SizeBytes: 1,
    53  						},
    54  					}
    55  				}
    56  				SoEdit(jd, func(je Editor) {
    57  					je.ClearCurrentIsolated()
    58  				})
    59  
    60  				for _, slc := range jd.GetSwarming().GetTask().GetTaskSlices() {
    61  					So(slc.Properties.CasInputRoot, ShouldBeNil)
    62  				}
    63  
    64  				iso, err := jd.Info().CurrentIsolated()
    65  				So(err, ShouldBeNil)
    66  				So(iso, ShouldBeNil)
    67  			},
    68  		},
    69  	})
    70  }
    71  
    72  func TestEnv(t *testing.T) {
    73  	t.Parallel()
    74  
    75  	runCases(t, `Env`, []testCase{
    76  		{
    77  			name: "empty",
    78  			fn: func(jd *Definition) {
    79  				SoEdit(jd, func(je Editor) {
    80  					je.Env(nil)
    81  				})
    82  				So(must(jd.Info().Env()), ShouldBeEmpty)
    83  			},
    84  		},
    85  
    86  		{
    87  			name:        "new",
    88  			skipSWEmpty: true,
    89  			fn: func(jd *Definition) {
    90  				SoEdit(jd, func(je Editor) {
    91  					je.Env(map[string]string{
    92  						"KEY": "VALUE",
    93  						"DEL": "", // noop
    94  					})
    95  				})
    96  				So(must(jd.Info().Env()), ShouldResemble, map[string]string{
    97  					"KEY": "VALUE",
    98  				})
    99  			},
   100  		},
   101  
   102  		{
   103  			name:        "add",
   104  			skipSWEmpty: true,
   105  			fn: func(jd *Definition) {
   106  				SoEdit(jd, func(je Editor) {
   107  					je.Env(map[string]string{
   108  						"KEY": "VALUE",
   109  					})
   110  				})
   111  				SoEdit(jd, func(je Editor) {
   112  					je.Env(map[string]string{
   113  						"OTHER": "NEW_VAL",
   114  						"DEL":   "", // noop
   115  					})
   116  				})
   117  				So(must(jd.Info().Env()), ShouldResemble, map[string]string{
   118  					"KEY":   "VALUE",
   119  					"OTHER": "NEW_VAL",
   120  				})
   121  			},
   122  		},
   123  
   124  		{
   125  			name:        "del",
   126  			skipSWEmpty: true,
   127  			fn: func(jd *Definition) {
   128  				SoEdit(jd, func(je Editor) {
   129  					je.Env(map[string]string{
   130  						"KEY": "VALUE",
   131  					})
   132  				})
   133  				SoEdit(jd, func(je Editor) {
   134  					je.Env(map[string]string{
   135  						"OTHER": "NEW_VAL",
   136  						"KEY":   "", // delete
   137  					})
   138  				})
   139  				So(must(jd.Info().Env()), ShouldResemble, map[string]string{
   140  					"OTHER": "NEW_VAL",
   141  				})
   142  			},
   143  		},
   144  	})
   145  }
   146  
   147  func TestPriority(t *testing.T) {
   148  	t.Parallel()
   149  
   150  	runCases(t, `Priority`, []testCase{
   151  		{
   152  			name: "negative",
   153  			fn: func(jd *Definition) {
   154  				So(jd.Edit(func(je Editor) {
   155  					je.Priority(-1)
   156  				}), ShouldErrLike, "negative Priority")
   157  			},
   158  		},
   159  
   160  		{
   161  			name: "set",
   162  			fn: func(jd *Definition) {
   163  				SoEdit(jd, func(je Editor) {
   164  					je.Priority(100)
   165  				})
   166  				So(jd.Info().Priority(), ShouldEqual, 100)
   167  			},
   168  		},
   169  
   170  		{
   171  			name: "reset",
   172  			fn: func(jd *Definition) {
   173  				SoEdit(jd, func(je Editor) {
   174  					je.Priority(100)
   175  				})
   176  				SoEdit(jd, func(je Editor) {
   177  					je.Priority(200)
   178  				})
   179  				So(jd.Info().Priority(), ShouldEqual, 200)
   180  			},
   181  		},
   182  	})
   183  }
   184  
   185  func TestSwarmingHostname(t *testing.T) {
   186  	t.Parallel()
   187  
   188  	runCases(t, `SwarmingHostname`, []testCase{
   189  		{
   190  			name: "empty",
   191  			fn: func(jd *Definition) {
   192  				So(jd.Edit(func(je Editor) {
   193  					je.SwarmingHostname("")
   194  				}), ShouldErrLike, "empty SwarmingHostname")
   195  			},
   196  		},
   197  
   198  		{
   199  			name: "set",
   200  			fn: func(jd *Definition) {
   201  				SoEdit(jd, func(je Editor) {
   202  					je.SwarmingHostname("example.com")
   203  				})
   204  				So(jd.Info().SwarmingHostname(), ShouldEqual, "example.com")
   205  			},
   206  		},
   207  
   208  		{
   209  			name: "reset",
   210  			fn: func(jd *Definition) {
   211  				SoEdit(jd, func(je Editor) {
   212  					je.SwarmingHostname("example.com")
   213  				})
   214  				SoEdit(jd, func(je Editor) {
   215  					je.SwarmingHostname("other.example.com")
   216  				})
   217  				So(jd.Info().SwarmingHostname(), ShouldEqual, "other.example.com")
   218  			},
   219  		},
   220  	})
   221  }
   222  
   223  func TestTaskName(t *testing.T) {
   224  	t.Parallel()
   225  
   226  	runCases(t, `TaskName`, []testCase{
   227  		{
   228  			name: "set",
   229  			fn: func(jd *Definition) {
   230  				if jd.GetBuildbucket() == nil && jd.GetSwarming().GetTask() == nil {
   231  					So(jd.Info().TaskName(), ShouldResemble, "")
   232  				} else {
   233  					So(jd.Info().TaskName(), ShouldResemble, "default-task-name")
   234  				}
   235  
   236  				SoEdit(jd, func(je Editor) {
   237  					je.TaskName("")
   238  				})
   239  				So(jd.Info().TaskName(), ShouldResemble, "")
   240  
   241  				SoEdit(jd, func(je Editor) {
   242  					je.TaskName("something")
   243  				})
   244  				So(jd.Info().TaskName(), ShouldResemble, "something")
   245  			},
   246  		},
   247  	})
   248  }
   249  
   250  func TestPrefixPathEnv(t *testing.T) {
   251  	t.Parallel()
   252  
   253  	runCases(t, `PrefixPathEnv`, []testCase{
   254  		{
   255  			name: "empty",
   256  			fn: func(jd *Definition) {
   257  				SoEdit(jd, func(je Editor) {
   258  					je.PrefixPathEnv(nil)
   259  				})
   260  				So(must(jd.Info().PrefixPathEnv()), ShouldBeEmpty)
   261  			},
   262  		},
   263  
   264  		{
   265  			name:        "add",
   266  			skipSWEmpty: true,
   267  			fn: func(jd *Definition) {
   268  				SoEdit(jd, func(je Editor) {
   269  					je.PrefixPathEnv([]string{"some/path", "other/path"})
   270  				})
   271  				So(must(jd.Info().PrefixPathEnv()), ShouldResemble, []string{
   272  					"some/path", "other/path",
   273  				})
   274  			},
   275  		},
   276  
   277  		{
   278  			name:        "remove",
   279  			skipSWEmpty: true,
   280  			fn: func(jd *Definition) {
   281  				SoEdit(jd, func(je Editor) {
   282  					je.PrefixPathEnv([]string{"some/path", "other/path", "third"})
   283  				})
   284  				SoEdit(jd, func(je Editor) {
   285  					je.PrefixPathEnv([]string{"!other/path"})
   286  				})
   287  				So(must(jd.Info().PrefixPathEnv()), ShouldResemble, []string{
   288  					"some/path", "third",
   289  				})
   290  			},
   291  		},
   292  	})
   293  }
   294  
   295  func TestTags(t *testing.T) {
   296  	t.Parallel()
   297  
   298  	runCases(t, `Tags`, []testCase{
   299  		{
   300  			name: "empty",
   301  			fn: func(jd *Definition) {
   302  				SoEdit(jd, func(je Editor) {
   303  					je.Tags(nil)
   304  				})
   305  				So(jd.Info().Tags(), ShouldBeEmpty)
   306  			},
   307  		},
   308  
   309  		{
   310  			name: "add",
   311  			fn: func(jd *Definition) {
   312  				SoEdit(jd, func(je Editor) {
   313  					je.Tags([]string{"other:value", "key:value"})
   314  				})
   315  				So(jd.Info().Tags(), ShouldResemble, []string{
   316  					"key:value", "other:value",
   317  				})
   318  			},
   319  		},
   320  	})
   321  }
   322  
   323  func TestExperimental(t *testing.T) {
   324  	t.Parallel()
   325  
   326  	runCases(t, `Experimental`, []testCase{
   327  		{
   328  			name:   "full",
   329  			skipSW: true,
   330  			fn: func(jd *Definition) {
   331  				So(jd.HighLevelInfo().Experimental(), ShouldBeFalse)
   332  				SoHLEdit(jd, func(je HighLevelEditor) {
   333  					je.Experimental(true)
   334  				})
   335  				So(jd.HighLevelInfo().Experimental(), ShouldBeTrue)
   336  				So(jd.HighLevelInfo().Experiments(), ShouldResemble, []string{buildbucket.ExperimentNonProduction})
   337  
   338  				SoHLEdit(jd, func(je HighLevelEditor) {
   339  					je.Experimental(false)
   340  				})
   341  				So(jd.HighLevelInfo().Experimental(), ShouldBeFalse)
   342  				So(jd.HighLevelInfo().Experiments(), ShouldHaveLength, 0)
   343  			},
   344  		},
   345  	})
   346  }
   347  
   348  func TestExperiments(t *testing.T) {
   349  	t.Parallel()
   350  
   351  	runCases(t, `Experiments`, []testCase{
   352  		{
   353  			name:   "full",
   354  			skipSW: true,
   355  			fn: func(jd *Definition) {
   356  				So(jd.HighLevelInfo().Experiments(), ShouldHaveLength, 0)
   357  
   358  				SoHLEdit(jd, func(je HighLevelEditor) {
   359  					je.Experiments(map[string]bool{
   360  						"exp1":         true,
   361  						"exp2":         true,
   362  						"delMissingOK": false,
   363  					})
   364  				})
   365  				er := jd.GetBuildbucket().BbagentArgs.Build.Infra.Buildbucket.ExperimentReasons
   366  				So(jd.HighLevelInfo().Experiments(), ShouldResemble, []string{"exp1", "exp2"})
   367  				So(er, ShouldResemble, map[string]bbpb.BuildInfra_Buildbucket_ExperimentReason{
   368  					"exp1": bbpb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED,
   369  					"exp2": bbpb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED,
   370  				})
   371  				SoHLEdit(jd, func(je HighLevelEditor) {
   372  					je.Experiments(map[string]bool{
   373  						"exp1": false,
   374  					})
   375  				})
   376  				So(jd.HighLevelInfo().Experiments(), ShouldResemble, []string{"exp2"})
   377  				So(er, ShouldResemble, map[string]bbpb.BuildInfra_Buildbucket_ExperimentReason{
   378  					"exp2": bbpb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED,
   379  				})
   380  			},
   381  		},
   382  	})
   383  }
   384  
   385  func TestProperties(t *testing.T) {
   386  	t.Parallel()
   387  
   388  	runCases(t, `Properties`, []testCase{
   389  		{
   390  			name:   "nil",
   391  			skipSW: true,
   392  			fn: func(jd *Definition) {
   393  				SoHLEdit(jd, func(je HighLevelEditor) {
   394  					je.Properties(nil, false)
   395  				})
   396  				So(must(jd.HighLevelInfo().Properties()), ShouldBeEmpty)
   397  			},
   398  		},
   399  
   400  		{
   401  			name:   "add",
   402  			skipSW: true,
   403  			fn: func(jd *Definition) {
   404  				SoHLEdit(jd, func(je HighLevelEditor) {
   405  					je.Properties(map[string]string{
   406  						"key":    `"value"`,
   407  						"other":  `{"something": [1, 2, "subvalue"]}`,
   408  						"delete": "", // noop
   409  					}, false)
   410  				})
   411  				So(must(jd.HighLevelInfo().Properties()), ShouldResemble, map[string]string{
   412  					"key":   `"value"`,
   413  					"other": `{"something":[1,2,"subvalue"]}`,
   414  				})
   415  			},
   416  		},
   417  
   418  		{
   419  			name:   "add (auto)",
   420  			skipSW: true,
   421  			fn: func(jd *Definition) {
   422  				SoHLEdit(jd, func(je HighLevelEditor) {
   423  					je.Properties(map[string]string{
   424  						"json":    `{"thingy": "kerplop"}`,
   425  						"literal": `{I am a banana}`,
   426  						"delete":  "", // noop
   427  					}, true)
   428  				})
   429  				So(must(jd.HighLevelInfo().Properties()), ShouldResemble, map[string]string{
   430  					"json":    `{"thingy":"kerplop"}`,
   431  					"literal": `"{I am a banana}"`, // string now
   432  				})
   433  			},
   434  		},
   435  
   436  		{
   437  			name:   "delete",
   438  			skipSW: true,
   439  			fn: func(jd *Definition) {
   440  				SoHLEdit(jd, func(je HighLevelEditor) {
   441  					je.Properties(map[string]string{
   442  						"key": `"value"`,
   443  					}, false)
   444  				})
   445  				SoHLEdit(jd, func(je HighLevelEditor) {
   446  					je.Properties(map[string]string{
   447  						"key": "",
   448  					}, false)
   449  				})
   450  				So(must(jd.HighLevelInfo().Properties()), ShouldBeEmpty)
   451  			},
   452  		},
   453  	})
   454  }
   455  
   456  func TestGerritChange(t *testing.T) {
   457  	t.Parallel()
   458  
   459  	mkChange := func(project string) *bbpb.GerritChange {
   460  		return &bbpb.GerritChange{
   461  			Host:     "host.example.com",
   462  			Project:  project,
   463  			Change:   1,
   464  			Patchset: 1,
   465  		}
   466  	}
   467  
   468  	runCases(t, `GerritChange`, []testCase{
   469  		{
   470  			name:   "nil",
   471  			skipSW: true,
   472  			fn: func(jd *Definition) {
   473  				SoHLEdit(jd, func(je HighLevelEditor) {
   474  					je.AddGerritChange(nil)
   475  					je.RemoveGerritChange(nil)
   476  				})
   477  				So(jd.HighLevelInfo().GerritChanges(), ShouldBeEmpty)
   478  			},
   479  		},
   480  
   481  		{
   482  			name:   "new",
   483  			skipSW: true,
   484  			fn: func(jd *Definition) {
   485  				SoHLEdit(jd, func(je HighLevelEditor) {
   486  					je.AddGerritChange(mkChange("project"))
   487  				})
   488  				So(jd.HighLevelInfo().GerritChanges(), ShouldResembleProto, []*bbpb.GerritChange{
   489  					mkChange("project"),
   490  				})
   491  			},
   492  		},
   493  
   494  		{
   495  			name:   "clear",
   496  			skipSW: true,
   497  			fn: func(jd *Definition) {
   498  				SoHLEdit(jd, func(je HighLevelEditor) {
   499  					je.AddGerritChange(mkChange("project"))
   500  				})
   501  				So(jd.HighLevelInfo().GerritChanges(), ShouldResembleProto, []*bbpb.GerritChange{
   502  					mkChange("project"),
   503  				})
   504  				SoHLEdit(jd, func(je HighLevelEditor) {
   505  					je.ClearGerritChanges()
   506  				})
   507  				So(jd.HighLevelInfo().GerritChanges(), ShouldBeEmpty)
   508  			},
   509  		},
   510  
   511  		{
   512  			name:   "dupe",
   513  			skipSW: true,
   514  			fn: func(jd *Definition) {
   515  				SoHLEdit(jd, func(je HighLevelEditor) {
   516  					je.AddGerritChange(mkChange("project"))
   517  					je.AddGerritChange(mkChange("project"))
   518  				})
   519  				So(jd.HighLevelInfo().GerritChanges(), ShouldResembleProto, []*bbpb.GerritChange{
   520  					mkChange("project"),
   521  				})
   522  			},
   523  		},
   524  
   525  		{
   526  			name:   "remove",
   527  			skipSW: true,
   528  			fn: func(jd *Definition) {
   529  				bb := jd.GetBuildbucket()
   530  				bb.EnsureBasics()
   531  				bb.BbagentArgs.Build.Input.GerritChanges = []*bbpb.GerritChange{
   532  					mkChange("a"),
   533  					mkChange("b"),
   534  					mkChange("c"),
   535  				}
   536  				So(jd.HighLevelInfo().GerritChanges(), ShouldResembleProto, []*bbpb.GerritChange{
   537  					mkChange("a"),
   538  					mkChange("b"),
   539  					mkChange("c"),
   540  				})
   541  
   542  				SoHLEdit(jd, func(je HighLevelEditor) {
   543  					je.RemoveGerritChange(mkChange("b"))
   544  				})
   545  				So(jd.HighLevelInfo().GerritChanges(), ShouldResembleProto, []*bbpb.GerritChange{
   546  					mkChange("a"),
   547  					mkChange("c"),
   548  				})
   549  			},
   550  		},
   551  
   552  		{
   553  			name:   "remove (noop)",
   554  			skipSW: true,
   555  			fn: func(jd *Definition) {
   556  				SoHLEdit(jd, func(je HighLevelEditor) {
   557  					je.AddGerritChange(mkChange("a"))
   558  
   559  					je.RemoveGerritChange(mkChange("b"))
   560  				})
   561  				So(jd.HighLevelInfo().GerritChanges(), ShouldResembleProto, []*bbpb.GerritChange{
   562  					mkChange("a"),
   563  				})
   564  			},
   565  		},
   566  	})
   567  }
   568  
   569  func TestGitilesCommit(t *testing.T) {
   570  	t.Parallel()
   571  
   572  	runCases(t, `GitilesChange`, []testCase{
   573  		{
   574  			name:   "nil",
   575  			skipSW: true,
   576  			fn: func(jd *Definition) {
   577  				SoHLEdit(jd, func(je HighLevelEditor) {
   578  					je.GitilesCommit(nil)
   579  				})
   580  				So(jd.HighLevelInfo().GitilesCommit(), ShouldBeNil)
   581  			},
   582  		},
   583  
   584  		{
   585  			name:   "add",
   586  			skipSW: true,
   587  			fn: func(jd *Definition) {
   588  				SoHLEdit(jd, func(je HighLevelEditor) {
   589  					je.GitilesCommit(&bbpb.GitilesCommit{Id: "deadbeef"})
   590  				})
   591  				So(jd.HighLevelInfo().GitilesCommit(), ShouldResembleProto, &bbpb.GitilesCommit{
   592  					Id: "deadbeef",
   593  				})
   594  			},
   595  		},
   596  
   597  		{
   598  			name:   "del",
   599  			skipSW: true,
   600  			fn: func(jd *Definition) {
   601  				SoHLEdit(jd, func(je HighLevelEditor) {
   602  					je.GitilesCommit(&bbpb.GitilesCommit{Id: "deadbeef"})
   603  				})
   604  				SoHLEdit(jd, func(je HighLevelEditor) {
   605  					je.GitilesCommit(nil)
   606  				})
   607  				So(jd.HighLevelInfo().GitilesCommit(), ShouldBeNil)
   608  			},
   609  		},
   610  	})
   611  }
   612  
   613  func TestTaskPayload(t *testing.T) {
   614  	t.Parallel()
   615  
   616  	runCases(t, `TaskPayload`, []testCase{
   617  		{
   618  			name:   "empty",
   619  			skipSW: true,
   620  			fn: func(jd *Definition) {
   621  				SoHLEdit(jd, func(je HighLevelEditor) {
   622  					je.TaskPayloadSource("", "")
   623  					je.TaskPayloadPath("")
   624  					je.TaskPayloadCmd(nil)
   625  				})
   626  				hli := jd.HighLevelInfo()
   627  				pkg, vers := hli.TaskPayloadSource()
   628  				So(pkg, ShouldEqual, "")
   629  				So(vers, ShouldEqual, "")
   630  				So(hli.TaskPayloadPath(), ShouldEqual, "")
   631  				So(hli.TaskPayloadCmd(), ShouldResemble, []string{"luciexe"})
   632  			},
   633  		},
   634  
   635  		{
   636  			name:   "isolate",
   637  			skipSW: true,
   638  			fn: func(jd *Definition) {
   639  				SoHLEdit(jd, func(je HighLevelEditor) {
   640  					je.TaskPayloadSource("", "")
   641  					je.TaskPayloadPath("some/path")
   642  				})
   643  				hli := jd.HighLevelInfo()
   644  				pkg, vers := hli.TaskPayloadSource()
   645  				So(pkg, ShouldEqual, "")
   646  				So(vers, ShouldEqual, "")
   647  				So(hli.TaskPayloadPath(), ShouldEqual, "some/path")
   648  			},
   649  		},
   650  
   651  		{
   652  			name:   "cipd",
   653  			skipSW: true,
   654  			fn: func(jd *Definition) {
   655  				SoHLEdit(jd, func(je HighLevelEditor) {
   656  					je.TaskPayloadSource("pkgname", "latest")
   657  					je.TaskPayloadPath("some/path")
   658  				})
   659  				hli := jd.HighLevelInfo()
   660  				pkg, vers := hli.TaskPayloadSource()
   661  				So(pkg, ShouldEqual, "pkgname")
   662  				So(vers, ShouldEqual, "latest")
   663  				So(hli.TaskPayloadPath(), ShouldEqual, "some/path")
   664  			},
   665  		},
   666  
   667  		{
   668  			name:   "cipd latest",
   669  			skipSW: true,
   670  			fn: func(jd *Definition) {
   671  				SoHLEdit(jd, func(je HighLevelEditor) {
   672  					je.TaskPayloadSource("pkgname", "")
   673  					je.TaskPayloadPath("some/path")
   674  				})
   675  				hli := jd.HighLevelInfo()
   676  				pkg, vers := hli.TaskPayloadSource()
   677  				So(pkg, ShouldEqual, "pkgname")
   678  				So(vers, ShouldEqual, "latest")
   679  				So(hli.TaskPayloadPath(), ShouldEqual, "some/path")
   680  			},
   681  		},
   682  	})
   683  }