Skip to content

Commit 7d34a34

Browse files
committed
Adding summarizer unit tests
1 parent 922e114 commit 7d34a34

File tree

8 files changed

+133
-36
lines changed

8 files changed

+133
-36
lines changed

libs/common/include/events/detail/asio_event_processor.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#include "context_filter.hpp"
1313
#include "events/detail/conn_pool.hpp"
1414
#include "events/detail/outbox.hpp"
15-
#include "events/detail/summary_state.hpp"
15+
#include "events/detail/summarizer.hpp"
1616
#include "events/event_processor.hpp"
1717
#include "events/events.hpp"
1818
#include "logger.hpp"

libs/common/include/events/detail/summary_state.hpp renamed to libs/common/include/events/detail/summarizer.hpp

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#pragma once
2+
#include <boost/container_hash/hash.hpp>
23
#include <chrono>
34
#include <functional>
45
#include <unordered_map>
@@ -10,9 +11,17 @@ namespace launchdarkly::events::detail {
1011

1112
class Summarizer {
1213
public:
13-
Summarizer(std::chrono::system_clock::time_point start);
14+
using Date = std::chrono::system_clock::time_point;
15+
using FlagKey = std::string;
16+
17+
explicit Summarizer(Date start);
18+
Summarizer();
1419
void update(client::FeatureEventParams const& event);
1520

21+
bool empty() const;
22+
23+
Date start_time() const;
24+
1625
struct VariationSummary {
1726
std::size_t count;
1827
Value value;
@@ -21,7 +30,6 @@ class Summarizer {
2130
};
2231

2332
struct VariationKey {
24-
VariationKey(VariationKey const& key);
2533
std::optional<Version> version;
2634
std::optional<VariationIndex> variation;
2735
VariationKey(Version version, std::optional<VariationIndex> variation);
@@ -34,8 +42,10 @@ class Summarizer {
3442

3543
struct Hash {
3644
auto operator()(VariationKey const& p) const -> size_t {
37-
return std::hash<decltype(p.version)>{}(p.version) ^
38-
std::hash<decltype(p.variation)>{}(p.variation);
45+
std::size_t seed = 0;
46+
boost::hash_combine(seed, p.version);
47+
boost::hash_combine(seed, p.variation);
48+
return seed;
3949
}
4050
};
4151
};
@@ -51,10 +61,16 @@ class Summarizer {
5161
State(Value defaultVal);
5262
};
5363

54-
using FlagKey = std::string;
55-
std::chrono::system_clock::time_point start_time_;
56-
std::chrono::system_clock::time_point end_time_;
64+
std::unordered_map<FlagKey, State> const& features() const;
65+
66+
private:
67+
Date start_time_;
5768
std::unordered_map<FlagKey, State> features_;
5869
};
5970

71+
struct Summary {
72+
Summarizer const& summarizer;
73+
Summarizer::Date end_time;
74+
};
75+
6076
} // namespace launchdarkly::events::detail

libs/common/include/serialization/events/json_events.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#include <boost/json.hpp>
44

5-
#include "events/detail/summary_state.hpp"
5+
#include "events/detail/summarizer.hpp"
66
#include "events/events.hpp"
77

88
namespace launchdarkly::events::client {
@@ -48,5 +48,5 @@ void tag_invoke(boost::json::value_from_tag const&,
4848
Summarizer::State const& state);
4949
void tag_invoke(boost::json::value_from_tag const&,
5050
boost::json::value& json_value,
51-
Summarizer const& summarizer);
51+
Summary const& summarizer);
5252
} // namespace launchdarkly::events::detail

libs/common/src/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ add_library(${LIBNAME}
4141
events/asio_event_processor.cpp
4242
events/outbox.cpp
4343
events/conn_pool.cpp
44-
events/summary_state.cpp
44+
events/summarizer.cpp
4545
config/http_properties.cpp
4646
config/data_source_builder.cpp
4747
config/http_properties_builder.cpp)
48-
48+
4949

5050
add_library(launchdarkly::common ALIAS ${LIBNAME})
5151

libs/common/src/events/asio_event_processor.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ void AsioEventProcessor::AsyncSend(InputEvent input_event) {
7575
}
7676

7777
void AsioEventProcessor::HandleSend(InputEvent e) {
78-
7978
std::vector<OutputEvent> output_events = Process(std::move(e));
8079

8180
bool inserted = outbox_.PushDiscardingOverflow(std::move(output_events));
@@ -94,6 +93,7 @@ void AsioEventProcessor::Flush(FlushTrigger flush_type) {
9493
LD_LOG(logger_, LogLevel::kDebug)
9594
<< "event-processor: nothing to flush";
9695
}
96+
summary_state_ = Summarizer(std::chrono::system_clock::now());
9797
if (flush_type == FlushTrigger::Automatic) {
9898
ScheduleFlush();
9999
}
@@ -132,6 +132,12 @@ AsioEventProcessor::MakeRequest() {
132132
return std::nullopt;
133133
}
134134

135+
boost::json::value summary_event;
136+
if (!summary_state_.empty()) {
137+
summary_event = boost::json::value_from(
138+
Summary{summary_state_, std::chrono::system_clock::now()});
139+
}
140+
135141
LD_LOG(logger_, LogLevel::kDebug)
136142
<< "event-processor: generating http request";
137143
RequestType req;
@@ -144,8 +150,12 @@ AsioEventProcessor::MakeRequest() {
144150
req.set(kPayloadIdHeader, boost::lexical_cast<std::string>(uuids_()));
145151
req.target(host_ + path_);
146152

147-
req.body() =
148-
boost::json::serialize(boost::json::value_from(outbox_.Consume()));
153+
auto events = boost::json::value_from(outbox_.Consume());
154+
if (!summary_event.is_null()) {
155+
events.as_array().push_back(summary_event);
156+
}
157+
158+
req.body() = boost::json::serialize(events);
149159
req.prepare_payload();
150160
return req;
151161
}
@@ -164,7 +174,6 @@ std::vector<OutputEvent> AsioEventProcessor::Process(InputEvent event) {
164174
std::visit(
165175
overloaded{
166176
[&](client::FeatureEventParams&& e) {
167-
168177
summary_state_.update(e);
169178

170179
if (!e.eval_result.track_events()) {

libs/common/src/events/summary_state.cpp renamed to libs/common/src/events/summarizer.cpp

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
#include "events/detail/summary_state.hpp"
1+
#include "events/detail/summarizer.hpp"
22

33
namespace launchdarkly::events::detail {
44

55
Summarizer::Summarizer(std::chrono::system_clock::time_point start)
66
: start_time_(start), features_() {}
77

8-
static std::unordered_set<std::string> CopyKinds(
9-
std::vector<std::string_view> const& kinds) {
10-
std::unordered_set<std::string> kind_set;
11-
kind_set.insert(kinds.begin(), kinds.end());
12-
return kind_set;
8+
Summarizer::Summarizer() : start_time_(), features_() {}
9+
10+
bool Summarizer::empty() const {
11+
return features_.empty();
12+
}
13+
14+
std::unordered_map<Summarizer::FlagKey, Summarizer::State> const&
15+
Summarizer::features() const {
16+
return features_;
1317
}
1418

1519
static bool FlagNotFound(client::FeatureEventParams const& event) {
@@ -33,14 +37,16 @@ void Summarizer::update(client::FeatureEventParams const& event) {
3337
feature_state_iterator->second.context_kinds_.insert(kinds.begin(),
3438
kinds.end());
3539

36-
decltype(std::begin(feature_state_iterator->second.counters_)) summary_counter;
40+
decltype(std::begin(
41+
feature_state_iterator->second.counters_)) summary_counter;
3742

3843
if (FlagNotFound(event)) {
3944
auto key = VariationKey();
40-
summary_counter = feature_state_iterator->second.counters_
41-
.try_emplace(std::move(key),
42-
feature_state_iterator->second.default_)
43-
.first;
45+
summary_counter =
46+
feature_state_iterator->second.counters_
47+
.try_emplace(std::move(key),
48+
feature_state_iterator->second.default_)
49+
.first;
4450

4551
} else {
4652
auto key = VariationKey(event.eval_result.version(),
@@ -53,15 +59,16 @@ void Summarizer::update(client::FeatureEventParams const& event) {
5359

5460
summary_counter->second.Increment();
5561
}
62+
Summarizer::Date Summarizer::start_time() const {
63+
return start_time_;
64+
}
5665
Summarizer::VariationKey::VariationKey(Version version,
5766
std::optional<VariationIndex> variation)
5867
: version(version), variation(variation) {}
5968

6069
Summarizer::VariationKey::VariationKey()
6170
: version(std::nullopt), variation(std::nullopt) {}
6271

63-
Summarizer::VariationKey::VariationKey(Summarizer::VariationKey const& key) {}
64-
6572
Summarizer::VariationSummary::VariationSummary(Value value)
6673
: count(0), value(std::move(value)) {}
6774

libs/common/src/serialization/events/json_events.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,12 @@ void tag_invoke(boost::json::value_from_tag const&,
100100
}
101101
void tag_invoke(boost::json::value_from_tag const&,
102102
boost::json::value& json_value,
103-
Summarizer const& summarizer) {
103+
Summary const& s) {
104104
auto& obj = json_value.emplace_object();
105105
obj.emplace("kind", "summary");
106106
obj.emplace("startDate",
107-
boost::json::value_from(Date{summarizer.start_time_}));
108-
obj.emplace("endDate", boost::json::value_from(Date{summarizer.end_time_}));
109-
obj.emplace("features", boost::json::value_from(summarizer.features_));
107+
boost::json::value_from(Date{s.summarizer.start_time()}));
108+
obj.emplace("endDate", boost::json::value_from(Date{s.end_time}));
109+
obj.emplace("features", boost::json::value_from(s.summarizer.features()));
110110
}
111111
} // namespace launchdarkly::events::detail

libs/common/tests/event_processor_test.cpp

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,79 @@
55
#include "config/client.hpp"
66
#include "console_backend.hpp"
77
#include "context_builder.hpp"
8+
#include "events/client_events.hpp"
89
#include "events/detail/asio_event_processor.hpp"
10+
#include "events/detail/summarizer.hpp"
911

10-
using namespace launchdarkly;
11-
class EventProcessorTests : public ::testing::Test {};
12+
using namespace launchdarkly::events::detail;
13+
// class EventProcessorTests : public ::testing::Test {};
14+
15+
static std::chrono::system_clock::time_point ZeroTime() {
16+
return std::chrono::system_clock::time_point{};
17+
}
18+
19+
TEST(SummarizerTests, IsEmptyOnConstruction) {
20+
Summarizer summarizer;
21+
ASSERT_TRUE(summarizer.empty());
22+
}
23+
24+
TEST(SummarizerTests, DefaultConstructionUsesZeroStartTime) {
25+
Summarizer summarizer;
26+
ASSERT_EQ(summarizer.start_time(), ZeroTime());
27+
}
28+
29+
TEST(SummarizerTests, ExplicitStartTimeIsCorrect) {
30+
auto start = std::chrono::system_clock::now();
31+
Summarizer summarizer(start);
32+
ASSERT_EQ(summarizer.start_time(), start);
33+
}
34+
35+
TEST(SummarizerTests, SummaryCounterUpdates) {
36+
using namespace launchdarkly::events::client;
37+
using namespace launchdarkly;
38+
Summarizer summarizer;
39+
40+
auto const feature_key = "cat-food-amount";
41+
auto const feature_version = 1;
42+
auto const context = ContextBuilder().kind("cat", "shadow").build();
43+
auto const feature_value = Value(3);
44+
auto const feature_variation = 0;
45+
46+
auto const event = FeatureEventParams{
47+
ZeroTime(),
48+
feature_key,
49+
context,
50+
EvaluationResult(
51+
feature_version, std::nullopt, false, false, std::nullopt,
52+
EvaluationDetailInternal(
53+
feature_value, feature_variation,
54+
EvaluationReason("FALLTHROUGH", std::nullopt, std::nullopt,
55+
std::nullopt, std::nullopt, false,
56+
std::nullopt))),
57+
};
58+
59+
auto const num_events = 10;
60+
for (size_t i = 0; i < num_events; i++) {
61+
summarizer.update(event);
62+
}
63+
64+
auto const& features = summarizer.features();
65+
auto const& cat_food = features.find(feature_key);
66+
ASSERT_TRUE(cat_food != features.end());
67+
68+
auto const& counter = cat_food->second.counters_.find(
69+
Summarizer::VariationKey(feature_version, feature_variation));
70+
ASSERT_TRUE(counter != cat_food->second.counters_.end());
71+
72+
ASSERT_EQ(counter->second.value.as_double(), feature_value.as_double());
73+
ASSERT_EQ(counter->second.count, num_events);
74+
}
1275

1376
// This test is a temporary test that exists only to ensure the event processor
1477
// compiles; it should be replaced by more robust tests (and contract tests.)
15-
TEST_F(EventProcessorTests, ProcessorCompiles) {
78+
TEST(EventProcessorTests, ProcessorCompiles) {
79+
using namespace launchdarkly;
80+
1681
Logger logger{std::make_unique<ConsoleBackend>(LogLevel::kDebug, "test")};
1782
boost::asio::io_context io;
1883

0 commit comments

Comments
 (0)