Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
2edfc8d
chore: Add support for persistent store contract tests.
kinyoklion Nov 3, 2025
baeb833
Merge main into rlamb/persistence-contract-tests
devin-ai-integration[bot] Mar 9, 2026
eb918dd
fix: add use_redis flag to server-redis.yml workflow steps
devin-ai-integration[bot] Mar 9, 2026
d960e6f
fix: update OpenSSL chocolatey version from 3.5.4 to 3.6.1 for Window…
devin-ai-integration[bot] Mar 9, 2026
14b2e22
Revert "fix: update OpenSSL chocolatey version from 3.5.4 to 3.6.1 fo…
devin-ai-integration[bot] Mar 9, 2026
d09ca12
Merge remote-tracking branch 'origin/main' into rlamb/persistence-con…
devin-ai-integration[bot] Mar 10, 2026
b9a13e4
fix: move enable_persistence_tests to Redis contract test jobs
devin-ai-integration[bot] Mar 12, 2026
9a95f3e
fix: change use_redis default to empty string to preserve auto-detection
devin-ai-integration[bot] Mar 12, 2026
bdcb385
fix: update contract-tests action to v1.3.0 for enable_persistence_te…
devin-ai-integration[bot] Mar 12, 2026
8d4e39c
fix: correct TTL field name mismatch in persistent cache config
devin-ai-integration[bot] Mar 12, 2026
e5bf095
fix: allow lazy load evaluations when $inited key is not set
devin-ai-integration[bot] Mar 12, 2026
cc44864
fix: move warn-and-proceed logic to evaluation path for lazy load
devin-ai-integration[bot] Mar 13, 2026
6cf1a3c
Merge branch 'devin/1773359647-lazy-load-initialized-warning' into rl…
devin-ai-integration[bot] Mar 13, 2026
3fe6eb3
chore: add suppressions for read-write persistent store tests
devin-ai-integration[bot] Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/actions/ci/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ inputs:
description: 'Whether to install CURL development libraries. Required for OpenTelemetry builds (server-sdk-otel), but does not enable CURL networking for the SDK itself.'
required: false
default: 'false'
use_redis:
description: 'Whether to enable Redis support (LD_BUILD_REDIS_SUPPORT=ON). Leave unset to preserve automatic detection for Redis targets.'
required: false
default: ''

runs:
using: composite
Expand All @@ -58,7 +62,7 @@ runs:
id: install-curl
- name: Build Library
shell: bash
run: ./scripts/build.sh ${{ inputs.cmake_target }} ON ${{ inputs.use_curl }}
run: ./scripts/build.sh ${{ inputs.cmake_target }} ON ${{ inputs.use_curl }} ${{ inputs.use_redis }}
env:
BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }}
Boost_DIR: ${{ steps.install-boost.outputs.Boost_DIR }}
Expand All @@ -69,7 +73,7 @@ runs:
id: build-tests
if: inputs.run_tests == 'true'
shell: bash
run: ./scripts/build.sh gtest_${{ inputs.cmake_target }} ON ${{ inputs.use_curl }}
run: ./scripts/build.sh gtest_${{ inputs.cmake_target }} ON ${{ inputs.use_curl }} ${{ inputs.use_redis }}
env:
BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }}
Boost_DIR: ${{ steps.install-boost.outputs.Boost_DIR }}
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/server-redis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,61 @@ on:
- cron: '0 8 * * *'

jobs:
contract-tests:
runs-on: ubuntu-22.04
services:
redis:
image: redis
ports:
- 6379:6379
env:
TEST_SERVICE_PORT: 8123
TEST_SERVICE_BINARY: ./build/contract-tests/server-contract-tests/server-tests
steps:
# https://github.com/actions/checkout/releases/tag/v4.3.0
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955
- uses: ./.github/actions/ci
with:
cmake_target: server-tests
run_tests: false
use_redis: true
- name: 'Launch test service as background task'
run: $TEST_SERVICE_BINARY $TEST_SERVICE_PORT 2>&1 &
# https://github.com/launchdarkly/gh-actions/releases/tag/contract-tests-v1.1.0
- uses: launchdarkly/gh-actions/actions/contract-tests@2715574e04448246bc529a23a81766491bbc4aae
with:
test_service_port: ${{ env.TEST_SERVICE_PORT }}
token: ${{ secrets.GITHUB_TOKEN }}
enable_persistence_tests: true

contract-tests-curl:
runs-on: ubuntu-22.04
services:
redis:
image: redis
ports:
- 6379:6379
env:
TEST_SERVICE_PORT: 8123
TEST_SERVICE_BINARY: ./build/contract-tests/server-contract-tests/server-tests
steps:
# https://github.com/actions/checkout/releases/tag/v4.3.0
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955
- uses: ./.github/actions/ci
with:
cmake_target: server-tests
run_tests: false
use_curl: true
use_redis: true
- name: 'Launch test service as background task'
run: $TEST_SERVICE_BINARY $TEST_SERVICE_PORT 2>&1 &
# https://github.com/launchdarkly/gh-actions/releases/tag/contract-tests-v1.1.0
- uses: launchdarkly/gh-actions/actions/contract-tests@2715574e04448246bc529a23a81766491bbc4aae
with:
test_service_port: ${{ env.TEST_SERVICE_PORT }}
token: ${{ secrets.GITHUB_TOKEN }}
enable_persistence_tests: true

build-test-redis:
runs-on: ubuntu-22.04
services:
Expand All @@ -28,6 +83,7 @@ jobs:
with:
cmake_target: launchdarkly-cpp-server-redis-source
simulate_release: true
use_redis: true
build-redis-mac:
runs-on: macos-15
steps:
Expand All @@ -39,6 +95,7 @@ jobs:
platform_version: 12
run_tests: false # TODO: figure out how to run Redis service on Mac
simulate_release: true
use_redis: true
build-test-redis-windows:
runs-on: windows-2022
steps:
Expand All @@ -57,3 +114,4 @@ jobs:
toolset: msvc
run_tests: false # TODO: figure out how to run Redis service on Windows
simulate_windows_release: true
use_redis: true
32 changes: 31 additions & 1 deletion contract-tests/data-model/include/data_model/data_model.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#pragma once

#include <optional>
Expand Down Expand Up @@ -150,6 +150,34 @@

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigWrapper, name, version);

struct ConfigPersistentCache {
std::string mode; // "off", "ttl", "infinite"
std::optional<int> ttlMs;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPersistentCache,
mode,
ttlMs);

struct ConfigPersistentStore {
std::string type; // "redis", "consul", "dynamodb"
std::string dsn;
std::optional<std::string> prefix;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPersistentStore,
type,
dsn,
prefix);

struct ConfigPersistentDataStore {
ConfigPersistentStore store;
ConfigPersistentCache cache;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPersistentDataStore,
store,
cache);
struct ConfigParams {
std::string credential;
std::optional<uint32_t> startWaitTimeMs;
Expand All @@ -164,6 +192,7 @@
std::optional<ConfigProxyParams> proxy;
std::optional<ConfigHooksParams> hooks;
std::optional<ConfigWrapper> wrapper;
std::optional<ConfigPersistentDataStore> persistentDataStore;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams,
Expand All @@ -179,7 +208,8 @@
tls,
proxy,
hooks,
wrapper);
wrapper,
persistentDataStore);

struct ContextSingleParams {
std::optional<std::string> kind;
Expand Down
5 changes: 5 additions & 0 deletions contract-tests/server-contract-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ target_link_libraries(server-tests PRIVATE
contract-test-data-model
)

if (LD_BUILD_REDIS_SUPPORT)
target_link_libraries(server-tests PRIVATE launchdarkly::server_redis_source)
target_compile_definitions(server-tests PRIVATE LD_REDIS_SUPPORT_ENABLED)
endif ()

target_include_directories(server-tests PUBLIC include)
54 changes: 54 additions & 0 deletions contract-tests/server-contract-tests/src/entity_manager.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#include "entity_manager.hpp"
#include "contract_test_hook.hpp"

Expand All @@ -7,6 +7,10 @@

#include <boost/json.hpp>

#ifdef LD_REDIS_SUPPORT_ENABLED
#include <launchdarkly/server_side/integrations/redis/redis_source.hpp>
#endif

using launchdarkly::LogLevel;
using namespace launchdarkly::server_side;

Expand Down Expand Up @@ -154,6 +158,56 @@
}
}

#ifdef LD_REDIS_SUPPORT_ENABLED
if (in.persistentDataStore) {
if (in.persistentDataStore->store.type == "redis") {
std::string prefix =
in.persistentDataStore->store.prefix.value_or("launchdarkly");

auto redis_result = launchdarkly::server_side::integrations::
RedisDataSource::Create(in.persistentDataStore->store.dsn,
prefix);

if (!redis_result) {
LD_LOG(logger_, LogLevel::kWarn)
<< "entity_manager: couldn't create Redis data source: "
<< redis_result.error();
return std::nullopt;
}

auto lazy_load = config::builders::LazyLoadBuilder();
lazy_load.Source(std::move(*redis_result));

// Configure cache mode
// Default is 5 minutes, but contract tests may specify:
// - "off": disable caching (fetch from DB every time)
// - "ttl": custom TTL in milliseconds
// - "infinite": never expire cached items
if (in.persistentDataStore->cache.mode == "off") {
lazy_load.CacheRefresh(std::chrono::seconds(0));
} else if (in.persistentDataStore->cache.mode == "ttl") {
if (in.persistentDataStore->cache.ttlMs) {
lazy_load.CacheRefresh(std::chrono::milliseconds(
*in.persistentDataStore->cache.ttlMs));
}
} else if (in.persistentDataStore->cache.mode == "infinite") {
// Use a very large TTL to effectively never expire
lazy_load.CacheRefresh(std::chrono::hours(24 * 365));
}
// If no mode specified, the default 5-minute TTL is used

config_builder.DataSystem().Method(
config::builders::DataSystemBuilder::LazyLoad(
std::move(lazy_load)));
} else {
LD_LOG(logger_, LogLevel::kWarn)
<< "entity_manager: unsupported persistent store type: "
<< in.persistentDataStore->store.type;
return std::nullopt;
}
}
#endif

auto config = config_builder.Build();
if (!config) {
LD_LOG(logger_, LogLevel::kWarn)
Expand Down
3 changes: 3 additions & 0 deletions contract-tests/server-contract-tests/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ int main(int argc, char* argv[]) {
srv.add_capability("evaluation-hooks");
srv.add_capability("track-hooks");
srv.add_capability("wrapper");
#ifdef LD_REDIS_SUPPORT_ENABLED
srv.add_capability("persistent-data-store-redis");
#endif

net::signal_set signals{ioc, SIGINT, SIGTERM};

Expand Down
8 changes: 8 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# $1 the name of the target. For example "launchdarkly-cpp-common".
# $2 ON/OFF which enables/disables building in a test configuration (unit tests + contract tests.)
# $3 (optional) true/false to enable/disable CURL networking (LD_CURL_NETWORKING)
# $4 (optional) true/false to enable/disable Redis support (LD_BUILD_REDIS_SUPPORT)

function cleanup {
cd ..
Expand All @@ -31,6 +32,13 @@ if [ "$3" == "true" ]; then
build_curl="ON"
fi

# Check for Redis support option (override the automatic detection if explicitly passed)
if [ "$4" == "true" ]; then
build_redis="ON"
elif [ "$4" == "false" ]; then
build_redis="OFF"
fi

# Set build type to Debug when testing is enabled
build_type="Release"
if [ "$2" == "ON" ]; then
Expand Down
Loading