Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7984adc
add beginnings of sdk contract test server
cwaldren-ld May 8, 2023
725e05e
trying to make client movable
cwaldren-ld May 9, 2023
c7218ed
fix non-movable Session object
cwaldren-ld May 9, 2023
565c8ea
fixing contract tests
cwaldren-ld May 9, 2023
6d66f9e
allow for null evaluation reason when deserializing flag evaluation r…
cwaldren-ld May 9, 2023
d7dca32
add github action for SDK tests
cwaldren-ld May 9, 2023
85ece7b
unify the github actions for sse/sdk contrac tests
cwaldren-ld May 9, 2023
4a754a0
fix log tag on sdk-contract-test server
cwaldren-ld May 9, 2023
1df9646
feat: implement AllFlags [contract-tests] (#53)
cwaldren-ld May 10, 2023
e3f3e85
pass all context type/build tests
cwaldren-ld May 10, 2023
97cfe51
pass more tests
cwaldren-ld May 10, 2023
bb3c47e
update suppressions
cwaldren-ld May 10, 2023
fb685d4
fix: send identify event on client creation (#54)
cwaldren-ld May 10, 2023
717a968
Merge branch 'cw/sc-195303/contract-tests' into cw/parse-contexts
cwaldren-ld May 10, 2023
c7bbe7f
have ContextFilter take the global private attributes by value
cwaldren-ld May 10, 2023
753b138
Merge branch 'cw/sc-195303/contract-tests' into cw/parse-contexts
cwaldren-ld May 10, 2023
1cdbbf8
make context parsing slightly cleaner
cwaldren-ld May 10, 2023
462f32a
add json error ToString function
cwaldren-ld May 10, 2023
f83c44a
updating date parsing logic to account for local time offset
cwaldren-ld May 11, 2023
dc12b80
add invalid date test
cwaldren-ld May 11, 2023
e1ba1c0
remove debug event tests from suppressions
cwaldren-ld May 11, 2023
1a7e6b8
Merge branch 'main' into cw/debug-event-fixes
cwaldren-ld May 11, 2023
85979a0
remove obsolete code
cwaldren-ld May 11, 2023
07edba9
make ParseValidDateHeader test not dependent on time unit count
cwaldren-ld May 11, 2023
165899e
remove unecessary headers
cwaldren-ld May 11, 2023
3794989
lints
cwaldren-ld May 11, 2023
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
20 changes: 0 additions & 20 deletions apps/sdk-contract-tests/test-suppressions.txt
Original file line number Diff line number Diff line change
@@ -1,26 +1,6 @@
events/summary events/basic counter behavior
events/summary events/context kinds
events/summary events/reset after each flush
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK does not know LD time/without reasons/type: bool
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK does not know LD time/without reasons/type: int
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK does not know LD time/without reasons/type: double
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK does not know LD time/without reasons/type: string
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK does not know LD time/without reasons/type: any
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK does not know LD time/with reasons/type: bool
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK does not know LD time/with reasons/type: int
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK does not know LD time/with reasons/type: double
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK does not know LD time/with reasons/type: string
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK does not know LD time/with reasons/type: any
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK knows LD time is before debugEventsUntilDate/without reasons/type: bool
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK knows LD time is before debugEventsUntilDate/without reasons/type: int
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK knows LD time is before debugEventsUntilDate/without reasons/type: double
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK knows LD time is before debugEventsUntilDate/without reasons/type: string
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK knows LD time is before debugEventsUntilDate/without reasons/type: any
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK knows LD time is before debugEventsUntilDate/with reasons/type: bool
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK knows LD time is before debugEventsUntilDate/with reasons/type: int
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK knows LD time is before debugEventsUntilDate/with reasons/type: double
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK knows LD time is before debugEventsUntilDate/with reasons/type: string
events/debug events/should see debug event/debugEventsUntilDate is after SDK time/SDK knows LD time is before debugEventsUntilDate/with reasons/type: any
events/identify events/basic properties/single kind default
events/identify events/basic properties/single kind non-default
events/identify events/basic properties/multi-kind
Expand Down
34 changes: 26 additions & 8 deletions libs/common/include/events/detail/parse_date_header.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,34 @@ namespace launchdarkly::events::detail {
template <typename Clock>
static std::optional<typename Clock::time_point> ParseDateHeader(
std::string const& datetime) {
// TODO: SC-199582
std::tm t = {};
std::istringstream ss(datetime);
ss.imbue(std::locale("en_US.utf-8"));
ss >> std::get_time(&t, "%a, %d %b %Y %H:%M:%S GMT");
if (ss.fail()) {
// The following comments may not be entirely accurate.
// TODO: There must be a better way.

std::tm gmt_tm = {};
std::istringstream string_stream(datetime);
string_stream.imbue(std::locale("en_US.utf-8"));
string_stream >> std::get_time(&gmt_tm, "%a, %d %b %Y %H:%M:%S GMT");
if (string_stream.fail()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bugfix: simply parsing using std::get_time followed by mktime gives us a std::chrono::system_clock::time_point in local time. We need to actually compute the offset and subtract it out.

This code is just so painful. C++20 has a parse method which might help, or we can import a 3rd party library..

return std::nullopt;
}
std::time_t tt = std::mktime(&t);
return Clock::from_time_t(tt);
// Obtain a time_t. Caveat: mktime will interpret the tm as a local time,
// but it's not, so we're going to need to cancel that out later.
std::time_t local_t = std::mktime(&gmt_tm);

// Convert the fake local time into UTC.
std::tm* utc_tm = std::gmtime(&local_t);
utc_tm->tm_isdst = false;

// Now since the utc_tm has the UTC offset baked in, convert it back
// into a time_t.
std::time_t gm_t = std::mktime(utc_tm);

// Obtain the offset by subtracting from the original local_t.
std::time_t gm_offset = (gm_t - local_t);

// Finally, get the actual time in GMT by subtracting the offset.
std::time_t real_gm_t = local_t - gm_offset;
return Clock::from_time_t(real_gm_t);
}

} // namespace launchdarkly::events::detail
33 changes: 17 additions & 16 deletions libs/common/src/events/asio_event_processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid_io.hpp>
#include "config/detail/builders/http_properties_builder.hpp"
#include "config/detail/sdks.hpp"
#include "network/detail/asio_requester.hpp"

#include "serialization/events/json_events.hpp"

namespace http = boost::beast::http;
Expand Down Expand Up @@ -111,7 +108,7 @@ void AsioEventProcessor<SDK>::HandleSend(InputEvent event) {
template <typename SDK>
void AsioEventProcessor<SDK>::Flush(FlushTrigger flush_type) {
workers_.Get([this](RequestWorker* worker) {
if (!worker) {
if (worker == nullptr) {
LD_LOG(logger_, LogLevel::kDebug)
<< "event-processor: no flush workers available; skipping "
"flush";
Expand Down Expand Up @@ -143,8 +140,8 @@ void AsioEventProcessor<SDK>::OnEventDeliveryResult(
boost::ignore_unused(event_count);

std::visit(
overloaded{[&](Clock::time_point&& server_time) {
last_known_past_time_ = std::move(server_time);
overloaded{[&](Clock::time_point server_time) {
last_known_past_time_ = server_time;
},
[&](network::detail::HttpResult::StatusCode status) {
std::lock_guard<std::mutex> guard{this->inbox_mutex_};
Expand All @@ -153,7 +150,7 @@ void AsioEventProcessor<SDK>::OnEventDeliveryResult(
permanent_delivery_failure_ = true;
}
}},
std::move(result));
result);
}

template <typename SDK>
Expand Down Expand Up @@ -220,29 +217,33 @@ std::vector<OutputEvent> AsioEventProcessor<SDK>::Process(
overloaded{[&](client::FeatureEventParams&& event) {
summarizer_.Update(event);

if (!event.require_full_event) {
return;
}

Copy link
Contributor Author

@cwaldren-ld cwaldren-ld May 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bugfix: debug event emit logic shouldn't be gated by require_full_event (which right now is equivalent to the flag's track_events prop.)

client::FeatureEventBase base{event};

auto debug_until_date = event.debug_events_until_date;

auto max_time = std::max(
// To be conservative, use as the current time the
// maximum of the actual current time and the server's
// time. This way if the local host is running behind, we
// won't accidentally keep emitting events.

auto conservative_now = std::max(
std::chrono::system_clock::now(),
last_known_past_time_.value_or(
std::chrono::system_clock::time_point{}));
std::chrono::system_clock::from_time_t(0)));

bool emit_debug_event =
debug_until_date && debug_until_date->t > max_time;
debug_until_date &&
conservative_now < debug_until_date->t;

if (emit_debug_event) {
out.emplace_back(client::DebugEvent{
base, filter_.filter(event.context)});
}

out.emplace_back(client::FeatureEvent{
std::move(base), event.context.kinds_to_keys()});
if (event.require_full_event) {
out.emplace_back(client::FeatureEvent{
std::move(base), event.context.kinds_to_keys()});
}
},
[&](client::IdentifyEventParams&& event) {
// Contexts should already have been checked for
Expand Down
44 changes: 39 additions & 5 deletions libs/common/tests/event_processor_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include "context_builder.hpp"
#include "events/client_events.hpp"
#include "events/detail/asio_event_processor.hpp"
#include "events/detail/worker_pool.hpp"
#include "events/detail/parse_date_header.hpp"

using namespace launchdarkly::events::detail;
using namespace launchdarkly::network::detail;
Expand All @@ -26,15 +26,15 @@ TEST(WorkerPool, PoolReturnsAvailableWorker) {
boost::asio::io_context ioc;

auto work = boost::asio::make_work_guard(ioc);
std::thread t([&]() { ioc.run(); });
std::thread ioc_thread([&]() { ioc.run(); });

WorkerPool pool(ioc.get_executor(), 1, std::chrono::seconds(1), logger);

RequestWorker* worker = pool.Get(boost::asio::use_future).get();
ASSERT_TRUE(worker);

work.reset();
t.join();
ioc_thread.join();
}

TEST(WorkerPool, PoolReturnsNullptrWhenNoWorkerAvaialable) {
Expand All @@ -43,15 +43,15 @@ TEST(WorkerPool, PoolReturnsNullptrWhenNoWorkerAvaialable) {
boost::asio::io_context ioc;

auto work = boost::asio::make_work_guard(ioc);
std::thread t([&]() { ioc.run(); });
std::thread ioc_thread([&]() { ioc.run(); });

WorkerPool pool(ioc.get_executor(), 0, std::chrono::seconds(1), logger);

RequestWorker* worker = pool.Get(boost::asio::use_future).get();
ASSERT_FALSE(worker);

work.reset();
t.join();
ioc_thread.join();
}

// This test is a temporary test that exists only to ensure the event processor
Expand Down Expand Up @@ -89,3 +89,37 @@ TEST(EventProcessorTests, ProcessorCompiles) {
processor.AsyncClose();
ioc_thread.join();
}

TEST(EventProcessorTests, ParseValidDateHeader) {
using namespace launchdarkly;

using Clock = std::chrono::system_clock;
auto date =
events::detail::ParseDateHeader<Clock>("Wed, 21 Oct 2015 07:28:00 GMT");

ASSERT_TRUE(date);

ASSERT_EQ(date->time_since_epoch(),
std::chrono::microseconds(1445412480000000));
}

TEST(EventProcessorTests, ParseInvalidDateHeader) {
using namespace launchdarkly;

auto not_a_date =
events::detail::ParseDateHeader<std::chrono::system_clock>(
"this is definitely not a date");

ASSERT_FALSE(not_a_date);

auto not_gmt = events::detail::ParseDateHeader<std::chrono::system_clock>(
"Wed, 21 Oct 2015 07:28:00 PST");

ASSERT_FALSE(not_gmt);

auto missing_year =
events::detail::ParseDateHeader<std::chrono::system_clock>(
"Wed, 21 Oct 07:28:00 GMT");

ASSERT_FALSE(missing_year);
}