go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/tryjob/util_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 tryjob
    16  
    17  import (
    18  	"context"
    19  	"slices"
    20  	"testing"
    21  	"time"
    22  
    23  	"go.chromium.org/luci/common/clock"
    24  	"go.chromium.org/luci/gae/service/datastore"
    25  
    26  	"go.chromium.org/luci/cv/internal/common"
    27  	"go.chromium.org/luci/cv/internal/cvtesting"
    28  
    29  	. "github.com/smartystreets/goconvey/convey"
    30  	. "go.chromium.org/luci/common/testing/assertions"
    31  )
    32  
    33  func TestSaveTryjobs(t *testing.T) {
    34  	t.Parallel()
    35  
    36  	Convey("SaveTryjobs works", t, func() {
    37  		ct := cvtesting.Test{}
    38  		ctx, cancel := ct.SetUp(t)
    39  		defer cancel()
    40  		const bbHost = "buildbucket.example.com"
    41  		now := ct.Clock.Now().UTC()
    42  		var runID = common.MakeRunID("infra", now.Add(-1*time.Hour), 1, []byte("foo"))
    43  		var notified map[common.RunID]common.TryjobIDs
    44  		notifyFn := func(ctx context.Context, runID common.RunID, events *TryjobUpdatedEvents) error {
    45  			if notified == nil {
    46  				notified = make(map[common.RunID]common.TryjobIDs)
    47  			}
    48  			for _, evt := range events.Events {
    49  				notified[runID] = append(notified[runID], common.TryjobID(evt.TryjobId))
    50  			}
    51  			return nil
    52  		}
    53  
    54  		Convey("No internal ID", func() {
    55  			Convey("No external ID", func() {
    56  				tj := &Tryjob{
    57  					EVersion:         1,
    58  					EntityCreateTime: now,
    59  					EntityUpdateTime: now,
    60  					LaunchedBy:       runID,
    61  				}
    62  				So(datastore.RunInTransaction(ctx, func(ctx context.Context) error {
    63  					return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn)
    64  				}, nil), ShouldBeNil)
    65  				So(tj.ID, ShouldNotBeEmpty)
    66  				So(notified, ShouldResemble, map[common.RunID]common.TryjobIDs{
    67  					runID: {tj.ID},
    68  				})
    69  			})
    70  			Convey("External ID provided", func() {
    71  				Convey("Does not map to any existing tryjob", func() {
    72  					eid := MustBuildbucketID(bbHost, 10)
    73  					tj := &Tryjob{
    74  						ExternalID:       eid,
    75  						EVersion:         1,
    76  						EntityCreateTime: now,
    77  						EntityUpdateTime: now,
    78  						LaunchedBy:       runID,
    79  					}
    80  					So(datastore.RunInTransaction(ctx, func(ctx context.Context) error {
    81  						return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn)
    82  					}, nil), ShouldBeNil)
    83  					tj, err := eid.Load(ctx)
    84  					So(err, ShouldBeNil)
    85  					So(tj, ShouldNotBeNil)
    86  					So(notified, ShouldResemble, map[common.RunID]common.TryjobIDs{
    87  						runID: {tj.ID},
    88  					})
    89  				})
    90  				Convey("Map to an existing tryjob", func() {
    91  					eid := MustBuildbucketID(bbHost, 10)
    92  					eid.MustCreateIfNotExists(ctx)
    93  					tj := &Tryjob{
    94  						ExternalID:       eid,
    95  						EVersion:         1,
    96  						EntityCreateTime: now,
    97  						EntityUpdateTime: now,
    98  						LaunchedBy:       runID,
    99  					}
   100  					So(datastore.RunInTransaction(ctx, func(ctx context.Context) error {
   101  						return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn)
   102  					}, nil), ShouldErrLike, "has already mapped to internal id")
   103  					So(notified, ShouldBeEmpty)
   104  				})
   105  			})
   106  		})
   107  
   108  		Convey("Internal ID provided", func() {
   109  			const tjID = common.TryjobID(45366)
   110  			Convey("No external ID", func() {
   111  				tj := &Tryjob{
   112  					ID:               tjID,
   113  					EVersion:         1,
   114  					EntityCreateTime: now,
   115  					EntityUpdateTime: now,
   116  					ReusedBy:         common.RunIDs{runID},
   117  				}
   118  				So(datastore.RunInTransaction(ctx, func(ctx context.Context) error {
   119  					return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn)
   120  				}, nil), ShouldBeNil)
   121  				So(notified, ShouldResemble, map[common.RunID]common.TryjobIDs{
   122  					runID: {tjID},
   123  				})
   124  			})
   125  			Convey("External ID provided", func() {
   126  				Convey("Does not map to any existing tryjob", func() {
   127  					eid := MustBuildbucketID(bbHost, 10)
   128  					tj := &Tryjob{
   129  						ID:               tjID,
   130  						ExternalID:       eid,
   131  						EVersion:         1,
   132  						EntityCreateTime: now,
   133  						EntityUpdateTime: now,
   134  						ReusedBy:         common.RunIDs{runID},
   135  					}
   136  					So(datastore.RunInTransaction(ctx, func(ctx context.Context) error {
   137  						return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn)
   138  					}, nil), ShouldBeNil)
   139  					So(eid.MustLoad(ctx).ID, ShouldEqual, tjID)
   140  					So(notified, ShouldResemble, map[common.RunID]common.TryjobIDs{
   141  						runID: {tjID},
   142  					})
   143  				})
   144  
   145  				Convey("Map to an existing tryjob", func() {
   146  					Convey("existing tryjob has the same ID", func() {
   147  						eid := MustBuildbucketID(bbHost, 10)
   148  						existing := eid.MustCreateIfNotExists(ctx)
   149  						ct.Clock.Add(10 * time.Minute)
   150  						tj := &Tryjob{
   151  							ID:               existing.ID,
   152  							ExternalID:       eid,
   153  							EVersion:         existing.EVersion + 1,
   154  							EntityUpdateTime: datastore.RoundTime(clock.Now(ctx).UTC()),
   155  							ReusedBy:         common.RunIDs{runID},
   156  						}
   157  						So(datastore.RunInTransaction(ctx, func(ctx context.Context) error {
   158  							return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn)
   159  						}, nil), ShouldBeNil)
   160  						So(eid.MustLoad(ctx).EVersion, ShouldEqual, existing.EVersion+1)
   161  						So(notified, ShouldResemble, map[common.RunID]common.TryjobIDs{
   162  							runID: {tj.ID},
   163  						})
   164  					})
   165  					Convey("existing tryjob has a different ID", func() {
   166  						eid := MustBuildbucketID(bbHost, 10)
   167  						existing := eid.MustCreateIfNotExists(ctx)
   168  						ct.Clock.Add(10 * time.Minute)
   169  						tj := &Tryjob{
   170  							ID:               existing.ID + 10,
   171  							ExternalID:       eid,
   172  							EVersion:         existing.EVersion + 1,
   173  							EntityUpdateTime: datastore.RoundTime(clock.Now(ctx).UTC()),
   174  							ReusedBy:         common.RunIDs{runID},
   175  						}
   176  						So(datastore.RunInTransaction(ctx, func(ctx context.Context) error {
   177  							return SaveTryjobs(ctx, []*Tryjob{tj}, notifyFn)
   178  						}, nil), ShouldErrLike, "has already mapped to")
   179  						So(notified, ShouldBeEmpty)
   180  					})
   181  				})
   182  			})
   183  		})
   184  	})
   185  }
   186  
   187  func TestQueryTryjobIDsUpdatedBefore(t *testing.T) {
   188  	t.Parallel()
   189  
   190  	Convey("QueryTryjobIDsUpdatedBefore", t, func() {
   191  		ct := cvtesting.Test{}
   192  		ctx, cancel := ct.SetUp(t)
   193  		defer cancel()
   194  
   195  		nextBuildID := int64(1)
   196  		createNTryjobs := func(n int) []*Tryjob {
   197  			tryjobs := make([]*Tryjob, n)
   198  			for i := range tryjobs {
   199  				eid := MustBuildbucketID("example.com", nextBuildID)
   200  				nextBuildID++
   201  				tryjobs[i] = eid.MustCreateIfNotExists(ctx)
   202  			}
   203  			return tryjobs
   204  		}
   205  
   206  		var allTryjobs []*Tryjob
   207  		allTryjobs = append(allTryjobs, createNTryjobs(1000)...)
   208  		ct.Clock.Add(1 * time.Minute)
   209  		allTryjobs = append(allTryjobs, createNTryjobs(1000)...)
   210  		ct.Clock.Add(1 * time.Minute)
   211  		allTryjobs = append(allTryjobs, createNTryjobs(1000)...)
   212  
   213  		before := ct.Clock.Now().Add(-30 * time.Second)
   214  		var expected common.TryjobIDs
   215  		for _, tj := range allTryjobs {
   216  			if tj.EntityUpdateTime.Before(before) {
   217  				expected = append(expected, tj.ID)
   218  			}
   219  		}
   220  		slices.Sort(expected)
   221  
   222  		actual, err := QueryTryjobIDsUpdatedBefore(ctx, before)
   223  		So(err, ShouldBeNil)
   224  		So(actual, ShouldResemble, expected)
   225  	})
   226  }