Skip to content

chore(coderd/database/dbfake): add support for provisioner job timestamp control - #21944

Merged
johnstcn merged 20 commits into
mainfrom
test-infra-8tk7
Feb 6, 2026
Merged

johnstcn merged 20 commits into
mainfrom
test-infra-8tk7

Conversation

@johnstcn

@johnstcn johnstcn commented Feb 5, 2026 •

Copy link
Copy Markdown
Member

Motivation: reduce dbgen boilerplate when creating tests that depend on specific pieces of provisioner job timing.

Relates to #21922 / coder/internal#1259

Original API design:

Extends dbfake.WorkspaceBuildBuilder with timestamp and status control methods

  • Adds an optional timestamp to Starting(), which sets the provisioner job start and update time.
  • Adds an optional timestamp to Pending(), which sets provisioner job create and update time
  • Adds an optional timestamp to Canceled(), which sets provisioner job complete and cancel time
  • Adds method Failed() which sets a job to the failed state with a given error message / error code, and optionally allows setting job complete time.
  • Adds method Succeeded() which allows setting the completed at time of the job.

Updated after review feedback:

  • Adds dbfake.BuilderOption func(*WorkspaceBuildBuilder)
  • Adds BuilderOption methods for setting various provisioner job related fields on WorkspaceBuildBuilder.
  • Migrates a number of existing tests that previously dependeded on provisioner job timing to use these updated methods in the following packages:
    • coderd/jobreaper
    • coderd/notifications/reports
    • enterprise/coderd/schedule
    • enterprise/coderd/prebuilds
    • scripts/workspace-runtime-audit

🤖 Created using Mux (Opus 4.5)

Add a Failed() method to WorkspaceBuildBuilder that sets the provisioner
job to a failed state with the given error message and error code. This
allows tests to create failed workspace builds using the builder pattern
instead of manually creating provisioner jobs with dbgen.

Migrate coderd/notifications/reports/generator_internal_test.go to use
the new dbfake.WorkspaceBuild builder with:
- Failed() method for failed builds
- WithJobCompletedAt() method for timestamp control

This simplifies test setup by consolidating workspace build and
provisioner job creation into a single fluent builder call.
Migrate TestTemplateUpdateBuildDeadlines to use the new
WorkspaceBuildBuilder timestamp control methods instead of manual
AcquireProvisionerJob + UpdateProvisionerJobWithCompleteByID calls.

This simplifies test setup by using:
- dbfake.WorkspaceBuild() to create workspace, job, and build together
- .WithJobCompletedAt(buildTime) to set the job completion timestamp
- .Seed() to pass custom build parameters like ProvisionerState

The second test (TestTemplateUpdateBuildDeadlinesSkip) was not migrated
because it requires failed build states which the builder doesn't
currently support.
…p control

Replace custom withCreatedAt helper pattern with the standard
dbfake.WorkspaceBuildBuilder.WithJobCreatedAt() method in
TestExpiredPrebuildsMultipleActions.

Changes:
- Replace setupTestDBPrebuild call with dbfake.WorkspaceBuild() when
  timestamp control is needed for expiration testing
- Remove prebuildOptions struct, prebuildOption type, and withCreatedAt
  helper function (no longer needed)
- Remove opts parameter from setupTestDBPrebuild and setupTestDBWorkspace
  functions

This standardizes timestamp control in tests to use the new builder
methods available in dbfake.WorkspaceBuildBuilder.
… timestamp methods

This commit migrates the workspace runtime audit tests to use the
WorkspaceBuildBuilder's new timestamp control methods instead of manually
creating provisioner jobs with explicit timestamps.

Changes:
- Add Failed() method to WorkspaceBuildBuilder for failed job status
- Add ProvisionerJobStatusFailed case handling in builder's switch
- Refactor createWorkspace() to use dbfake.WorkspaceBuild with
  WithJobCreatedAt/WithJobCompletedAt methods
- Remove redundant 'canceled' field from workspaceBuildArgs struct
- Remove unused 'database/sql' import

The new approach simplifies test setup while preserving existing behavior.
All tests continue to pass.
…spaceBuildBuilder

Migrate the following tests to use the new dbfake.WorkspaceBuildBuilder
timestamp control methods instead of manual dbgen.ProvisionerJob setup:

- TestDetectorHungWorkspaceBuild
- TestDetectorHungWorkspaceBuildNoOverrideState
- TestDetectorHungWorkspaceBuildNoOverrideStateIfNoExistingBuild
- TestDetectorPendingWorkspaceBuildNoOverrideStateIfNoExistingBuild
- TestDetectorWorkspaceBuildForDormantWorkspace

This significantly reduces boilerplate while making the test setup clearer.
The builder handles all the necessary relationships automatically.
This method allows setting the UpdatedAt field separately from StartedAt
when creating running jobs. This is essential for testing hung job
detection where the detector checks UpdatedAt to determine if a job
has been idle for too long.

The AcquireProvisionerJob query sets UpdatedAt = StartedAt, so this
method updates UpdatedAt after job acquisition to enable the necessary
test scenario.
Address review feedback on WorkspaceBuildBuilder migration:

1. Use correct timestamps for hung job tests: the original tests had
   CreatedAt=tenMinAgo, StartedAt=tenMinAgo, UpdatedAt=sixMinAgo.
   The hung job detector checks UpdatedAt to determine if a job is
   hung, so these values must match the original test behavior.

2. Set DormantAt directly in workspace seed instead of using post-hoc
   UpdateWorkspaceDormantDeletingAt call. This simplifies the test
   and eliminates the need to refetch the workspace.
Remove ineffassign warning by eliminating unused workspace assignment
and using buildResp.Build directly instead of creating an alias.
Address review feedback on the WorkspaceBuildBuilder timestamp migration:

1. Remove unnecessary comment in runtimeaudit_test.go (line 383)

2. Remove unnecessary comment in reconcile_test.go (lines 1833-1834)

3. Remove unnecessary alias in reconcile_test.go (line 1850)
   Use `resp.Workspace` directly instead of aliasing to `workspace`.

4. Simplify API by accepting variadic time.Time in status methods:
   - Starting(ts ...time.Time): timestamp used for StartedAt
   - Pending(ts ...time.Time): timestamp used for CreatedAt
   - Canceled(ts ...time.Time): timestamp used for CompletedAt
   - Failed(jobError, jobErrorCode string, ts ...time.Time): timestamp used for CompletedAt

5. Remove WithJobCreatedAt and WithJobStartedAt methods

6. Keep WithJobUpdatedAt for hung job tests where UpdatedAt != StartedAt

7. Keep WithJobCompletedAt for succeeded builds that need completion timestamps

8. Job CreatedAt now defaults to workspace CreatedAt when not explicitly set,
   which allows expiration tests to work without needing WithJobCreatedAt.

All callers updated to use the simplified API:
- Starting().WithJobStartedAt(t) → Starting(t)
- Pending().WithJobCreatedAt(t) → Pending(t)
- Canceled().WithJobCompletedAt(t) → Canceled(t)
- Failed(...).WithJobCompletedAt(t) → Failed(..., t)
…ary comments

- Rename WithJobUpdatedAt to JobUpdatedAt
- Rename WithJobCompletedAt to JobCompletedAt
- Remove unnecessary comments in doInTX()
- Add Succeeded(ts ...time.Time) for consistency with other status methods
- Remove JobCompletedAt() as Succeeded() now handles this case
- Fix Canceled() comment to mention both CanceledAt and CompletedAt
@johnstcn johnstcn self-assigned this Feb 5, 2026
@johnstcn johnstcn changed the title chore(dbfake): Add support for provisioner job timestamp control chore(dbfake): add support for provisioner job timestamp control Feb 5, 2026
@johnstcn johnstcn changed the title chore(dbfake): add support for provisioner job timestamp control chore(coderd/database/dbfake): add support for provisioner job timestamp control Feb 5, 2026
@johnstcn
johnstcn requested review from Emyrk and mafredri February 5, 2026 11:22
@johnstcn
johnstcn marked this pull request as ready for review February 5, 2026 11:22
Copilot AI review requested due to automatic review settings February 5, 2026 11:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the dbfake.WorkspaceBuildBuilder with methods for controlling provisioner job timestamps and statuses, reducing boilerplate when creating test workspaces with specific timing requirements. The changes simplify test code by replacing manual provisioner job creation and manipulation with a cleaner builder API.

Changes:

  • Extended WorkspaceBuildBuilder with timestamp control methods (Starting(), Pending(), Canceled(), Succeeded(), Failed(), JobUpdatedAt())
  • Migrated tests in 5 packages to use the new builder methods instead of manually creating provisioner jobs with dbgen
  • Removed unused helper functions and options that are now obsolete with the new API

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
coderd/database/dbfake/dbfake.go Core implementation adding timestamp and status control to WorkspaceBuildBuilder
coderd/jobreaper/detector_test.go Migrated 5 test cases to use new builder methods for creating hung/pending jobs
coderd/notifications/reports/generator_internal_test.go Replaced manual job creation with builder methods for failed/succeeded builds
enterprise/coderd/schedule/template_test.go Simplified workspace build creation using new Succeeded() method
enterprise/coderd/prebuilds/reconcile_test.go Updated prebuild creation to use builder API and removed obsolete helper options
scripts/workspace-runtime-audit/runtimeaudit_test.go Refactored workspace build creation to use builder pattern with status methods

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread coderd/database/dbfake/dbfake.go Resolved

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a solid improvement 👍🏻.

Although I'm wondering if we should just introduce:

func (b WorkspaceBuildBuilder) UpdateJob(job database.ProvisionerJob)

For more advanced use-cases.

Think:

b.
	Failed(error, code).
	UpdateJob(database.ProvisionerJob{CompletedAt: ts})

Or a less-generic approach:

b.
	Failed(error, code).
	CompletedAt(ts)

And respective methods for StartedAt/CreatedAt/UpdatedAt.

Comment thread coderd/database/dbfake/dbfake.go Resolved
Comment thread coderd/database/dbfake/dbfake.go Resolved
@johnstcn

johnstcn commented Feb 5, 2026

Copy link
Copy Markdown
Member Author

@mafredri

Although I'm wondering if we should just introduce:
func (b WorkspaceBuildBuilder) UpdateJob(job database.ProvisionerJob)
For more advanced use-cases.

I'm down for that. Alternatively, WDYT about

func (b WorkspaceBuildBuilder) UpdateJob(func(j *database.ProvisionerJob))

@mafredri

mafredri commented Feb 5, 2026

Copy link
Copy Markdown
Member

I think the alternative raises the question of when does the callback function run. 🤔

Do you have a use-case in mind where the cb method provides an edge?

@johnstcn

johnstcn commented Feb 5, 2026

Copy link
Copy Markdown
Member Author

@mafredri I think the alternative raises the question of when does the callback function run. 🤔

Do you have a use-case in mind where the cb method provides an edge?

Yeah, good point. Your proposed API is more congruent with the existing usage of dbgen.

Replace variadic timestamps in WorkspaceBuildBuilder status methods
(Starting, Pending, Canceled, Failed, Succeeded) and remove JobUpdatedAt
method in favor of a single UpdateJob(database.ProvisionerJob) method.

This provides a cleaner API for customizing provisioner job fields by
allowing callers to specify any combination of job field overrides using
the ProvisionerJob struct.

Changes:
- Remove variadic timestamp parameters from status methods
- Remove JobUpdatedAt method
- Add jobOverrides field to WorkspaceBuildBuilder struct
- Add UpdateJob method that accepts database.ProvisionerJob
- Add takeFirstTime helper function for time.Time zero-value handling
- Update doInTX to apply non-zero overrides after job creation
- Update all test callers to use the new API
@Emyrk

Emyrk commented Feb 5, 2026

Copy link
Copy Markdown
Member

@mafredri

Although I'm wondering if we should just introduce:
func (b WorkspaceBuildBuilder) UpdateJob(job database.ProvisionerJob)
For more advanced use-cases.

I'm down for that. Alternatively, WDYT about

func (b WorkspaceBuildBuilder) UpdateJob(func(j *database.ProvisionerJob))

This feels the easiest to implement.

I think the alternative raises the question of when does the callback function run. 🤔

I would assume at do time. The more accurate method definition is the insert params.

func (b WorkspaceBuildBuilder) UpdateJob(func(j *database.InsertProvisionerJobParams))

We can keep it as database.ProvisionerJob, and do the takefirst magic we do in dbgen. More boiler plate, but sometimes a bit nicer than knowing when to use the insert param type.

So the callbacks would be used above this line to make those insert params:

job, err := b.db.InsertProvisionerJob(ownerCtx, database.InsertProvisionerJobParams{

@johnstcn

johnstcn commented Feb 5, 2026

Copy link
Copy Markdown
Member Author

Part of the issue with allowing passing in an entire ProvisionerJob or func(*ProvisionerJob) is that you can request a particular ProvisionerJobStatus while overriding those fields from which the status is computed (CanceledAt, etc.), potentially leading to a contradiction.

Remove the jobOverrides field and UpdateJob method from
WorkspaceBuildBuilder. Add individual timestamp fields
(jobCreatedAt, jobStartedAt, jobUpdatedAt, jobCompletedAt) and
functional BuilderOption type with WithJob* option functions.

Status methods (Starting, Pending, Canceled, Succeeded, Failed) now
accept variadic BuilderOption to set job timestamps directly:

  .Succeeded(dbfake.WithJobCompletedAt(twentyMinAgo))
  .Starting(dbfake.WithJobStartedAt(tenMinAgo), dbfake.WithJobUpdatedAt(sixMinAgo))
  .Failed(error, code, dbfake.WithJobCompletedAt(now))

This eliminates the dual source-of-truth between jobStatus and
jobOverrides.JobStatus that existed before.
Update the 4 remaining callers of the removed UpdateJob method to use
the new functional options pattern (WithJobCompletedAt). Also removes
unused 'database/sql' import from runtimeaudit_test.go.
Add WithJobError and WithJobErrorCode BuilderOption functions and change
Failed() to only accept ...BuilderOption instead of explicit jobError and
jobErrorCode string parameters. This makes the API more consistent with
other builder methods where all customization is done via options.

The default error message 'failed' is still used when no WithJobError
option is provided.
@Emyrk

Emyrk commented Feb 5, 2026

Copy link
Copy Markdown
Member

Part of the issue with allowing passing in an entire ProvisionerJob or func(*ProvisionerJob) is that you can request a particular ProvisionerJobStatus while overriding those fields from which the status is computed (CanceledAt, etc.), potentially leading to a contradiction.

good point

@Emyrk Emyrk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most straight forward 👍

Is there any value to setting the times without the status? The only thing I see is you need to do a .Pending/Succeeded/Etc to set the times.

not a big deal though

@johnstcn

johnstcn commented Feb 5, 2026

Copy link
Copy Markdown
Member Author

Is there any value to setting the times without the status? The only thing I see is you need to do a .Pending/Succeeded/Etc to set the times.

I can't think of a reason to do that, apart from if you're testing something really weird.

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

Just for completeness, I'm going to document one last idea that came to my mind. Do with it what you will 😄:

dbfake.JobBuilder().CreatedAt(ts).Error("foo").Do()

Transition: b.transition,
InitiatorID: s.usr.ID,
}).
Succeeded(dbfake.WithJobCompletedAt(b.at))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach turned out pretty good IMO! 👍🏻

@johnstcn
johnstcn merged commit 25a0c80 into main Feb 6, 2026
27 checks passed
@johnstcn
johnstcn deleted the test-infra-8tk7 branch February 6, 2026 09:44
@johnstcn

johnstcn commented Feb 6, 2026

Copy link
Copy Markdown
Member Author

Filed coder/internal#1344 for the dbfake.JobBuilder idea 👍

@github-actions github-actions Bot locked and limited conversation to collaborators Feb 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants