Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions libs/server-sdk/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ file(GLOB HEADER_LIST CONFIGURE_DEPENDS

add_library(${LIBNAME}
${HEADER_LIST}
evaluation/detail/evaluation_stack.cpp
evaluation/detail/semver_operations.cpp
evaluation/detail/timestamp_operations.cpp)

Expand Down
30 changes: 30 additions & 0 deletions libs/server-sdk/src/evaluation/detail/evaluation_stack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include "evaluation_stack.hpp"

namespace launchdarkly::server_side::evaluation::detail {

Guard::Guard(std::unordered_set<std::string>& set, std::string const& key)
: set_(set), key_(key) {
set_.insert(key_);
}

Guard::~Guard() {
set_.erase(key_);
}

std::optional<Guard> EvaluationStack::NoticePrerequisite(
std::string const& prerequisite_key) {
if (prerequisites_seen_.count(prerequisite_key) != 0) {
return std::nullopt;
}
return std::make_optional<Guard>(prerequisites_seen_, prerequisite_key);
}

std::optional<Guard> EvaluationStack::NoticeSegment(
std::string const& segment_key) {
if (segments_seen_.count(segment_key) != 0) {
return std::nullopt;
}
return std::make_optional<Guard>(segments_seen_, segment_key);
}

} // namespace launchdarkly::server_side::evaluation::detail
61 changes: 61 additions & 0 deletions libs/server-sdk/src/evaluation/detail/evaluation_stack.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#pragma once

#include <optional>
#include <string>
#include <unordered_set>

namespace launchdarkly::server_side::evaluation::detail {

/**
* Guard is an object used to track that a segment or flag key has been noticed.
* Upon destruction, the key is forgotten.
*/
struct Guard {
Guard(std::unordered_set<std::string>& set, std::string const& key);
~Guard();

Guard(Guard const&) = delete;
Guard& operator=(Guard const&) = delete;

Guard(Guard&&) = delete;
Guard& operator=(Guard&&) = delete;

private:
std::unordered_set<std::string>& set_;
std::string const& key_;
};

/**
* EvaluationStack is used to track which segments and flags have been noticed
* during evaluation in order to detect circular references.
*/
class EvaluationStack {
public:
EvaluationStack() = default;

/**
* If the given prerequisite key has not been seen, marks it as seen
* and returns a Guard object. Otherwise, returns std::nullopt.
*
* @param prerequisite_key Key of the prerequisite.
* @return Guard object if not seen before, otherwise std::nullopt.
*/
[[nodiscard]] std::optional<Guard> NoticePrerequisite(
std::string const& prerequisite_key);

/**
* If the given segment key has not been seen, marks it as seen
* and returns a Guard object. Otherwise, returns std::nullopt.
*
* @param prerequisite_key Key of the segment.
* @return Guard object if not seen before, otherwise std::nullopt.
*/
[[nodiscard]] std::optional<Guard> NoticeSegment(
std::string const& segment_key);

private:
std::unordered_set<std::string> prerequisites_seen_;
std::unordered_set<std::string> segments_seen_;
};

} // namespace launchdarkly::server_side::evaluation::detail
53 changes: 53 additions & 0 deletions libs/server-sdk/tests/evaluation_stack_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <gtest/gtest.h>

#include "evaluation/detail/evaluation_stack.hpp"

using namespace launchdarkly::server_side::evaluation::detail;

TEST(EvalStackTests, SegmentIsNoticed) {
EvaluationStack stack;
auto g1 = stack.NoticeSegment("foo");
ASSERT_TRUE(g1);
ASSERT_FALSE(stack.NoticeSegment("foo"));
}

TEST(EvalStackTests, PrereqIsNoticed) {
EvaluationStack stack;
auto g1 = stack.NoticePrerequisite("foo");
ASSERT_TRUE(g1);
ASSERT_FALSE(stack.NoticePrerequisite("foo"));
}

TEST(EvalStackTests, NestedScopes) {
EvaluationStack stack;
{
auto g1 = stack.NoticeSegment("foo");
ASSERT_TRUE(g1);
ASSERT_FALSE(stack.NoticeSegment("foo"));
{
auto g2 = stack.NoticeSegment("bar");
ASSERT_TRUE(g2);
ASSERT_FALSE(stack.NoticeSegment("bar"));
ASSERT_FALSE(stack.NoticeSegment("foo"));
}
ASSERT_TRUE(stack.NoticeSegment("bar"));
}
ASSERT_TRUE(stack.NoticeSegment("foo"));
ASSERT_TRUE(stack.NoticeSegment("bar"));
}

TEST(EvalStackTests, SegmentAndPrereqHaveSeparateCaches) {
EvaluationStack stack;
auto g1 = stack.NoticeSegment("foo");
ASSERT_TRUE(g1);
auto g2 = stack.NoticePrerequisite("foo");
ASSERT_TRUE(g2);
}

TEST(EvalStackTests, ImmediateDestructionOfGuard) {
EvaluationStack stack;

ASSERT_TRUE(stack.NoticeSegment("foo"));
ASSERT_TRUE(stack.NoticeSegment("foo"));
ASSERT_TRUE(stack.NoticeSegment("foo"));
}