blob: fb4c58a78570b8a4dc2fd719510fc26263ff00e2 [file] [log] [blame]
// Copyright 2021 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package e2e
import (
"fmt"
"sort"
"testing"
"time"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
cvbqpb "go.chromium.org/luci/cv/api/bigquery/v1"
cfgpb "go.chromium.org/luci/cv/api/config/v2"
migrationpb "go.chromium.org/luci/cv/api/migration"
"go.chromium.org/luci/cv/internal/configs/prjcfg/prjcfgtest"
gf "go.chromium.org/luci/cv/internal/gerrit/gerritfake"
"go.chromium.org/luci/cv/internal/run"
"go.chromium.org/luci/cv/internal/run/runtest"
"go.chromium.org/luci/gae/service/datastore"
. "github.com/smartystreets/goconvey/convey"
)
func TestSubmissionObeySubmitOptions(t *testing.T) {
t.Parallel()
Convey("Burst requests to sumbit", t, func() {
ct := Test{}
ctx, cancel := ct.SetUp()
defer cancel()
const lProject = "infra"
const gHost = "g-review"
const gRepo = "re/po"
const gRef = "refs/heads/main"
const burstN = 20
cfg := MakeCfgSingular("cg0", gHost, gRepo, gRef)
maxBurst := 2
burstDelay := 10 * time.Second
cfg.SubmitOptions = &cfgpb.SubmitOptions{
MaxBurst: int32(maxBurst),
BurstDelay: durationpb.New(burstDelay),
}
prjcfgtest.Create(ctx, lProject, cfg)
So(ct.PMNotifier.UpdateConfig(ctx, lProject), ShouldBeNil)
// burstN CLs request FULL_RUN at the same time.
for gChange := 1; gChange <= burstN; gChange++ {
ct.GFake.AddFrom(gf.WithCIs(gHost, gf.ACLRestricted(lProject), gf.CI(
gChange, gf.Project(gRepo), gf.Ref(gRef),
gf.Owner("user-1"),
gf.CQ(+2, ct.Clock.Now(), gf.U("user-2")),
gf.Approve(),
gf.Updated(ct.Clock.Now()),
)))
}
// Only a committer can trigger a FullRun for someone else' CL.
ct.AddCommitter("user-2")
// Start CQDaemon and make it succeed the Run immediately.
ct.MustCQD(ctx, lProject).SetVerifyClbk(
func(r *migrationpb.ReportedRun) *migrationpb.ReportedRun {
r = proto.Clone(r).(*migrationpb.ReportedRun)
r.Attempt.Status = cvbqpb.AttemptStatus_SUCCESS
r.Attempt.Substatus = cvbqpb.AttemptSubstatus_NO_SUBSTATUS
return r
},
)
ct.LogPhase(ctx, fmt.Sprintf("CV starts and creates %d Runs", burstN))
ct.RunUntilT(ctx, burstN*5 /* ~5 tasks per Run */, func() bool {
return len(ct.LoadRunsOf(ctx, lProject)) == burstN
})
ct.LogPhase(ctx, fmt.Sprintf("CV successfully submitts %d Runs", burstN))
remaining := ct.LoadRunsOf(ctx, lProject)
ct.RunUntilT(ctx, burstN*25 /* ~25 tasks per Run */, func() bool {
switch err := datastore.Get(ctx, remaining); {
case err != nil:
panic(err)
default:
remaining = runtest.FilterNot(run.Status_SUCCEEDED, remaining...)
return len(remaining) == 0
}
})
var submittedTimes []time.Time
for gChange := 1; gChange <= burstN; gChange++ {
c := ct.GFake.GetChange(gHost, gChange)
// last updated time of a CL should be submitted time.
submittedTimes = append(submittedTimes, c.Info.GetUpdated().AsTime())
}
sort.Slice(submittedTimes, func(i, j int) bool {
return submittedTimes[i].Before(submittedTimes[j])
})
for i := 0; i < burstN-maxBurst; i++ {
So(submittedTimes[i+maxBurst], ShouldHappenOnOrAfter, submittedTimes[i].Add(burstDelay))
}
})
}