diff --git a/.github/workflows/dapr-bot/bot.go b/.github/workflows/dapr-bot/bot.go index 82efb632..49bbdf67 100644 --- a/.github/workflows/dapr-bot/bot.go +++ b/.github/workflows/dapr-bot/bot.go @@ -49,9 +49,9 @@ func (b *Bot) HandleEvent(ctx context.Context, event Event) (res string, err err switch command { case "/assign": assignee, err := b.AssignIssueToCommenter(event) - res = fmt.Sprintf("👍 Issue assigned to %s", assignee) + res = "👍 Issue assigned to " + assignee if err == nil { - err = b.CreateIssueComment(fmt.Sprintf("🚀 Issue assigned to you @%s", assignee), event) + err = b.CreateIssueComment("🚀 Issue assigned to you @"+assignee, event) } else { err = b.CreateIssueComment("⚠️ Unable to assign issue", event) } diff --git a/.github/workflows/dapr-bot/go.mod b/.github/workflows/dapr-bot/go.mod index 3d5a4d63..da5506da 100644 --- a/.github/workflows/dapr-bot/go.mod +++ b/.github/workflows/dapr-bot/go.mod @@ -1,8 +1,6 @@ module github.com/dapr/go-sdk/.github/workflows/dapr-bot -go 1.22 - -toolchain go1.22.0 +go 1.23.3 require ( github.com/google/go-github/v55 v55.0.0 diff --git a/.github/workflows/test-dapr-bot.yml b/.github/workflows/test-dapr-bot.yml index 21583217..67b3d31f 100644 --- a/.github/workflows/test-dapr-bot.yml +++ b/.github/workflows/test-dapr-bot.yml @@ -17,8 +17,7 @@ jobs: name: Test runs-on: ubuntu-latest env: - GOVER: ${{ matrix.gover }} - GOLANGCILINT_VER: v1.55.2 + GOLANGCILINT_VER: v1.61.0 steps: - name: Checkout @@ -38,7 +37,7 @@ jobs: run: make test - name: Lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 with: version: ${{ env.GOLANGCILINT_VER }} working-directory: ./.github/workflows/dapr-bot diff --git a/.github/workflows/test-on-push.yaml b/.github/workflows/test-on-push.yaml index 3e450d1e..7c1f34bd 100644 --- a/.github/workflows/test-on-push.yaml +++ b/.github/workflows/test-on-push.yaml @@ -8,10 +8,10 @@ on: jobs: build: - name: Test on ${{ matrix.gover }} + name: Test runs-on: ubuntu-latest env: - GOLANGCILINT_VER: v1.55.2 + GOLANGCILINT_VER: v1.61.0 steps: - name: Checkout diff --git a/.github/workflows/test-tooling.yml b/.github/workflows/test-tooling.yml index 101bc1fe..5a647a24 100644 --- a/.github/workflows/test-tooling.yml +++ b/.github/workflows/test-tooling.yml @@ -19,17 +19,13 @@ jobs: strategy: fail-fast: false matrix: - gover: - - "1.21" - - "1.22" os: - "ubuntu-latest" - "windows-latest" - "macos-latest" runs-on: ${{ matrix.os }} env: - GOVER: ${{ matrix.gover }} - GOLANGCILINT_VER: v1.55.2 # Make sure to bump /tools/check-lint-version/main_test.go + GOLANGCILINT_VER: v1.61.0 # Make sure to bump /tools/check-lint-version/main_test.go steps: - name: Checkout @@ -38,7 +34,7 @@ jobs: - name: Setup uses: actions/setup-go@v5 with: - go-version: ${{ env.GOVER }} + go-version-file: ./tools/check-lint-version/go.mod - name: Tidy working-directory: ./tools/check-lint-version diff --git a/.golangci.yml b/.golangci.yml index f526ef50..b344e19c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,7 +4,7 @@ run: concurrency: 4 # timeout for analysis, e.g. 30s, 5m, default is 1m - deadline: 10m + timeout: 15m # exit code when at least one issue was found, default is 1 issues-exit-code: 1 @@ -13,31 +13,35 @@ run: tests: true # list of build tags, all linters use it. Default is empty list. - #build-tags: - # - mytag + build-tags: + - unit + - allcomponents + - subtlecrypto + # which files to skip: they will be analyzed, but issues from them + # won't be reported. Default value is empty list, but there is + # no need to include all autogenerated files, we confidently recognize + # autogenerated files. If it's not please let us know. + # skip-files: + # - ".*\\.my\\.go$" + # - lib/bad.go + +issues: # which dirs to skip: they won't be analyzed; # can use regexp here: generated.*, regexp is applied on full path; # default value is empty list, but next dirs are always skipped independently # from this option's value: # third_party$, testdata$, examples$, Godeps$, builtin$ - skip-dirs: + exclude-dirs: - ^pkg.*client.*clientset.*versioned.* - ^pkg.*client.*informers.*externalversions.* - ^pkg.*proto.* - # which files to skip: they will be analyzed, but issues from them - # won't be reported. Default value is empty list, but there is - # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - # skip-files: - # - ".*\\.my\\.go$" - # - lib/bad.go - # output configuration options output: # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" - format: tab + formats: + - format: tab # print lines of code with issue, default is true print-issued-lines: true @@ -57,23 +61,19 @@ linters-settings: # default is false: such cases aren't reported by default. check-blank: false - # [deprecated] comma-separated list of pairs of the form pkg:regex - # the regex is used to ignore names within pkg. (default "fmt:.*"). - # see https://github.com/kisielk/errcheck#the-deprecated-method for details - ignore: fmt:.*,io/ioutil:^Read.* + exclude-functions: + - fmt:.* + - io/ioutil:^Read.* # path to a file containing a list of functions to exclude from checking # see https://github.com/kisielk/errcheck#excluding-functions for details - # exclude: + # exclude: funlen: lines: 60 statements: 40 govet: - # report about shadowed variables - check-shadowing: true - # settings per analyzer settings: printf: # analyzer name, run `go tool vet help` to see all analyzers @@ -86,28 +86,12 @@ linters-settings: # enable or disable analyzers by name enable: - atomicalign - enable-all: false - disable: - shadow + enable-all: false disable-all: false revive: - max-open-files: 2048 - # enable-all-rules: true - rules: - - name: cyclomatic - severity: warning - disabled: false - arguments: [20] - - name: argument-limit - severity: warning - disabled: false - arguments: [8] - - name: if-return - severity: warning - disabled: false - - name: unused-parameter - severity: warning - disabled: true + # minimal confidence for issues, default is 0.8 + confidence: 0.8 gofmt: # simplify code: gofmt with `-s` option, true by default simplify: true @@ -121,9 +105,6 @@ linters-settings: gocognit: # minimal code complexity to report, 30 by default (but we recommend 10-20) min-complexity: 10 - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true dupl: # tokens count to trigger issue, 150 by default threshold: 100 @@ -152,10 +133,9 @@ linters-settings: desc: "you must use github.com/cenkalti/backoff/v4" misspell: # Correct spellings using locale preferences for US or UK. - # Default is to use a neutral variety of English. (Do not specify a locale value) + # Default is to use a neutral variety of English. # Setting locale to US will correct the British spelling of 'colour' to 'color'. - # locale: - + # locale: default ignore-words: - someword lll: @@ -164,12 +144,6 @@ linters-settings: line-length: 120 # tab width in spaces. Default to 1. tab-width: 1 - unparam: - # Inspect exported functions, default is false. Set to true if no external program/library imports your code. - # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: - # if it's called for subdir of a project it can't find external interfaces. All text editor integrations - # with golangci-lint call it on a directory with the changed file. - check-exported: false nakedret: # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 max-func-lines: 30 @@ -187,7 +161,7 @@ linters-settings: # See https://go-critic.github.io/overview#checks-overview # To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run` # By default list of stable checks is used. - # enabled-checks: + # enabled-checks: # Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty disabled-checks: @@ -235,17 +209,20 @@ linters-settings: allow-assign-and-call: true # Allow multiline assignments to be cuddled. Default is true. allow-multiline-assign: true - # Allow case blocks to end with a whitespace. - force-case-trailing-whitespace: 0 # Allow declarations (var) to be cuddled. allow-cuddle-declarations: false + # If the number of lines in a case block is equal to or lager than this number, + # the case *must* end white a newline. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#force-case-trailing-whitespace + # Default: 0 + force-case-trailing-whitespace: 1 linters: fast: false enable-all: true disable: # TODO Enforce the below linters later - - nosnakecase + - musttag - dupl - errcheck - funlen @@ -254,26 +231,17 @@ linters: - gocyclo - gocognit - godox - - interfacer - lll - - maligned - - scopelint - unparam - wsl - gomnd - testpackage - - goerr113 - nestif - nlreturn - - tagliatelle - - ifshort - - forbidigo - exhaustive - exhaustruct - - exhaustivestruct - noctx - gci - - golint - tparallel - paralleltest - wrapcheck @@ -287,7 +255,6 @@ linters: - varnamelen - errorlint - forcetypeassert - - ifshort - maintidx - nilnil - predeclared @@ -300,14 +267,12 @@ linters: - asasalint - rowserrcheck - sqlclosecheck - - structcheck - - varcheck - - deadcode - - golint - inamedparam -issues: - exclude-rules: - - path: .*_test.go - linters: - - godot - + - tagalign + - mnd + - canonicalheader + - exportloopref + - execinquery + - err113 + - fatcontext + - forbidigo # TODO: Re-enable and remove fmt.println \ No newline at end of file diff --git a/actor/manager/manager.go b/actor/manager/manager.go index 8b08de38..abacb3b8 100644 --- a/actor/manager/manager.go +++ b/actor/manager/manager.go @@ -37,11 +37,11 @@ var ignoredActorMethods = []string{"Type"} // init initializes the action method exclusion list with methods from ServerImplBaseCtx and ReminderCallee interfaces. func init() { serverImplBaseCtxType := reflect.TypeOf(&actor.ServerImplBaseCtx{}) - for i := 0; i < serverImplBaseCtxType.NumMethod(); i++ { + for i := range serverImplBaseCtxType.NumMethod() { ignoredActorMethods = append(ignoredActorMethods, serverImplBaseCtxType.Method(i).Name) } ReminderCallType := reflect.TypeOf((*actor.ReminderCallee)(nil)).Elem() - for i := 0; i < ReminderCallType.NumMethod(); i++ { + for i := range ReminderCallType.NumMethod() { ignoredActorMethods = append(ignoredActorMethods, ReminderCallType.Method(i).Name) } } @@ -265,7 +265,7 @@ type MethodType struct { // suitableMethods returns suitable Rpc methods of typ. func suitableMethods(typ reflect.Type) map[string]*MethodType { methods := make(map[string]*MethodType) - for m := 0; m < typ.NumMethod(); m++ { + for m := range typ.NumMethod() { method := typ.Method(m) // skip methods from ServerImplBaseCtx struct and ServerContext and ReminderCallee interfaces. if slices.Contains(ignoredActorMethods, method.Name) { diff --git a/actor/state/actor_state_change_test.go b/actor/state/actor_state_change_test.go index 3ea8d7e7..64184aaa 100644 --- a/actor/state/actor_state_change_test.go +++ b/actor/state/actor_state_change_test.go @@ -46,7 +46,6 @@ func TestNewActorStateChange(t *testing.T) { }, } for name, test := range tests { - test := test t.Run(name, func(t *testing.T) { assert.Equal(t, test.want, NewActorStateChange(test.stateName, test.value, test.changeKind, &test.ttl)) }) diff --git a/client/actor.go b/client/actor.go index 5d348757..940c20e1 100644 --- a/client/actor.go +++ b/client/actor.go @@ -289,7 +289,7 @@ func (c *GRPCClient) implActor(actor actor.Client, serializer codec.Codec) { } numField := valueOfActor.NumField() - for i := 0; i < numField; i++ { + for i := range numField { t := typeOfActor.Field(i) methodName := t.Name if methodName == "Type" { @@ -312,7 +312,7 @@ func (c *GRPCClient) implActor(actor actor.Client, serializer codec.Codec) { } funcOuts := make([]reflect.Type, outNum) - for i := 0; i < outNum; i++ { + for i := range outNum { funcOuts[i] = t.Type.Out(i) } diff --git a/client/client_test.go b/client/client_test.go index a6a81fd3..9b8e969b 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -466,7 +466,7 @@ func (s *testDaprServer) SubscribeConfiguration(in *pb.SubscribeConfigurationReq return err } - for i := 0; i < 5; i++ { + for range 5 { select { case <-stopCh: return nil diff --git a/client/internal/parse.go b/client/internal/parse.go index d1749748..32bafb6e 100644 --- a/client/internal/parse.go +++ b/client/internal/parse.go @@ -27,7 +27,6 @@ type Parsed struct { TLS bool } -//nolint:revive func ParseGRPCEndpoint(endpoint string) (Parsed, error) { target := endpoint if len(target) == 0 { diff --git a/client/state.go b/client/state.go index e2c70629..ed790ca9 100644 --- a/client/state.go +++ b/client/state.go @@ -17,6 +17,7 @@ import ( "context" "errors" "fmt" + "math" "time" "google.golang.org/protobuf/types/known/durationpb" @@ -65,11 +66,11 @@ const ( type ( // StateConsistency is the consistency enum type. - StateConsistency int + StateConsistency int32 // StateConcurrency is the concurrency enum type. - StateConcurrency int + StateConcurrency int32 // OperationType is the operation enum type. - OperationType int + OperationType int32 ) // GetPBConsistency get consistency pb value. @@ -252,9 +253,15 @@ func toProtoDuration(d time.Duration) *durationpb.Duration { nanos := d.Nanoseconds() secs := nanos / 1e9 nanos -= secs * 1e9 + + // conversion check - gosec ignored below for conversion + if nanos <= int64(math.MinInt32) && nanos >= int64(math.MaxInt32) { + panic("integer overflow converting duration to proto") + } + return &durationpb.Duration{ Seconds: secs, - Nanos: int32(nanos), + Nanos: int32(nanos), //nolint:gosec } } @@ -484,7 +491,7 @@ func (c *GRPCClient) DeleteBulkState(ctx context.Context, storeName string, keys } items := make([]*DeleteStateItem, 0, len(keys)) - for i := 0; i < len(keys); i++ { + for i := range keys { item := &DeleteStateItem{ Key: keys[i], Metadata: meta, @@ -502,7 +509,7 @@ func (c *GRPCClient) DeleteBulkStateItems(ctx context.Context, storeName string, } states := make([]*v1.StateItem, 0, len(items)) - for i := 0; i < len(items); i++ { + for i := range items { item := items[i] if err := hasRequiredStateArgs(storeName, item.Key); err != nil { return fmt.Errorf("missing required arguments: %w", err) diff --git a/examples/go.mod b/examples/go.mod index d504837f..a70622bf 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -1,6 +1,6 @@ module github.com/dapr/go-sdk/examples -go 1.22.6 +go 1.23.3 replace github.com/dapr/go-sdk => ../ diff --git a/go.mod b/go.mod index 139a636c..5c2549f2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/dapr/go-sdk -go 1.22.6 +go 1.23.3 require ( github.com/dapr/dapr v1.14.1 diff --git a/service/grpc/binding.go b/service/grpc/binding.go index 311e89f6..288021bc 100644 --- a/service/grpc/binding.go +++ b/service/grpc/binding.go @@ -27,10 +27,10 @@ import ( // AddBindingInvocationHandler appends provided binding invocation handler with its name to the service. func (s *Server) AddBindingInvocationHandler(name string, fn common.BindingInvocationHandler) error { if name == "" { - return fmt.Errorf("binding name required") + return errors.New("binding name required") } if fn == nil { - return fmt.Errorf("binding handler required") + return errors.New("binding handler required") } s.bindingHandlers[name] = fn return nil diff --git a/service/grpc/health_check.go b/service/grpc/health_check.go index 722a244f..d835b58d 100644 --- a/service/grpc/health_check.go +++ b/service/grpc/health_check.go @@ -15,7 +15,7 @@ package grpc import ( "context" - "fmt" + "errors" pb "github.com/dapr/dapr/pkg/proto/runtime/v1" "github.com/dapr/go-sdk/service/common" @@ -26,7 +26,7 @@ import ( // AddHealthCheckHandler appends provided app health check handler. func (s *Server) AddHealthCheckHandler(_ string, fn common.HealthCheckHandler) error { if fn == nil { - return fmt.Errorf("health check handler required") + return errors.New("health check handler required") } s.healthCheckHandler = fn @@ -44,5 +44,5 @@ func (s *Server) HealthCheck(ctx context.Context, _ *emptypb.Empty) (*pb.HealthC return &pb.HealthCheckResponse{}, nil } - return nil, fmt.Errorf("health check handler not implemented") + return nil, errors.New("health check handler not implemented") } diff --git a/service/grpc/invoke.go b/service/grpc/invoke.go index 23363ae3..0ed8bee1 100644 --- a/service/grpc/invoke.go +++ b/service/grpc/invoke.go @@ -28,7 +28,7 @@ import ( // AddServiceInvocationHandler appends provided service invocation handler with its method to the service. func (s *Server) AddServiceInvocationHandler(method string, fn cc.ServiceInvocationHandler) error { if method == "" || method == "/" { - return fmt.Errorf("servie name required") + return errors.New("service name required") } if method[0] == '/' { @@ -36,7 +36,7 @@ func (s *Server) AddServiceInvocationHandler(method string, fn cc.ServiceInvocat } if fn == nil { - return fmt.Errorf("invocation handler required") + return errors.New("invocation handler required") } s.invokeHandlers[method] = fn return nil diff --git a/service/http/binding.go b/service/http/binding.go index 54faab10..e421fc9f 100644 --- a/service/http/binding.go +++ b/service/http/binding.go @@ -14,7 +14,7 @@ limitations under the License. package http import ( - "fmt" + "errors" "io" "net/http" "strings" @@ -25,14 +25,14 @@ import ( // AddBindingInvocationHandler appends provided binding invocation handler with its route to the service. func (s *Server) AddBindingInvocationHandler(route string, fn common.BindingInvocationHandler) error { if route == "" { - return fmt.Errorf("binding route required") + return errors.New("binding route required") } if fn == nil { - return fmt.Errorf("binding handler required") + return errors.New("binding handler required") } if !strings.HasPrefix(route, "/") { - route = fmt.Sprintf("/%s", route) + route = "/" + route } s.mux.Handle(route, optionsHandler(http.HandlerFunc( diff --git a/service/http/health_check.go b/service/http/health_check.go index 3f4ca7c1..8d481c23 100644 --- a/service/http/health_check.go +++ b/service/http/health_check.go @@ -14,7 +14,7 @@ limitations under the License. package http import ( - "fmt" + "errors" "net/http" "strings" @@ -24,11 +24,11 @@ import ( // AddHealthCheckHandler appends provided app health check handler. func (s *Server) AddHealthCheckHandler(route string, fn common.HealthCheckHandler) error { if fn == nil { - return fmt.Errorf("health check handler required") + return errors.New("health check handler required") } if !strings.HasPrefix(route, "/") { - route = fmt.Sprintf("/%s", route) + route = "/" + route } s.mux.Handle(route, optionsHandler(http.HandlerFunc( diff --git a/service/http/invoke.go b/service/http/invoke.go index c78477f2..849c2805 100644 --- a/service/http/invoke.go +++ b/service/http/invoke.go @@ -14,7 +14,7 @@ limitations under the License. package http import ( - "fmt" + "errors" "io" "net/http" "strings" @@ -27,11 +27,11 @@ import ( // AddServiceInvocationHandler appends provided service invocation handler with its route to the service. func (s *Server) AddServiceInvocationHandler(route string, fn common.ServiceInvocationHandler) error { if route == "" || route == "/" { - return fmt.Errorf("service route required") + return errors.New("service route required") } if fn == nil { - return fmt.Errorf("invocation handler required") + return errors.New("invocation handler required") } if !strings.HasPrefix(route, "/") { diff --git a/service/http/scheduling.go b/service/http/scheduling.go index 9bf43cb5..1c1f5b2c 100644 --- a/service/http/scheduling.go +++ b/service/http/scheduling.go @@ -2,17 +2,16 @@ package http import ( "errors" - "fmt" "github.com/dapr/go-sdk/service/common" ) func (s *Server) AddJobEventHandler(name string, fn common.JobEventHandler) error { if name == "" { - return fmt.Errorf("job event name required") + return errors.New("job event name required") } if fn == nil { - return fmt.Errorf("job event handler required") + return errors.New("job event handler required") } return errors.New("handling http scheduling requests has not been implemented in this sdk") diff --git a/service/internal/topicregistrar.go b/service/internal/topicregistrar.go index ec4efb9e..a443af0d 100644 --- a/service/internal/topicregistrar.go +++ b/service/internal/topicregistrar.go @@ -2,7 +2,6 @@ package internal import ( "errors" - "fmt" "github.com/dapr/go-sdk/service/common" ) @@ -27,7 +26,7 @@ func (m TopicRegistrar) AddSubscription(sub *common.Subscription, fn common.Topi return errors.New("pub/sub name required") } if fn == nil { - return fmt.Errorf("topic handler required") + return errors.New("topic handler required") } var key string diff --git a/service/internal/topicregistrar_test.go b/service/internal/topicregistrar_test.go index 64be563c..cb5d4fb0 100644 --- a/service/internal/topicregistrar_test.go +++ b/service/internal/topicregistrar_test.go @@ -63,7 +63,6 @@ func TestTopicRegistrarValidation(t *testing.T) { }, } for name, tt := range tests { - tt := tt // dereference loop var t.Run(name, func(t *testing.T) { m := internal.TopicRegistrar{} if tt.err != "" { diff --git a/tools/check-lint-version/go.mod b/tools/check-lint-version/go.mod index 71a8f901..6f3e2b80 100644 --- a/tools/check-lint-version/go.mod +++ b/tools/check-lint-version/go.mod @@ -1,8 +1,6 @@ module github.com/dapr/go-sdk/tools/check-lint-version -go 1.21 - -toolchain go1.21.6 +go 1.23.3 require ( github.com/stretchr/testify v1.8.4 diff --git a/tools/check-lint-version/main.go b/tools/check-lint-version/main.go index fcdf75fe..8805ba78 100644 --- a/tools/check-lint-version/main.go +++ b/tools/check-lint-version/main.go @@ -51,7 +51,7 @@ func getCurrentVersion() (string, error) { if matches == nil { return "", fmt.Errorf("no version found: %v", string(out)) } - return fmt.Sprintf("v%s", matches[1]), err + return "v" + matches[1], err } func isVersionValid(workflowVersion, currentVersion string) bool { @@ -72,7 +72,7 @@ func compareVersions(path string) string { if !validVersion { return fmt.Sprintf("Invalid version, expected: %s, current: %s - See: https://golangci-lint.run/usage/install/ for instructions to update", workflowVersion, currentVersion) } - return fmt.Sprintf("Linter version is valid (MajorMinor): %s", currentVersion) + return "Linter version is valid (MajorMinor): " + currentVersion } func main() { diff --git a/tools/check-lint-version/main_test.go b/tools/check-lint-version/main_test.go index 043ba5d0..ee9613cb 100644 --- a/tools/check-lint-version/main_test.go +++ b/tools/check-lint-version/main_test.go @@ -28,7 +28,7 @@ func TestParseWorkflow(t *testing.T) { t.Run("parse testing workflow file", func(t *testing.T) { parsedVersion, err := parseWorkflowVersionFromFile("../../.github/workflows/test-tooling.yml") - assert.Equal(t, "v1.55.2", parsedVersion) + assert.Equal(t, "v1.61.0", parsedVersion) require.NoError(t, err) }) } @@ -36,7 +36,7 @@ func TestParseWorkflow(t *testing.T) { func TestGetCurrentVersion(t *testing.T) { t.Run("get current version from system", func(t *testing.T) { currentVersion, err := getCurrentVersion() - assert.Equal(t, "v1.55.2", currentVersion) + assert.Equal(t, "v1.61.0", currentVersion) require.NoError(t, err) })