go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/tryjob/requirement/diff_test.go (about)

     1  // Copyright 2022 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 requirement
    16  
    17  import (
    18  	"math/rand"
    19  	"testing"
    20  
    21  	"google.golang.org/protobuf/proto"
    22  
    23  	buildbucketpb "go.chromium.org/luci/buildbucket/proto"
    24  	"go.chromium.org/luci/common/clock/testclock"
    25  
    26  	cfgpb "go.chromium.org/luci/cv/api/config/v2"
    27  	"go.chromium.org/luci/cv/internal/tryjob"
    28  
    29  	. "github.com/smartystreets/goconvey/convey"
    30  	. "go.chromium.org/luci/common/testing/assertions"
    31  )
    32  
    33  func TestDiff(t *testing.T) {
    34  	t.Parallel()
    35  
    36  	Convey("Diff works", t, func() {
    37  		makeBBTryjobDefinition := func(host, project, bucket, builder string) *tryjob.Definition {
    38  			return &tryjob.Definition{
    39  				Backend: &tryjob.Definition_Buildbucket_{
    40  					Buildbucket: &tryjob.Definition_Buildbucket{
    41  						Host: host,
    42  						Builder: &buildbucketpb.BuilderID{
    43  							Project: project,
    44  							Bucket:  bucket,
    45  							Builder: builder,
    46  						},
    47  					},
    48  				},
    49  			}
    50  		}
    51  
    52  		Convey("Compare definitions", func() {
    53  			Convey("Empty base and empty target", func() {
    54  				res := Diff(&tryjob.Requirement{}, &tryjob.Requirement{})
    55  				So(res.AddedDefs, ShouldBeEmpty)
    56  				So(res.ChangedDefs, ShouldBeEmpty)
    57  				So(res.RemovedDefs, ShouldBeEmpty)
    58  			})
    59  			Convey("Empty base", func() {
    60  				def := makeBBTryjobDefinition("a.example.com", "infra", "try", "someBuilder")
    61  				res := Diff(
    62  					&tryjob.Requirement{},
    63  					&tryjob.Requirement{
    64  						Definitions: []*tryjob.Definition{def},
    65  					})
    66  				So(res.AddedDefs, ShouldHaveLength, 1)
    67  				So(res.AddedDefs, ShouldContainKey, def)
    68  				So(res.ChangedDefs, ShouldBeEmpty)
    69  				So(res.RemovedDefs, ShouldBeEmpty)
    70  			})
    71  			Convey("Empty target", func() {
    72  				def := makeBBTryjobDefinition("a.example.com", "infra", "try", "someBuilder")
    73  				res := Diff(
    74  					&tryjob.Requirement{
    75  						Definitions: []*tryjob.Definition{def},
    76  					},
    77  					&tryjob.Requirement{})
    78  				So(res.AddedDefs, ShouldBeEmpty)
    79  				So(res.ChangedDefs, ShouldBeEmpty)
    80  				So(res.RemovedDefs, ShouldHaveLength, 1)
    81  				So(res.RemovedDefs, ShouldContainKey, def)
    82  			})
    83  			Convey("Target has one extra", func() {
    84  				shared := makeBBTryjobDefinition("a.example.com", "infra", "try", "someBuilder")
    85  				extra := makeBBTryjobDefinition("a.example.com", "infra", "ci", "someBuilder")
    86  				res := Diff(
    87  					&tryjob.Requirement{
    88  						Definitions: []*tryjob.Definition{shared},
    89  					},
    90  					&tryjob.Requirement{
    91  						Definitions: []*tryjob.Definition{proto.Clone(shared).(*tryjob.Definition), extra},
    92  					})
    93  				So(res.AddedDefs, ShouldHaveLength, 1)
    94  				So(res.AddedDefs, ShouldContainKey, extra)
    95  				So(res.ChangedDefs, ShouldBeEmpty)
    96  				So(res.UnchangedDefs, ShouldHaveLength, 1)
    97  				So(res.UnchangedDefs[shared], ShouldResembleProto, shared)
    98  				So(res.RemovedDefs, ShouldBeEmpty)
    99  			})
   100  			Convey("Target has one removed", func() {
   101  				shared := makeBBTryjobDefinition("a.example.com", "infra", "try", "someBuilder")
   102  				removed := makeBBTryjobDefinition("a.example.com", "infra", "ci", "someBuilder")
   103  				res := Diff(
   104  					&tryjob.Requirement{
   105  						Definitions: []*tryjob.Definition{shared, removed},
   106  					},
   107  					&tryjob.Requirement{
   108  						Definitions: []*tryjob.Definition{proto.Clone(shared).(*tryjob.Definition)},
   109  					})
   110  				So(res.AddedDefs, ShouldBeEmpty)
   111  				So(res.ChangedDefs, ShouldBeEmpty)
   112  				So(res.UnchangedDefs, ShouldHaveLength, 1)
   113  				So(res.UnchangedDefs[shared], ShouldResembleProto, shared)
   114  				So(res.RemovedDefs, ShouldHaveLength, 1)
   115  				So(res.RemovedDefs, ShouldContainKey, removed)
   116  			})
   117  			Convey("Target has one changed", func() {
   118  				baseDef := makeBBTryjobDefinition("a.example.com", "infra", "try", "someBuilder")
   119  				targetDef := proto.Clone(baseDef).(*tryjob.Definition)
   120  				Convey("Change in equivalent", func() {
   121  					targetDef.EquivalentTo = makeBBTryjobDefinition("a.example.com", "infra", "try", "equiBuilder")
   122  				})
   123  				Convey("Change in disable_reuse", func() {
   124  					targetDef.DisableReuse = !baseDef.GetDisableReuse()
   125  				})
   126  				res := Diff(
   127  					&tryjob.Requirement{
   128  						Definitions: []*tryjob.Definition{baseDef},
   129  					},
   130  					&tryjob.Requirement{
   131  						Definitions: []*tryjob.Definition{targetDef},
   132  					})
   133  				So(res.AddedDefs, ShouldBeEmpty)
   134  				So(res.ChangedDefs, ShouldHaveLength, 1)
   135  				So(res.ChangedDefs[baseDef], ShouldResembleProto, targetDef)
   136  				So(res.ChangedDefsReverse, ShouldHaveLength, 1)
   137  				So(res.ChangedDefsReverse[targetDef], ShouldResembleProto, baseDef)
   138  				So(res.RemovedDefs, ShouldBeEmpty)
   139  			})
   140  			Convey("Multiple Definitions", func() {
   141  				builder1 := makeBBTryjobDefinition("a.example.com", "infra", "try", "builder1")
   142  				builder2 := makeBBTryjobDefinition("a.example.com", "infra", "try", "builder2")
   143  				builder3 := makeBBTryjobDefinition("a.example.com", "infra", "try", "builder3")
   144  				builder3Changed := proto.Clone(builder3).(*tryjob.Definition)
   145  				builder3Changed.DisableReuse = !builder3.GetDisableReuse()
   146  				builder4 := makeBBTryjobDefinition("a.example.com", "infra", "try", "builder4")
   147  				builderDiffProj := makeBBTryjobDefinition("a.example.com", "chrome", "try", "builder1")
   148  				builderDiffProjChanged := proto.Clone(builderDiffProj).(*tryjob.Definition)
   149  				builderDiffProjChanged.EquivalentTo = makeBBTryjobDefinition("a.example.com", "chrome", "try", "equi-builder1")
   150  
   151  				base := &tryjob.Requirement{
   152  					Definitions: []*tryjob.Definition{builder1, builder2, builder3, builderDiffProj},
   153  				}
   154  				target := &tryjob.Requirement{
   155  					Definitions: []*tryjob.Definition{proto.Clone(builder1).(*tryjob.Definition), builder3Changed, builder4, builderDiffProjChanged},
   156  				}
   157  				rand.Seed(testclock.TestRecentTimeUTC.Unix())
   158  				rand.Shuffle(len(base.Definitions), func(i, j int) {
   159  					base.Definitions[i], base.Definitions[j] = base.Definitions[j], base.Definitions[i]
   160  				})
   161  				rand.Shuffle(len(target.Definitions), func(i, j int) {
   162  					target.Definitions[i], target.Definitions[j] = target.Definitions[j], target.Definitions[i]
   163  				})
   164  				res := Diff(base, target)
   165  				So(res.AddedDefs, ShouldHaveLength, 1)
   166  				So(res.AddedDefs, ShouldContainKey, builder4)
   167  				So(res.RemovedDefs, ShouldHaveLength, 1)
   168  				So(res.RemovedDefs, ShouldContainKey, builder2)
   169  				So(res.ChangedDefs, ShouldHaveLength, 2)
   170  				So(res.ChangedDefs[builder3], ShouldResembleProto, builder3Changed)
   171  				So(res.ChangedDefs[builderDiffProj], ShouldResembleProto, builderDiffProjChanged)
   172  				So(res.ChangedDefsReverse, ShouldHaveLength, 2)
   173  				So(res.ChangedDefsReverse[builder3Changed], ShouldResembleProto, builder3)
   174  				So(res.ChangedDefsReverse[builderDiffProjChanged], ShouldResembleProto, builderDiffProj)
   175  				So(res.UnchangedDefs, ShouldHaveLength, 1)
   176  				So(res.UnchangedDefs[builder1], ShouldResembleProto, builder1)
   177  			})
   178  		})
   179  
   180  		Convey("Compare retry configuration", func() {
   181  			Convey("Both empty", func() {
   182  				res := Diff(&tryjob.Requirement{}, &tryjob.Requirement{})
   183  				So(res.RetryConfigChanged, ShouldBeFalse)
   184  			})
   185  			Convey("Base nil, target non nil", func() {
   186  				res := Diff(
   187  					&tryjob.Requirement{},
   188  					&tryjob.Requirement{
   189  						RetryConfig: &cfgpb.Verifiers_Tryjob_RetryConfig{
   190  							SingleQuota: 1,
   191  						},
   192  					})
   193  				So(res.RetryConfigChanged, ShouldBeTrue)
   194  			})
   195  			Convey("Base non nil, target nil", func() {
   196  				res := Diff(
   197  					&tryjob.Requirement{
   198  						RetryConfig: &cfgpb.Verifiers_Tryjob_RetryConfig{
   199  							SingleQuota: 1,
   200  						},
   201  					},
   202  					&tryjob.Requirement{})
   203  				So(res.RetryConfigChanged, ShouldBeTrue)
   204  			})
   205  			Convey("Retry config changed", func() {
   206  				res := Diff(
   207  					&tryjob.Requirement{
   208  						RetryConfig: &cfgpb.Verifiers_Tryjob_RetryConfig{
   209  							SingleQuota: 1,
   210  						},
   211  					},
   212  					&tryjob.Requirement{
   213  						RetryConfig: &cfgpb.Verifiers_Tryjob_RetryConfig{
   214  							SingleQuota: 2,
   215  						},
   216  					})
   217  				So(res.RetryConfigChanged, ShouldBeTrue)
   218  			})
   219  		})
   220  	})
   221  }