chore: wire quartz.Clock into Acquirer (#27291)

- Wires quartz.Clock into provisionerdserver.Acquirer
- Allows overriding Acquirer in coderd.Options
- Updates existing tests to use an Acquirer driven by a quartz.Mock 

Before this change `enterprise/coderd/prebuilds` package tests would
take ~60-70s to run.
After this change, it's down to ~10s.

> Generated by Coder agents, massaged by this human.
This commit is contained in:
Cian Johnston
2026-07-20 10:30:02 +01:00
committed by GitHub
parent 9f4ddea571
commit 54fa4a087e
7 changed files with 176 additions and 50 deletions
+29 -2
View File
@@ -23,6 +23,7 @@ import (
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/files"
agplprebuilds "github.com/coder/coder/v2/coderd/prebuilds"
"github.com/coder/coder/v2/coderd/provisionerdserver"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
@@ -131,19 +132,28 @@ func TestClaimPrebuild(t *testing.T) {
// Setup
clock := quartz.NewMock(t)
acquirerClock := quartz.NewMock(t)
clock.Set(dbtime.Now())
ctx := testutil.Context(t, testutil.WaitSuperLong)
db, pubsub := dbtestutil.NewDB(t)
logger := testutil.Logger(t)
acquirer := provisionerdserver.NewAcquirer(
ctx,
logger.Named("acquirer"),
db,
pubsub,
provisionerdserver.WithClock(acquirerClock),
)
spy := newStoreSpy(db, tc.claimingErr)
expectedPrebuildsCount := desiredInstances * presetCount
logger := testutil.Logger(t)
client, _, api, owner := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: spy,
Pubsub: pubsub,
Clock: clock,
Acquirer: acquirer,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
@@ -160,10 +170,18 @@ func TestClaimPrebuild(t *testing.T) {
orgID = secondOrg.ID
}
acquirerTickerTrap := acquirerClock.Trap().NewTicker("acquirer", "backup_poll")
defer acquirerTickerTrap.Close()
provisionerCloser := coderdenttest.NewExternalProvisionerDaemon(t, client, orgID, map[string]string{
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
})
defer provisionerCloser.Close()
acquirerTickerTrap.MustWait(ctx).MustRelease(ctx)
secondAcquirerTickerReady := make(chan struct{})
go func() {
acquirerTickerTrap.MustWait(ctx).MustRelease(ctx)
close(secondAcquirerTickerReady)
}()
cache := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
reconciler := prebuilds.NewStoreReconciler(
@@ -201,12 +219,12 @@ func TestClaimPrebuild(t *testing.T) {
actions, err := reconciler.CalculateActions(ctx, *ps)
require.NoError(t, err)
require.NotNil(t, actions)
require.NoError(t, reconciler.ReconcilePreset(ctx, *ps))
}
// Given: a set of running, eligible prebuilds eventually starts up.
runningPrebuilds := make(map[uuid.UUID]database.GetRunningPrebuiltWorkspacesRow, desiredInstances*presetCount)
advancedAcquirerClock := false
require.Eventually(t, func() bool {
rows, err := spy.GetRunningPrebuiltWorkspaces(ctx)
if err != nil {
@@ -239,6 +257,15 @@ func TestClaimPrebuild(t *testing.T) {
}
}
if !advancedAcquirerClock {
select {
case <-secondAcquirerTickerReady:
acquirerClock.Advance(30 * time.Second).MustWait(ctx)
advancedAcquirerClock = true
default:
}
}
t.Logf("found %d running prebuilds so far, want %d", len(runningPrebuilds), expectedPrebuildsCount)
return len(runningPrebuilds) == expectedPrebuildsCount
+18
View File
@@ -2559,12 +2559,21 @@ func TestPrebuildsAutobuild(t *testing.T) {
// Set the clock to Monday, January 1st, 2024 at 8:00 AM UTC to keep the test deterministic
clock := quartz.NewMock(t)
acquirerClock := quartz.NewMock(t)
clock.Set(time.Date(2024, 1, 1, 8, 0, 0, 0, time.UTC))
acquirerTickerTrap := acquirerClock.Trap().NewTicker("acquirer", "backup_poll")
// Setup
ctx := testutil.Context(t, testutil.WaitSuperLong)
db, pb := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
logger := testutil.Logger(t)
acquirer := provisionerdserver.NewAcquirer(
ctx,
logger.Named("acquirer"),
db,
pb,
provisionerdserver.WithClock(acquirerClock),
)
tickCh := make(chan time.Time)
statsCh := make(chan autobuild.Stats)
notificationsNoop := notifications.NewNoopEnqueuer()
@@ -2576,6 +2585,7 @@ func TestPrebuildsAutobuild(t *testing.T) {
IncludeProvisionerDaemon: true,
AutobuildStats: statsCh,
Clock: clock,
Acquirer: acquirer,
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(
agplUserQuietHoursScheduleStore(),
notificationsNoop,
@@ -2589,6 +2599,10 @@ func TestPrebuildsAutobuild(t *testing.T) {
},
},
})
// The Acquirer creates a fresh backup-poll ticker for the initial idle
// wait and again after completing the template import job. Release both
// so the second ticker exists before the clock advances below.
acquirerTickerTrap.MustWait(ctx).MustRelease(ctx)
// Setup Prebuild reconciler
cache := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
@@ -2620,8 +2634,12 @@ func TestPrebuildsAutobuild(t *testing.T) {
require.NoError(t, err)
require.Len(t, presets, 1)
acquirerTickerTrap.MustWait(ctx).MustRelease(ctx)
acquirerTickerTrap.Close()
// Given: reconciliation loop runs and starts prebuilt workspace in failed state
runReconciliationLoop(t, ctx, db, reconciler, presets)
acquirerClock.Advance(30 * time.Second).MustWait(ctx)
var failedWorkspaceBuilds []database.GetFailedWorkspaceBuildsByTemplateIDRow
require.Eventually(t, func() bool {
rows, err := db.GetFailedWorkspaceBuildsByTemplateID(ctx, database.GetFailedWorkspaceBuildsByTemplateIDParams{