From 13e2259adf005f5781de39b199f9467a4f0c46e6 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Fri, 4 Oct 2024 15:36:49 -0400 Subject: [PATCH 01/10] chore: Introduce read-write persistent data store tests --- docs/service_spec.md | 6 + sdktests/server_side_persistence_base.go | 45 +- .../server_side_persistence_read_write.go | 469 ++++++++++++++++++ sdktests/server_side_persistence_redis.go | 15 + sdktests/testsuite_entry_point.go | 5 +- servicedef/service_params.go | 42 +- 6 files changed, 544 insertions(+), 38 deletions(-) create mode 100644 sdktests/server_side_persistence_read_write.go diff --git a/docs/service_spec.md b/docs/service_spec.md index f3ce7b0..27f7311 100644 --- a/docs/service_spec.md +++ b/docs/service_spec.md @@ -94,6 +94,12 @@ v4 of the event schema originally required a `contextKeys` property on all featu This means that the SDK supports technology migrations, a feature which allows customers to migrate between data sources using well-defined migration stages. +#### Capability `"persistent-data-store"` + +This means the SDK is capable of interacting with external persistent data stores. The test harness must further identify which store types are supported through additional capabilities listed below. + +- `persistent-data-store-redis`: This means the SDK is capable of interacting with a Redis data store. + #### Capability `"polling-gzip"` This means the SDK is requesting gzip compression support on polling payloads. The SDK is expected to set the `Accept-Encoding` header to `gzip` in addition to enabling this capability. diff --git a/sdktests/server_side_persistence_base.go b/sdktests/server_side_persistence_base.go index dec4dde..957dc48 100644 --- a/sdktests/server_side_persistence_base.go +++ b/sdktests/server_side_persistence_base.go @@ -14,52 +14,62 @@ import ( ) func doServerSidePersistentTests(t *ldtest.T) { - rdb := redis.NewClient(&redis.Options{ - Addr: "localhost:6379", - Password: "", // no password set - DB: 0, // use default DB - }) - - newServerSidePersistentTests(t, &RedisPersistentStore{redis: rdb}).Run(t) + if t.Capabilities().Has(servicedef.CapabilityPersistentDataStoreRedis) { + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + + t.Run("redis", newServerSidePersistentTests(t, &RedisPersistentStore{redis: rdb}).Run) + } } type PersistentStore interface { DSN() string + // TODO: Change these names to something less terrible, or make them even + // more generic. + ReadField(key string) (string, error) + ReadData(key string) (map[string]string, error) WriteData(key string, data map[string]string) error + Type() servicedef.SDKConfigPersistentType + Reset() error } type ServerSidePersistentTests struct { + CommonStreamingTests persistentStore PersistentStore initialFlags map[string]string } func newServerSidePersistentTests(t *ldtest.T, persistentStore PersistentStore) *ServerSidePersistentTests { flagKeyBytes, err := - ldbuilders.NewFlagBuilder("flag-key").Version(1). - On(true).Variations(ldvalue.String("off"), ldvalue.String("match"), ldvalue.String("fallthrough")). - OffVariation(0). - FallthroughVariation(2). + ldbuilders.NewFlagBuilder("flag-key").Version(100). + On(true).Variations(ldvalue.String("fallthrough"), ldvalue.String("other")). + OffVariation(1). + FallthroughVariation(0). Build().MarshalJSON() require.NoError(t, err) initialFlags := map[string]string{"flag-key": string(flagKeyBytes)} uncachedFlagKeyBytes, err := - ldbuilders.NewFlagBuilder("uncached-flag-key").Version(1). - On(true).Variations(ldvalue.String("off"), ldvalue.String("match"), ldvalue.String("fallthrough")). - OffVariation(0). - FallthroughVariation(2). + ldbuilders.NewFlagBuilder("uncached-flag-key").Version(100). + On(true).Variations(ldvalue.String("fallthrough"), ldvalue.String("other")). + OffVariation(1). + FallthroughVariation(0). Build().MarshalJSON() require.NoError(t, err) initialFlags["uncached-flag-key"] = string(uncachedFlagKeyBytes) return &ServerSidePersistentTests{ - persistentStore: persistentStore, - initialFlags: initialFlags, + CommonStreamingTests: NewCommonStreamingTests(t, "serverSidePersistenceTests"), + persistentStore: persistentStore, + initialFlags: initialFlags, } } @@ -68,6 +78,7 @@ func (s *ServerSidePersistentTests) Run(t *ldtest.T) { t.Run("uses custom prefix", s.usesCustomPrefix) t.Run("daemon mode", s.doDaemonModeTests) + t.Run("read-write", s.doReadWriteTests) } func (s *ServerSidePersistentTests) usesDefaultPrefix(t *ldtest.T) { diff --git a/sdktests/server_side_persistence_read_write.go b/sdktests/server_side_persistence_read_write.go new file mode 100644 index 0000000..491ddbf --- /dev/null +++ b/sdktests/server_side_persistence_read_write.go @@ -0,0 +1,469 @@ +package sdktests + +import ( + "fmt" + "time" + + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + m "github.com/launchdarkly/go-test-helpers/v2/matchers" + 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" + "github.com/launchdarkly/sdk-test-harness/v2/mockld" + "github.com/launchdarkly/sdk-test-harness/v2/servicedef" + "github.com/stretchr/testify/require" +) + +func (s *ServerSidePersistentTests) doReadWriteTests(t *ldtest.T) { + // No cache is enabled + t.Run("initializes store when data received", s.initializesStoreWhenDataReceived) + t.Run("applies updates to store", s.appliesUpdatesToStore) + + t.Run("data source updates respect versioning", s.dataSourceUpdatesRespectVersioning) + t.Run("data source deletions respect versioning", s.dataSourceDeletesRespectVersioning) + + cacheConfigs := []servicedef.SDKConfigPersistentCache{ + {Mode: servicedef.Infinite}, + {Mode: servicedef.TTL, TTL: o.Some(1)}, + } + + for _, cacheConfig := range cacheConfigs { + t.Run(fmt.Sprintf("cache mode %s", cacheConfig.Mode), func(t *ldtest.T) { + t.Run("does not cache flag miss", func(t *ldtest.T) { + s.doesNotCacheFlagMiss(t, cacheConfig) + }) + t.Run("sdk reflects data source updates even with cache", func(t *ldtest.T) { + s.sdkReflectsDataSourceUpdatesEvenWithCache(t, cacheConfig) + }) + t.Run("ignores dropped flags", func(t *ldtest.T) { + s.ignoresDroppedFlagsWithForeverCache(t) + }) + }) + } + + t.Run("infinite cache", func(t *ldtest.T) { + t.Run("ignores dropped flags", s.ignoresDroppedFlagsWithForeverCache) + }) + + t.Run("ttl cache", func(t *ldtest.T) { + t.Run("ignores dropped flags", s.ignoresDroppedFlagsWithTTLCache) + }) +} + +func (s *ServerSidePersistentTests) initializesStoreWhenDataReceived(t *ldtest.T) { + require.NoError(t, s.persistentStore.Reset()) + + persistence := NewPersistence() + persistence.SetStore(servicedef.SDKConfigPersistentStore{ + Type: s.persistentStore.Type(), + DSN: s.persistentStore.DSN(), + }) + persistence.SetCache(servicedef.SDKConfigPersistentCache{ + Mode: servicedef.Off, + }) + + sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) + _, configurers := s.setupDataSources(t, sdkData) + configurers = append(configurers, persistence) + + _, err := s.persistentStore.ReadField("launchdarkly:$inited") + require.Error(t, err) // should not exist + + _ = NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) + s.eventuallyRequireDataStoreInit(t, "launchdarkly") +} + +func (s *ServerSidePersistentTests) appliesUpdatesToStore(t *ldtest.T) { + require.NoError(t, s.persistentStore.Reset()) + + persistence := NewPersistence() + persistence.SetStore(servicedef.SDKConfigPersistentStore{ + Type: s.persistentStore.Type(), + DSN: s.persistentStore.DSN(), + }) + persistence.SetCache(servicedef.SDKConfigPersistentCache{ + Mode: servicedef.Off, + }) + + sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) + stream, configurers := s.setupDataSources(t, sdkData) + configurers = append(configurers, persistence) + + _, err := s.persistentStore.ReadField("launchdarkly:$inited") + require.Error(t, err) // should not exist + + _ = NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) + s.eventuallyRequireDataStoreInit(t, "launchdarkly") + s.eventuallyValidateFlagData(t, "launchdarkly", map[string]m.Matcher{ + "flag-key": basicFlagValidationMatcher("flag-key", 1, "value"), + }) + + updateData := s.makeFlagData("flag-key", 2, ldvalue.String("new-value")) + stream.StreamingService().PushUpdate("flags", "flag-key", updateData) + s.eventuallyValidateFlagData(t, "launchdarkly", map[string]m.Matcher{ + "flag-key": basicFlagValidationMatcher("flag-key", 2, "new-value"), + }) +} + +func (s *ServerSidePersistentTests) dataSourceUpdatesRespectVersioning(t *ldtest.T) { + require.NoError(t, s.persistentStore.Reset()) + + persistence := NewPersistence() + persistence.SetStore(servicedef.SDKConfigPersistentStore{ + Type: s.persistentStore.Type(), + DSN: s.persistentStore.DSN(), + }) + persistence.SetCache(servicedef.SDKConfigPersistentCache{ + Mode: servicedef.Off, + }) + + sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) + stream, configurers := s.setupDataSources(t, sdkData) + configurers = append(configurers, persistence) + + _ = NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) + s.eventuallyRequireDataStoreInit(t, "launchdarkly") + + require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + + // Lower versioned updates are ignored + updateData := s.makeFlagData("flag-key", 1, ldvalue.String("new-value")) + stream.StreamingService().PushUpdate("flags", "flag-key", updateData) + s.neverValidateFlagData(t, "launchdarkly", map[string]m.Matcher{ + "flag-key": basicFlagValidationMatcher("flag-key", 1, "new-value"), + "uncached-flag-key": basicFlagValidationMatcher("uncached-flag-key", 100, "value"), + }) + + // Higher versioned updates are applied + updateData = s.makeFlagData("flag-key", 200, ldvalue.String("new-value")) + stream.StreamingService().PushUpdate("flags", "flag-key", updateData) + s.neverValidateFlagData(t, "launchdarkly", map[string]m.Matcher{ + "flag-key": basicFlagValidationMatcher("flag-key", 200, "new-value"), + "uncached-flag-key": basicFlagValidationMatcher("uncached-flag-key", 100, "value"), + }) +} + +func (s *ServerSidePersistentTests) dataSourceDeletesRespectVersioning(t *ldtest.T) { + require.NoError(t, s.persistentStore.Reset()) + + persistence := NewPersistence() + persistence.SetStore(servicedef.SDKConfigPersistentStore{ + Type: s.persistentStore.Type(), + DSN: s.persistentStore.DSN(), + }) + persistence.SetCache(servicedef.SDKConfigPersistentCache{ + Mode: servicedef.Off, + }) + + sdkData := s.makeSDKDataWithFlag("flag-key", 100, ldvalue.String("value")) + stream, configurers := s.setupDataSources(t, sdkData) + configurers = append(configurers, persistence) + + _ = NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) + s.eventuallyRequireDataStoreInit(t, "launchdarkly") + + require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + + // Lower versioned deletes are ignored + stream.StreamingService().PushDelete("flags", "flag-key", 1) + s.neverValidateFlagData(t, "launchdarkly", map[string]m.Matcher{ + "flag-key": basicDeletedFlagValidationMatcher(1), + "uncached-flag-key": basicFlagValidationMatcher("uncached-flag-key", 100, "fallthrough"), + }) + + // Higher versioned deletes are applied + stream.StreamingService().PushDelete("flags", "flag-key", 200) + s.eventuallyValidateFlagData(t, "launchdarkly", map[string]m.Matcher{ + "flag-key": basicDeletedFlagValidationMatcher(200), + "uncached-flag-key": basicFlagValidationMatcher("uncached-flag-key", 100, "fallthrough"), + }) +} + +func (s *ServerSidePersistentTests) ignoresDirectDatabaseModifications(t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { + require.NoError(t, s.persistentStore.Reset()) + + persistence := NewPersistence() + persistence.SetStore(servicedef.SDKConfigPersistentStore{ + Type: s.persistentStore.Type(), + DSN: s.persistentStore.DSN(), + }) + persistence.SetCache(cacheConfig) + + sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) + _, configurers := s.setupDataSources(t, sdkData) + configurers = append(configurers, persistence) + + client := NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) + context := ldcontext.New("user-key") + s.eventuallyRequireDataStoreInit(t, "launchdarkly") + + pollUntilFlagValueUpdated(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("value"), ldvalue.String("default")) + + require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + + // This key was already cached, so it shouldn't see the change above. + h.RequireNever(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "flag-key was incorrectly updated") + + if cacheConfig.Mode == servicedef.Infinite { + // But since we didn't evaluate this flag, this should actually be + // reflected by directly changing the database. + h.RequireEventually(t, + checkForUpdatedValue(t, client, "uncached-flag-key", context, + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "uncached-flag-key was incorrectly cached") + } else if cacheConfig.Mode == servicedef.TTL { + // But eventually, it will expire and then we will fetch it from the database. + h.RequireEventually(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("value"), ldvalue.String("fallthrough"), ldvalue.String("default")), + time.Second, time.Millisecond*20, "flag-key was incorrectly cached") + } +} + +func (s *ServerSidePersistentTests) ignoresFlagsBeingDiscardedFromStore(t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { + require.NoError(t, s.persistentStore.Reset()) + + persistence := NewPersistence() + persistence.SetStore(servicedef.SDKConfigPersistentStore{ + Type: s.persistentStore.Type(), + DSN: s.persistentStore.DSN(), + }) + persistence.SetCache(servicedef.SDKConfigPersistentCache{ + Mode: servicedef.Infinite, + }) + + sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) + _, configurers := s.setupDataSources(t, sdkData) + configurers = append(configurers, persistence) + + client := NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) + context := ldcontext.New("user-key") + s.eventuallyRequireDataStoreInit(t, "launchdarkly") + + pollUntilFlagValueUpdated(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("value"), ldvalue.String("default")) + + require.NoError(t, s.persistentStore.Reset()) + + // This key was already cached, so it shouldn't see the change above. + h.RequireNever(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "flag was never updated") + + if cacheConfig.Mode == servicedef.TTL { + // But eventually, it will expire and then we will fetch it from the database. + h.RequireEventually(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("value"), ldvalue.String("default"), ldvalue.String("default")), + time.Second, time.Millisecond*20, "flag-key was incorrectly cached") + } +} + +func (s *ServerSidePersistentTests) ignoresDroppedFlagsWithForeverCache(t *ldtest.T) { + require.NoError(t, s.persistentStore.Reset()) + + persistence := NewPersistence() + persistence.SetStore(servicedef.SDKConfigPersistentStore{ + Type: s.persistentStore.Type(), + DSN: s.persistentStore.DSN(), + }) + persistence.SetCache(servicedef.SDKConfigPersistentCache{ + Mode: servicedef.Infinite, + }) + + sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) + _, configurers := s.setupDataSources(t, sdkData) + configurers = append(configurers, persistence) + + client := NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) + context := ldcontext.New("user-key") + s.eventuallyRequireDataStoreInit(t, "launchdarkly") + + pollUntilFlagValueUpdated(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("value"), ldvalue.String("default")) + + require.NoError(t, s.persistentStore.Reset()) + + // This key was already cached, so it shouldn't see the change above. + h.RequireNever(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "flag was never updated") +} + +func (s *ServerSidePersistentTests) doesNotCacheFlagMiss(t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { + require.NoError(t, s.persistentStore.Reset()) + + persistence := NewPersistence() + persistence.SetStore(servicedef.SDKConfigPersistentStore{ + Type: s.persistentStore.Type(), + DSN: s.persistentStore.DSN(), + }) + persistence.SetCache(cacheConfig) + + stream, configurers := s.setupDataSources(t, mockld.NewServerSDKDataBuilder().Build()) + configurers = append(configurers, persistence) + + client := NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) + context := ldcontext.New("user-key") + s.eventuallyRequireDataStoreInit(t, "launchdarkly") + + response := client.EvaluateFlag(t, servicedef.EvaluateFlagParams{ + FlagKey: "flag-key", + Context: o.Some(context), + ValueType: servicedef.ValueTypeAny, + DefaultValue: ldvalue.String("default"), + }) + + m.In(t).Assert(response.Value, m.Equal(ldvalue.String("default"))) + + updateData := s.makeFlagData("flag-key", 2, ldvalue.String("new-value")) + stream.StreamingService().PushUpdate("flags", "flag-key", updateData) + + h.RequireEventually(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("new-value"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "flag was never updated") +} + +func (s *ServerSidePersistentTests) sdkReflectsDataSourceUpdatesEvenWithCache(t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { + require.NoError(t, s.persistentStore.Reset()) + + persistence := NewPersistence() + persistence.SetStore(servicedef.SDKConfigPersistentStore{ + Type: s.persistentStore.Type(), + DSN: s.persistentStore.DSN(), + }) + persistence.SetCache(cacheConfig) + + sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) + stream, configurers := s.setupDataSources(t, sdkData) + configurers = append(configurers, persistence) + + client := NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) + context := ldcontext.New("user-key") + s.eventuallyRequireDataStoreInit(t, "launchdarkly") + + pollUntilFlagValueUpdated(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("value"), ldvalue.String("default")) + + updateData := s.makeFlagData("flag-key", 2, ldvalue.String("new-value")) + stream.StreamingService().PushUpdate("flags", "flag-key", updateData) + + // This change is reflected in less time than the cache TTL. This should + // prove it isn't caching that value. + h.RequireEventually(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "flag was updated") +} + +func (s *ServerSidePersistentTests) ignoresDroppedFlagsWithTTLCache(t *ldtest.T) { + require.NoError(t, s.persistentStore.Reset()) + + persistence := NewPersistence() + persistence.SetStore(servicedef.SDKConfigPersistentStore{ + Type: s.persistentStore.Type(), + DSN: s.persistentStore.DSN(), + }) + persistence.SetCache(servicedef.SDKConfigPersistentCache{ + Mode: servicedef.TTL, + TTL: o.Some(1), + }) + + sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) + _, configurers := s.setupDataSources(t, sdkData) + configurers = append(configurers, persistence) + + client := NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) + context := ldcontext.New("user-key") + s.eventuallyRequireDataStoreInit(t, "launchdarkly") + + pollUntilFlagValueUpdated(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("value"), ldvalue.String("default")) + + require.NoError(t, s.persistentStore.Reset()) + + // The flag change isn't going to get noticed for some period of time. + h.RequireNever(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("value"), ldvalue.String("default"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "flag-key was incorrectly cached") + + // But eventually, it will expire and then we will fetch it from the database. + h.RequireEventually(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("value"), ldvalue.String("default"), ldvalue.String("default")), + time.Second, time.Millisecond*20, "flag-key was incorrectly cached") +} + +func (s *ServerSidePersistentTests) eventuallyRequireDataStoreInit(t *ldtest.T, prefix string) { + h.RequireEventually(t, func() bool { + _, err := s.persistentStore.ReadField(prefix + ":$inited") + return err == nil + }, time.Second, time.Millisecond*20, prefix+":$inited key was not set") +} + +func (s *ServerSidePersistentTests) eventuallyValidateFlagData(t *ldtest.T, prefix string, matchers map[string]m.Matcher) { + h.RequireEventually(t, func() bool { + data, err := s.persistentStore.ReadData(prefix + ":features") + if err != nil { + return false + } + + return validateFlagData(data, matchers) + }, time.Second, time.Millisecond*20, "flag data did not match") +} + +func (s *ServerSidePersistentTests) neverValidateFlagData(t *ldtest.T, prefix string, matchers map[string]m.Matcher) { + h.RequireNever(t, func() bool { + data, err := s.persistentStore.ReadData(prefix + ":features") + if err != nil { + return false + } + + return validateFlagData(data, matchers) + }, time.Second, time.Millisecond*20, "flag data did not match") +} + +func basicFlagValidationMatcher(key string, version int, value string) m.Matcher { + return m.AllOf( + m.JSONProperty("key").Should(m.Equal(key)), + m.JSONProperty("version").Should(m.Equal(version)), + m.JSONProperty("variations").Should(m.Equal([]interface{}{value, "other"})), + ) +} + +func basicDeletedFlagValidationMatcher(version int) m.Matcher { + return m.AllOf( + m.JSONProperty("key").Should(m.Equal("$deleted")), + m.JSONProperty("version").Should(m.Equal(version)), + m.JSONProperty("deleted").Should(m.Equal(true)), + ) +} + +func validateFlagData(data map[string]string, matchers map[string]m.Matcher) bool { + if len(data) != len(matchers) { + return false + } + + for key, matcher := range matchers { + flag, ok := data[key] + if !ok { + return false + } + + result, _ := matcher.Test(flag) + if !result { + return false + } + } + + return true +} diff --git a/sdktests/server_side_persistence_redis.go b/sdktests/server_side_persistence_redis.go index 61f3cc0..028c328 100644 --- a/sdktests/server_side_persistence_redis.go +++ b/sdktests/server_side_persistence_redis.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/launchdarkly/sdk-test-harness/v2/servicedef" "github.com/redis/go-redis/v9" ) @@ -17,11 +18,25 @@ func (r RedisPersistentStore) DSN() string { return fmt.Sprintf("redis://%s", r.redis.Options().Addr) } +func (r *RedisPersistentStore) Type() servicedef.SDKConfigPersistentType { + return servicedef.Redis +} + func (r *RedisPersistentStore) Reset() error { var ctx = context.Background() return r.redis.FlushAll(ctx).Err() } +func (r *RedisPersistentStore) ReadField(key string) (string, error) { + var ctx = context.Background() + return r.redis.Get(ctx, key).Result() +} + +func (r *RedisPersistentStore) ReadData(key string) (map[string]string, error) { + var ctx = context.Background() + return r.redis.HGetAll(ctx, key).Result() +} + func (r *RedisPersistentStore) WriteData(key string, data map[string]string) error { var ctx = context.Background() _, err := r.redis.HSet(ctx, key, data).Result() diff --git a/sdktests/testsuite_entry_point.go b/sdktests/testsuite_entry_point.go index c7f5778..3500c04 100644 --- a/sdktests/testsuite_entry_point.go +++ b/sdktests/testsuite_entry_point.go @@ -90,9 +90,12 @@ func doAllServerSideTests(t *ldtest.T) { t.Run("secure mode hash", doServerSideSecureModeHashTests) t.Run("context type", doSDKContextTypeTests) t.Run("migrations", doServerSideMigrationTests) - t.Run("persistent data store", doServerSidePersistentTests) t.Run("hooks", doCommonHooksTests) t.Run("wrapper", doServerSideWrapperTests) + + if t.Capabilities().Has(servicedef.CapabilityPersistentDataStore) { + t.Run("persistent data store", doServerSidePersistentTests) + } } func doAllClientSideTests(t *ldtest.T) { diff --git a/servicedef/service_params.go b/servicedef/service_params.go index 09e8ccf..c17938d 100644 --- a/servicedef/service_params.go +++ b/servicedef/service_params.go @@ -18,26 +18,28 @@ const ( CapabilityAllFlagsClientSideOnly = "all-flags-client-side-only" CapabilityAllFlagsDetailsOnlyForTrackedFlags = "all-flags-details-only-for-tracked-flags" - CapabilityBigSegments = "big-segments" - CapabilityContextType = "context-type" - CapabilityContextComparison = "context-comparison" - CapabilitySecureModeHash = "secure-mode-hash" - CapabilityServerSidePolling = "server-side-polling" - CapabilityServiceEndpoints = "service-endpoints" - CapabilityTags = "tags" - CapabilityUserType = "user-type" - CapabilityFiltering = "filtering" - CapabilityFilteringStrict = "filtering-strict" - CapabilityAutoEnvAttributes = "auto-env-attributes" - CapabilityMigrations = "migrations" - CapabilityEventSampling = "event-sampling" - CapabilityEventGzip = "event-gzip" - CapabilityOptionalEventGzip = "optional-event-gzip" - CapabilityETagCaching = "etag-caching" - CapabilityInlineContext = "inline-context" - CapabilityAnonymousRedaction = "anonymous-redaction" - CapabilityPollingGzip = "polling-gzip" - CapabilityEvaluationHooks = "evaluation-hooks" + CapabilityBigSegments = "big-segments" + CapabilityContextType = "context-type" + CapabilityContextComparison = "context-comparison" + CapabilitySecureModeHash = "secure-mode-hash" + CapabilityServerSidePolling = "server-side-polling" + CapabilityServiceEndpoints = "service-endpoints" + CapabilityTags = "tags" + CapabilityUserType = "user-type" + CapabilityFiltering = "filtering" + CapabilityFilteringStrict = "filtering-strict" + CapabilityAutoEnvAttributes = "auto-env-attributes" + CapabilityMigrations = "migrations" + CapabilityEventSampling = "event-sampling" + CapabilityEventGzip = "event-gzip" + CapabilityOptionalEventGzip = "optional-event-gzip" + CapabilityETagCaching = "etag-caching" + CapabilityInlineContext = "inline-context" + CapabilityAnonymousRedaction = "anonymous-redaction" + CapabilityPollingGzip = "polling-gzip" + CapabilityEvaluationHooks = "evaluation-hooks" + CapabilityPersistentDataStore = "persistent-data-store" + CapabilityPersistentDataStoreRedis = "persistent-data-store-redis" // CapabilityTLSVerifyPeer means the SDK is capable of establishing a TLS session and verifying // its peer. This is generally a standard capability of all SDKs. From 2ad925188c85cb6b4fa98957f7191e9f9e4a81dc Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Tue, 15 Oct 2024 16:00:38 -0400 Subject: [PATCH 02/10] Change read and write names --- sdktests/server_side_persistence_base.go | 10 +++++----- sdktests/server_side_persistence_daemon.go | 10 +++++----- sdktests/server_side_persistence_read_write.go | 15 ++++++++------- sdktests/server_side_persistence_redis.go | 6 +++--- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/sdktests/server_side_persistence_base.go b/sdktests/server_side_persistence_base.go index 957dc48..b48ff6e 100644 --- a/sdktests/server_side_persistence_base.go +++ b/sdktests/server_side_persistence_base.go @@ -30,9 +30,9 @@ type PersistentStore interface { // TODO: Change these names to something less terrible, or make them even // more generic. - ReadField(key string) (string, error) - ReadData(key string) (map[string]string, error) - WriteData(key string, data map[string]string) error + Get(key string) (string, error) + GetMap(key string) (map[string]string, error) + WriteMap(key string, data map[string]string) error Type() servicedef.SDKConfigPersistentType @@ -83,7 +83,7 @@ func (s *ServerSidePersistentTests) Run(t *ldtest.T) { func (s *ServerSidePersistentTests) usesDefaultPrefix(t *ldtest.T) { require.NoError(t, s.persistentStore.Reset()) - require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + require.NoError(t, s.persistentStore.WriteMap("launchdarkly:features", s.initialFlags)) persistence := NewPersistence() persistence.SetStore(servicedef.SDKConfigPersistentStore{ @@ -124,7 +124,7 @@ func (s *ServerSidePersistentTests) usesCustomPrefix(t *ldtest.T) { "flag value was updated, but it should not have been", ) - require.NoError(t, s.persistentStore.WriteData(customPrefix+":features", s.initialFlags)) + require.NoError(t, s.persistentStore.WriteMap(customPrefix+":features", s.initialFlags)) pollUntilFlagValueUpdated(t, client, "flag-key", ldcontext.New("user-key"), ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")) diff --git a/sdktests/server_side_persistence_daemon.go b/sdktests/server_side_persistence_daemon.go index 5d532bf..e0e118a 100644 --- a/sdktests/server_side_persistence_daemon.go +++ b/sdktests/server_side_persistence_daemon.go @@ -49,14 +49,14 @@ func (s *ServerSidePersistentTests) ignoresInitialization(t *ldtest.T) { result.Reason.Value().GetErrorKind() == ldreason.EvalErrorFlagNotFound }, time.Second, time.Millisecond*20, "flag was found before it should have been") - require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + require.NoError(t, s.persistentStore.WriteMap("launchdarkly:features", s.initialFlags)) pollUntilFlagValueUpdated(t, client, "flag-key", context, ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")) } func (s *ServerSidePersistentTests) canDisableCache(t *ldtest.T) { require.NoError(t, s.persistentStore.Reset()) - require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + require.NoError(t, s.persistentStore.WriteMap("launchdarkly:features", s.initialFlags)) persistence := NewPersistence() persistence.SetStore(servicedef.SDKConfigPersistentStore{ @@ -98,7 +98,7 @@ func (s *ServerSidePersistentTests) cachesFlagForDuration(t *ldtest.T) { require.NoError(t, s.persistentStore.Reset()) client := NewSDKClient(t, persistence) - require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + require.NoError(t, s.persistentStore.WriteMap("launchdarkly:features", s.initialFlags)) pollUntilFlagValueUpdated(t, client, "flag-key", context, ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")) @@ -132,7 +132,7 @@ func (s *ServerSidePersistentTests) cachesFlagForDuration(t *ldtest.T) { m.In(t).Assert(result.Value, m.Equal(ldvalue.String("default"))) m.In(t).Assert(result.Reason.Value().GetErrorKind(), m.Equal(ldreason.EvalErrorFlagNotFound)) - require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + require.NoError(t, s.persistentStore.WriteMap("launchdarkly:features", s.initialFlags)) h.RequireNever(t, checkForUpdatedValue(t, client, "flag-key", context, @@ -158,7 +158,7 @@ func (s *ServerSidePersistentTests) cachesFlagForever(t *ldtest.T) { context := ldcontext.New("user-key") require.NoError(t, s.persistentStore.Reset()) - require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + require.NoError(t, s.persistentStore.WriteMap("launchdarkly:features", s.initialFlags)) client := NewSDKClient(t, persistence) diff --git a/sdktests/server_side_persistence_read_write.go b/sdktests/server_side_persistence_read_write.go index 491ddbf..5a4002b 100644 --- a/sdktests/server_side_persistence_read_write.go +++ b/sdktests/server_side_persistence_read_write.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/go-redis/redis" "github.com/launchdarkly/go-sdk-common/v3/ldcontext" "github.com/launchdarkly/go-sdk-common/v3/ldvalue" m "github.com/launchdarkly/go-test-helpers/v2/matchers" @@ -67,7 +68,7 @@ func (s *ServerSidePersistentTests) initializesStoreWhenDataReceived(t *ldtest.T _, configurers := s.setupDataSources(t, sdkData) configurers = append(configurers, persistence) - _, err := s.persistentStore.ReadField("launchdarkly:$inited") + _, err := s.persistentStore.Get("launchdarkly:$inited") require.Error(t, err) // should not exist _ = NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) @@ -125,7 +126,7 @@ func (s *ServerSidePersistentTests) dataSourceUpdatesRespectVersioning(t *ldtest _ = NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) s.eventuallyRequireDataStoreInit(t, "launchdarkly") - require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + require.NoError(t, s.persistentStore.WriteMap("launchdarkly:features", s.initialFlags)) // Lower versioned updates are ignored updateData := s.makeFlagData("flag-key", 1, ldvalue.String("new-value")) @@ -163,7 +164,7 @@ func (s *ServerSidePersistentTests) dataSourceDeletesRespectVersioning(t *ldtest _ = NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) s.eventuallyRequireDataStoreInit(t, "launchdarkly") - require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + require.NoError(t, s.persistentStore.WriteMap("launchdarkly:features", s.initialFlags)) // Lower versioned deletes are ignored stream.StreamingService().PushDelete("flags", "flag-key", 1) @@ -201,7 +202,7 @@ func (s *ServerSidePersistentTests) ignoresDirectDatabaseModifications(t *ldtest pollUntilFlagValueUpdated(t, client, "flag-key", context, ldvalue.String("default"), ldvalue.String("value"), ldvalue.String("default")) - require.NoError(t, s.persistentStore.WriteData("launchdarkly:features", s.initialFlags)) + require.NoError(t, s.persistentStore.WriteMap("launchdarkly:features", s.initialFlags)) // This key was already cached, so it shouldn't see the change above. h.RequireNever(t, @@ -405,14 +406,14 @@ func (s *ServerSidePersistentTests) ignoresDroppedFlagsWithTTLCache(t *ldtest.T) func (s *ServerSidePersistentTests) eventuallyRequireDataStoreInit(t *ldtest.T, prefix string) { h.RequireEventually(t, func() bool { - _, err := s.persistentStore.ReadField(prefix + ":$inited") + _, err := s.persistentStore.Get(prefix + ":$inited") return err == nil }, time.Second, time.Millisecond*20, prefix+":$inited key was not set") } func (s *ServerSidePersistentTests) eventuallyValidateFlagData(t *ldtest.T, prefix string, matchers map[string]m.Matcher) { h.RequireEventually(t, func() bool { - data, err := s.persistentStore.ReadData(prefix + ":features") + data, err := s.persistentStore.GetMap(prefix + ":features") if err != nil { return false } @@ -423,7 +424,7 @@ func (s *ServerSidePersistentTests) eventuallyValidateFlagData(t *ldtest.T, pref func (s *ServerSidePersistentTests) neverValidateFlagData(t *ldtest.T, prefix string, matchers map[string]m.Matcher) { h.RequireNever(t, func() bool { - data, err := s.persistentStore.ReadData(prefix + ":features") + data, err := s.persistentStore.GetMap(prefix + ":features") if err != nil { return false } diff --git a/sdktests/server_side_persistence_redis.go b/sdktests/server_side_persistence_redis.go index 028c328..51b3847 100644 --- a/sdktests/server_side_persistence_redis.go +++ b/sdktests/server_side_persistence_redis.go @@ -27,17 +27,17 @@ func (r *RedisPersistentStore) Reset() error { return r.redis.FlushAll(ctx).Err() } -func (r *RedisPersistentStore) ReadField(key string) (string, error) { +func (r *RedisPersistentStore) Get(key string) (string, error) { var ctx = context.Background() return r.redis.Get(ctx, key).Result() } -func (r *RedisPersistentStore) ReadData(key string) (map[string]string, error) { +func (r *RedisPersistentStore) GetMap(key string) (map[string]string, error) { var ctx = context.Background() return r.redis.HGetAll(ctx, key).Result() } -func (r *RedisPersistentStore) WriteData(key string, data map[string]string) error { +func (r *RedisPersistentStore) WriteMap(key string, data map[string]string) error { var ctx = context.Background() _, err := r.redis.HSet(ctx, key, data).Result() return err From c0f23964d272fc864a7d6a87ed757c432cd0c8fe Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Tue, 15 Oct 2024 16:01:01 -0400 Subject: [PATCH 03/10] Prefix cache mode constants --- sdktests/server_side_persistence_base.go | 4 ++-- sdktests/server_side_persistence_daemon.go | 8 +++---- .../server_side_persistence_read_write.go | 22 +++++++++---------- servicedef/sdk_config.go | 6 ++--- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/sdktests/server_side_persistence_base.go b/sdktests/server_side_persistence_base.go index b48ff6e..45eb1ff 100644 --- a/sdktests/server_side_persistence_base.go +++ b/sdktests/server_side_persistence_base.go @@ -91,7 +91,7 @@ func (s *ServerSidePersistentTests) usesDefaultPrefix(t *ldtest.T) { DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Off, + Mode: servicedef.CacheModeOff, }) client := NewSDKClient(t, persistence) @@ -110,7 +110,7 @@ func (s *ServerSidePersistentTests) usesCustomPrefix(t *ldtest.T) { Prefix: customPrefix, }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Off, + Mode: servicedef.CacheModeOff, }) client := NewSDKClient(t, persistence) diff --git a/sdktests/server_side_persistence_daemon.go b/sdktests/server_side_persistence_daemon.go index e0e118a..a4614ed 100644 --- a/sdktests/server_side_persistence_daemon.go +++ b/sdktests/server_side_persistence_daemon.go @@ -29,7 +29,7 @@ func (s *ServerSidePersistentTests) ignoresInitialization(t *ldtest.T) { DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Off, + Mode: servicedef.CacheModeOff, }) context := ldcontext.New("user-key") @@ -64,7 +64,7 @@ func (s *ServerSidePersistentTests) canDisableCache(t *ldtest.T) { DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Off, + Mode: servicedef.CacheModeOff, }) context := ldcontext.New("user-key") @@ -89,7 +89,7 @@ func (s *ServerSidePersistentTests) cachesFlagForDuration(t *ldtest.T) { DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.TTL, + Mode: servicedef.CacheModeTTL, TTL: o.Some(1), }) context := ldcontext.New("user-key") @@ -153,7 +153,7 @@ func (s *ServerSidePersistentTests) cachesFlagForever(t *ldtest.T) { DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Infinite, + Mode: servicedef.CacheModeInfinite, }) context := ldcontext.New("user-key") diff --git a/sdktests/server_side_persistence_read_write.go b/sdktests/server_side_persistence_read_write.go index 5a4002b..8b85f72 100644 --- a/sdktests/server_side_persistence_read_write.go +++ b/sdktests/server_side_persistence_read_write.go @@ -25,8 +25,8 @@ func (s *ServerSidePersistentTests) doReadWriteTests(t *ldtest.T) { t.Run("data source deletions respect versioning", s.dataSourceDeletesRespectVersioning) cacheConfigs := []servicedef.SDKConfigPersistentCache{ - {Mode: servicedef.Infinite}, - {Mode: servicedef.TTL, TTL: o.Some(1)}, + {Mode: servicedef.CacheModeInfinite}, + {Mode: servicedef.CacheModeTTL, TTL: o.Some(1)}, } for _, cacheConfig := range cacheConfigs { @@ -61,7 +61,7 @@ func (s *ServerSidePersistentTests) initializesStoreWhenDataReceived(t *ldtest.T DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Off, + Mode: servicedef.CacheModeOff, }) sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) @@ -116,7 +116,7 @@ func (s *ServerSidePersistentTests) dataSourceUpdatesRespectVersioning(t *ldtest DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Off, + Mode: servicedef.CacheModeOff, }) sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) @@ -154,7 +154,7 @@ func (s *ServerSidePersistentTests) dataSourceDeletesRespectVersioning(t *ldtest DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Off, + Mode: servicedef.CacheModeOff, }) sdkData := s.makeSDKDataWithFlag("flag-key", 100, ldvalue.String("value")) @@ -210,14 +210,14 @@ func (s *ServerSidePersistentTests) ignoresDirectDatabaseModifications(t *ldtest ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), time.Millisecond*500, time.Millisecond*20, "flag-key was incorrectly updated") - if cacheConfig.Mode == servicedef.Infinite { + if cacheConfig.Mode == servicedef.CacheModeInfinite { // But since we didn't evaluate this flag, this should actually be // reflected by directly changing the database. h.RequireEventually(t, checkForUpdatedValue(t, client, "uncached-flag-key", context, ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")), time.Millisecond*500, time.Millisecond*20, "uncached-flag-key was incorrectly cached") - } else if cacheConfig.Mode == servicedef.TTL { + } else if cacheConfig.Mode == servicedef.CacheModeTTL { // But eventually, it will expire and then we will fetch it from the database. h.RequireEventually(t, checkForUpdatedValue(t, client, "flag-key", context, @@ -235,7 +235,7 @@ func (s *ServerSidePersistentTests) ignoresFlagsBeingDiscardedFromStore(t *ldtes DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Infinite, + Mode: servicedef.CacheModeInfinite, }) sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) @@ -257,7 +257,7 @@ func (s *ServerSidePersistentTests) ignoresFlagsBeingDiscardedFromStore(t *ldtes ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), time.Millisecond*500, time.Millisecond*20, "flag was never updated") - if cacheConfig.Mode == servicedef.TTL { + if cacheConfig.Mode == servicedef.CacheModeTTL { // But eventually, it will expire and then we will fetch it from the database. h.RequireEventually(t, checkForUpdatedValue(t, client, "flag-key", context, @@ -275,7 +275,7 @@ func (s *ServerSidePersistentTests) ignoresDroppedFlagsWithForeverCache(t *ldtes DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Infinite, + Mode: servicedef.CacheModeInfinite, }) sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) @@ -374,7 +374,7 @@ func (s *ServerSidePersistentTests) ignoresDroppedFlagsWithTTLCache(t *ldtest.T) DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.TTL, + Mode: servicedef.CacheModeTTL, TTL: o.Some(1), }) diff --git a/servicedef/sdk_config.go b/servicedef/sdk_config.go index fdafe81..d45b9f8 100644 --- a/servicedef/sdk_config.go +++ b/servicedef/sdk_config.go @@ -123,9 +123,9 @@ type SDKConfigPersistentStore struct { type SDKConfigPersistentMode string const ( - Off = SDKConfigPersistentMode("off") - TTL = SDKConfigPersistentMode("ttl") - Infinite = SDKConfigPersistentMode("infinite") + CacheModeOff = SDKConfigPersistentMode("off") + CacheModeTTL = SDKConfigPersistentMode("ttl") + CacheModeInfinite = SDKConfigPersistentMode("infinite") ) type SDKConfigPersistentCache struct { From ef8c0ecbd34deb6db3ca0cd62037450505b1640c Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Tue, 15 Oct 2024 16:01:16 -0400 Subject: [PATCH 04/10] Add version equality test --- sdktests/server_side_persistence_read_write.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sdktests/server_side_persistence_read_write.go b/sdktests/server_side_persistence_read_write.go index 8b85f72..c412f81 100644 --- a/sdktests/server_side_persistence_read_write.go +++ b/sdktests/server_side_persistence_read_write.go @@ -136,6 +136,14 @@ func (s *ServerSidePersistentTests) dataSourceUpdatesRespectVersioning(t *ldtest "uncached-flag-key": basicFlagValidationMatcher("uncached-flag-key", 100, "value"), }) + // Same versioned updates are ignored + updateData = s.makeFlagData("flag-key", 100, ldvalue.String("new-value")) + stream.StreamingService().PushUpdate("flags", "flag-key", updateData) + s.neverValidateFlagData(t, "launchdarkly", map[string]m.Matcher{ + "flag-key": basicFlagValidationMatcher("flag-key", 1, "new-value"), + "uncached-flag-key": basicFlagValidationMatcher("uncached-flag-key", 100, "value"), + }) + // Higher versioned updates are applied updateData = s.makeFlagData("flag-key", 200, ldvalue.String("new-value")) stream.StreamingService().PushUpdate("flags", "flag-key", updateData) From 10aad72b675e666bc1d95828cb5a541dd35ba1fa Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Tue, 15 Oct 2024 16:01:29 -0400 Subject: [PATCH 05/10] More specific assertion to ensure success --- go.mod | 11 ++- go.sum | 90 ++++++++++++++++++- .../server_side_persistence_read_write.go | 10 ++- 3 files changed, 101 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 0f76bbb..b346f9e 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,12 @@ module github.com/launchdarkly/sdk-test-harness/v2 -go 1.19 +go 1.22 + +toolchain go1.23.1 require ( github.com/fatih/color v1.13.0 + github.com/go-redis/redis v6.15.9+incompatible github.com/gorilla/mux v1.8.0 github.com/launchdarkly/eventsource v1.6.2 github.com/launchdarkly/go-jsonstream/v3 v3.0.0 @@ -13,7 +16,7 @@ require ( github.com/redis/go-redis/v9 v9.6.1 github.com/stretchr/testify v1.7.0 golang.org/x/exp v0.0.0-20220823124025-807a23277127 - gopkg.in/yaml.v3 v3.0.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -25,7 +28,9 @@ require ( github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.34.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/go.sum b/go.sum index 854ba58..f8f5a48 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -9,12 +11,33 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/launchdarkly/eventsource v1.6.2 h1:5SbcIqzUomn+/zmJDrkb4LYw7ryoKFzH/0TbR0/3Bdg= github.com/launchdarkly/eventsource v1.6.2/go.mod h1:LHxSeb4OnqznNZxCSXbFghxS/CjIQfzHovNoAqbO/Wk= github.com/launchdarkly/go-jsonstream/v3 v3.0.0 h1:qJF/WI09EUJ7kSpmP5d1Rhc81NQdYUhP17McKfUq17E= @@ -29,6 +52,7 @@ github.com/launchdarkly/go-test-helpers/v2 v2.2.0/go.mod h1:L7+th5govYp5oKU9iN7T github.com/launchdarkly/go-test-helpers/v2 v2.3.2 h1:WX6qSzt7v8xz6d94nVcoil9ljuLTC/6OzQt0MhWYxsQ= github.com/launchdarkly/go-test-helpers/v2 v2.3.2/go.mod h1:L7+th5govYp5oKU9iN7To5PgznBuIjBPn+ejqKR0avw= github.com/launchdarkly/go-test-helpers/v3 v3.0.1 h1:Z4lUVrh7+hIvL47KVjEBE/owbqqjKUEYTp4aBX/5OZM= +github.com/launchdarkly/go-test-helpers/v3 v3.0.1/go.mod h1:u2ZvJlc/DDJTFrshWW50tWMZHLVYXofuSHUfTU/eIwM= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= @@ -36,6 +60,17 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= +github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= @@ -48,17 +83,64 @@ github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20220823124025-807a23277127 h1:S4NrSKDfihhl3+4jSTgwoIevKxX9p7Iv9x++OEIptDo= golang.org/x/exp v0.0.0-20220823124025-807a23277127/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdktests/server_side_persistence_read_write.go b/sdktests/server_side_persistence_read_write.go index c412f81..cb8e4b1 100644 --- a/sdktests/server_side_persistence_read_write.go +++ b/sdktests/server_side_persistence_read_write.go @@ -84,15 +84,19 @@ func (s *ServerSidePersistentTests) appliesUpdatesToStore(t *ldtest.T) { DSN: s.persistentStore.DSN(), }) persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.Off, + Mode: servicedef.CacheModeOff, }) sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) stream, configurers := s.setupDataSources(t, sdkData) configurers = append(configurers, persistence) - _, err := s.persistentStore.ReadField("launchdarkly:$inited") - require.Error(t, err) // should not exist + _, err := s.persistentStore.Get("launchdarkly:$inited") + if err != nil && err.Error() == string(redis.Nil) { + // as expected, inited key does not exist + } else { + require.Fail(t, "unexpected error failure", err) + } _ = NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) s.eventuallyRequireDataStoreInit(t, "launchdarkly") From 7d6ecfc9295f5370a5c4b10b9df2c2d5a663b946 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Tue, 15 Oct 2024 16:59:08 -0400 Subject: [PATCH 06/10] timeout based on cache --- go.mod | 4 +--- go.sum | 9 -------- .../server_side_persistence_read_write.go | 23 +++++++++++++------ servicedef/sdk_config.go | 2 +- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index b346f9e..997eac1 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/launchdarkly/sdk-test-harness/v2 -go 1.22 - -toolchain go1.23.1 +go 1.19 require ( github.com/fatih/color v1.13.0 diff --git a/go.sum b/go.sum index f8f5a48..02cf262 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= -github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= -github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -28,16 +26,13 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/launchdarkly/eventsource v1.6.2 h1:5SbcIqzUomn+/zmJDrkb4LYw7ryoKFzH/0TbR0/3Bdg= github.com/launchdarkly/eventsource v1.6.2/go.mod h1:LHxSeb4OnqznNZxCSXbFghxS/CjIQfzHovNoAqbO/Wk= github.com/launchdarkly/go-jsonstream/v3 v3.0.0 h1:qJF/WI09EUJ7kSpmP5d1Rhc81NQdYUhP17McKfUq17E= @@ -52,7 +47,6 @@ github.com/launchdarkly/go-test-helpers/v2 v2.2.0/go.mod h1:L7+th5govYp5oKU9iN7T github.com/launchdarkly/go-test-helpers/v2 v2.3.2 h1:WX6qSzt7v8xz6d94nVcoil9ljuLTC/6OzQt0MhWYxsQ= github.com/launchdarkly/go-test-helpers/v2 v2.3.2/go.mod h1:L7+th5govYp5oKU9iN7To5PgznBuIjBPn+ejqKR0avw= github.com/launchdarkly/go-test-helpers/v3 v3.0.1 h1:Z4lUVrh7+hIvL47KVjEBE/owbqqjKUEYTp4aBX/5OZM= -github.com/launchdarkly/go-test-helpers/v3 v3.0.1/go.mod h1:u2ZvJlc/DDJTFrshWW50tWMZHLVYXofuSHUfTU/eIwM= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= @@ -96,7 +90,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -117,7 +110,6 @@ golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -133,7 +125,6 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/sdktests/server_side_persistence_read_write.go b/sdktests/server_side_persistence_read_write.go index cb8e4b1..dfce3e1 100644 --- a/sdktests/server_side_persistence_read_write.go +++ b/sdktests/server_side_persistence_read_write.go @@ -37,6 +37,9 @@ func (s *ServerSidePersistentTests) doReadWriteTests(t *ldtest.T) { t.Run("sdk reflects data source updates even with cache", func(t *ldtest.T) { s.sdkReflectsDataSourceUpdatesEvenWithCache(t, cacheConfig) }) + t.Run("ignores direct database modifications", func(t *ldtest.T) { + s.ignoresDirectDatabaseModifications(t, cacheConfig) + }) t.Run("ignores dropped flags", func(t *ldtest.T) { s.ignoresDroppedFlagsWithForeverCache(t) }) @@ -216,13 +219,13 @@ func (s *ServerSidePersistentTests) ignoresDirectDatabaseModifications(t *ldtest require.NoError(t, s.persistentStore.WriteMap("launchdarkly:features", s.initialFlags)) - // This key was already cached, so it shouldn't see the change above. - h.RequireNever(t, - checkForUpdatedValue(t, client, "flag-key", context, - ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), - time.Millisecond*500, time.Millisecond*20, "flag-key was incorrectly updated") - if cacheConfig.Mode == servicedef.CacheModeInfinite { + // This key was already cached, so it shouldn't see the change above. + h.RequireNever(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "flag-key was incorrectly updated") + // But since we didn't evaluate this flag, this should actually be // reflected by directly changing the database. h.RequireEventually(t, @@ -230,11 +233,17 @@ func (s *ServerSidePersistentTests) ignoresDirectDatabaseModifications(t *ldtest ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")), time.Millisecond*500, time.Millisecond*20, "uncached-flag-key was incorrectly cached") } else if cacheConfig.Mode == servicedef.CacheModeTTL { + // This key was already cached, so it shouldn't see the change above. + h.RequireNever(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), + time.Duration(int(time.Second)*cacheConfig.TTL.Value()/2), time.Millisecond*20, "flag-key was incorrectly updated") + // But eventually, it will expire and then we will fetch it from the database. h.RequireEventually(t, checkForUpdatedValue(t, client, "flag-key", context, ldvalue.String("value"), ldvalue.String("fallthrough"), ldvalue.String("default")), - time.Second, time.Millisecond*20, "flag-key was incorrectly cached") + time.Duration(int(time.Second)*cacheConfig.TTL.Value()), time.Millisecond*20, "flag-key was incorrectly cached") } } diff --git a/servicedef/sdk_config.go b/servicedef/sdk_config.go index d45b9f8..ad736d6 100644 --- a/servicedef/sdk_config.go +++ b/servicedef/sdk_config.go @@ -3,12 +3,12 @@ package servicedef import ( "encoding/json" + "github.com/launchdarkly/go-sdk-common/v3/ldtime" "github.com/launchdarkly/go-sdk-common/v3/ldvalue" o "github.com/launchdarkly/sdk-test-harness/v2/framework/opt" "github.com/launchdarkly/go-sdk-common/v3/ldcontext" - "github.com/launchdarkly/go-sdk-common/v3/ldtime" ) type SDKConfigParams struct { From d44b52c43210aabfc3bfec2e8b4a76c61a7de9a0 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Wed, 16 Oct 2024 16:47:05 -0400 Subject: [PATCH 07/10] nolint --- sdktests/server_side_persistence_read_write.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdktests/server_side_persistence_read_write.go b/sdktests/server_side_persistence_read_write.go index dfce3e1..cba16c6 100644 --- a/sdktests/server_side_persistence_read_write.go +++ b/sdktests/server_side_persistence_read_write.go @@ -425,6 +425,7 @@ func (s *ServerSidePersistentTests) ignoresDroppedFlagsWithTTLCache(t *ldtest.T) time.Second, time.Millisecond*20, "flag-key was incorrectly cached") } +//nolint:unparam func (s *ServerSidePersistentTests) eventuallyRequireDataStoreInit(t *ldtest.T, prefix string) { h.RequireEventually(t, func() bool { _, err := s.persistentStore.Get(prefix + ":$inited") @@ -443,6 +444,7 @@ func (s *ServerSidePersistentTests) eventuallyValidateFlagData(t *ldtest.T, pref }, time.Second, time.Millisecond*20, "flag data did not match") } +//nolint:unparam func (s *ServerSidePersistentTests) neverValidateFlagData(t *ldtest.T, prefix string, matchers map[string]m.Matcher) { h.RequireNever(t, func() bool { data, err := s.persistentStore.GetMap(prefix + ":features") From f0abb6071fc41bf715e7ee55b0d422ec05e149b6 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Wed, 16 Oct 2024 16:50:39 -0400 Subject: [PATCH 08/10] goimport --- sdktests/server_side_persistence_read_write.go | 3 ++- sdktests/server_side_persistence_redis.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sdktests/server_side_persistence_read_write.go b/sdktests/server_side_persistence_read_write.go index cba16c6..5710656 100644 --- a/sdktests/server_side_persistence_read_write.go +++ b/sdktests/server_side_persistence_read_write.go @@ -5,6 +5,8 @@ import ( "time" "github.com/go-redis/redis" + "github.com/stretchr/testify/require" + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" "github.com/launchdarkly/go-sdk-common/v3/ldvalue" m "github.com/launchdarkly/go-test-helpers/v2/matchers" @@ -13,7 +15,6 @@ import ( o "github.com/launchdarkly/sdk-test-harness/v2/framework/opt" "github.com/launchdarkly/sdk-test-harness/v2/mockld" "github.com/launchdarkly/sdk-test-harness/v2/servicedef" - "github.com/stretchr/testify/require" ) func (s *ServerSidePersistentTests) doReadWriteTests(t *ldtest.T) { diff --git a/sdktests/server_side_persistence_redis.go b/sdktests/server_side_persistence_redis.go index 51b3847..f4db116 100644 --- a/sdktests/server_side_persistence_redis.go +++ b/sdktests/server_side_persistence_redis.go @@ -4,8 +4,9 @@ import ( "context" "fmt" - "github.com/launchdarkly/sdk-test-harness/v2/servicedef" "github.com/redis/go-redis/v9" + + "github.com/launchdarkly/sdk-test-harness/v2/servicedef" ) type RedisPersistentStore struct { From 492659bbbd1b881abed6bbb2c4ad273baffaa523 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Wed, 16 Oct 2024 16:51:37 -0400 Subject: [PATCH 09/10] long lines --- sdktests/server_side_persistence_read_write.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sdktests/server_side_persistence_read_write.go b/sdktests/server_side_persistence_read_write.go index 5710656..a36c479 100644 --- a/sdktests/server_side_persistence_read_write.go +++ b/sdktests/server_side_persistence_read_write.go @@ -197,7 +197,8 @@ func (s *ServerSidePersistentTests) dataSourceDeletesRespectVersioning(t *ldtest }) } -func (s *ServerSidePersistentTests) ignoresDirectDatabaseModifications(t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { +func (s *ServerSidePersistentTests) ignoresDirectDatabaseModifications( + t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { require.NoError(t, s.persistentStore.Reset()) persistence := NewPersistence() @@ -248,7 +249,8 @@ func (s *ServerSidePersistentTests) ignoresDirectDatabaseModifications(t *ldtest } } -func (s *ServerSidePersistentTests) ignoresFlagsBeingDiscardedFromStore(t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { +func (s *ServerSidePersistentTests) ignoresFlagsBeingDiscardedFromStore( + t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { require.NoError(t, s.persistentStore.Reset()) persistence := NewPersistence() @@ -355,7 +357,8 @@ func (s *ServerSidePersistentTests) doesNotCacheFlagMiss(t *ldtest.T, cacheConfi time.Millisecond*500, time.Millisecond*20, "flag was never updated") } -func (s *ServerSidePersistentTests) sdkReflectsDataSourceUpdatesEvenWithCache(t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { +func (s *ServerSidePersistentTests) sdkReflectsDataSourceUpdatesEvenWithCache( + t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { require.NoError(t, s.persistentStore.Reset()) persistence := NewPersistence() @@ -434,7 +437,8 @@ func (s *ServerSidePersistentTests) eventuallyRequireDataStoreInit(t *ldtest.T, }, time.Second, time.Millisecond*20, prefix+":$inited key was not set") } -func (s *ServerSidePersistentTests) eventuallyValidateFlagData(t *ldtest.T, prefix string, matchers map[string]m.Matcher) { +func (s *ServerSidePersistentTests) eventuallyValidateFlagData( + t *ldtest.T, prefix string, matchers map[string]m.Matcher) { h.RequireEventually(t, func() bool { data, err := s.persistentStore.GetMap(prefix + ":features") if err != nil { From 44351d5cfb45b220c55efe9e3c9bdbaa72147e6b Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Wed, 16 Oct 2024 17:03:38 -0400 Subject: [PATCH 10/10] combine test --- sdktests/server_side_persistence_base.go | 2 - .../server_side_persistence_read_write.go | 87 +------------------ 2 files changed, 3 insertions(+), 86 deletions(-) diff --git a/sdktests/server_side_persistence_base.go b/sdktests/server_side_persistence_base.go index 45eb1ff..0b2dac3 100644 --- a/sdktests/server_side_persistence_base.go +++ b/sdktests/server_side_persistence_base.go @@ -28,8 +28,6 @@ func doServerSidePersistentTests(t *ldtest.T) { type PersistentStore interface { DSN() string - // TODO: Change these names to something less terrible, or make them even - // more generic. Get(key string) (string, error) GetMap(key string) (map[string]string, error) WriteMap(key string, data map[string]string) error diff --git a/sdktests/server_side_persistence_read_write.go b/sdktests/server_side_persistence_read_write.go index a36c479..b11deb7 100644 --- a/sdktests/server_side_persistence_read_write.go +++ b/sdktests/server_side_persistence_read_write.go @@ -42,18 +42,10 @@ func (s *ServerSidePersistentTests) doReadWriteTests(t *ldtest.T) { s.ignoresDirectDatabaseModifications(t, cacheConfig) }) t.Run("ignores dropped flags", func(t *ldtest.T) { - s.ignoresDroppedFlagsWithForeverCache(t) + s.ignoresFlagsBeingDiscardedFromStore(t, cacheConfig) }) }) } - - t.Run("infinite cache", func(t *ldtest.T) { - t.Run("ignores dropped flags", s.ignoresDroppedFlagsWithForeverCache) - }) - - t.Run("ttl cache", func(t *ldtest.T) { - t.Run("ignores dropped flags", s.ignoresDroppedFlagsWithTTLCache) - }) } func (s *ServerSidePersistentTests) initializesStoreWhenDataReceived(t *ldtest.T) { @@ -258,9 +250,7 @@ func (s *ServerSidePersistentTests) ignoresFlagsBeingDiscardedFromStore( Type: s.persistentStore.Type(), DSN: s.persistentStore.DSN(), }) - persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.CacheModeInfinite, - }) + persistence.SetCache(cacheConfig) sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) _, configurers := s.setupDataSources(t, sdkData) @@ -278,7 +268,7 @@ func (s *ServerSidePersistentTests) ignoresFlagsBeingDiscardedFromStore( // This key was already cached, so it shouldn't see the change above. h.RequireNever(t, checkForUpdatedValue(t, client, "flag-key", context, - ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), + ldvalue.String("value"), ldvalue.String("default"), ldvalue.String("default")), time.Millisecond*500, time.Millisecond*20, "flag was never updated") if cacheConfig.Mode == servicedef.CacheModeTTL { @@ -290,38 +280,6 @@ func (s *ServerSidePersistentTests) ignoresFlagsBeingDiscardedFromStore( } } -func (s *ServerSidePersistentTests) ignoresDroppedFlagsWithForeverCache(t *ldtest.T) { - require.NoError(t, s.persistentStore.Reset()) - - persistence := NewPersistence() - persistence.SetStore(servicedef.SDKConfigPersistentStore{ - Type: s.persistentStore.Type(), - DSN: s.persistentStore.DSN(), - }) - persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.CacheModeInfinite, - }) - - sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) - _, configurers := s.setupDataSources(t, sdkData) - configurers = append(configurers, persistence) - - client := NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) - context := ldcontext.New("user-key") - s.eventuallyRequireDataStoreInit(t, "launchdarkly") - - pollUntilFlagValueUpdated(t, client, "flag-key", context, - ldvalue.String("default"), ldvalue.String("value"), ldvalue.String("default")) - - require.NoError(t, s.persistentStore.Reset()) - - // This key was already cached, so it shouldn't see the change above. - h.RequireNever(t, - checkForUpdatedValue(t, client, "flag-key", context, - ldvalue.String("value"), ldvalue.String("new-value"), ldvalue.String("default")), - time.Millisecond*500, time.Millisecond*20, "flag was never updated") -} - func (s *ServerSidePersistentTests) doesNotCacheFlagMiss(t *ldtest.T, cacheConfig servicedef.SDKConfigPersistentCache) { require.NoError(t, s.persistentStore.Reset()) @@ -390,45 +348,6 @@ func (s *ServerSidePersistentTests) sdkReflectsDataSourceUpdatesEvenWithCache( time.Millisecond*500, time.Millisecond*20, "flag was updated") } -func (s *ServerSidePersistentTests) ignoresDroppedFlagsWithTTLCache(t *ldtest.T) { - require.NoError(t, s.persistentStore.Reset()) - - persistence := NewPersistence() - persistence.SetStore(servicedef.SDKConfigPersistentStore{ - Type: s.persistentStore.Type(), - DSN: s.persistentStore.DSN(), - }) - persistence.SetCache(servicedef.SDKConfigPersistentCache{ - Mode: servicedef.CacheModeTTL, - TTL: o.Some(1), - }) - - sdkData := s.makeSDKDataWithFlag("flag-key", 1, ldvalue.String("value")) - _, configurers := s.setupDataSources(t, sdkData) - configurers = append(configurers, persistence) - - client := NewSDKClient(t, s.baseSDKConfigurationPlus(configurers...)...) - context := ldcontext.New("user-key") - s.eventuallyRequireDataStoreInit(t, "launchdarkly") - - pollUntilFlagValueUpdated(t, client, "flag-key", context, - ldvalue.String("default"), ldvalue.String("value"), ldvalue.String("default")) - - require.NoError(t, s.persistentStore.Reset()) - - // The flag change isn't going to get noticed for some period of time. - h.RequireNever(t, - checkForUpdatedValue(t, client, "flag-key", context, - ldvalue.String("value"), ldvalue.String("default"), ldvalue.String("default")), - time.Millisecond*500, time.Millisecond*20, "flag-key was incorrectly cached") - - // But eventually, it will expire and then we will fetch it from the database. - h.RequireEventually(t, - checkForUpdatedValue(t, client, "flag-key", context, - ldvalue.String("value"), ldvalue.String("default"), ldvalue.String("default")), - time.Second, time.Millisecond*20, "flag-key was incorrectly cached") -} - //nolint:unparam func (s *ServerSidePersistentTests) eventuallyRequireDataStoreInit(t *ldtest.T, prefix string) { h.RequireEventually(t, func() bool {