diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index d7a20fb..37d1146 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -12,7 +12,7 @@ runs: - name: Setup Go uses: actions/setup-go@v4 with: - go-version: 1.18 + go-version: 1.22 - name: Run tests shell: bash run: make test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 067343f..fbf93ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,5 @@ +name: Publish Release + on: workflow_call: inputs: diff --git a/.golangci.yml b/.golangci.yml index 9340d90..d35d67b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,8 +5,6 @@ run: linters: enable: - bodyclose - - deadcode - - depguard - dupl - errcheck - gochecknoglobals @@ -24,18 +22,14 @@ linters: - megacheck - misspell - nakedret - #- nolintlint - # nolintlint is currently disabled because some linters don't yet work with Go 1.18, but we may have nolint - # directives that would be needed to suppress those linters if they did work. + - nolintlint - prealloc - staticcheck - - structcheck - stylecheck - typecheck - unconvert - unparam - unused - - varcheck - whitespace fast: false @@ -44,7 +38,7 @@ linters-settings: simplify: false goimports: local-prefixes: gopkg.in/launchdarkly,github.com/launchdarkly - + issues: exclude-use-default: false max-same-issues: 1000 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 943fc89..ea85f0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ This page is for people doing development of the SDK test harness itself. See al ## Tools used -To build and test the tool locally, you will need Go 1.18 or higher. +To build and test the tool locally, you will need Go 1.22 or higher. You do not need to install any other development tools used for the SDKs in order to build the test harness. Generally, each SDK project will include a corresponding test service which will be built using the same tools as that SDK. diff --git a/Makefile b/Makefile index d3fa680..85e2435 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -GORELEASER_VERSION=v1.7.0 -GORELEASER_DOWNLOAD_URL=https://github.com/goreleaser/goreleaser/releases/download/v1.7.0/goreleaser_$(shell uname)_$(shell uname -m).tar.gz +GORELEASER_VERSION=v1.24.0 +GORELEASER_DOWNLOAD_URL=https://github.com/goreleaser/goreleaser/releases/download/v1.24.0/goreleaser_$(shell uname)_$(shell uname -m).tar.gz GORELEASER=./bin/goreleaser/goreleaser -GOLANGCI_LINT_VERSION=v1.45.2 +GOLANGCI_LINT_VERSION=v1.56.2 LINTER=./bin/golangci-lint LINTER_VERSION_FILE=./bin/.golangci-lint-version-$(GOLANGCI_LINT_VERSION) diff --git a/data/context_factory.go b/data/context_factory.go index eaeccf7..9d3d851 100644 --- a/data/context_factory.go +++ b/data/context_factory.go @@ -100,11 +100,11 @@ func (f *ContextFactory) SetKeyDisambiguatorValueSameAs(f1 *ContextFactory) { // Each will have an appropriate Description, so the logic for running a test against each one can look // like this: // -// for _, contexts := range data.NewContextFactoriesForSingleAndMultiKind("NameOfTest") { -// t.Run(contexts.Description(), func(t *testing.T) { -// context := contexts.NextUniqueContext() // do something with this -// }) -// } +// for _, contexts := range data.NewContextFactoriesForSingleAndMultiKind("NameOfTest") { +// t.Run(contexts.Description(), func(t *testing.T) { +// context := contexts.NextUniqueContext() // do something with this +// }) +// } func NewContextFactoriesForSingleAndMultiKind( prefix string, builderActions ...func(*ldcontext.Builder), ) []*ContextFactory { @@ -130,11 +130,11 @@ func NewContextFactoriesForSingleAndMultiKind( // Each will have an appropriate Description, so the logic for running a test against each one can look // like this: // -// for _, contexts := range data.NewContextFactoriesForExercisingAllAttributes("NameOfTest") { -// t.Run(contexts.Description(), func(t *testing.T) { -// context := contexts.NextUniqueContext() // do something with this -// }) -// } +// for _, contexts := range data.NewContextFactoriesForExercisingAllAttributes("NameOfTest") { +// t.Run(contexts.Description(), func(t *testing.T) { +// context := contexts.NextUniqueContext() // do something with this +// }) +// } func NewContextFactoriesForExercisingAllAttributes( prefix string, ) []*ContextFactory { diff --git a/framework/harness/harness.go b/framework/harness/harness.go index 7329342..b25e0e5 100644 --- a/framework/harness/harness.go +++ b/framework/harness/harness.go @@ -108,6 +108,7 @@ func startServer(port int, handler http.Handler) error { } handler.ServeHTTP(w, r) }), + ReadHeaderTimeout: 10 * time.Second, // arbitrary but non-infinite timeout to avoid Slowloris Attack } go func() { if err := server.ListenAndServe(); err != nil { diff --git a/framework/helpers/assertions.go b/framework/helpers/assertions.go index 962c3ed..187bb54 100644 --- a/framework/helpers/assertions.go +++ b/framework/helpers/assertions.go @@ -4,7 +4,8 @@ import ( "time" ) -// Calls testFn repeatedly at intervals until the expected value is seen or the timeout elapses. +// PollForSpecificResultValue calls testFn repeatedly at intervals until the expected value is seen or the timeout +// elapses. // Returns true if the value was matched, false if timed out. func PollForSpecificResultValue[V comparable]( testFn func() V, @@ -28,7 +29,7 @@ func PollForSpecificResultValue[V comparable]( } } -// Equivalent to assert.Eventually from stretchr/testify/assert, except that it does not use a +// AssertEventually is equivalent to assert.Eventually from stretchr/testify/assert, except that it does not use a // separate goroutine so it does not cause problems with our test framework. It calls testFn // repeatedly at intervals until it gets a true value; if the timeout elapses, the test fails. func AssertEventually( @@ -46,7 +47,7 @@ func AssertEventually( return false } -// Equivalent to require.Eventually from stretchr/testify/assert, except that it does not use a +// RequireEventually is equivalent to require.Eventually from stretchr/testify/assert, except that it does not use a // separate goroutine so it does not cause problems with our test framework. It calls testFn // repeatedly at intervals until it gets a true value; if the timeout elapses, the test fails // and immediately exits. @@ -63,7 +64,7 @@ func RequireEventually( } } -// Equivalent to assert.Never from stretchr/testify/assert, except that it does not use a +// AssertNever is equivalent to assert.Never from stretchr/testify/assert, except that it does not use a // separate goroutine so it does not cause problems with our test framework. It calls testFn // repeatedly at intervals until either the timeout elapses or it receives a true value; if // it receives a true value, the test fails. @@ -82,7 +83,7 @@ func AssertNever( return true } -// Equivalent to require.Never from stretchr/testify/assert, except that it does not use a +// RequireNever is equivalent to require.Never from stretchr/testify/assert, except that it does not use a // separate goroutine so it does not cause problems with our test framework. It calls testFn // repeatedly at intervals until either the timeout elapses or it receives a true value; if // it receives a true value, the test fails and exits immediately diff --git a/framework/helpers/package_info.go b/framework/helpers/package_info.go new file mode 100644 index 0000000..8b1ca30 --- /dev/null +++ b/framework/helpers/package_info.go @@ -0,0 +1,2 @@ +// Package helpers contains various utilities for writing tests. +package helpers diff --git a/framework/opt/maybe.go b/framework/opt/maybe.go index 4340c1b..bb85eb2 100644 --- a/framework/opt/maybe.go +++ b/framework/opt/maybe.go @@ -55,8 +55,7 @@ func (m Maybe[V]) OrElse(valueIfUndefined V) V { // result of fmt.Sprintf with "%v". func (m Maybe[V]) String() string { if m.defined { - var v interface{} - v = m.value + v := interface{}(m.value) if s, ok := v.(fmt.Stringer); ok { return s.String() } diff --git a/mockld/streaming_service.go b/mockld/streaming_service.go index 1045abd..0875082 100644 --- a/mockld/streaming_service.go +++ b/mockld/streaming_service.go @@ -147,7 +147,7 @@ func (s *StreamingService) makePutEvent() eventsource.Event { } } -// Sends an SSE event to all clients that are currently connected to the stream-- or, if no client +// PushEvent sends an SSE event to all clients that are currently connected to the stream-- or, if no client // has connected yet, queues the event so that it will be sent (after the initial data) to the // first client that connects. (The latter is necessary to avoid race conditions, since even after // a connection is received on the stream endpoint, it is hard for the test logic to know when the diff --git a/sdktests/client_side_auto_env_attributes.go b/sdktests/client_side_auto_env_attributes.go index 8ad929b..8edf540 100644 --- a/sdktests/client_side_auto_env_attributes.go +++ b/sdktests/client_side_auto_env_attributes.go @@ -88,23 +88,23 @@ func doClientSideAutoEnvAttributesEventsNoCollisionsTests(t *ldtest.T) { client.FlushEvents(t) payload := events.ExpectAnalyticsEvents(t, defaultEventTimeout) - context_matchers := []m.Matcher{ + contextMatchers := []m.Matcher{ m.JSONOptProperty("ld_application").Should(m.BeNil()), m.JSONOptProperty("ld_device").Should(m.BeNil()), } if context.Multiple() { for _, c := range context.GetAllIndividualContexts(nil) { - context_matchers = append(context_matchers, m.JSONProperty(string(c.Kind())).Should(m.Not(m.BeNil()))) + contextMatchers = append(contextMatchers, m.JSONProperty(string(c.Kind())).Should(m.Not(m.BeNil()))) } } else { - context_matchers = append(context_matchers, m.JSONProperty("kind").Should(m.Equal(string(context.Kind())))) + contextMatchers = append(contextMatchers, m.JSONProperty("kind").Should(m.Equal(string(context.Kind())))) } m.In(t).Assert(payload, m.Items( append( []m.Matcher{IsIdentifyEvent()}, - m.JSONProperty("context").Should(m.AllOf(context_matchers...)), + m.JSONProperty("context").Should(m.AllOf(contextMatchers...)), )..., )) }) diff --git a/sdktests/common_tests_events_base.go b/sdktests/common_tests_events_base.go index 7fc8e03..a644fb3 100644 --- a/sdktests/common_tests_events_base.go +++ b/sdktests/common_tests_events_base.go @@ -19,6 +19,7 @@ func NewCommonEventTests(t *ldtest.T, testName string, baseSDKConfigurers ...SDK return CommonEventTests{newCommonTestsBase(t, testName, baseSDKConfigurers...)} } +//nolint:unused // May not be used now, but could be helpful in new tests. func (c CommonEventTests) discardIdentifyEventIfClientSide(t *ldtest.T, client *SDKClient, events *SDKEventSink) { if c.isClientSide { client.FlushEvents(t) @@ -36,6 +37,7 @@ func (c CommonEventTests) initialEventPayloadExpectations() []m.Matcher { return []m.Matcher{IsIdentifyEvent()} } +//nolint:unused // May not be used now, but could be helpful in new tests. func (c CommonEventTests) eventsWithIndexEventIfAppropriate(matchers ...m.Matcher) []m.Matcher { // Server-side SDKs (excluding PHP) send an index event for each never-before-seen user. Client-side // SDKs and the PHP SDK do not. @@ -45,12 +47,14 @@ func (c CommonEventTests) eventsWithIndexEventIfAppropriate(matchers ...m.Matche return append([]m.Matcher{IsIndexEvent()}, matchers...) } +//nolint:unused // May not be used now, but could be helpful in new tests. func (c CommonEventTests) eventsWithIndexEventAndSummaryEventIfAppropriate(matchers ...m.Matcher) []m.Matcher { return c.eventsWithSummaryEventIfAppropriate( c.eventsWithIndexEventIfAppropriate(matchers...)..., ) } +//nolint:unused // May not be used now, but could be helpful in new tests. func (c CommonEventTests) eventsWithSummaryEventIfAppropriate(matchers ...m.Matcher) []m.Matcher { // The PHP SDK is the only one that never sends a summary event. if c.isPHP { diff --git a/sdktests/common_tests_events_contexts.go b/sdktests/common_tests_events_contexts.go index fe06415..b0f276b 100644 --- a/sdktests/common_tests_events_contexts.go +++ b/sdktests/common_tests_events_contexts.go @@ -227,7 +227,7 @@ func (c CommonEventTests) EventContexts(t *ldtest.T) { if user := representContextAsOldUser(t, context); user != nil { t.Run("with old user", func(t *ldtest.T) { - _ = basicEvaluateFlagWithOldUser(t, client, flagKey, user, defaultValue) + basicEvaluateFlagWithOldUser(t, client, flagKey, user, defaultValue) verifyResult(t) }) } @@ -263,7 +263,7 @@ func (c CommonEventTests) EventContexts(t *ldtest.T) { if c.isClientSide { client.SendIdentifyEventWithOldUser(t, user) } - _ = basicEvaluateFlagWithOldUser(t, client, debuggedFlagKey, user, defaultValue) + basicEvaluateFlagWithOldUser(t, client, debuggedFlagKey, user, defaultValue) client.FlushEvents(t) payload := events.ExpectAnalyticsEvents(t, defaultEventTimeout) eventMatchers := []m.Matcher{debugEventMatcher, IsSummaryEvent()} diff --git a/sdktests/common_tests_tags.go b/sdktests/common_tests_tags.go index d988910..32d27e0 100644 --- a/sdktests/common_tests_tags.go +++ b/sdktests/common_tests_tags.go @@ -5,7 +5,6 @@ import ( "time" "github.com/launchdarkly/sdk-test-harness/v2/framework/harness" - "github.com/launchdarkly/sdk-test-harness/v2/framework/helpers" h "github.com/launchdarkly/sdk-test-harness/v2/framework/helpers" "github.com/launchdarkly/sdk-test-harness/v2/framework/ldtest" o "github.com/launchdarkly/sdk-test-harness/v2/framework/opt" @@ -48,7 +47,7 @@ func (c CommonTagsTests) Run(t *ldtest.T) { } withTagsConfig := func(tags servicedef.SDKConfigTagsParams) SDKConfigurer { - return helpers.ConfigOptionFunc[servicedef.SDKConfigParams](func(config *servicedef.SDKConfigParams) error { + return h.ConfigOptionFunc[servicedef.SDKConfigParams](func(config *servicedef.SDKConfigParams) error { config.Tags = o.Some(tags) return nil }) @@ -171,7 +170,7 @@ func (c CommonTagsTests) Run(t *ldtest.T) { makeStringOfLength := func(n int) string { // makes nice strings that look like "12345678901234" etc. so it's easier to see when one is longer than another - b := make([]byte, n, n) + b := make([]byte, n) for i := 0; i < n; i++ { b[i] = byte('0' + ((i + 1) % 10)) } diff --git a/sdktests/helpers.go b/sdktests/helpers.go index dd2e2e4..b5c2745 100644 --- a/sdktests/helpers.go +++ b/sdktests/helpers.go @@ -52,14 +52,13 @@ func basicEvaluateFlagWithOldUser( flagKey string, user json.RawMessage, defaultValue ldvalue.Value, -) ldvalue.Value { - result := client.EvaluateFlag(t, servicedef.EvaluateFlagParams{ +) { + client.EvaluateFlag(t, servicedef.EvaluateFlagParams{ FlagKey: flagKey, User: user, ValueType: servicedef.ValueTypeAny, DefaultValue: defaultValue, }) - return result.Value } // computeExpectedBucketValue implements the bucketing hash value calculation as per the evaluation spec, diff --git a/sdktests/php_events_eval.go b/sdktests/php_events_eval.go index 048d1f7..72ec1f6 100644 --- a/sdktests/php_events_eval.go +++ b/sdktests/php_events_eval.go @@ -166,8 +166,7 @@ func doPHPFeatureEventTests(t *ldtest.T) { withDebug: false, malformed: false, }].ReuseFlagForValueType(valueType) - var expectedValue ldvalue.Value - expectedValue = flagValues(valueType) + expectedValue := flagValues(valueType) context := anonymousFactory.NextUniqueContext() resp := client.EvaluateFlag(t, servicedef.EvaluateFlagParams{ FlagKey: flag.Key, @@ -227,8 +226,7 @@ func doPHPFeatureEventTests(t *ldtest.T) { withDebug: false, malformed: false, }].ReuseFlagForValueType(valueType) - var expectedValue ldvalue.Value - expectedValue = flagValues(valueType) + expectedValue := flagValues(valueType) resp := client.EvaluateFlag(t, servicedef.EvaluateFlagParams{ FlagKey: flag.Key, Context: o.Some(multiContext), diff --git a/sdktests/server_side_migrations.go b/sdktests/server_side_migrations.go index ca3cd11..91ef62a 100644 --- a/sdktests/server_side_migrations.go +++ b/sdktests/server_side_migrations.go @@ -1,4 +1,4 @@ -// nolint:lll,dupl +//nolint:lll,dupl package sdktests import ( @@ -139,11 +139,11 @@ func executesOriginsInCorrectOrder(t *ldtest.T) { t.DebugLogger(), func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("old read")) // nolint:errcheck,gosec + _, _ = w.Write([]byte("old read")) }, func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("new read")) // nolint:errcheck,gosec + _, _ = w.Write([]byte("new read")) }, ) t.Defer(service.Close) @@ -208,11 +208,11 @@ func executesReads(t *ldtest.T) { t.DebugLogger(), func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("old read")) // nolint:errcheck,gosec + _, _ = w.Write([]byte("old read")) }, func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("new read")) // nolint:errcheck,gosec + _, _ = w.Write([]byte("new read")) }, ) t.Defer(service.Close) @@ -428,7 +428,7 @@ func tracksInvoked(t *ldtest.T, order ldmigration.ExecutionOrder) { } } -// nolint:dupl // Invokes and latency happen to share the same setup, but should be tested independently. +//nolint:dupl // Invokes and latency happen to share the same setup, but should be tested independently. func tracksLatency(t *ldtest.T, order ldmigration.ExecutionOrder) { onlyOld := []m.Matcher{m.JSONOptProperty("old").Should(m.Not(m.BeNil())), m.JSONOptProperty("new").Should(m.BeNil())} both := []m.Matcher{m.JSONOptProperty("old").Should(m.Not(m.BeNil())), m.JSONOptProperty("new").Should(m.Not(m.BeNil()))} @@ -936,7 +936,7 @@ func tracksConsistencyCorrectlyBasedOnStage(t *ldtest.T, order ldmigration.Execu handler := func(response string) func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(response)) // nolint:errcheck,gosec + _, _ = w.Write([]byte(response)) } } ld := handler("LaunchDarkly")