From c2dfd63894b40e843ce0b6a742454cc4a8779f1b Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Tue, 10 Mar 2026 17:28:01 +0100 Subject: [PATCH 1/2] Use nowMilli() in testserver to prevent flaky timestamp collisions (#4693) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Replace `time.Now().UnixMilli()` with `nowMilli()` in testserver fake implementations (pipelines, catalogs, external locations, registered models) to guarantee strictly increasing millisecond timestamps - Add a `ruleguard` lint rule to prevent `time.Now().UnixMilli()` in `libs/testserver/` (except `fake_workspace.go` where the helper is defined) - Fix `CreatedAt`/`UpdatedAt` in create handlers to use the same timestamp value, matching real API behavior Fixes flaky test `TestAccept/bundle/resource_deps/pipelines_recreate/DATABRICKS_BUNDLE_ENGINE=direct` where a pipeline's `last_modified` and a job's `created_time` could collide to the same millisecond, causing `[UNIX_TIME_MILLIS][0]` vs `[UNIX_TIME_MILLIS][1]` index mismatch in test output. Example failure: https://github.com/databricks/cli/actions/runs/22710298364/job/65846540329 ## Test plan - [x] `TestAccept/bundle/resource_deps/pipelines_recreate` passes consistently (verified with `-count=3`) - [x] `make lintfull` passes with 0 issues - [x] `make test` passes (template test failures are pre-existing and unrelated) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 --- libs/gorules/rule_time_now_in_testserver.go | 14 ++++++++++++++ libs/testserver/catalogs.go | 7 +++---- libs/testserver/external_locations.go | 7 +++---- libs/testserver/pipelines.go | 3 +-- libs/testserver/registered_models.go | 7 +++---- 5 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 libs/gorules/rule_time_now_in_testserver.go diff --git a/libs/gorules/rule_time_now_in_testserver.go b/libs/gorules/rule_time_now_in_testserver.go new file mode 100644 index 0000000000..ac20fb8e42 --- /dev/null +++ b/libs/gorules/rule_time_now_in_testserver.go @@ -0,0 +1,14 @@ +package gorules + +import "github.com/quasilyte/go-ruleguard/dsl" + +// NoTimeNowUnixMilliInTestServer forbids direct time.Now().UnixMilli() calls in libs/testserver. +// Use nowMilli() instead to guarantee unique, strictly increasing timestamps. +// Integer millisecond timestamps get indexed replacements in test output (e.g. [UNIX_TIME_MILLIS][0]) +// and collisions between resources cause flaky tests. +func NoTimeNowUnixMilliInTestServer(m dsl.Matcher) { + m.Match(`time.Now().UnixMilli()`). + Where(m.File().PkgPath.Matches(`.*/libs/testserver`) && + !m.File().Name.Matches(`fake_workspace\.go$`)). + Report(`Use nowMilli() instead of time.Now().UnixMilli() in testserver to ensure unique timestamps`) +} diff --git a/libs/testserver/catalogs.go b/libs/testserver/catalogs.go index bd9598d600..859721ee73 100644 --- a/libs/testserver/catalogs.go +++ b/libs/testserver/catalogs.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/http" - "time" "github.com/databricks/databricks-sdk-go/service/catalog" ) @@ -29,14 +28,14 @@ func (s *FakeWorkspace) CatalogsCreate(req Request) Response { Options: createRequest.Options, Properties: createRequest.Properties, FullName: createRequest.Name, - CreatedAt: time.Now().UnixMilli(), + CreatedAt: nowMilli(), CreatedBy: s.CurrentUser().UserName, - UpdatedAt: time.Now().UnixMilli(), UpdatedBy: s.CurrentUser().UserName, MetastoreId: nextUUID(), Owner: s.CurrentUser().UserName, CatalogType: catalog.CatalogTypeManagedCatalog, } + catalogInfo.UpdatedAt = catalogInfo.CreatedAt s.Catalogs[createRequest.Name] = catalogInfo return Response{ @@ -79,7 +78,7 @@ func (s *FakeWorkspace) CatalogsUpdate(req Request, name string) Response { name = updateRequest.NewName } - existing.UpdatedAt = time.Now().UnixMilli() + existing.UpdatedAt = nowMilli() existing.UpdatedBy = s.CurrentUser().UserName s.Catalogs[name] = existing diff --git a/libs/testserver/external_locations.go b/libs/testserver/external_locations.go index 0606ad76e5..b0000deb2a 100644 --- a/libs/testserver/external_locations.go +++ b/libs/testserver/external_locations.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/http" - "time" "github.com/databricks/databricks-sdk-go/service/catalog" ) @@ -37,13 +36,13 @@ func (s *FakeWorkspace) ExternalLocationsCreate(req Request) Response { Fallback: createRequest.Fallback, EncryptionDetails: createRequest.EncryptionDetails, FileEventQueue: createRequest.FileEventQueue, - CreatedAt: time.Now().UnixMilli(), + CreatedAt: nowMilli(), CreatedBy: s.CurrentUser().UserName, - UpdatedAt: time.Now().UnixMilli(), UpdatedBy: s.CurrentUser().UserName, MetastoreId: nextUUID(), Owner: s.CurrentUser().UserName, } + locationInfo.UpdatedAt = locationInfo.CreatedAt s.ExternalLocations[createRequest.Name] = locationInfo return Response{ @@ -95,7 +94,7 @@ func (s *FakeWorkspace) ExternalLocationsUpdate(req Request, name string) Respon name = updateRequest.NewName } - existing.UpdatedAt = time.Now().UnixMilli() + existing.UpdatedAt = nowMilli() existing.UpdatedBy = s.CurrentUser().UserName s.ExternalLocations[name] = existing diff --git a/libs/testserver/pipelines.go b/libs/testserver/pipelines.go index 763a38be84..a6ce25b022 100644 --- a/libs/testserver/pipelines.go +++ b/libs/testserver/pipelines.go @@ -3,7 +3,6 @@ package testserver import ( "encoding/json" "fmt" - "time" "github.com/databricks/databricks-sdk-go/service/pipelines" ) @@ -41,7 +40,7 @@ func (s *FakeWorkspace) PipelineCreate(req Request) Response { pipelineId := nextUUID() r.PipelineId = pipelineId r.CreatorUserName = "tester@databricks.com" - r.LastModified = time.Now().UnixMilli() + r.LastModified = nowMilli() r.Name = r.Spec.Name r.RunAsUserName = "tester@databricks.com" r.State = "IDLE" diff --git a/libs/testserver/registered_models.go b/libs/testserver/registered_models.go index 74815ae7a8..e3723a95e9 100644 --- a/libs/testserver/registered_models.go +++ b/libs/testserver/registered_models.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/http" - "time" "github.com/databricks/databricks-sdk-go/service/catalog" ) @@ -30,13 +29,13 @@ func (s *FakeWorkspace) RegisteredModelsCreate(req Request) Response { SchemaName: createRequest.SchemaName, StorageLocation: createRequest.StorageLocation, FullName: fullName, - CreatedAt: time.Now().UnixMilli(), + CreatedAt: nowMilli(), CreatedBy: s.CurrentUser().UserName, - UpdatedAt: time.Now().UnixMilli(), UpdatedBy: s.CurrentUser().UserName, MetastoreId: nextUUID(), Owner: s.CurrentUser().UserName, } + registeredModel.UpdatedAt = registeredModel.CreatedAt s.RegisteredModels[fullName] = registeredModel return Response{ @@ -78,7 +77,7 @@ func (s *FakeWorkspace) RegisteredModelsUpdate(req Request, fullName string) Res fullName = existing.CatalogName + "." + existing.SchemaName + "." + updateRequest.NewName } - existing.UpdatedAt = time.Now().UnixMilli() + existing.UpdatedAt = nowMilli() s.RegisteredModels[fullName] = existing return Response{ Body: existing, From b221e9b7014f41f81426ee262f7725474196b89e Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Wed, 11 Mar 2026 12:13:15 +0100 Subject: [PATCH 2/2] Improve IDE settings prompt: better formatting and default to yes - Wrap settings JSON in { } with proper indentation for visual clarity - Add blank lines around the settings block - Default to yes (Y/n) when prompting to apply settings - Shorten inline comments for less noise Co-Authored-By: Claude Opus 4.6 --- experimental/ssh/internal/vscode/settings.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/experimental/ssh/internal/vscode/settings.go b/experimental/ssh/internal/vscode/settings.go index dd3e7842b5..0877a57bff 100644 --- a/experimental/ssh/internal/vscode/settings.go +++ b/experimental/ssh/internal/vscode/settings.go @@ -214,29 +214,33 @@ func validateSettings(v hujson.Value, connectionName string) *missingSettings { func settingsMessage(connectionName string, missing *missingSettings) string { var lines []string if missing.portRange { - lines = append(lines, fmt.Sprintf(" \"%s\": {\"%s\": \"%s\"}", serverPickPortsKey, connectionName, portRange)) + lines = append(lines, fmt.Sprintf(" \"%s\": {\"%s\": \"%s\"}", serverPickPortsKey, connectionName, portRange)) } if missing.platform { - lines = append(lines, fmt.Sprintf(" \"%s\": {\"%s\": \"%s\"}", remotePlatformKey, connectionName, remotePlatform)) + lines = append(lines, fmt.Sprintf(" \"%s\": {\"%s\": \"%s\"}", remotePlatformKey, connectionName, remotePlatform)) } if missing.listenOnSocket { - lines = append(lines, fmt.Sprintf(" \"%s\": true // Global setting that affects all remote ssh connections", listenOnSocketKey)) + lines = append(lines, fmt.Sprintf(" \"%s\": true // Global setting", listenOnSocketKey)) } if len(missing.extensions) > 0 { quoted := make([]string, len(missing.extensions)) for i, ext := range missing.extensions { quoted[i] = fmt.Sprintf("\"%s\"", ext) } - lines = append(lines, fmt.Sprintf(" \"%s\": [%s] // Global setting that affects all remote ssh connections", defaultExtensionsKey, strings.Join(quoted, ", "))) + lines = append(lines, fmt.Sprintf(" \"%s\": [%s] // Global setting", defaultExtensionsKey, strings.Join(quoted, ", "))) } - return strings.Join(lines, "\n") + return " {\n" + strings.Join(lines, ",\n") + "\n }" } func promptUserForUpdate(ctx context.Context, ide, connectionName string, missing *missingSettings) (bool, error) { question := fmt.Sprintf( - "The following settings will be applied to %s for '%s':\n%s\nApply these settings?", + "The following settings will be applied to %s for '%s':\n\n%s\n\nApply these settings?", getIDE(ide).Name, connectionName, settingsMessage(connectionName, missing)) - return cmdio.AskYesOrNo(ctx, question) + ans, err := cmdio.Ask(ctx, question+" [Y/n]", "y") + if err != nil { + return false, err + } + return strings.ToLower(ans) == "y", nil } func handleMissingFile(ctx context.Context, ide, connectionName, settingsPath string) error {